/** * Template Name: Bootslander * Template URL: https://bootstrapmade.com/bootslander-free-bootstrap-landing-page-template/ * Updated: Mar 17 2024 with Bootstrap v5.3.3 * Author: BootstrapMade.com * License: https://bootstrapmade.com/license/ */ const analyses = { steps: { 2022: ` <h3>2022 : Une année de progression générale</h3> <ul> <li><strong>Corentin :</strong> Pic impressionnant de 10 500 pas, le plus actif de l'année.</li> <li><strong>Anis :</strong> Progression régulière avec une constance remarquable.</li> <li><strong>Maya :</strong> Montée progressive culminant en fin d'année.</li> </ul> <p>Tous les utilisateurs montrent une augmentation continue de leur activité, avec Corentin en tête, suivi d'Anis et de Maya.</p> `, 2023: ` <h3>2023 : Analyse des habitudes de marche</h3> <ul> <li><strong>Anis :</strong> Domine l'année avec un pic de 11 500 pas en mars, mais baisse notable en été.</li> <li><strong>Corentin :</strong> Activité croissante dès février, mais baisse hivernale.</li> <li><strong>Maya :</strong> Rythme stable avec une hausse estivale due à ses jobs étudiants.</li> <li><strong>Amira :</strong> Moins active avec des données partielles, baisse notable en fin d'année.</li> </ul> <p>Des tendances saisonnières et des obligations personnelles influencent l'activité de marche.</p> `, 2024: ` <h3>2024 : Une année de comportements variés</h3> <ul> <li><strong>Corentin :</strong> Forte activité en décembre, dépassant les 10 000 pas.</li> <li><strong>Anis :</strong> Régularité tout au long de l'année avec des variations en fin d'année.</li> <li><strong>Maya :</strong> Baisse liée au stress des examens en fin d'année.</li> <li><strong>Amira :</strong> Activité faible, mais légèrement accrue en été.</li> </ul> <p>Des tendances saisonnières comme la baisse hivernale et le pic de décembre sont observées.</p> `, }, distanceCalories: { 2023: ` <h3>2023 : Distance et calories</h3> <ul> <li><strong>Anis :</strong> Leader incontesté, atteignant un pic en mars avec 484 calories brûlées.</li> <li><strong>Corentin :</strong> Activité constante, avec une hausse estivale marquée.</li> <li><strong>Maya et Amira :</strong> Moins actives, mais hausse notable en fin d'année.</li> </ul> <p>Les tendances montrent des variations saisonnières avec des pics en été et en décembre.</p> `, 2024: ` <h3>2024 : Comparaison distance-calories</h3> <ul> <li><strong>Anis :</strong> Plus actif, avec 6,73 km parcourus et 484 calories brûlées en mai.</li> <li><strong>Corentin :</strong> Activité accrue en décembre, mais reste derrière Anis.</li> <li><strong>Maya et Amira :</strong> Activité moindre, avec une hausse en décembre liée aux fêtes.</li> </ul> <p>Les mois de faible activité varient selon les utilisateurs, sans tendance claire.</p> `, }, calories: { 2023: ` <h3>2023 : Analyse des calories brûlées</h3> <ul> <li><strong>Anis :</strong> Pic de 500 calories brûlées en mars, régularité tout au long de l'année.</li> <li><strong>Corentin :</strong> Activité stable, légère baisse estivale.</li> <li><strong>Maya :</strong> Constance avec un pic à 110 calories en septembre.</li> <li><strong>Amira :</strong> Progression vers la fin d'année, pic à 100 calories en décembre.</li> </ul> <p>Une fin d'année plus active pour tout le monde.</p> `, 2024: ` <h3>2024 : Analyse des calories</h3> <ul> <li><strong>Anis :</strong> Leader incontesté avec un pic de 450 calories en mai.</li> <li><strong>Corentin :</strong> Efforts notables, pic à 300 calories en avril et décembre.</li> <li><strong>Maya :</strong> Activité régulière entre 100 et 145 calories par mois.</li> <li><strong>Amira :</strong> Plus sédentaire, mais pic exceptionnel de 150 calories en septembre.</li> </ul> <p>Les résultats reflètent des dynamiques variées entre les membres.</p> `, }, }; function updateAnalysis(visualization, year) { const container = document.getElementById("analysis-content-" + visualization); if (analyses[visualization] && analyses[visualization][year]) { container.innerHTML = analyses[visualization][year]; } else { container.innerHTML = "<p>Analyse indisponible pour cette année.</p>"; } } (function() { "use strict"; /** * Easy selector helper function */ const select = (el, all = false) => { el = el.trim() if (all) { return [...document.querySelectorAll(el)] } else { return document.querySelector(el) } } /** * Easy event listener function */ const on = (type, el, listener, all = false) => { let selectEl = select(el, all) if (selectEl) { if (all) { selectEl.forEach(e => e.addEventListener(type, listener)) } else { selectEl.addEventListener(type, listener) } } } /** * Easy on scroll event listener */ const onscroll = (el, listener) => { el.addEventListener('scroll', listener) } /** * Navbar links active state on scroll */ let navbarlinks = select('#navbar .scrollto', true) const navbarlinksActive = () => { let position = window.scrollY + 200 navbarlinks.forEach(navbarlink => { if (!navbarlink.hash) return let section = select(navbarlink.hash) if (!section) return if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) { navbarlink.classList.add('active') } else { navbarlink.classList.remove('active') } }) } window.addEventListener('load', navbarlinksActive) onscroll(document, navbarlinksActive) /** * Scrolls to an element with header offset */ const scrollto = (el) => { let header = select('#header') let offset = header.offsetHeight if (!header.classList.contains('header-scrolled')) { offset -= 20 } let elementPos = select(el).offsetTop window.scrollTo({ top: elementPos - offset, behavior: 'smooth' }) } /** * Toggle .header-scrolled class to #header when page is scrolled */ let selectHeader = select('#header') if (selectHeader) { const headerScrolled = () => { if (window.scrollY > 100) { selectHeader.classList.add('header-scrolled') } else { selectHeader.classList.remove('header-scrolled') } } window.addEventListener('load', headerScrolled) onscroll(document, headerScrolled) } /** * Back to top button */ let backtotop = select('.back-to-top') if (backtotop) { const toggleBacktotop = () => { if (window.scrollY > 100) { backtotop.classList.add('active') } else { backtotop.classList.remove('active') } } window.addEventListener('load', toggleBacktotop) onscroll(document, toggleBacktotop) } /** * Mobile nav toggle */ on('click', '.mobile-nav-toggle', function(e) { select('#navbar').classList.toggle('navbar-mobile') this.classList.toggle('bi-list') this.classList.toggle('bi-x') }) /** * Mobile nav dropdowns activate */ on('click', '.navbar .dropdown > a', function(e) { if (select('#navbar').classList.contains('navbar-mobile')) { e.preventDefault() this.nextElementSibling.classList.toggle('dropdown-active') } }, true) /** * Scrool with ofset on links with a class name .scrollto */ on('click', '.scrollto', function(e) { if (select(this.hash)) { e.preventDefault() let navbar = select('#navbar') if (navbar.classList.contains('navbar-mobile')) { navbar.classList.remove('navbar-mobile') let navbarToggle = select('.mobile-nav-toggle') navbarToggle.classList.toggle('bi-list') navbarToggle.classList.toggle('bi-x') } scrollto(this.hash) } }, true) /** * Scroll with ofset on page load with hash links in the url */ window.addEventListener('load', () => { if (window.location.hash) { if (select(window.location.hash)) { scrollto(window.location.hash) } } }); /** * Preloader */ let preloader = select('#preloader'); if (preloader) { window.addEventListener('load', () => { preloader.remove() }); } /** * Initiate glightbox */ const glightbox = GLightbox({ selector: '.glightbox' }); /** * Initiate gallery lightbox */ const galleryLightbox = GLightbox({ selector: '.gallery-lightbox' }); /** * Testimonials slider */ new Swiper('.testimonials-slider', { speed: 600, loop: true, autoplay: { delay: 5000, disableOnInteraction: false }, slidesPerView: 'auto', pagination: { el: '.swiper-pagination', type: 'bullets', clickable: true } }); /** * Animation on scroll */ window.addEventListener('load', () => { AOS.init({ duration: 1000, easing: 'ease-in-out', once: true, mirror: false }) }); /** * Initiate Pure Counter */ new PureCounter(); })(); function renderStepsVisualization() { fetch('../static/js/final_combined_with_all_data.json') // Chemin à adapter si nécessaire .then((response) => response.json()) .then((data) => { const parseDate = d3.timeParse("%Y-%m-%d"); const formatYear = d3.timeFormat("%Y"); const formatMonth = d3.timeFormat("%Y-%m"); const members = ["Corentin", "Maya", "Anis", "Amira"]; data.forEach(d => d.date = parseDate(d.date)); const margin = { top: 50, right: 230, bottom: 150, left: 70 }; const width = 800 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; const svgContainer = d3.select("#steps-visualization") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom + 50); // +50 pour la légende // Ajout du titre svgContainer.append("text") .attr("x", (width + margin.left + margin.right) / 2.5 ) // Centré horizontalement .attr("y", 20) // Positionné en haut .attr("text-anchor", "middle") .style("font-size", "18px") .style("font-weight", "bold") .text("Comparaison des pas des utilisateurs"); const svg = svgContainer.append("g") .attr("transform", `translate(${margin.left},${margin.top})`); const years = [2022, 2023, 2024]; const stepsPrevYearBtn = document.getElementById("stepsPrevYear"); const stepsNextYearBtn = document.getElementById("stepsNextYear"); const stepsCurrentYearDisplay = document.getElementById("stepsCurrentYear"); stepsPrevYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)); if (currentYearIndex > 0) { const newYear = years[currentYearIndex - 1]; stepsCurrentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("steps", newYear); stepsNextYearBtn.disabled = false; if (currentYearIndex - 1 === 0) { stepsPrevYearBtn.disabled = true; } } }); stepsNextYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)); if (currentYearIndex < years.length - 1) { const newYear = years[currentYearIndex + 1]; stepsCurrentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("steps", newYear); stepsPrevYearBtn.disabled = false; if (currentYearIndex + 1 === years.length - 1) { stepsNextYearBtn.disabled = true; } } }); stepsPrevYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === 0; stepsNextYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === years.length - 1; updateVisualization(2024); function updateVisualization(selectedYear) { const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString()); if (filteredData.length === 0) { console.log(`Aucune donnée pour l'année ${selectedYear}`); return; } const groupedData = d3.groups(filteredData, d => formatMonth(d.date)); const aggregatedData = groupedData.map(([month, records]) => { const aggregated = { month }; members.forEach(member => { aggregated[`Steps_${member}`] = d3.mean(records, d => d[`Steps_${member}`] || 0); }); return aggregated; }); svg.selectAll("*").remove(); const xScale = d3.scaleBand() .domain(aggregatedData.map(d => d.month)) .range([0, width]) .padding(0.2) const yScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Steps_${member}`])))]).nice() .range([height, 0]); const colorMap = { "Maya": "#0f7e06", "Corentin": "#1d38e3", "Anis": "#d6bff4", "Amira": "#7e09bd" }; svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(xScale)) .selectAll("text") .attr("transform", "rotate(-45)") .style("text-anchor", "end"); svg.append("g").call(d3.axisLeft(yScale)); svg.append("text") .attr("x", width / 2) .attr("y", height + 70) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Mois"); svg.append("text") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("y", -50) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Nombre de pas"); members.forEach((member) => { const lineData = aggregatedData.map(d => { let steps = d[`Steps_${member}`] === -1.0 ? 0 : d[`Steps_${member}`]; return { month: d.month, steps }; }); const line = d3.line() .x(d => xScale(d.month) + xScale.bandwidth() / 2) .y(d => yScale(d.steps)) .defined(d => d.steps !== 0); svg.append("path") .data([lineData]) .attr("class", `line-${member}`) .attr("d", line) .attr("fill", "none") .attr("stroke", colorMap[member]) .attr("stroke-width", 2); }); const legend = svg.append("g") .attr("transform", `translate(${width / 20}, ${height + 80})`); members.forEach((member, i) => { const legendGroup = legend.append("g") .attr("transform", `translate(${i * 150}, 0)`); legendGroup.append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", colorMap[member]); legendGroup.append("text") .attr("x", 20) .attr("y", 12) .style("font-size", "12px") .text(member); }); } }); } // VISU 2 function renderDistanceVisualization() { fetch('../static/js/final_combined_with_all_data.json') // Chemin à adapter si nécessaire .then((response) => response.json()) .then((data) => { const parseDate = d3.timeParse("%Y-%m-%d"); const formatYear = d3.timeFormat("%Y"); const formatMonth = d3.timeFormat("%Y-%m"); const members = ["Corentin", "Maya", "Anis", "Amira"]; data.forEach(d => d.date = parseDate(d.date)); const margin = { top: 50, right: 230, bottom: 150, left: 70 }; const width = 800 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; const svg = d3.select("#distance-visualization") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); const years = [2023, 2024]; const prevYearBtn = document.getElementById("distancePrevYear"); const nextYearBtn = document.getElementById("distanceNextYear"); const currentYearDisplay = document.getElementById("distanceCurrentYear"); // Gestion des événements des boutons prevYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex > 0) { const newYear = years[currentYearIndex - 1]; currentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("distanceCalories", newYear); // Activer/désactiver les boutons nextYearBtn.disabled = false; if (currentYearIndex - 1 === 0) { prevYearBtn.disabled = true; } } }); nextYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex < years.length - 1) { const newYear = years[currentYearIndex + 1]; currentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("distanceCalories", newYear); // Activer/désactiver les boutons prevYearBtn.disabled = false; if (currentYearIndex + 1 === years.length - 1) { nextYearBtn.disabled = true; } } }); // Initialisation des boutons prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0; nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1; const tooltip = d3.select("body").append("div") .attr("class", "tooltip-distance") .style("opacity", 0); function updateVisualization(selectedYear) { const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString()); const groupedData = d3.groups(filteredData, d => formatMonth(d.date)); const colorMap = { "Maya": "#0f7e06", "Corentin": "#1d38e3", "Anis": "#d6bff4", "Amira": "#7e09bd" }; const aggregatedData = groupedData.map(([month, records]) => { const aggregated = { month }; members.forEach(member => { aggregated[`Distance_${member}`] = d3.mean(records, d => { const distance = d[`Distance_${member}`]; return (distance !== -1) ? distance : undefined; }) || 0; aggregated[`Calories_${member}`] = d3.mean(records, d => { const calories = d[`Calories_${member}`]; return (calories !== -1) ? calories : undefined; }) || 0; }); return aggregated; }); svg.selectAll("*").remove(); const bubbleSizeScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Distance_${member}`])) )]) .range([2, 20]); const xScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Distance_${member}`])) )]) .range([0, width]); const yScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Calories_${member}`])) )]) .range([height, 0]); svg.append("text") .attr("x", width / 2) .attr("y", margin.top - 75) .attr("text-anchor", "middle") .style("font-size", "16px") .style("font-weight", "bold") .text("Comparaison des calories brûlées vs distance parcourue"); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(xScale)); svg.append("text") .attr("x", width / 2) .attr("y", height + 70) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Distance (KM)"); svg.append("g").call(d3.axisLeft(yScale)); svg.append("text") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("y", -50) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Calories brûlées"); members.forEach((member) => { svg.selectAll(`.bubble-${member}`) .data(aggregatedData) .enter() .append("circle") .attr("cx", d => xScale(d[`Distance_${member}`])) .attr("cy", d => yScale(d[`Calories_${member}`])) .attr("r", d => 0.8* bubbleSizeScale(d[`Distance_${member}`])) .attr("fill", d => { return (d[`Distance_${member}`] === 0 || d[`Calories_${member}`] === 0) ? "gray" : colorMap[member]; }) .style("opacity", 0.7) .on("mouseover", function(event, d) { tooltip.transition().duration(200).style("opacity", 0.9); const monthFormatted = d3.timeFormat("%B")(parseDate(d.month)); // Formatage du mois const distance = d[`Distance_${member}`] ? d[`Distance_${member}`].toFixed(2) : "Pas de données"; const calories = d[`Calories_${member}`] ? d[`Calories_${member}`].toFixed(2) : "Pas de données"; tooltip.html(` <strong>${member}</strong><br> Mois : ${d.month}<br> Distance : ${distance} km<br> Calories : ${calories} cal `) .style("left", `${event.pageX + 10}px`) .style("top", `${event.pageY - 28}px`); }) .on("mousemove", function(event) { tooltip.style("left", `${event.pageX + 10}px`).style("top", `${event.pageY - 28}px`); }) .on("mouseout", function() { tooltip.transition().duration(500).style("opacity", 0); }); // Ajouter une légende horizontale sous le graphique const legend = svg.append("g") .attr("transform", `translate(${width / 2 - (members.length * 110) / 2}, ${height + 90})`); // Positionnement horizontal centré members.forEach((member, i) => { const legendGroup = legend.append("g") .attr("transform", `translate(${i * 100}, 0)`); // Espacement horizontal entre les éléments // Rectangle coloré pour la légende legendGroup.append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", colorMap[member]) .style("opacity", 0.8); // Texte descriptif à côté du rectangle legendGroup.append("text") .attr("x", 20) // Décalage horizontal par rapport au rectangle .attr("y", 12) // Alignement vertical au centre .style("font-size", "12px") .text(member); }); }); } updateVisualization(years[1]); }); } //VISU 3 function renderCaloriesVisualization() { fetch('../static/js/final_combined_with_all_data.json') .then((response) => response.json()) .then((data) => { const parseDate = d3.timeParse("%Y-%m-%d"); const formatYear = d3.timeFormat("%Y"); const formatMonth = d3.timeFormat("%Y-%m"); const members = ["Corentin", "Maya", "Anis", "Amira"]; data.forEach(d => d.date = parseDate(d.date)); const margin = { top: 50, right: 230, bottom: 150, left: 70 }; const width = 800 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; const svg = d3.select("#calories-visualization") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); const years = [2022, 2023, 2024]; const prevYearBtn = document.getElementById("caloriesPrevYear"); const nextYearBtn = document.getElementById("caloriesNextYear"); const currentYearDisplay = document.getElementById("caloriesCurrentYear"); // Gestion des événements des boutons prevYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex > 0) { const newYear = years[currentYearIndex - 1]; currentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("calories", newYear); // Activer/désactiver les boutons nextYearBtn.disabled = false; if (currentYearIndex - 1 === 0) { prevYearBtn.disabled = true; } } }); nextYearBtn.addEventListener("click", () => { const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex < years.length - 1) { const newYear = years[currentYearIndex + 1]; currentYearDisplay.textContent = newYear; updateVisualization(newYear); updateAnalysis("calories", newYear); // Activer/désactiver les boutons prevYearBtn.disabled = false; if (currentYearIndex + 1 === years.length - 1) { nextYearBtn.disabled = true; } } }); // Initialisation des boutons prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0; nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1; updateVisualization(2024); function updateVisualization(selectedYear) { const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString()); if (filteredData.length === 0) { console.log(`Aucune donnée pour l'année ${selectedYear}`); return; } const groupedData = d3.groups(filteredData, d => formatMonth(d.date)); const aggregatedData = groupedData.map(([month, records]) => { const aggregated = { month }; members.forEach(member => { aggregated[`Calories_${member}`] = d3.mean(records, d => d[`Calories_${member}`] || 0); }); return aggregated; }); svg.selectAll("*").remove(); svg.append("text") .attr("x", width / 2) .attr("y", margin.top - 60) .attr("text-anchor", "middle") .style("font-size", "16px") .style("font-weight", "bold") .text("Analyse des calories brûlées par mois"); const xScale = d3.scaleBand() .domain(aggregatedData.map(d => d.month)) .range([0, width]) .padding(0.2); const yScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Calories_${member}`])))]).nice() .range([height, 0]); const colorScale = d3.scaleOrdinal() .domain(members) .range(["#1d38e3", "#0f7e06", "#d6bff4", "#7e09bd"]); svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(xScale)) .selectAll("text") .attr("transform", "rotate(-45)") .style("text-anchor", "end"); svg.append("text") .attr("x", width / 2) .attr("y", height + 70) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Mois"); svg.append("g").call(d3.axisLeft(yScale)); svg.append("text") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("y", -50) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Calories brûlées"); // Ajout de la légende const legend = svg.append("g") .attr("transform", `translate(${width / 2 - (members.length * 120) / 2}, ${height + 100})`); // Positionnement members.forEach((member, i) => { const legendGroup = legend.append("g") .attr("transform", `translate(${i * 120}, 0)`); // Espacement horizontal // Rectangle coloré legendGroup.append("rect") .attr("width", 15) .attr("height", 15) .attr("fill", colorScale(member)) .style("opacity", 0.8); // Texte descriptif legendGroup.append("text") .attr("x", 20) // Position par rapport au rectangle .attr("y", 12) // Alignement vertical .style("font-size", "12px") .text(member); }); members.forEach(member => { const areaData = aggregatedData.map(d => ({ month: d.month, calories: d[`Calories_${member}`] || 0 })); const area = d3.area() .x(d => xScale(d.month) + xScale.bandwidth() / 2) .y0(yScale(0.8)) .y1(d => yScale(d.calories )); svg.append("path") .data([areaData]) .attr("fill", colorScale(member)) .attr("opacity", 0.8) .attr("d", area); }); } }); } // Visu4 function renderSleepVisualization() { fetch('../static/js/final_combined_with_all_data.json') // Adapter le chemin si nécessaire .then((response) => response.json()) .then((data) => { const parseDate = d3.timeParse("%Y-%m-%d"); const formatYear = d3.timeFormat("%Y"); const formatMonth = d3.timeFormat("%Y-%m"); const formatDay = d3.timeFormat("%d"); const members = ["Corentin", "Maya", "Anis", "Amira"]; data.forEach(d => d.date = parseDate(d.date)); // Dimensions et marges const margin = { top: 50, right: 230, bottom: 150, left: 70 }; const width = 800 - margin.left - margin.right; const height = 500 - margin.top - margin.bottom; // Créer le conteneur SVG principal const svg = d3.select("#sleep-visualization") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // Créer un conteneur pour le graphique détaillé const detailContainer = d3.select("#sleep-visualization") .append("div") .attr("id", "detail-container") .style("margin-top", "20px"); // Définir les années et la plage d'affichage const years = [2021, 2022, 2023, 2024]; // Créer le slider // Ajoutez les boutons Bootstrap pour naviguer entre les années // Gestion des événements des boutons let currentYearIndex = years.indexOf(2024); // Année initiale // Fonction pour mettre à jour l'état des boutons function updateButtonStates() { if (currentYearIndex <= 0) { document.getElementById("prevYear").style.display = "hidden"; // Cacher le bouton précédent } else { document.getElementById("prevYear").style.display = "inline-block"; // Réafficher le bouton précédent } if (currentYearIndex >= years.length - 1) { document.getElementById("nextYear").style.display = "hidden"; // Cacher le bouton suivant } else { document.getElementById("nextYear").style.display = "inline-block"; // Réafficher le bouton suivant } } // Initialiser l'état des boutons updateButtonStates(); // Événement pour le bouton précédent document.getElementById("prevYear").addEventListener("click", () => { if (currentYearIndex > 0) { currentYearIndex--; document.getElementById("currentYear").textContent = years[currentYearIndex]; updateVisualization(years[currentYearIndex]); updateButtonStates(); // Mettre à jour l'état des boutons } }); // Événement pour le bouton suivant document.getElementById("nextYear").addEventListener("click", () => { if (currentYearIndex < years.length - 1) { currentYearIndex++; document.getElementById("currentYear").textContent = years[currentYearIndex]; updateVisualization(years[currentYearIndex]); updateButtonStates(); // Mettre à jour l'état des boutons } }); // Tooltip const tooltip = d3.select("body").append("div") .attr("class", "tooltip-sleep") .style("opacity", 0); // Fonction de mise à jour de la visualisation function updateVisualization(selectedYear) { const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString()); const groupedData = d3.groups(filteredData, d => formatMonth(d.date)); const colorMap = { "Maya": "#0f7e06", "Corentin": "#1d38e3", "Anis": "#d6bff4", "Amira": "#7e09bd" }; const aggregatedData = groupedData.map(([month, records]) => { const aggregated = { month }; members.forEach(member => { aggregated[`Sleep_${member}`] = d3.mean(records, d => d[`Sleep_${member}`] || 0); }); return aggregated; }); svg.selectAll("*").remove(); svg.append("text") .attr("x", width / 2) // Centrer horizontalement .attr("y", margin.top - 60) // Positionner légèrement au-dessus du graphique .attr("text-anchor", "middle") // Centrer le texte .style("font-size", "16px") // Taille de police .style("font-weight", "bold") // Gras .text("Analyse des heures de sommeil par mois"); const xScale = d3.scaleBand() .domain(aggregatedData.map(d => d.month)) .range([0, width]) .padding(0.2); const yScale = d3.scaleLinear() .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Sleep_${member}`])))]).nice() .range([height, 0]); const colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(members); // Axe X svg.append("g") .attr("transform", `translate(0, ${height})`) .call(d3.axisBottom(xScale)) .selectAll("text") .attr("transform", "rotate(-45)") .style("text-anchor", "end"); // Légende de l'axe X svg.append("text") .attr("x", width / 2) .attr("y", height + 70) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Mois"); // Axe Y svg.append("g").call(d3.axisLeft(yScale)); // Légende de l'axe Y svg.append("text") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("y", -50) .attr("text-anchor", "middle") .style("font-size", "14px") .text("Sommeil moyen (heures)"); // Légende des membres (rectangles colorés) // Créer un conteneur pour la légende const legend = svg.append("g") .attr("transform", `translate(${width / 3}, ${height + margin.bottom - 50})`) // Positionner la légende en bas, centrée .attr("text-anchor", "middle"); // Ajouter les éléments de la légende members.forEach((member, i) => { legend.append("rect") .attr("x", i * 100 - (members.length * 50)) // Espacement horizontal entre les rectangles .attr("y", 0) .attr("width", 15) .attr("height", 15) .attr("fill", colorMap[member]); legend.append("text") .attr("x", i * 100 - (members.length * 50) + 20) // Texte à côté du rectangle .attr("y", 12) .text(member) .style("font-size", "12px") .attr("text-anchor", "start"); }); // Ajouter un élément pour "Pas de données" legend.append("rect") .attr("x", members.length * 100 - (members.length * 50)) // Position pour le rectangle gris .attr("y", 0) .attr("width", 15) .attr("height", 15) .attr("fill", "lightgrey"); legend.append("text") .attr("x", members.length * 100 - (members.length * 50) + 20) // Texte à côté du rectangle gris .attr("y", 12) .text("Pas de données") .style("font-size", "12px") .attr("text-anchor", "start"); // Barres pour chaque membre members.forEach((member, i) => { svg.selectAll(`.bar-sleep-${member}`) .data(aggregatedData) .enter() .append("rect") .attr("x", d => xScale(d.month) + i * (xScale.bandwidth() / members.length)) .attr("y", d => { const value = d[`Sleep_${member}`]; return value === -1.0 ? yScale(2) : yScale(value); // Placer les -1.0 à une hauteur fixe, ici 2 heures }) .attr("width", xScale.bandwidth() / members.length) .attr("height", d => { const value = d[`Sleep_${member}`]; return value === -1.0 ? height - yScale(2) : height - yScale(value); // Barres grisées si -1.0 }) .attr("fill", d => { const value = d[`Sleep_${member}`]; return value === -1.0 ? "#D3D3D3" : colorMap[member]; // Gris pour -1.0 }) .on("mouseover", function(event, d) { tooltip.transition().duration(200).style("opacity", .9); // Transition d'apparition du tooltip tooltip.html(`Mois : ${d.month}<br>Sommeil moyen : ${d[`Sleep_${member}`] === -1.0 ? "Pas de données" : d[`Sleep_${member}`].toFixed(2)} heures`) // Affichage du tooltip .style("left", (event.pageX + 5) + "px") .style("top", (event.pageY - 28) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(500).style("opacity", 0); // Transition de disparition du tooltip }) .on("click", (event, d) => { const memberColor = colorMap[member]; // Récupérer la couleur du membre showDetailChart(d.month, member, selectedYear, memberColor); // Passer la couleur à la fonction showDetailChart }); }); } // Fonction pour afficher les détails function showDetailChart(month, member, year, memberColor) { // Affiche le modal const modal = d3.select("#modal"); modal.style("display", "block"); // Fermer le modal modal.select(".close").on("click", () => { modal.style("display", "none"); d3.select("#detail-visualization").selectAll("*").remove(); }); const detailContainer = d3.select("#detail-visualization"); detailContainer.selectAll("*").remove(); const filteredData = data.filter(d => formatYear(d.date) === year.toString() && formatMonth(d.date) === month); const dailyData = d3.groups(filteredData, d => formatDay(d.date)).map(([day, records]) => ({ day: day, value: d3.mean(records, d => d[`Sleep_${member}`] || 0) })); const detailSvg = detailContainer.append("svg") .attr("width", 600) .attr("height", 400) .append("g") .attr("transform", "translate(50, 50)"); const xScale = d3.scaleBand() .domain(dailyData.map(d => d.day)) .range([0, 500]) .padding(0.1); const yScale = d3.scaleLinear() .domain([0, d3.max(dailyData, d => d.value)]).nice() .range([300, 0]); // Ajout de l'axe X detailSvg.append("g") .attr("transform", "translate(0, 300)") .call(d3.axisBottom(xScale)); // Ajout de l'axe Y detailSvg.append("g") .call(d3.axisLeft(yScale)); // Titre du graphique detailSvg.append("text") .attr("x", 250) .attr("y", -20) .attr("text-anchor", "middle") .style("font-size", "16px") .text(`${member} - Sommeil du mois de ${month} (${year})`); // Ajouter la légende de l'axe X (Jours) detailSvg.append("text") .attr("x", 250) .attr("y", 340) .attr("text-anchor", "middle") .style("font-size", "12px") .text("Jours du mois"); // Ajouter la légende de l'axe Y (Heures de sommeil) detailSvg.append("text") .attr("transform", "rotate(-90)") .attr("x", -200) .attr("y", -40) .attr("text-anchor", "middle") .style("font-size", "12px") .text("Heures de sommeil moyen"); // Dessin des barres detailSvg.selectAll(".bar") .data(dailyData) .enter() .append("rect") .attr("x", d => xScale(d.day)) .attr("y", d => { const value = d.value; return value < 0 || value === null ? yScale(2) : yScale(value); }) .attr("width", xScale.bandwidth()) .attr("height", d => { const value = d.value; return value < 0 || value === null ? 300 - yScale(2) : 300 - yScale(value); }) .attr("fill", d => { const value = d.value; return value < 0 || value === null ? "lightgrey" : memberColor; }) .on("mouseover", function(event, d) { tooltip.transition().duration(200).style("opacity", 1); tooltip.html(` <div style="text-align: center;"> <strong>Jour :</strong> ${d.day}<br> <strong>Sommeil :</strong> ${d.value === -1.0 || d.value === null ? "Pas de données" : d.value.toFixed(2)} heures </div> `); }) .on("mousemove", function(event) { tooltip .style("left", (event.pageX + 15) + "px") // Décalage pour positionner le tooltip .style("top", (event.pageY + 15) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(500).style("opacity", 0); }); } // Initialisation de la visualisation updateVisualization(years[3]); // Mettre à jour la visualisation lorsque le slider est déplacé rangeSlider.on("input", function() { const selectedYear = years[this.value]; yearDisplay.text(selectedYear); updateVisualization(selectedYear); }); }); } // Visu 5 function renderSleepActivityVisualization() { fetch('../static/js/final_combined_with_all_data.json') // Adapter le chemin si nécessaire .then(response => response.json()) .then(data => { const svg = d3.select("#sleep-activity-visualization") .append("svg") .attr("width", 700) .attr("height", 300); const margin = { top: 20, right: 150, bottom: 50, left: 50 }; const width = +svg.attr("width") - margin.left - margin.right; const height = +svg.attr("height") - margin.top - margin.bottom; const g = svg.append("g").attr("transform", `translate(${margin.left},${margin.top})`); const tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("position", "absolute") .style("visibility", "hidden") .style("background", "#fff") .style("border", "1px solid #ccc") .style("padding", "5px") .style("border-radius", "4px") .style("font-size", "12px"); const colorMap = { "Maya": "#0f7e06", "Corentin": "#1d38e3", "Anis": "#d6bff4", "Amira": "#7e09bd" }; const getISOWeekNumber = (date) => { const tempDate = new Date(date); tempDate.setHours(0, 0, 0, 0); tempDate.setDate(tempDate.getDate() + 4 - (tempDate.getDay() || 7)); const yearStart = new Date(tempDate.getFullYear(), 0, 1); return Math.ceil(((tempDate - yearStart) / 86400000 + 1) / 7); }; const filteredData = data.filter(d => { const date = new Date(d.date); return date >= new Date("2023-10-01") && date <= new Date("2024-12-31"); }); const groupedData = d3.group(filteredData, d => { const date = new Date(d.date); const weekNumber = getISOWeekNumber(date); return `${date.getFullYear()}-W${weekNumber}`; }); const processedData = Array.from(groupedData, ([week, records]) => { return records.map(d => ([ { name: "Anis", steps: d.Steps_Anis, sleep: d.Sleep_Anis, calories: d.Calories_Anis }, { name: "Maya", steps: d.Steps_Maya, sleep: d.Sleep_Maya, calories: d.Calories_Maya }, { name: "Corentin", steps: d.Steps_Corentin, sleep: d.Sleep_Corentin, calories: d.Calories_Corentin }, { name: "Amira", steps: d.Steps_Amira, sleep: d.Sleep_Amira, calories: d.Calories_Amira } ].filter(d => d.steps > 0 && d.sleep > 0))).flat(); }); const x = d3.scaleLinear() .domain([0, Math.ceil(d3.max(processedData.flat(), d => d.steps))]) .range([0, width]); const y = d3.scaleLinear() .domain([0, 18]) .range([height, 0]); const radius = d3.scaleSqrt() .domain([0, Math.ceil(d3.max(processedData.flat(), d => d.calories))]) .range([3, 15]); g.append("g") .attr("transform", `translate(0,${height})`) .call(d3.axisBottom(x).ticks(10)) .append("text") .attr("fill", "black") .attr("x", width / 2) .attr("y", 40) .attr("text-anchor", "middle") .text("Steps"); g.append("g") .call(d3.axisLeft(y)) .append("text") .attr("fill", "black") .attr("transform", "rotate(-90)") .attr("x", -height / 2) .attr("y", -40) .attr("text-anchor", "middle") .text("Sleep (hours)"); const legend = svg.append("g") .attr("transform", `translate(${width + 20}, 50)`); legend.selectAll("rect") .data(Object.keys(colorMap)) .enter() .append("rect") .attr("x", 0) .attr("y", (d, i) => i * 20) .attr("width", 15) .attr("height", 15) .attr("fill", d => colorMap[d]); legend.selectAll("text") .data(Object.keys(colorMap)) .enter() .append("text") .attr("x", 20) .attr("y", (d, i) => i * 20 + 12) .text(d => d); const slider = document.getElementById("date-slider"); const playButton = document.getElementById("play-button"); let playing = false; let interval; slider.max = processedData.length - 1; const update = (index) => { const currentData = processedData[index]; const weekLabel = Array.from(groupedData.keys())[index]; document.getElementById("date-label").textContent = weekLabel; g.selectAll("circle").remove(); g.selectAll("circle") .data(currentData) .enter() .append("circle") .attr("cx", d => x(d.steps)) .attr("cy", d => y(d.sleep)) .attr("r", d => radius(d.calories)) .attr("fill", d => colorMap[d.name]) .attr("opacity", 0.7) .on("mouseover", (event, d) => { tooltip.style("visibility", "visible") .text(`${d.name}: Steps: ${d.steps}, Sleep: ${d.sleep}, Calories: ${d.calories}`); }) .on("mousemove", event => { tooltip.style("top", `${event.pageY - 10}px`) .style("left", `${event.pageX + 10}px`); }) .on("mouseout", () => { tooltip.style("visibility", "hidden"); }); }; playButton.addEventListener("click", () => { if (!playing) { playing = true; playButton.textContent = "Pause"; let index = 0; interval = setInterval(() => { if (index >= processedData.length) { clearInterval(interval); playButton.textContent = "Play"; playing = false; } else { slider.value = index; update(index); index++; } }, 800); } else { clearInterval(interval); playButton.textContent = "Play"; playing = false; } }); slider.addEventListener("input", (event) => update(+event.target.value)); update(0); }) .catch(error => console.error("Error loading data:", error)); } // Visu 6 function renderRadialDistanceChart() { fetch("../static/js/final_combined_with_all_data.json") .then((response) => response.json()) .then((data) => { const width = 300; const height = 300; const innerRadius = 30; const outerRadius = Math.min(width, height) / 2 - 20; // Filter data const filteredData = data.filter((d) => { const date = new Date(d.date); return date >= new Date("2023-10-01") && date <= new Date("2024-12-31"); }); // Group data by ISO week const groupedData = d3.group(filteredData, (d) => { const date = new Date(d.date); const weekNumber = getISOWeekNumber(date); return `${date.getFullYear()}-W${weekNumber}`; }); const processedData = Array.from(groupedData, ([week, records]) => { const aggregated = { week: week, year: week.split("-")[0], Distance_Anis: d3.sum(records, (d) => (d.Distance_Anis > 0 ? d.Distance_Anis : 0)), Distance_Maya: d3.sum(records, (d) => (d.Distance_Maya > 0 ? d.Distance_Maya : 0)), Distance_Corentin: d3.sum(records, (d) => (d.Distance_Corentin > 0 ? d.Distance_Corentin : 0)), Distance_Amira: d3.sum(records, (d) => (d.Distance_Amira > 0 ? d.Distance_Amira : 0)), Sleep_Anis: d3.mean(records, (d) => (d.Sleep_Anis > 0 ? d.Sleep_Anis : 0)), Sleep_Maya: d3.mean(records, (d) => (d.Sleep_Maya > 0 ? d.Sleep_Maya : 0)), Sleep_Corentin: d3.mean(records, (d) => (d.Sleep_Corentin > 0 ? d.Sleep_Corentin : 0)), Sleep_Amira: d3.mean(records, (d) => (d.Sleep_Amira > 0 ? d.Sleep_Amira : 0)), }; return aggregated; }); const users = ["Anis", "Maya", "Corentin", "Amira"]; users.forEach((user) => { const personKey = `Distance_${user}`; const sleepKey = `Sleep_${user}`; d3.select(`#chart-${user}`).html(""); // Clear the previous chart const svg = d3.select(`#chart-${user}`) .append("svg") .attr("width", width) .attr("height", height) .attr("viewBox", [-width / 2, -height / 2, width, height]) .attr("style", "width: 100%; height: auto; font: 10px sans-serif;"); // Tooltip const tooltip = d3.select("body").append("div") .attr("class", "tooltip-radial") .style("opacity", 0) .style("position", "absolute") .style("background", "rgba(0, 0, 0, 0.7)") .style("color", "white") .style("padding", "8px") .style("border-radius", "4px") .style("pointer-events", "none"); // Scales const x = d3.scaleBand() .domain(processedData.map((d) => d.week)) .range([0, 2 * Math.PI]) .align(0); const y = d3.scaleRadial() .domain([0, d3.max(processedData, (d) => d[personKey])]) .range([innerRadius, outerRadius]); const color = d3.scaleLinear() .domain([0, d3.max(processedData, (d) => d[sleepKey])]) .range(["lightblue", "darkblue"]); // Bars svg.append("g") .selectAll("path") .data(processedData) .join("path") .attr("d", d3.arc() .innerRadius(innerRadius) .outerRadius((d) => d[personKey] > 0 ? y(d[personKey]) : y(10)) .startAngle((d) => x(d.week)) .endAngle((d) => x(d.week) + x.bandwidth()) .padAngle(0.02) .padRadius(innerRadius)) .attr("fill", (d) => d[personKey] > 0 ? color(d[sleepKey]) : "#ccc") .on("mouseover", function (event, d) { tooltip.transition().duration(200).style("opacity", 0.9); tooltip.html(` <strong>${user}</strong><br> Semaine : ${d.week}<br> Distance : ${d[personKey] > 0 ? d[personKey].toFixed(2) : "N/A"} km<br> Sommeil : ${d[sleepKey] > 0 ? d[sleepKey].toFixed(2) + "h" : "N/A"} `) .style("left", `${event.pageX + 10}px`) .style("top", `${event.pageY - 28}px`); }) .on("mousemove", function (event) { tooltip.style("left", `${event.pageX + 10}px`).style("top", `${event.pageY - 28}px`); }) .on("mouseout", function () { tooltip.transition().duration(500).style("opacity", 0); }); // Week Labels with grouped years svg.append("g") .selectAll("g") .data(processedData) .join("g") .attr("transform", (d) => { const midAngle = (x(d.week) + x.bandwidth() / 2) * 180 / Math.PI - 90; // Angle médian const radius = outerRadius + 10; // Position juste en dehors des barres return ` rotate(${midAngle}) translate(${radius},0) `; }) .call((g) => { g.append("text") .text((d, i) => { // Affiche l'année une seule fois pour la première semaine de chaque année if (i === 0 || d.year !== processedData[i - 1].year) { return `${d.year} ${d.week.split("-")[1]}`; // Année et numéro de semaine } return `${d.week.split("-")[1]}`; // Numéro de semaine }) .attr("text-anchor", "middle") .style("font-size", "6px") // Réduction de la taille pour ne pas encombrer .style("fill", "#666"); // Couleur discrète pour les étiquettes }); // Add user label svg.append("text") .attr("text-anchor", "middle") .attr("dy", "0.5em") .style("font-size", "10px") .style("font-weight", "bold") .text(user); // Radial circles const distanceTicks = y.ticks(5); const circleGroup = svg.append("g"); circleGroup.selectAll("circle") .data(distanceTicks) .join("circle") .attr("r", (d) => y(d)) .attr("fill", "none") .attr("stroke", "#ccc") .attr("stroke-dasharray", "4 2"); circleGroup.selectAll("text") .data(distanceTicks) .join("text") .attr("x", 0) .attr("y", (d) => -y(d)) .attr("dy", "-0.3em") .attr("text-anchor", "middle") .style("font-size", "6px") .text((d) => `${d.toFixed(0)} km`); const defs = svg.append("defs"); const gradient = defs.append("linearGradient") .attr("id", "gradient") .attr("x1", "0%") .attr("y1", "0%") .attr("x2", "100%") .attr("y2", "0%"); gradient.append("stop").attr("offset", "0%").attr("stop-color", "lightblue"); gradient.append("stop").attr("offset", "100%").attr("stop-color", "darkblue"); const legendTooltip = d3.select("body").append("div") .attr("class", "tooltip-legend") .style("position", "absolute") .style("padding", "10px") .style("background", "#fff") .style("border", "1px solid #ccc") .style("border-radius", "5px") .style("box-shadow", "0 4px 8px rgba(0, 0, 0, 0.1)") // Ajout d'un effet d'ombre .style("font-size", "12px") // Police ajustée .style("display", "none"); // Extraction des valeurs dynamiques pour Min et Max const sleepMin = d3.min(processedData, (d) => d[sleepKey]); const sleepMax = d3.max(processedData, (d) => d[sleepKey]); // Afficher la légende au survol svg.on("mouseover", (event) => { legendTooltip.style("display", "block") .style("left", `${event.pageX + 10}px`) .style("top", `${event.pageY}px`) .html(` <strong>Durée de sommeil</strong><br> <div style="width: 100px; height: 10px; background: linear-gradient(lightblue, darkblue); margin-top: 5px;"></div> <div style="display: flex; justify-content: space-between; margin-top: 5px;"> <small>${sleepMin.toFixed(1)}h</small> <small>${sleepMax.toFixed(1)}h</small> </div> <div style="width: 100px; height: 10px; background: #ccc; margin-top: 10px;"></div> <small style="display: block; text-align: center; margin-top: 5px;">Valeurs manquantes</small> `); }); // Masquer la légende svg.on("mouseout", () => { legendTooltip.style("display", "none"); }); }); }) .catch((error) => console.error("Error loading data:", error)); function getISOWeekNumber(date) { const tempDate = new Date(date); tempDate.setDate(tempDate.getDate() + 4 - (tempDate.getDay() || 7)); const yearStart = new Date(tempDate.getFullYear(), 0, 1); return Math.ceil(((tempDate - yearStart) / 86400000 + 1) / 7); } } document.addEventListener("DOMContentLoaded", function () { renderStepsVisualization(); renderDistanceVisualization(); renderCaloriesVisualization(); renderSleepVisualization(); renderSleepActivityVisualization(); renderRadialDistanceChart() });