components/chart/dailyActivity.js

import * as d3 from "d3";


/**
 * draw rounded top edges at the bars of the graph
 * @param {object} x - d3 object 
 * @param {object} y - d3 object
 * @param {number} height - svg height 
 * @param {number} width - svg width 
 * @returns {string} coordinates of the drawn element
 */
 const topRoundedColumn = function(x, y, height, width) {
  const radius = width / 2;
  const heightBeforeArc = height - radius;
  return (
    `M${x-11.76},${y} ` + // Mx,y Move the pen to(x, y)
    `v-${heightBeforeArc} ` + // h<length> Draw a vertical line of length <height>px
    `a ${radius},${radius} 0 0 1 ${radius},-${radius} ` + // arc
    `a ${radius},${radius} 0 0 1 ${radius},${radius} ` +
    `v${heightBeforeArc} ` +
    `z` // close shape
  );
}


/**
 * create user daily activity chart
 * @param {array} data - array of objects containing the data needed for the daily activity graph
 * @example data : [ { day: <string>, kilogram : <integer>, calories : <integer>}]
 * @param {number} height - chart height 
 * @param {object} svg - svg contains chart
 * @param {number} width - chart width
 * @param {object} [margin] - contains attributes : marginTop, marginRight, marginBottom, marginLeft
 */
function dailyActivity(data, height, svg, width, margin) {
  
  svg.attr("transform", `translate(5 ,${margin.top+margin.bottom})`);
    let chartHeight = height * 0.85

    // Create axes
    const xScale = d3.scaleBand()
    .range([0, width])
    .padding(0.1)
    .domain(data.map((d) => d.day.substring(9)));

    const yScale = d3.scaleLinear()
    .range([chartHeight, 0])
    .domain([0, d3.max(data, d => d.kilogram > d.calories ? d.kilogram : d.calories+100)]);

    // Add tooltip
    const div = d3.select("body").append("div")
    .attr("class", "tooltip")         
    .style("opacity", 0);

   // Add X grid lines with labels
   const xAxis = d3.axisBottom(xScale).tickSize(0);
   const xAxisGroup = svg.append("g")
     .attr("transform", `translate(${-width*0.11}, ${chartHeight*0.95})`)
     .attr("color", "#DEDEDE")
     .call(xAxis);
   xAxisGroup.selectAll("text")
     .style("text-anchor", "end")
     .style("color", "#9B9EAC")
     .style("font-size", "14px")
     .style("font-weight", "bold")
     .attr("dx", "-2.5em")
     .attr("dy", "1.5em")
     .attr("transform", `translate(${width < 600 ? width*0.11 : width*0.09}, 0)`);

    // Add Y grid lines with labels
    const yAxis = d3.axisRight(yScale).ticks(3).tickSize(-width*0.9);
    const yAxisGroup = svg.append("g")
    .attr("transform", `translate(${width-(margin.right)}, ${-chartHeight*0.05})`)
    .attr("stroke-width", "0").call(yAxis);
    yAxisGroup.selectAll("line")
      .attr("transform", `translate(${-width*0.08}, 0)`)
      .attr("stroke", "#DEDEDE")
      .attr("stroke-width", "1")
      .style("stroke-dasharray", "2 2");

    // Add the day bar
    svg.selectAll(".day")
      .data(data)
      .enter().append("path")
      .attr("class", "day")
      .attr("d", d => `M${xScale(d.day.substring(9))-28.76},${chartHeight} ` + // Mx,y Move the pen to(x, y)
      `v-${chartHeight - yScale(d.calories) + 20} ` +  // arc
      `a ${width},0 1 1 1 56,0  ` +
      `v${chartHeight- yScale(d.calories)+20} ` +
      `z`)
      .attr("fill", "transparent")
      .attr("height", d => chartHeight - yScale(d.kilogram))
      .attr("transform", `translate(0, ${-chartHeight*0.05})`)
      .style("position", "relative")
      .style("z-index", 9)
      .on("mouseover", (event, d) => {
          div.transition()        
              .duration(200)      
              .style("opacity", .9);
            div.html("<p>"+d.kilogram + "kg</p><p>" + d.calories + "Kcal</p>")
              .style("left", (event.pageX + 25) + "px")     
              .style("top", (event.pageY - 50) + "px");
      })
      .on("mouseout", (event, d) => {
          div.transition()
              .duration(500)
              .style("opacity", 0);
    });
    
    // Add the kilogram bars
    svg.selectAll(".bar.kilogram")
      .data(data)
      .enter().append("path")
      .attr("class", "kilogram")
      .attr("x", d => xScale('kilogram'))
      .attr("y", d => yScale(d.kilogram))
      .attr("d", d => topRoundedColumn(xScale(d.day.substring(9)), chartHeight, chartHeight - yScale(d.kilogram), 7))
      .attr("fill", "#282D30")
      .attr("height", d => chartHeight - yScale(d.kilogram))
      .attr("transform", `scale(0)`)
      .attr("transform-origin", `left ${chartHeight*0.95}px`)
      .transition("scale").duration(1000)
      .attr("transform", `translate(0, ${-chartHeight*0.05}) scale(1)`)
      .style("position", "relative")
      .style("z-index", 1);

    // Add the calories bars
    svg.selectAll(".bar.calories")
      .data(data)
      .enter().append("path")
      .attr("class", "calories")
      .attr("x", d => xScale('calories'))
      .attr("y", d => yScale(d.calories))
      .attr("d", d => topRoundedColumn(xScale(d.day.substring(9)), chartHeight, chartHeight - yScale(d.calories), 7))
      .attr("fill", "#E60000")
      .attr("height", d => chartHeight - yScale(d.calories))
      .attr("transform", `scale(0)`)
      .attr("transform-origin", `left ${chartHeight*0.95}px`)
      .transition("scale").duration(1000)
      .attr("transform", `translate(${(margin.left/3)}, ${-chartHeight*0.05}) scale(1)`)
      .style("position", "relative")
      .style("z-index", 1);

    // add title   
    var title = svg.append("g")
    .attr("id", "daily-activity-graphic-title")
    .attr("x", 65)
    .attr("y", 25)
    .attr("height", 100)
    .attr("width", 150)
    .style("transform", `translateY(-${chartHeight*0.025}px)`);
    title.append("text")
    .text("Activité quotidienne")
    .style("font-weight", "bold")
    .style("font-size", "large");

    // add legend   
    var legend = svg.append("g")
    .attr("class", "legend")
    .attr("x", width - 65)
    .attr("y", 25)
    .attr("height", 100)
    .attr("width", 100)
    .style("transform", `translate(-${width*0.15}px, -${chartHeight*0.05}px)`)
    .style("font-size", width>600 ? null : "small");
    legend.selectAll('g').data(["Poids (kg)", "Calories brûlées (kCal)"])
      .enter()
      .append('g')
      .each(function(d, i){
        var g = d3.select(this);
        g.append("circle")
          .attr("cx", ((width * 0.65)+(125*i)))
          .attr("cy", 0)
          .attr("r", 4)
          .style("fill", i === 0 ? "#282D30" : "#E60000");
        g.append("text")
          .attr("x", ((width * 0.65)+10+(125*i)))
          .attr("y", 4)
          .attr("height",30)
          .attr("width",100)
          .style("fill", "rgb(116, 121, 140)")
          .text(d);

      });
};
 
export default dailyActivity;