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); } }