Skip to content
Snippets Groups Projects
main.js 52.4 KiB
Newer Older
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/
*/

(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);

                  stepsNextYearBtn.disabled = false;
                  if (currentYearIndex - 1 === 0) {
                      stepsPrevYearBtn.disabled = true;
                  }
              }
          });
          stepsNextYearBtn.addEventListener("click", () => {
              const currentYearIndex = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent));
              if (currentYearIndex < years.length - 1) {
                  const newYear = years[currentYearIndex + 1];
                  stepsCurrentYearDisplay.textContent = newYear;
                  updateVisualization(newYear);

                  stepsPrevYearBtn.disabled = false;
                  if (currentYearIndex + 1 === years.length - 1) {
                      stepsNextYearBtn.disabled = true;
                  }
              }
          });
          stepsPrevYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === 0;
          stepsNextYearBtn.disabled = years.indexOf(parseInt(stepsCurrentYearDisplay.textContent)) === years.length - 1;

          updateVisualization(2024);

          function updateVisualization(selectedYear) {
              const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());

              if (filteredData.length === 0) {
                  console.log(`Aucune donnée pour l'année ${selectedYear}`);
                  return;
              }

              const groupedData = d3.groups(filteredData, d => formatMonth(d.date));

              const aggregatedData = groupedData.map(([month, records]) => {
                  const aggregated = { month };
                  members.forEach(member => {
                      aggregated[`Steps_${member}`] = d3.mean(records, d => d[`Steps_${member}`] || 0);
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);

        // Activer/désactiver les boutons
        nextYearBtn.disabled = false;
        if (currentYearIndex - 1 === 0) {
            prevYearBtn.disabled = true;
        }
    }
});

nextYearBtn.addEventListener("click", () => {
    const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
    if (currentYearIndex < years.length - 1) {
        const newYear = years[currentYearIndex + 1];
        currentYearDisplay.textContent = newYear;
        updateVisualization(newYear);

        // Activer/désactiver les boutons
        prevYearBtn.disabled = false;
        if (currentYearIndex + 1 === years.length - 1) {
            nextYearBtn.disabled = true;
        }
    }
});

// Initialisation des boutons
prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0;
nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1;
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 : ${monthFormatted}<br>
        Distance : ${distance} km<br>
        Calories : ${calories} cal
    `)
    .style("left", `${event.pageX + 10}px`)
    .style("top", `${event.pageY - 28}px`);
})
.on("mousemove", function(event) {
    tooltip.style("left", `${event.pageX + 10}px`).style("top", `${event.pageY - 28}px`);
})
.on("mouseout", function() {
    tooltip.transition().duration(500).style("opacity", 0);
});
// Ajouter une légende horizontale sous le graphique
const legend = svg.append("g")
    .attr("transform", `translate(${width / 2 - (members.length * 110) / 2}, ${height + 90})`); // Positionnement horizontal centré

members.forEach((member, i) => {
    const legendGroup = legend.append("g")
        .attr("transform", `translate(${i * 100}, 0)`); // Espacement horizontal entre les éléments

    // Rectangle coloré pour la légende
    legendGroup.append("rect")
        .attr("width", 15)
        .attr("height", 15)
        .attr("fill", colorMap[member])
        .style("opacity", 0.8);

    // Texte descriptif à côté du rectangle
    legendGroup.append("text")
        .attr("x", 20) // Décalage horizontal par rapport au rectangle
        .attr("y", 12) // Alignement vertical au centre
        .style("font-size", "12px")
        .text(member);
});
          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);

        // Activer/désactiver les boutons
        nextYearBtn.disabled = false;
        if (currentYearIndex - 1 === 0) {
            prevYearBtn.disabled = true;
        }
    }
});

nextYearBtn.addEventListener("click", () => {
    const currentYearIndex = years.indexOf(parseInt(currentYearDisplay.textContent));
    if (currentYearIndex < years.length - 1) {
        const newYear = years[currentYearIndex + 1];
        currentYearDisplay.textContent = newYear;
        updateVisualization(newYear);

        // Activer/désactiver les boutons
        prevYearBtn.disabled = false;
        if (currentYearIndex + 1 === years.length - 1) {
            nextYearBtn.disabled = true;
        }
    }
});
// Initialisation des boutons
prevYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === 0;
nextYearBtn.disabled = years.indexOf(parseInt(currentYearDisplay.textContent)) === years.length - 1;

      updateVisualization(2024);

      function updateVisualization(selectedYear) {
        const filteredData = data.filter(d => formatYear(d.date) === selectedYear.toString());

        if (filteredData.length === 0) {
          console.log(`Aucune donnée pour l'année ${selectedYear}`);
          return;
        }

        const groupedData = d3.groups(filteredData, d => formatMonth(d.date));

        const aggregatedData = groupedData.map(([month, records]) => {
          const aggregated = { month };
          members.forEach(member => {
            aggregated[`Calories_${member}`] = d3.mean(records, d => d[`Calories_${member}`] || 0);
          });
          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)")
              .style("text-anchor", "end");

          // Légende de l'axe X
          svg.append("text")
              .attr("x", width / 2)
              .attr("y", height + 70)
              .attr("text-anchor", "middle")
              .style("font-size", "14px")
              .text("Mois");

          // Axe Y
          svg.append("g").call(d3.axisLeft(yScale));

          // Légende de l'axe Y
          svg.append("text")
              .attr("transform", "rotate(-90)")
              .attr("x", -height / 2)
              .attr("y", -50)
              .attr("text-anchor", "middle")
              .style("font-size", "14px")
              .text("Sommeil moyen (heures)");

          // Légende des membres (rectangles colorés)
          // Créer un conteneur pour la légende
const legend = svg.append("g")
.attr("transform", `translate(${width / 3}, ${height + margin.bottom - 50})`) // Positionner la légende en bas, centrée
.attr("text-anchor", "middle");

// Ajouter les éléments de la légende
members.forEach((member, i) => {
legend.append("rect")
    .attr("x", i * 100 - (members.length * 50)) // Espacement horizontal entre les rectangles
    .attr("y", 0)
    .attr("width", 15)
    .attr("height", 15)
    .attr("fill", colorMap[member]);

legend.append("text")
    .attr("x", i * 100 - (members.length * 50) + 20) // Texte à côté du rectangle
    .attr("y", 12)
    .text(member)
    .style("font-size", "12px")
    .attr("text-anchor", "start");
});

// Ajouter un élément pour "Pas de données"
legend.append("rect")
.attr("x", members.length * 100 - (members.length * 50)) // Position pour le rectangle gris
.attr("y", 0)
.attr("width", 15)
.attr("height", 15)
.attr("fill", "lightgrey");

legend.append("text")
.attr("x", members.length * 100 - (members.length * 50) + 20) // Texte à côté du rectangle gris
.attr("y", 12)
.text("Pas de données")
.style("font-size", "12px")
.attr("text-anchor", "start");

          // Barres pour chaque membre
          members.forEach((member, i) => {
              svg.selectAll(`.bar-sleep-${member}`)
                  .data(aggregatedData)
                  .enter()
                  .append("rect")
                  .attr("x", d => xScale(d.month) + i * (xScale.bandwidth() / members.length))
                  .attr("y", d => {
                      const value = d[`Sleep_${member}`];
                      return value === -1.0 ? yScale(2) : yScale(value); // Placer les -1.0 à une hauteur fixe, ici 2 heures
                  })
                  .attr("width", xScale.bandwidth() / members.length)
                  .attr("height", d => {
                      const value = d[`Sleep_${member}`];
                      return value === -1.0 ? height - yScale(2) : height - yScale(value); // Barres grisées si -1.0
                  })
                  .attr("fill", d => {
                      const value = d[`Sleep_${member}`];
                      return value === -1.0 ? "#D3D3D3" : colorMap[member]; // Gris pour -1.0
                  })
                  .on("mouseover", function(event, d) {
                      tooltip.transition().duration(200).style("opacity", .9); // Transition d'apparition du tooltip
                      tooltip.html(`Mois : ${d.month}<br>Sommeil moyen : ${d[`Sleep_${member}`] === -1.0 ? "Pas de données" : d[`Sleep_${member}`].toFixed(2)} heures`) // Affichage du tooltip
                          .style("left", (event.pageX + 5) + "px")
                          .style("top", (event.pageY - 28) + "px");
                  })
                  .on("mouseout", function() {