diff --git a/static/css/style.css b/static/css/style.css index 748eeb18c4fe8beef73657e7b9cb8ce3b2ebf8ab..7de14d6699677bbb7af90ab925a81e127e61202b 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2044,7 +2044,7 @@ button:hover { transform: translate(-50%, -50%); /* Centrer autour de la souris */ z-index: 9999; } -.tooltip-distance { +.tooltip-distance .tooltip-radial { position: absolute; background-color: rgba(0, 0, 0, 0.7); /* Fond noir semi-transparent */ color: white; /* Texte blanc */ diff --git a/static/js/main.js b/static/js/main.js index e8e875df6d85cdf7b07d8ea3c5f2830ed68b9f5c..4d2afcf043ccb8fc2d1bda8411fba02069668345 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1369,6 +1369,17 @@ function renderRadialDistanceChart() { .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)) @@ -1396,14 +1407,55 @@ function renderRadialDistanceChart() { .padAngle(0.02) .padRadius(innerRadius)) .attr("fill", (d) => d[personKey] > 0 ? color(d[sleepKey]) : "#ccc") - .append("title") - .text((d) => `${d.week}\nDistance: ${d[personKey] > 0 ? d[personKey].toFixed(2) : "N/A"} km\nSommeil: ${d[sleepKey] > 0 ? d[sleepKey].toFixed(2) + "h" : "N/A"}`); + .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", "-1em") - .style("font-size", "14px") + .attr("dy", "0.5em") + .style("font-size", "10px") .style("font-weight", "bold") .text(user); @@ -1426,8 +1478,71 @@ function renderRadialDistanceChart() { .attr("y", (d) => -y(d)) .attr("dy", "-0.3em") .attr("text-anchor", "middle") - .style("font-size", "10px") + .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 legend = svg.append("g") + .attr("transform", `translate(${width / 2 - 100}, ${-height / 2 })`); // Position en haut à droite + +// Titre de la légende +legend.append("text") + .attr("y", -10) + .attr("x", 0) + .text("Durée de sommeil") + .attr("font-size", "9px") // Taille réduite + .attr("text-anchor", "start") + .attr("font-weight", "bold"); + +// Barre dégradée pour la durée de sommeil +legend.append("rect") + .attr("x", 0) + .attr("y", 0) + .attr("width", 60) // Réduction de la largeur + .attr("height", 6) // Réduction de la hauteur + .style("fill", "url(#gradient)"); + +// Min et Max valeurs pour le sommeil +legend.append("text") + .attr("x", 0) + .attr("y", 15) // Position ajustée pour aligner sous la barre + .text("Min") + .attr("font-size", "7px") // Taille encore réduite + .attr("text-anchor", "start"); + +legend.append("text") + .attr("x", 60) // Aligné avec la fin de la barre + .attr("y", 15) // Position ajustée pour aligner sous la barre + .text("Max") + .attr("font-size", "7px") // Taille encore réduite + .attr("text-anchor", "end"); + +// Rectangle gris pour les valeurs manquantes +legend.append("rect") + .attr("x", 0) + .attr("y", 25) // Position ajustée sous les min/max + .attr("width", 60) // Largeur alignée avec la barre dégradée + .attr("height", 6) // Hauteur réduite + .style("fill", "#ccc"); + +// Texte pour les valeurs manquantes +legend.append("text") + .attr("x", 0) + .attr("y", 40) // Position ajustée sous le rectangle gris + .text("Valeurs manquantes") + .attr("font-size", "7px") // Taille réduite + .attr("text-anchor", "start"); + + }); }) .catch((error) => console.error("Error loading data:", error)); @@ -1440,8 +1555,6 @@ function renderRadialDistanceChart() { } } - - document.addEventListener("DOMContentLoaded", function () { renderStepsVisualization(); renderDistanceVisualization();