-
=AZIZI Anis authored=AZIZI Anis authored
sleepActivityVisual.js 7.73 KiB
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));
}