export 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-sleepactivity"); 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("Nombres de pas"); 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("Nombres d'heurs de sommeil"); 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); // Ajout de la légende pour la taille des bulles legend .append("text") .attr("x", 0) .attr("y", Object.keys(colorMap).length * 20 + 50) .text("calories brûlées"); legend .append("circle") .attr("cx", 3) .attr("cy", Object.keys(colorMap).length * 20 + 70) .attr("r", radius(10)) // Taille de la bulle pour 10 calories (ajustez si nécessaire) .attr("fill", "#ccc") .attr("opacity", 0.7); legend .append("text") .attr("x", 30) .attr("y", Object.keys(colorMap).length * 20 + 75) .text("3 calories"); legend .append("circle") .attr("cx", 10) .attr("cy", Object.keys(colorMap).length * 20 + 100) .attr("r", radius(100)) // Taille de la bulle pour 100 calories .attr("fill", "#ccc") .attr("opacity", 0.7); legend .append("text") .attr("x", 30) .attr("y", Object.keys(colorMap).length * 20 + 105) .text("600 calories"); 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}: Pas: ${d.steps}, Sommeil: ${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++; } }, 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)); }