diff --git a/frontend/index.html b/frontend/index.html index 0e0e8546c557fa2d7dfa5435e08d64afd542c3e7..fc04504562c0b47b6103d75a24e13ab060b4ce94 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,4 +1,3 @@ - <!DOCTYPE html> <html lang="en"> @@ -71,7 +70,8 @@ <div class="row justify-content-between"> <div class="col-lg-7 pt-5 pt-lg-0 order-2 order-lg-1 d-flex align-items-center"> <div data-aos="zoom-out"> - <h1>Nos données santé racontent une histoire : découvrez comment nos habitudes façonnent notre bien-être !</h1> + <h1>Nos données santé racontent une histoire : découvrez comment nos habitudes façonnent notre bien-être ! + </h1> </div> </div> <div class="col-lg-4 order-1 order-lg-2 hero-img" data-aos="zoom-out" data-aos-delay="300"> @@ -82,112 +82,171 @@ </section><!-- End Hero --> <main id="main"> -<!-- ======= Details Section ======= --> -<section id="steps-analysis" class="details"> - <div class="container"> - <div class="row align-items-center"> - <!-- Texte à gauche --> - <div class="col-md-6" data-aos="fade-right"> - <h3>Analyse des Pas des Utilisateurs</h3> - <p> - Cette visualisation illustre les données sur le nombre de pas effectués par les utilisateurs - chaque mois au cours des années. Utilisez le slider ci-dessous pour explorer les données - par année et découvrir les tendances individuelles de chaque utilisateur. - </p> - <ul> - <li><i class="bi bi-check"></i> Analyse par mois et par année.</li> - <li><i class="bi bi-check"></i> Comparaison des pas entre utilisateurs.</li> - <li><i class="bi bi-check"></i> Visualisation interactive avec un slider.</li> - </ul> - </div> - - <!-- Visualisation à droite --> - <div class="col-md-6"> - <div id="visualization" style="position: relative;"></div> - <div id="slider-container" style="margin-top: 20px;"></div> + <!-- ======= Details Section ======= --> + <section id="steps-analysis" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Texte à gauche --> + <div class="col-md-6" data-aos="fade-right"> + <h3>Analyse des Pas des Utilisateurs</h3> + <p> + Cette visualisation illustre les données sur le nombre de pas effectués par les utilisateurs + chaque mois au cours des années. Utilisez le slider ci-dessous pour explorer les données + par année et découvrir les tendances individuelles de chaque utilisateur. + </p> + <ul> + <li><i class="bi bi-check"></i> Analyse par mois et par année.</li> + <li><i class="bi bi-check"></i> Comparaison des pas entre utilisateurs.</li> + <li><i class="bi bi-check"></i> Visualisation interactive avec un slider.</li> + </ul> + </div> + <!-- Visualisation à droite --> + <div class="col-md-6"> + <div id="visualization" style="position: relative;"></div> + <div id="slider-container" style="margin-top: 20px;"></div> + </div> + </div> </div> - </div> - </div> -</section> - + </section> - -<section id="distance-analysis" class="details"> - <div class="container"> - <div class="row align-items-center"> - <!-- Visualisation à gauche --> - <div class="col-md-6"> - <div id="distance-visualization" style="position: relative;"></div> - <div id="distance-slider-container" style="margin-top: 20px;"></div> - </div> - - <!-- Texte à droite --> - <div class="col-md-6" data-aos="fade-left"> - <h3>Analyse de la Distance Parcourue</h3> - <p> - Découvrez comment la distance parcourue est liée aux calories brûlées pour chaque utilisateur. - Utilisez le slider pour explorer les données par année et comparez les performances des différents membres. - </p> - <ul> - <li><i class="bi bi-check"></i> Relation distance-calories par mois et par année.</li> - <li><i class="bi bi-check"></i> Comparaison des membres.</li> - <li><i class="bi bi-check"></i> Visualisation interactive avec bulles et tooltip.</li> - </ul> + <section id="distance-analysis" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Visualisation à gauche --> + <div class="col-md-6 order-md-1"> + <div id="distance-visualization" style="position: relative;"></div> + <div id="distance-slider-container" style="margin-top: 20px;"></div> + </div> + <!-- Texte à droite --> + <div class="col-md-6 order-md-2" data-aos="fade-left"> + <h3>Analyse de la Distance Parcourue</h3> + <p> + Découvrez comment la distance parcourue est liée aux calories brûlées pour chaque utilisateur. + Utilisez le slider pour explorer les données par année et comparez les performances des différents + membres. + </p> + <ul> + <li><i class="bi bi-check"></i> Relation distance-calories par mois et par année.</li> + <li><i class="bi bi-check"></i> Comparaison des membres.</li> + <li><i class="bi bi-check"></i> Visualisation interactive avec bulles et tooltip.</li> + </ul> + </div> + </div> </div> - </div> - </div> -</section> -<section id="calories-analysis" class="details"> - <div class="container"> - <div class="row align-items-center"> - <!-- Texte à gauche --> - <div class="col-md-6" data-aos="fade-right"> - <h3>Analyse des Calories Brûlées</h3> - <p> - Cette visualisation explore les calories brûlées par chaque utilisateur au fil des mois. - Utilisez le slider pour naviguer entre les années et analyser les tendances des différents membres. - </p> - <ul> - <li><i class="bi bi-check"></i> Analyse des calories par mois et par année.</li> - <li><i class="bi bi-check"></i> Comparaison des utilisateurs.</li> - <li><i class="bi bi-check"></i> Visualisation interactive avec des aires.</li> - </ul> + </section> + + <section id="calories-analysis" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Texte à gauche --> + <div class="col-md-6" data-aos="fade-right"> + <h3>Analyse des Calories Brûlées</h3> + <p> + Cette visualisation explore les calories brûlées par chaque utilisateur au fil des mois. + Utilisez le slider pour naviguer entre les années et analyser les tendances des différents membres. + </p> + <ul> + <li><i class="bi bi-check"></i> Analyse des calories par mois et par année.</li> + <li><i class="bi bi-check"></i> Comparaison des utilisateurs.</li> + <li><i class="bi bi-check"></i> Visualisation interactive avec des aires.</li> + </ul> + </div> + <!-- Visualisation à droite --> + <div class="col-md-6"> + <div id="calories-visualization" style="position: relative;"></div> + <div id="calories-slider-container" style="margin-top: 20px;"></div> + </div> + </div> </div> - - <!-- Visualisation à droite --> - <div class="col-md-6"> - <div id="calories-visualization" style="position: relative;"></div> - <div id="calories-slider-container" style="margin-top: 20px;"></div> + </section> + + <section id="sleep-analysis" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Visualisation à gauche --> + <div class="col-md-6 order-md-1"> + <div id="sleep-visualization" style="position: relative;"></div> + <div id="slider-container" style="margin-top: 20px;"></div> + </div> + <!-- Texte à droite --> + <div class="col-md-6 order-md-2" data-aos="fade-left"> + <h3>Analyse des Heures de Sommeil</h3> + <p> + Cette visualisation met en évidence les heures de sommeil moyennes par mois pour chaque utilisateur. + Utilisez le slider pour explorer les données par année et visualiser les tendances individuelles. + </p> + <ul> + <li><i class="bi bi-check"></i> Analyse des heures de sommeil moyen par mois et par année.</li> + <li><i class="bi bi-check"></i> Comparaison entre utilisateurs.</li> + <li><i class="bi bi-check"></i> Détails interactifs par jour pour un mois donné.</li> + </ul> + </div> + </div> </div> - </div> - </div> -</section> -<section id="sleep-analysis" class="details"> - <div class="container"> - <div class="row align-items-center"> - <!-- Texte à gauche --> - <div class="col-md-6" data-aos="fade-right"> - <h3>Analyse des Heures de Sommeil</h3> - <p> - Cette visualisation met en évidence les heures de sommeil moyennes par mois pour chaque utilisateur. - Utilisez le slider pour explorer les données par année et visualiser les tendances individuelles. - </p> - <ul> - <li><i class="bi bi-check"></i> Analyse des heures de sommeil moyen par mois et par année.</li> - <li><i class="bi bi-check"></i> Comparaison entre utilisateurs.</li> - <li><i class="bi bi-check"></i> Détails interactifs par jour pour un mois donné.</li> - </ul> + </section> + + <section id="sleep-activity-analysis" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Texte à gauche --> + <div class="col-md-6" data-aos="fade-right"> + <h3>Analyse du Sommeil et de l'Activité</h3> + <p> + Cette visualisation interactive présente la relation entre les pas effectués, + les heures de sommeil et les calories brûlées par semaine. + Utilisez le slider pour explorer les données semaine par semaine. + </p> + <ul> + <li><i class="bi bi-check"></i> Relation entre les pas, le sommeil et les calories brûlées.</li> + <li><i class="bi bi-check"></i> Comparaison hebdomadaire pour chaque utilisateur.</li> + <li><i class="bi bi-check"></i> Régression et tendances visuelles.</li> + </ul> + </div> + <!-- Visualisation à droite --> + <div class="col-md-6"> + <div id="sleep-activity-visualization" style="position: relative;"></div> + <div class="slider-container-sleep-activity d-flex align-items-center mt-3"> + <div class="slider-label">Date: <span id="date-label">2023-W39</span></div> + <input type="range" id="date-slider" class="slider mx-2" min="0" max="10" step="1" style="flex-grow: 1;"> + <button id="play-button" class="btn btn-primary">Play</button> + </div> + </div> + </div> </div> - - <!-- Visualisation à droite --> - <div class="col-md-6"> - <div id="sleep-visualization" style="position: relative;"></div> - <div id="slider-container" style="margin-top: 20px;"></div> + </section> + + <section id="radial-distance-chart" class="details"> + <div class="container"> + <div class="row align-items-center"> + <!-- Visualisation à gauche --> + <div class="col-md-6 order-md-1"> + <div id="chart" class="d-flex justify-content-center align-items-center"></div> + </div> + <!-- Texte à droite --> + <div class="col-md-6 order-md-2" data-aos="fade-left"> + <h3>Analyse Radiale : Distance & Sommeil</h3> + <p> + Cette visualisation met en évidence la durée de sommeil moyenne en fonction de la distance parcourue chaque semaine. + Vous pouvez sélectionner une personne pour explorer ses données. + </p> + <ul> + <li><i class="bi bi-check"></i> Durée de sommeil moyenne par semaine.</li> + <li><i class="bi bi-check"></i> Distance totale parcourue chaque semaine.</li> + <li><i class="bi bi-check"></i> Visualisation intuitive et interactive en graphique radial.</li> + </ul> + <select class="form-select" id="personDropdown"> + <option value="Distance_Anis">Anis</option> + <option value="Distance_Maya">Maya</option> + <option value="Distance_Corentin">Corentin</option> + <option value="Distance_Amira">Amira</option> + </select> + </div> + </div> </div> - </div> - </div> -</section> - + </section> + </main> + + <div class="container"> <div class="copyright"> @@ -199,25 +258,25 @@ Designed by <a href="https://bootstrapmade.com/">BootstrapMade</a> </div> </div> - </footer><!-- End Footer --> + </footer><!-- End Footer --> - <a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i - class="bi bi-arrow-up-short"></i></a> - <div id="preloader"></div> + <a href="#" class="back-to-top d-flex align-items-center justify-content-center"><i + class="bi bi-arrow-up-short"></i></a> + <div id="preloader"></div> - <!-- Vendor JS Files --> - <script src="../static/vendor/purecounter/purecounter_vanilla.js"></script> - <script src="../static/vendor/aos/aos.js"></script> - <script src="../static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> - <script src="../static/vendor/glightbox/js/glightbox.min.js"></script> - <script src="../static/vendor/swiper/swiper-bundle.min.js"></script> - <script src="../static/vendor/php-email-form/validate.js"></script> - <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> + <!-- Vendor JS Files --> + <script src="../static/vendor/purecounter/purecounter_vanilla.js"></script> + <script src="../static/vendor/aos/aos.js"></script> + <script src="../static/vendor/bootstrap/js/bootstrap.bundle.min.js"></script> + <script src="../static/vendor/glightbox/js/glightbox.min.js"></script> + <script src="../static/vendor/swiper/swiper-bundle.min.js"></script> + <script src="../static/vendor/php-email-form/validate.js"></script> + <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> - <script src="https://d3js.org/d3.v7.min.js"></script> - <!-- Template Main JS File --> - <script src="../static/js/main.js"></script> + <script src="https://d3js.org/d3.v7.min.js"></script> + <!-- Template Main JS File --> + <script src="../static/js/main.js"></script> </body> diff --git a/static/css/style.css b/static/css/style.css index e02522ebacc2bca76ce4a2af1a558ce91cc70369..4cb9e47e50b42cc00ed9ea3734c12c884b3f7f5a 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1785,12 +1785,6 @@ select { text-align: center; /* Centrer le titre */ } -#chart { - height: 500px; - width: 70%; - background-color: #ffffff; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); -} .tooltip { position: absolute; @@ -1821,9 +1815,9 @@ button { cursor: pointer; border-radius: 5px; transition: background-color 0.3s; - position: fixed; + top: 20px; - right: 20px; + right: 80px; display: none; /* Hidden by default */ } @@ -2047,7 +2041,7 @@ button:hover { #tooltip { position: absolute; - background-color: rgba(0, 0, 0, 0.8); + color: white; padding: 8px; border-radius: 5px; @@ -2055,3 +2049,51 @@ button:hover { pointer-events: none; display: none; } +/* Visu 5 */ +/* Sleep and Activity Analysis */ +#sleep-activity-analysis { + width: 100%; + padding: 20px; + margin-bottom: 30px; +} + +#sleep-activity-visualization { + width: 100%; + height: auto; + + border-radius: 8px; + + padding: 20px; + position: relative; +} + + + +.play-button:hover { + background-color: #0056b3; +} + + +.slider-container { + display: flex; + align-items: center; + justify-content: space-between; + gap: 10px; + width: 100%; + margin-top: 20px; +} + +.slider-label { + font-size: 16px; + font-weight: bold; +} + +.slider { + flex-grow: 1; +} + +#play-button { + white-space: nowrap; +} + + diff --git a/static/js/main.js b/static/js/main.js index a2662154a6dc097829ec82968132004de9d80e98..74317201e89bbde0f38aef5daa30cecf60451b1b 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -761,9 +761,418 @@ function renderSleepVisualization() { }); } + + + +// 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++; + } + }, 1000); + } 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 = 600; + const height = 600; + const innerRadius = 50; + const outerRadius = Math.min(width, height) / 2 - 100; + + // 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], // Extract the year from the week + 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 dropdown = document.getElementById("personDropdown"); + dropdown.addEventListener("change", () => updateChart(dropdown.value)); + + // Initial chart rendering + updateChart(dropdown.value); + + function updateChart(personKey) { + const sleepKey = personKey.replace("Distance", "Sleep"); + d3.select("#chart").html(""); // Clear the previous chart + + const svg = d3.select("#chart") + .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;"); + + // 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"]); + + // Add year to the center + const uniqueYears = [...new Set(processedData.map((d) => d.year))]; + if (uniqueYears.length === 1) { + svg + .append("text") + .attr("text-anchor", "middle") + .attr("dy", "0.35em") + .style("font-size", "20px") + .text(uniqueYears[0]); + } + + // Bars + // 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(20)) // Hauteur fixe (20 km) pour valeurs manquantes + .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") // Couleur grise pour valeurs manquantes +.append("title") +.text((d) => `${d.week}\nDistance: ${d[personKey] > 0 ? d[personKey].toFixed(2) : "N/A"}\nSleep: ${d[sleepKey] > 0 ? d[sleepKey].toFixed(2) + "h" : "N/A"}`); + + // 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; + const radius = outerRadius + 25; + return ` + rotate(${midAngle}) + translate(${radius},0) + `; +}) +.call((g) => { + g.append("text") + .text((d, i) => { + // Only show the year once for the first week of each year + if (i === 0 || d.year !== processedData[i - 1].year) { + return `${d.year} ${d.week.split("-")[1]}`; + } + return `${d.week.split("-")[1]}`; + }) + .attr("text-anchor", "middle"); +}); +// Legend +const defs = svg.append("defs"); +const gradient = defs.append("linearGradient") + .attr("id", "sleepGradient") + .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 legend = svg.append("g") + .attr("transform", `translate(${-width / 2 + 30}, ${-height / 2 + 50})`); // Position à gauche + +// Title +legend.append("text") + .attr("y", 0) + .attr("x", 0) + .text("Durée de sommeil") + .attr("font-size", "12px") + .attr("text-anchor", "start") + .attr("font-weight", "bold"); + +// Gradient bar +legend.append("rect") + .attr("x", 0) + .attr("y", 15) // Space below the title + .attr("width", 100) // Longer gradient for better visibility + .attr("height", 10) + .style("fill", "url(#sleepGradient)"); + +// Min and Max values for sleep +legend.append("text") + .attr("x", -5) // Align with start of gradient + .attr("y", 35) + .text(`${d3.min(processedData, (d) => d[sleepKey]).toFixed(1)}h`) + .attr("font-size", "10px") + .attr("text-anchor", "start"); + +legend.append("text") + .attr("x", 105) // Align with end of gradient + .attr("y", 35) + .text(`${d3.max(processedData, (d) => d[sleepKey]).toFixed(1)}h`) + .attr("font-size", "10px") + .attr("text-anchor", "start"); + +// Gray bar for missing data +legend.append("rect") + .attr("x", 0) + .attr("y", 50) // Space below the gradient + .attr("width", 100) // Same width as gradient for consistency + .attr("height", 10) + .style("fill", "#ccc"); + +// Label for gray bar +legend.append("text") + .attr("x", 0) // Align with start of gray bar + .attr("y", 70) // Space below the gray bar + .text("Valeur manquante") + .attr("font-size", "10px") + .attr("text-anchor", "start"); + + + // Add radial distance circles +const distanceTicks = y.ticks(5); // Nombre de cercles (5 niveaux ici) +const circleGroup = svg.append("g") + .attr("class", "distance-circles"); + +// Dessiner les cercles +circleGroup.selectAll("circle") + .data(distanceTicks) + .join("circle") + .attr("r", (d) => y(d)) + .attr("fill", "none") + .attr("stroke", "#ccc") + .attr("stroke-dasharray", "4 2"); // Ligne pointillée (facultatif) + +// Ajouter les étiquettes pour les distances +circleGroup.selectAll("text") + .data(distanceTicks) + .join("text") + .attr("x", 0) + .attr("y", (d) => -y(d)) // Positionné au-dessus des cercles + .attr("dy", "-0.3em") + .attr("text-anchor", "middle") + .attr("font-size", "10px") + .text((d) => `${d.toFixed(0)} km`); // Format pour afficher les valeurs + } + }) + .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() });