Skip to content
Snippets Groups Projects
main.js 58 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: {
        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>";
      }
    }
    
    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);
    
              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)")