Skip to content
Snippets Groups Projects
distanceVisual.js 9.1 KiB
Newer Older
=AZIZI Anis's avatar
=AZIZI Anis committed
export 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]);
      });
  }