Skip to content
Snippets Groups Projects
main.js 63.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • 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++;
                }
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
              }, 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;");
    
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
            // 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")
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
              .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")
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
              .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")
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
              .style("font-size", "6px")
    
              .text((d) => `${d.toFixed(0)} km`);
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
              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()