Newer
Older
/**
* Template Name: Bootslander
* Template URL: https://bootstrapmade.com/bootslander-free-bootstrap-landing-page-template/
* Updated: Mar 17 2024 with Bootstrap v5.3.3
* Author: BootstrapMade.com
* License: https://bootstrapmade.com/license/
*/
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
const analyses = {
steps: {
2022: `
<h3>2022 : Une année de progression générale</h3>
<ul>
<li><strong>Corentin :</strong> Pic impressionnant de 10 500 pas, le plus actif de l'année.</li>
<li><strong>Anis :</strong> Progression régulière avec une constance remarquable.</li>
<li><strong>Maya :</strong> Montée progressive culminant en fin d'année.</li>
</ul>
<p>Tous les utilisateurs montrent une augmentation continue de leur activité, avec Corentin en tête, suivi d'Anis et de Maya.</p>
`,
2023: `
<h3>2023 : Analyse des habitudes de marche</h3>
<ul>
<li><strong>Anis :</strong> Domine l'année avec un pic de 11 500 pas en mars, mais baisse notable en été.</li>
<li><strong>Corentin :</strong> Activité croissante dès février, mais baisse hivernale.</li>
<li><strong>Maya :</strong> Rythme stable avec une hausse estivale due à ses jobs étudiants.</li>
<li><strong>Amira :</strong> Moins active avec des données partielles, baisse notable en fin d'année.</li>
</ul>
<p>Des tendances saisonnières et des obligations personnelles influencent l'activité de marche.</p>
`,
2024: `
<h3>2024 : Une année de comportements variés</h3>
<ul>
<li><strong>Corentin :</strong> Forte activité en décembre, dépassant les 10 000 pas.</li>
<li><strong>Anis :</strong> Régularité tout au long de l'année avec des variations en fin d'année.</li>
<li><strong>Maya :</strong> Baisse liée au stress des examens en fin d'année.</li>
<li><strong>Amira :</strong> Activité faible, mais légèrement accrue en été.</li>
</ul>
<p>Des tendances saisonnières comme la baisse hivernale et le pic de décembre sont observées.</p>
`,
},
distanceCalories: {
2023: `
<h3>2023 : Distance et calories</h3>
<ul>
<li><strong>Anis :</strong> Leader incontesté, atteignant un pic en mars avec 484 calories brûlées.</li>
<li><strong>Corentin :</strong> Activité constante, avec une hausse estivale marquée.</li>
<li><strong>Maya et Amira :</strong> Moins actives, mais hausse notable en fin d'année.</li>
</ul>
<p>Les tendances montrent des variations saisonnières avec des pics en été et en décembre.</p>
`,
2024: `
<h3>2024 : Comparaison distance-calories</h3>
<ul>
<li><strong>Anis :</strong> Plus actif, avec 6,73 km parcourus et 484 calories brûlées en mai.</li>
<li><strong>Corentin :</strong> Activité accrue en décembre, mais reste derrière Anis.</li>
<li><strong>Maya et Amira :</strong> Activité moindre, avec une hausse en décembre liée aux fêtes.</li>
</ul>
<p>Les mois de faible activité varient selon les utilisateurs, sans tendance claire.</p>
`,
},
calories: {
2023: `
<h3>2023 : Analyse des calories brûlées</h3>
<ul>
<li><strong>Anis :</strong> Pic de 500 calories brûlées en mars, régularité tout au long de l'année.</li>
<li><strong>Corentin :</strong> Activité stable, légère baisse estivale.</li>
<li><strong>Maya :</strong> Constance avec un pic à 110 calories en septembre.</li>
<li><strong>Amira :</strong> Progression vers la fin d'année, pic à 100 calories en décembre.</li>
</ul>
<p>Une fin d'année plus active pour tout le monde.</p>
`,
2024: `
<h3>2024 : Analyse des calories</h3>
<ul>
<li><strong>Anis :</strong> Leader incontesté avec un pic de 450 calories en mai.</li>
<li><strong>Corentin :</strong> Efforts notables, pic à 300 calories en avril et décembre.</li>
<li><strong>Maya :</strong> Activité régulière entre 100 et 145 calories par mois.</li>
<li><strong>Amira :</strong> Plus sédentaire, mais pic exceptionnel de 150 calories en septembre.</li>
</ul>
<p>Les résultats reflètent des dynamiques variées entre les membres.</p>
`,
},
};
function updateAnalysis(visualization, year) {
const container = document.getElementById("analysis-content-" + visualization);
if (analyses[visualization] && analyses[visualization][year]) {
container.innerHTML = analyses[visualization][year];
} else {
container.innerHTML = "<p>Analyse indisponible pour cette année.</p>";
}
}
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
(function() {
"use strict";
/**
* Easy selector helper function
*/
const select = (el, all = false) => {
el = el.trim()
if (all) {
return [...document.querySelectorAll(el)]
} else {
return document.querySelector(el)
}
}
/**
* Easy event listener function
*/
const on = (type, el, listener, all = false) => {
let selectEl = select(el, all)
if (selectEl) {
if (all) {
selectEl.forEach(e => e.addEventListener(type, listener))
} else {
selectEl.addEventListener(type, listener)
}
}
}
/**
* Easy on scroll event listener
*/
const onscroll = (el, listener) => {
el.addEventListener('scroll', listener)
}
/**
* Navbar links active state on scroll
*/
let navbarlinks = select('#navbar .scrollto', true)
const navbarlinksActive = () => {
let position = window.scrollY + 200
navbarlinks.forEach(navbarlink => {
if (!navbarlink.hash) return
let section = select(navbarlink.hash)
if (!section) return
if (position >= section.offsetTop && position <= (section.offsetTop + section.offsetHeight)) {
navbarlink.classList.add('active')
} else {
navbarlink.classList.remove('active')
}
})
}
window.addEventListener('load', navbarlinksActive)
onscroll(document, navbarlinksActive)
/**
* Scrolls to an element with header offset
*/
const scrollto = (el) => {
let header = select('#header')
let offset = header.offsetHeight
if (!header.classList.contains('header-scrolled')) {
offset -= 20
}
let elementPos = select(el).offsetTop
window.scrollTo({
top: elementPos - offset,
behavior: 'smooth'
})
}
/**
* Toggle .header-scrolled class to #header when page is scrolled
*/
let selectHeader = select('#header')
if (selectHeader) {
const headerScrolled = () => {
if (window.scrollY > 100) {
selectHeader.classList.add('header-scrolled')
} else {
selectHeader.classList.remove('header-scrolled')
}
}
window.addEventListener('load', headerScrolled)
onscroll(document, headerScrolled)
}
/**
* Back to top button
*/
let backtotop = select('.back-to-top')
if (backtotop) {
const toggleBacktotop = () => {
if (window.scrollY > 100) {
backtotop.classList.add('active')
} else {
backtotop.classList.remove('active')
}
}
window.addEventListener('load', toggleBacktotop)
onscroll(document, toggleBacktotop)
}
/**
* Mobile nav toggle
*/
on('click', '.mobile-nav-toggle', function(e) {
select('#navbar').classList.toggle('navbar-mobile')
this.classList.toggle('bi-list')
this.classList.toggle('bi-x')
})
/**
* Mobile nav dropdowns activate
*/
on('click', '.navbar .dropdown > a', function(e) {
if (select('#navbar').classList.contains('navbar-mobile')) {
e.preventDefault()
this.nextElementSibling.classList.toggle('dropdown-active')
}
}, true)
/**
* Scrool with ofset on links with a class name .scrollto
*/
on('click', '.scrollto', function(e) {
if (select(this.hash)) {
e.preventDefault()
let navbar = select('#navbar')
if (navbar.classList.contains('navbar-mobile')) {
navbar.classList.remove('navbar-mobile')
let navbarToggle = select('.mobile-nav-toggle')
navbarToggle.classList.toggle('bi-list')
navbarToggle.classList.toggle('bi-x')
}
scrollto(this.hash)
}
}, true)
/**
* Scroll with ofset on page load with hash links in the url
*/
window.addEventListener('load', () => {
if (window.location.hash) {
if (select(window.location.hash)) {
scrollto(window.location.hash)
}
}
});
/**
* Preloader
*/
let preloader = select('#preloader');
if (preloader) {
window.addEventListener('load', () => {
preloader.remove()
});
}
/**
* Initiate glightbox
*/
const glightbox = GLightbox({
selector: '.glightbox'
});
/**
* Initiate gallery lightbox
*/
const galleryLightbox = GLightbox({
selector: '.gallery-lightbox'
});
/**
* Testimonials slider
*/
new Swiper('.testimonials-slider', {
speed: 600,
loop: true,
autoplay: {
delay: 5000,
disableOnInteraction: false
},
slidesPerView: 'auto',
pagination: {
el: '.swiper-pagination',
type: 'bullets',
clickable: true
}
});
/**
* Animation on scroll
*/
window.addEventListener('load', () => {
AOS.init({
duration: 1000,
easing: 'ease-in-out',
once: true,
mirror: false
})
});
/**
* Initiate Pure Counter
*/
new PureCounter();
function renderStepsVisualization() {
fetch('../static/js/final_combined_with_all_data.json') // Chemin à adapter si nécessaire
.then((response) => response.json())
.then((data) => {
const parseDate = d3.timeParse("%Y-%m-%d");
const formatYear = d3.timeFormat("%Y");
const formatMonth = d3.timeFormat("%Y-%m");
const members = ["Corentin", "Maya", "Anis", "Amira"];
data.forEach(d => d.date = parseDate(d.date));
const margin = { top: 50, right: 230, bottom: 150, left: 70 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svgContainer = d3.select("#steps-visualization")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom + 50); // +50 pour la légende
// Ajout du titre
svgContainer.append("text")
.attr("x", (width + margin.left + margin.right) / 2.5 ) // Centré horizontalement
.attr("y", 20) // Positionné en haut
.attr("text-anchor", "middle")
.style("font-size", "18px")
.style("font-weight", "bold")
.text("Comparaison des pas des utilisateurs");
const svg = svgContainer.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const years = [2022, 2023, 2024];
const stepsPrevYearBtn = document.getElementById("stepsPrevYear");
const stepsNextYearBtn = document.getElementById("stepsNextYear");
const stepsCurrentYearDisplay = document.getElementById("stepsCurrentYear");
stepsPrevYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent));
if (currentYearIndex > 0) {
const newYear = years[currentYearIndex - 1];
stepsCurrentYearDisplay.textContent = newYear;
updateVisualization(newYear);
stepsNextYearBtn.disabled = false;
if (currentYearIndex - 1 === 0) {
stepsPrevYearBtn.disabled = true;
}
}
});
stepsNextYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent));
if (currentYearIndex < years.length - 1) {
const newYear = years[currentYearIndex + 1];
stepsCurrentYearDisplay.textContent = newYear;
updateVisualization(newYear);
stepsPrevYearBtn.disabled = false;
if (currentYearIndex + 1 === years.length - 1) {
stepsNextYearBtn.disabled = true;
}
}
});
stepsPrevYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === 0;
stepsNextYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === years.length - 1;
updateVisualization(2024);
function updateVisualization(selectedYear) {
const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());
if (filteredData.length === 0) {
console.log(`Aucune donnée pour l'année ${selectedYear}`);
return;
}
const groupedData = d3.groups(filteredData, d => formatMonth(d.date));
const aggregatedData = groupedData.map(([month, records]) => {
const aggregated = { month };
members.forEach(member => {
aggregated[`Steps_${member}`] = d3.mean(records, d => d[`Steps_${member}`] || 0);
const xScale = d3.scaleBand()
.domain(aggregatedData.map(d => d.month))
.range([0, width])
const yScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Steps_${member}`])))]).nice()
.range([height, 0]);
const colorMap = {
"Maya": "#0f7e06",
"Corentin": "#1d38e3",
"Anis": "#d6bff4",
"Amira": "#7e09bd"
};
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");
svg.append("g").call(d3.axisLeft(yScale));
svg.append("text")
.attr("x", width / 2)
.attr("y", height + 70)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Mois");
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -50)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Nombre de pas");
members.forEach((member) => {
const lineData = aggregatedData.map(d => {
let steps = d[`Steps_${member}`] === -1.0 ? 0 : d[`Steps_${member}`];
return { month: d.month, steps };
const line = d3.line()
.x(d => xScale(d.month) + xScale.bandwidth() / 2)
.y(d => yScale(d.steps))
.defined(d => d.steps !== 0);
svg.append("path")
.data([lineData])
.attr("class", `line-${member}`)
.attr("d", line)
.attr("fill", "none")
.attr("stroke", colorMap[member])
.attr("stroke-width", 2);
const legend = svg.append("g")
.attr("transform", `translate(${width / 20}, ${height + 80})`);
members.forEach((member, i) => {
const legendGroup = legend.append("g")
.attr("transform", `translate(${i * 150}, 0)`);
legendGroup.append("rect")
.attr("width", 15)
.attr("height", 15)
.attr("fill", colorMap[member]);
legendGroup.append("text")
.attr("x", 20)
.attr("y", 12)
.style("font-size", "12px")
.text(member);
});
}
// VISU 2
function renderDistanceVisualization() {
fetch('../static/js/final_combined_with_all_data.json') // Chemin à adapter si nécessaire
.then((response) => response.json())
.then((data) => {
const parseDate = d3.timeParse("%Y-%m-%d");
const formatYear = d3.timeFormat("%Y");
const formatMonth = d3.timeFormat("%Y-%m");
const members = ["Corentin", "Maya", "Anis", "Amira"];
data.forEach(d => d.date = parseDate(d.date));
const margin = { top: 50, right: 230, bottom: 150, left: 70 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3.select("#distance-visualization")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const years = [2023, 2024];
const prevYearBtn = document.getElementById("distancePrevYear");
const nextYearBtn = document.getElementById("distanceNextYear");
const currentYearDisplay = document.getElementById("distanceCurrentYear");
// Gestion des événements des boutons
prevYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
if (currentYearIndex > 0) {
const newYear = years[currentYearIndex - 1];
currentYearDisplay.textContent = newYear;
updateVisualization(newYear);
updateAnalysis("distanceCalories", newYear);
// Activer/désactiver les boutons
nextYearBtn.disabled = false;
if (currentYearIndex - 1 === 0) {
prevYearBtn.disabled = true;
}
}
});
nextYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
if (currentYearIndex < years.length - 1) {
const newYear = years[currentYearIndex + 1];
currentYearDisplay.textContent = newYear;
updateVisualization(newYear);
updateAnalysis("distanceCalories", newYear);
// Activer/désactiver les boutons
prevYearBtn.disabled = false;
if (currentYearIndex + 1 === years.length - 1) {
nextYearBtn.disabled = true;
}
}
});
// Initialisation des boutons
prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0;
nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1;
const tooltip = d3.select("body").append("div")
.style("opacity", 0);
function updateVisualization(selectedYear) {
const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());
const groupedData = d3.groups(filteredData, d => formatMonth(d.date));
const colorMap = {
"Maya": "#0f7e06",
"Corentin": "#1d38e3",
"Anis": "#d6bff4",
"Amira": "#7e09bd"
};
const aggregatedData = groupedData.map(([month, records]) => {
const aggregated = { month };
members.forEach(member => {
aggregated[`Distance_${member}`] = d3.mean(records, d => {
const distance = d[`Distance_${member}`];
return (distance !== -1) ? distance : undefined;
}) || 0;
aggregated[`Calories_${member}`] = d3.mean(records, d => {
const calories = d[`Calories_${member}`];
return (calories !== -1) ? calories : undefined;
}) || 0;
svg.selectAll("*").remove();
const bubbleSizeScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Distance_${member}`])) )])
.range([2, 20]);
const xScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Distance_${member}`])) )])
.range([0, width]);
const yScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Calories_${member}`])) )])
.range([height, 0]);
svg.append("text")
.attr("x", width / 2)
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Comparaison des calories brûlées vs distance parcourue");
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale));
svg.append("text")
.attr("x", width / 2)
.attr("y", height + 70)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Distance (KM)");
svg.append("g").call(d3.axisLeft(yScale));
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -50)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Calories brûlées");
members.forEach((member) => {
svg.selectAll(`.bubble-${member}`)
.data(aggregatedData)
.enter()
.append("circle")
.attr("cx", d => xScale(d[`Distance_${member}`]))
.attr("cy", d => yScale(d[`Calories_${member}`]))
.attr("r", d => 0.8* bubbleSizeScale(d[`Distance_${member}`]))
.attr("fill", d => {
return (d[`Distance_${member}`] === 0 || d[`Calories_${member}`] === 0) ? "gray" : colorMap[member];
})
.style("opacity", 0.7)
.on("mouseover", function(event, d) {
tooltip.transition().duration(200).style("opacity", 0.9);
const monthFormatted = d3.timeFormat("%B")(parseDate(d.month)); // Formatage du mois
const distance = d[`Distance_${member}`] ? d[`Distance_${member}`].toFixed(2) : "Pas de données";
const calories = d[`Calories_${member}`] ? d[`Calories_${member}`].toFixed(2) : "Pas de données";
tooltip.html(`
<strong>${member}</strong><br>
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
Distance : ${distance} km<br>
Calories : ${calories} cal
`)
.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);
});
// Ajouter une légende horizontale sous le graphique
const legend = svg.append("g")
.attr("transform", `translate(${width / 2 - (members.length * 110) / 2}, ${height + 90})`); // Positionnement horizontal centré
members.forEach((member, i) => {
const legendGroup = legend.append("g")
.attr("transform", `translate(${i * 100}, 0)`); // Espacement horizontal entre les éléments
// Rectangle coloré pour la légende
legendGroup.append("rect")
.attr("width", 15)
.attr("height", 15)
.attr("fill", colorMap[member])
.style("opacity", 0.8);
// Texte descriptif à côté du rectangle
legendGroup.append("text")
.attr("x", 20) // Décalage horizontal par rapport au rectangle
.attr("y", 12) // Alignement vertical au centre
.style("font-size", "12px")
.text(member);
});
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
}
//VISU 3
function renderCaloriesVisualization() {
fetch('../static/js/final_combined_with_all_data.json')
.then((response) => response.json())
.then((data) => {
const parseDate = d3.timeParse("%Y-%m-%d");
const formatYear = d3.timeFormat("%Y");
const formatMonth = d3.timeFormat("%Y-%m");
const members = ["Corentin", "Maya", "Anis", "Amira"];
data.forEach(d => d.date = parseDate(d.date));
const margin = { top: 50, right: 230, bottom: 150, left: 70 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
const svg = d3.select("#calories-visualization")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
const years = [2022, 2023, 2024];
const prevYearBtn = document.getElementById("caloriesPrevYear");
const nextYearBtn = document.getElementById("caloriesNextYear");
const currentYearDisplay = document.getElementById("caloriesCurrentYear");
// Gestion des événements des boutons
prevYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
if (currentYearIndex > 0) {
const newYear = years[currentYearIndex - 1];
currentYearDisplay.textContent = newYear;
updateVisualization(newYear);
updateAnalysis("calories", newYear);
// Activer/désactiver les boutons
nextYearBtn.disabled = false;
if (currentYearIndex - 1 === 0) {
prevYearBtn.disabled = true;
}
}
});
nextYearBtn.addEventListener("click", () => {
const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
if (currentYearIndex < years.length - 1) {
const newYear = years[currentYearIndex + 1];
currentYearDisplay.textContent = newYear;
updateVisualization(newYear);
updateAnalysis("calories", newYear);
// Activer/désactiver les boutons
prevYearBtn.disabled = false;
if (currentYearIndex + 1 === years.length - 1) {
nextYearBtn.disabled = true;
}
}
});
// Initialisation des boutons
prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0;
nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1;
updateVisualization(2024);
function updateVisualization(selectedYear) {
const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());
if (filteredData.length === 0) {
console.log(`Aucune donnée pour l'année ${selectedYear}`);
return;
}
const groupedData = d3.groups(filteredData, d => formatMonth(d.date));
const aggregatedData = groupedData.map(([month, records]) => {
const aggregated = { month };
members.forEach(member => {
aggregated[`Calories_${member}`] = d3.mean(records, d => d[`Calories_${member}`] || 0);
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
});
return aggregated;
});
svg.selectAll("*").remove();
svg.append("text")
.attr("x", width / 2)
.attr("y", margin.top - 60)
.attr("text-anchor", "middle")
.style("font-size", "16px")
.style("font-weight", "bold")
.text("Analyse des calories brûlées par mois");
const xScale = d3.scaleBand()
.domain(aggregatedData.map(d => d.month))
.range([0, width])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Calories_${member}`])))]).nice()
.range([height, 0]);
const colorScale = d3.scaleOrdinal()
.domain(members)
.range(["#1d38e3", "#0f7e06", "#d6bff4", "#7e09bd"]);
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");
svg.append("text")
.attr("x", width / 2)
.attr("y", height + 70)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Mois");
svg.append("g").call(d3.axisLeft(yScale));
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -50)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Calories brûlées");
// Ajout de la légende
const legend = svg.append("g")
.attr("transform", `translate(${width / 2 - (members.length * 120) / 2}, ${height + 100})`); // Positionnement
members.forEach((member, i) => {
const legendGroup = legend.append("g")
.attr("transform", `translate(${i * 120}, 0)`); // Espacement horizontal
// Rectangle coloré
legendGroup.append("rect")
.attr("width", 15)
.attr("height", 15)
.attr("fill", colorScale(member))
.style("opacity", 0.8);
// Texte descriptif
legendGroup.append("text")
.attr("x", 20) // Position par rapport au rectangle
.attr("y", 12) // Alignement vertical
.style("font-size", "12px")
.text(member);
});
members.forEach(member => {
const areaData = aggregatedData.map(d => ({
month: d.month,
calories: d[`Calories_${member}`] || 0
}));
const area = d3.area()
.x(d => xScale(d.month) + xScale.bandwidth() / 2)
.y0(yScale(0.8))
.y1(d => yScale(d.calories ));
svg.append("path")
.data([areaData])
.attr("fill", colorScale(member))
.attr("opacity", 0.8)
.attr("d", area);
});
}
// Visu4
function renderSleepVisualization() {
fetch('../static/js/final_combined_with_all_data.json') // Adapter le chemin si nécessaire
.then((response) => response.json())
.then((data) => {
const parseDate = d3.timeParse("%Y-%m-%d");
const formatYear = d3.timeFormat("%Y");
const formatMonth = d3.timeFormat("%Y-%m");
const formatDay = d3.timeFormat("%d");
const members = ["Corentin", "Maya", "Anis", "Amira"];
data.forEach(d => d.date = parseDate(d.date));
// Dimensions et marges
const margin = { top: 50, right: 230, bottom: 150, left: 70 };
const width = 800 - margin.left - margin.right;
const height = 500 - margin.top - margin.bottom;
// Créer le conteneur SVG principal
const svg = d3.select("#sleep-visualization")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", `translate(${margin.left},${margin.top})`);
// Créer un conteneur pour le graphique détaillé
const detailContainer = d3.select("#sleep-visualization")
.append("div")
.attr("id", "detail-container")
.style("margin-top", "20px");
// Définir les années et la plage d'affichage
const years = [2021, 2022, 2023, 2024];
// Créer le slider
// Ajoutez les boutons Bootstrap pour naviguer entre les années
// Gestion des événements des boutons
let currentYearIndex = years.indexOf(2024); // Année initiale
// Fonction pour mettre à jour l'état des boutons
function updateButtonStates() {
if (currentYearIndex <= 0) {
document.getElementById("prevYear").style.display = "hidden"; // Cacher le bouton précédent
} else {
document.getElementById("prevYear").style.display = "inline-block"; // Réafficher le bouton précédent
}
if (currentYearIndex >= years.length - 1) {
document.getElementById("nextYear").style.display = "hidden"; // Cacher le bouton suivant
} else {
document.getElementById("nextYear").style.display = "inline-block"; // Réafficher le bouton suivant
}
}
// Initialiser l'état des boutons
updateButtonStates();
// Événement pour le bouton précédent
document.getElementById("prevYear").addEventListener("click", () => {
if (currentYearIndex > 0) {
currentYearIndex--;
document.getElementById("currentYear").textContent = years[currentYearIndex];
updateVisualization(years[currentYearIndex]);
updateButtonStates(); // Mettre à jour l'état des boutons
}
});
// Événement pour le bouton suivant
document.getElementById("nextYear").addEventListener("click", () => {
if (currentYearIndex < years.length - 1) {
currentYearIndex++;
document.getElementById("currentYear").textContent = years[currentYearIndex];
updateVisualization(years[currentYearIndex]);
updateButtonStates(); // Mettre à jour l'état des boutons
}
});
// Tooltip
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip-sleep")
.style("opacity", 0);
// Fonction de mise à jour de la visualisation
function updateVisualization(selectedYear) {
const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());
const groupedData = d3.groups(filteredData, d => formatMonth(d.date));
const colorMap = {
"Maya": "#0f7e06",
"Corentin": "#1d38e3",
"Anis": "#d6bff4",
"Amira": "#7e09bd"
};
const aggregatedData = groupedData.map(([month, records]) => {
const aggregated = { month };
members.forEach(member => {
aggregated[`Sleep_${member}`] = d3.mean(records, d => d[`Sleep_${member}`] || 0);
});
return aggregated;
});
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
svg.selectAll("*").remove();
svg.append("text")
.attr("x", width / 2) // Centrer horizontalement
.attr("y", margin.top - 60) // Positionner légèrement au-dessus du graphique
.attr("text-anchor", "middle") // Centrer le texte
.style("font-size", "16px") // Taille de police
.style("font-weight", "bold") // Gras
.text("Analyse des heures de sommeil par mois");
const xScale = d3.scaleBand()
.domain(aggregatedData.map(d => d.month))
.range([0, width])
.padding(0.2);
const yScale = d3.scaleLinear()
.domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Sleep_${member}`])))]).nice()
.range([height, 0]);
const colorScale = d3.scaleOrdinal(d3.schemeCategory10).domain(members);
// Axe X
svg.append("g")
.attr("transform", `translate(0, ${height})`)
.call(d3.axisBottom(xScale))
.selectAll("text")
.attr("transform", "rotate(-45)")