"...logging-and-monitoring/central-logging-guide.rst" did not exist on "be2724a655185faae9abd57c8d07c12e48fd4aba"
Newer
Older
1
2
3
4
5
6
7
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
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
/**
* 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/
*/
(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"
};
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
.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];
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
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);
// 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);
// 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)
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
.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) {
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
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>
Mois : ${monthFormatted}<br>
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);
});
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
}
//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];
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
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);
// 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);
// 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);
685
686
687
688
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
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
});
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;
});
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
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)")
.style("text-anchor", "end");
// Légende de l'axe X
svg.append("text")
.attr("x", width / 2)
.attr("y", height + 70)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Mois");
// Axe Y
svg.append("g").call(d3.axisLeft(yScale));
// Légende de l'axe Y
svg.append("text")
.attr("transform", "rotate(-90)")
.attr("x", -height / 2)
.attr("y", -50)
.attr("text-anchor", "middle")
.style("font-size", "14px")
.text("Sommeil moyen (heures)");
// Légende des membres (rectangles colorés)
// Créer un conteneur pour la légende
const legend = svg.append("g")
.attr("transform", `translate(${width / 3}, ${height + margin.bottom - 50})`) // Positionner la légende en bas, centrée
.attr("text-anchor", "middle");
// Ajouter les éléments de la légende
members.forEach((member, i) => {
legend.append("rect")
.attr("x", i * 100 - (members.length * 50)) // Espacement horizontal entre les rectangles
.attr("y", 0)
.attr("width", 15)
.attr("height", 15)
.attr("fill", colorMap[member]);
legend.append("text")
.attr("x", i * 100 - (members.length * 50) + 20) // Texte à côté du rectangle
.attr("y", 12)
.text(member)
.style("font-size", "12px")
.attr("text-anchor", "start");
});
// Ajouter un élément pour "Pas de données"
legend.append("rect")
.attr("x", members.length * 100 - (members.length * 50)) // Position pour le rectangle gris
.attr("y", 0)
.attr("width", 15)
.attr("height", 15)
.attr("fill", "lightgrey");
legend.append("text")
.attr("x", members.length * 100 - (members.length * 50) + 20) // Texte à côté du rectangle gris
.attr("y", 12)
.text("Pas de données")
.style("font-size", "12px")
.attr("text-anchor", "start");
// Barres pour chaque membre
members.forEach((member, i) => {
svg.selectAll(`.bar-sleep-${member}`)
.data(aggregatedData)
.enter()
.append("rect")
.attr("x", d => xScale(d.month) + i * (xScale.bandwidth() / members.length))
.attr("y", d => {
const value = d[`Sleep_${member}`];
return value === -1.0 ? yScale(2) : yScale(value); // Placer les -1.0 à une hauteur fixe, ici 2 heures
})
.attr("width", xScale.bandwidth() / members.length)
.attr("height", d => {
const value = d[`Sleep_${member}`];
return value === -1.0 ? height - yScale(2) : height - yScale(value); // Barres grisées si -1.0
})
.attr("fill", d => {
const value = d[`Sleep_${member}`];
return value === -1.0 ? "#D3D3D3" : colorMap[member]; // Gris pour -1.0
})
.on("mouseover", function(event, d) {
tooltip.transition().duration(200).style("opacity", .9); // Transition d'apparition du tooltip
tooltip.html(`Mois : ${d.month}<br>Sommeil moyen : ${d[`Sleep_${member}`] === -1.0 ? "Pas de données" : d[`Sleep_${member}`].toFixed(2)} heures`) // Affichage du tooltip
.style("left", (event.pageX + 5) + "px")
.style("top", (event.pageY - 28) + "px");
})
.on("mouseout", function() {