Skip to content
Snippets Groups Projects
main.js 58.8 KiB
Newer Older
  • Learn to ignore specific revisions
  • RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    /**
    * 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/
    */
    
    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: {
    
          <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: {
    
        2022: `
          <h3>2022 : Distance et calories</h3>
          <ul>
            <li><strong>Anis :</strong> Anis reste actif, mais légèrement derrière Corentin.</li>
            <li><strong>Corentin prend la tête :</strong> En 2022, avec l’introduction du suivi des calories brûlées en juillet, Corentin prend la tête du groupe. Il dépasse légèrement Anis en atteignant un pic de 220 calories brûlées, tandis qu’Anis reste un peu en retrait.</li>
            <li><strong>Maya :</strong> Maya, une dépense calorique modeste</li>
            <li><strong>Amira :</strong> Amira ne dispose pas de données sur les calories brûlées, n'ayant pas encore d'iPhone.</li>
    
          </ul>
          <p>Les tendances montrent des variations saisonnières avec des pics en été et en décembre.</p>
        `,
    
        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>";
      }
    }
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    (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();
    
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    })();
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
    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);
    
                      updateAnalysis("steps", 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);
    
                      updateAnalysis("steps", 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);
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
                      });
    
                      return aggregated;
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
                  });
    
                  svg.selectAll("*").remove();
    
    
                  const xScale = d3.scaleBand()
                      .domain(aggregatedData.map(d => d.month))
                      .range([0, width])
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
                  const yScale = d3.scaleLinear()
                      .domain([0, d3.max(aggregatedData, d => Math.max(...members.map(member => d[`Steps_${member}`])))]).nice()
                      .range([height, 0]);
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
                  const colorMap = {
                      "Maya": "#0f7e06",
                      "Corentin": "#1d38e3",
                      "Anis": "#d6bff4",
                      "Amira": "#7e09bd"
                  };
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
                  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);
    
                  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"];
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
              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;
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
    
              const tooltip = d3.select("body").append("div")
    
                  .attr("class", "tooltip-distance")
    
                  .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;
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
                      });
    
                      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 - 75)
    
                      .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>
    
            Mois : ${d.month}<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);
    });
    
              updateVisualization(years[1]);
    
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
          });
    
    }
    
    //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);
    
              });
              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);
            });
          }
    
    RABEHI AMIRA p2312013's avatar
    RABEHI AMIRA p2312013 committed
    
    
    // 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;
              });
    
              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);