From 6234fa02167a7b36561a7f215c7c1b3f305a067a Mon Sep 17 00:00:00 2001 From: p2312013 <amira.rabehi@etu.univ-lyon1.fr> Date: Thu, 16 Jan 2025 19:06:39 +0100 Subject: [PATCH] started fixing the 4th visualisation --- static/js/main.js | 422 +++++++++++----------------------------------- 1 file changed, 102 insertions(+), 320 deletions(-) diff --git a/static/js/main.js b/static/js/main.js index e578e1d..5e8ca82 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -999,8 +999,7 @@ function renderCaloriesVisualization() { } }); } - -// Visu4 +// Visu4 - Découpe en 4 visualisations distinctes avec légende, modal et gestion des valeurs manquantes function renderSleepVisualization() { fetch("../static/js/final_combined_with_all_data.json") // Adapter le chemin si nécessaire .then((response) => response.json()) @@ -1012,85 +1011,58 @@ function renderSleepVisualization() { 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; + // Dimensions et marges pour chaque sous-graphe + const margin = { top: 30, right: 30, bottom: 50, left: 50 }; + const fullWidth = 600; + const fullHeight = 400; + const width = (fullWidth - margin.left - margin.right) / 2; + const height = (fullHeight - margin.top - margin.bottom) / 2; // 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})`); + .attr("width", fullWidth) + .attr("height", fullHeight); - // Créer un conteneur pour le graphique détaillé - const detailContainer = d3 - .select("#sleep-visualization") + // Tooltip + const tooltip = d3 + .select("body") .append("div") - .attr("id", "detail-container") - .style("margin-top", "20px"); + .attr("class", "tooltip-sleep") + .style("opacity", 0); - // Définir les années et la plage d'affichage - const years = [2021, 2022, 2023, 2024]; - // Créer le slider + // Modal pour afficher les détails + const modal = d3.select("#modal"); + modal.select(".close").on("click", () => { + modal.style("display", "none"); + d3.select("#detail-visualization").selectAll("*").remove(); + }); + + // Navigation temporelle const prevYearBtn = document.getElementById("sleepPrevYear"); const nextYearBtn = document.getElementById("sleepNextYear"); const currentYearDisplay = document.getElementById("sleepCurrentYear"); + const years = [2021, 2022, 2023, 2024]; - // Gestion des événements des boutons prevYearBtn.addEventListener("click", () => { - const currentYearIndex = years.indexOf( - parseInt(currentYearDisplay.textContent) - ); + const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex > 0) { const newYear = years[currentYearIndex - 1]; currentYearDisplay.textContent = newYear; - updateVisualization(newYear); // Affiche la visualisation pour 2024 (par défaut) - updateAnalysis("sleep", newYear); // Assure que l'analyse correspondante est également affichée - - // Activer/désactiver les boutons - nextYearBtn.disabled = false; - if (currentYearIndex - 1 === 0) { - prevYearBtn.disabled = true; - } + updateVisualization(newYear); } }); nextYearBtn.addEventListener("click", () => { - const currentYearIndex = years.indexOf( - parseInt(currentYearDisplay.textContent) - ); + const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent)); if (currentYearIndex < years.length - 1) { const newYear = years[currentYearIndex + 1]; currentYearDisplay.textContent = newYear; - updateVisualization(newYear); // Affiche la visualisation pour 2024 (par défaut) - updateAnalysis("sleep", newYear); // Assure que l'analyse correspondante est également affichée - - // Activer/désactiver les boutons - prevYearBtn.disabled = false; - if (currentYearIndex + 1 === years.length - 1) { - nextYearBtn.disabled = true; - } + updateVisualization(newYear); } }); - // Initialisation des boutons - prevYearBtn.disabled = - years.indexOf(parseInt(currentYearDisplay.textContent)) === 0; - nextYearBtn.disabled = - years.indexOf(parseInt(currentYearDisplay.textContent)) === - years.length - 1; - - // 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( @@ -1110,303 +1082,113 @@ function renderSleepVisualization() { members.forEach((member) => { aggregated[`Sleep_${member}`] = d3.mean( records, - (d) => d[`Sleep_${member}`] || 0 + (d) => d[`Sleep_${member}`] || -1 ); }); return aggregated; }); - svg.selectAll("*").remove(); + svg.selectAll("g.member-group").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"); + // Créer un graphe pour chaque membre + members.forEach((member, memberIndex) => { + const row = Math.floor(memberIndex / 2); + const col = memberIndex % 2; - const xScale = d3 - .scaleBand() - .domain(aggregatedData.map((d) => d.month)) - .range([0, width]) - .padding(0.2); + const memberGroup = svg + .append("g") + .attr("class", "member-group") + .attr( + "transform", + `translate(${margin.left + col * (width + margin.right)}, ${ + margin.top + row * (height + margin.bottom) + })` + ); - const yScale = d3 - .scaleLinear() - .domain([ - 0, - d3.max(aggregatedData, (d) => - Math.max(...members.map((member) => d[`Sleep_${member}`])) - ), - ]) - .nice() - .range([height, 0]); + const memberData = aggregatedData.map((d) => ({ + month: d.month, + sleep: d[`Sleep_${member}`], + })); - const colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(members); + const xScale = d3 + .scaleBand() + .domain(memberData.map((d) => d.month)) + .range([0, width]) + .padding(0.2); - // Axe X - svg + const yScale = d3 + .scaleLinear() + .domain([0, d3.max(memberData, (d) => (d.sleep === -1 ? 10 : d.sleep))]) + .nice() + .range([height, 0]); + + + // Axe X avec rotation des labels + memberGroup .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)"); + .attr("transform", "rotate(-45)") // Rotation pour éviter les chevauchements + .style("text-anchor", "end") // Alignement à droite pour plus de clarté + .style("font-size", "10px"); // Taille des labels ajustée pour s'adapter à l'espace - // 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]); + + // Axe Y + memberGroup.append("g").call(d3.axisLeft(yScale)); - legend + // Titre du sous-graphe + memberGroup .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) + .attr("x", width / 2) + .attr("y", -10) + .attr("text-anchor", "middle") + .style("font-size", "14px") + .style("font-weight", "bold") + .text(`Sommeil de ${member}`); + + // Barres pour le membre + memberGroup + .selectAll(".bar-sleep") + .data(memberData) .enter() .append("rect") - .attr( - "x", - (d) => xScale(d.month) + i * (xScale.bandwidth() / members.length) + .attr("x", (d) => xScale(d.month)) + .attr("y", (d) => (d.sleep === -1 ? yScale(yScale.domain()[1]) : yScale(d.sleep))) + .attr("width", xScale.bandwidth()) + .attr("height", (d) => + d.sleep === -1 ? height - yScale(10) : height - yScale(d.sleep) ) - .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 - }) + .attr("fill", (d) => (d.sleep === -1 ? "#D3D3D3" : colorMap[member])) .on("mouseover", function (event, d) { - tooltip.transition().duration(200).style("opacity", 0.9); // Transition d'apparition du tooltip + tooltip.transition().duration(200).style("opacity", 1); + tooltip.html(` + <div style="text-align: center;"> + <strong>Mois :</strong> ${d.month}<br> + <strong>Sommeil moyen :</strong> ${ + d.sleep === -1 ? "Pas de données" : d.sleep.toFixed(2) + } heures + </div> + `); + }) + .on("mousemove", function (event) { 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"); + .style("left", event.pageX + 15 + "px") + .style("top", event.pageY + 15 + "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 + tooltip.transition().duration(500).style("opacity", 0); }); }); } - // 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]); - + updateVisualization(2024); }); } + + // Visu 5 function renderSleepActivityVisualization() { fetch("../static/js/final_combined_with_all_data.json") // Adapter le chemin si nécessaire -- GitLab