/** * 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(); })(); // VISU 1 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 svg = d3.select("#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 = [2021, 2022, 2023, 2024]; const sliderContainer = d3.select("#slider-container"); const rangeSlider = sliderContainer.append("input") .attr("type", "range") .attr("min", 0) .attr("max", years.length - 1) .attr("value", years.indexOf(2024)) .style("width", "60%"); const yearDisplay = sliderContainer.append("span") .text(years[years.indexOf(2024)]); 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); }); return aggregated; }); svg.selectAll("*").remove(); 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[`Steps_${member}`])))]).nice() .range([height, 0]); const colorMap = { "Maya": "#0f7e06", "Corentin": "#1d38e3", "Anis": "#d6bff4", "Amira": "#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("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); }); } rangeSlider.on("input", function () { const selectedYear = years[this.value]; yearDisplay.text(selectedYear); updateVisualization(selectedYear); }); }); } // 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 sliderContainer = d3.select("#distance-slider-container"); const rangeSlider = sliderContainer.append("input") .attr("type", "range") .attr("min", 0) .attr("max", years.length - 1) .attr("value", years.indexOf(2024)) .style("width", "60%"); const yearDisplay = sliderContainer.append("span") .text(years[years.indexOf(2024)]); const tooltip = d3.select("body").append("div") .attr("class", "tooltip") .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; }); return aggregated; }); 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) .attr("y", margin.top - 60) .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 => 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", .9); tooltip.html(`Mois : ${d.month}<br>Distance : ${d[`Distance_${member}`]} km<br>Calories : ${d[`Calories_${member}`]} cal`) .style("left", (event.pageX + 5) + "px") .style("top", (event.pageY - 28) + "px"); }) .on("mouseout", function() { tooltip.transition().duration(500).style("opacity", 0); }); }); } updateVisualization(years[1]); rangeSlider.on("input", function() { const selectedYear = years[this.value]; yearDisplay.text(selectedYear); updateVisualization(selectedYear); }); }); } //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 sliderContainer = d3.select("#calories-slider-container"); const rangeSlider = sliderContainer.append("input") .attr("type", "range") .attr("min", 0) .attr("max", years.length - 1) .attr("value", years.indexOf(2024)) .style("width", "60%"); const yearDisplay = sliderContainer.append("span") .text(years[years.indexOf(2024)]); 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); }); 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"); 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)) .y1(d => yScale(d.calories)); svg.append("path") .data([areaData]) .attr("fill", colorScale(member)) .attr("opacity", 0.8) .attr("d", area); }); } rangeSlider.on("input", function () { const selectedYear = years[this.value]; yearDisplay.text(selectedYear); updateVisualization(selectedYear); }); }); } document.addEventListener("DOMContentLoaded", function () { renderStepsVisualization(); renderDistanceVisualization(); renderCaloriesVisualization(); });