Skip to content
Snippets Groups Projects
radialActivityVisual.js 10.2 KiB
Newer Older
=AZIZI Anis's avatar
=AZIZI Anis committed
export 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([2, 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) => {
              if (
                d[personKey] <= 0 ||
                d[sleepKey] <= 0 ||
                d[sleepKey] === null ||
                d[personKey] === null
              ) {
                return "#ccc"; // Couleur grise pour les valeurs nulles ou absentes
              }
              return color(d[sleepKey]); // Couleur selon la durée de sommeil
            })
  
            .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(0.25turn, 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);
    }
  }