Restrict Zooming-in (brushing) in stacked-area-chart d3js to dates only

24 Views Asked by At

By default it shows months quarterly. On brushing further, it shows all the months. ON brushing further, it shows dates. On brushing even further, it shows days. But, I want to limit brushing to dates at the max. How can I achieve that? Following is my code:

draw() {
// set the dimensions and margins of the graph
const formatDate = (dateInput) =>{
  const today = new Date(dateInput);
  const yyyy = today.getFullYear();
  let mm:any = today.getMonth() + 1; // Months start at 0!
  let dd:any = today.getDate();
  if (dd < 10) dd = '0' + dd;
  if (mm < 10) mm = '0' + mm;
  
  const formattedDate = dd + '-' + mm + '-' + yyyy;
  return formattedDate;
}

const margin = { top: 60, right: 230, bottom: 50, left: 50 },
  width = 660 - margin.left - margin.right,
  height = 400 - margin.top - margin.bottom;

// append the svg object to the body of the page
const svg = d3.select("#my_dataviz")
  .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})`);



// Parse the Data
d3.csv("./assets/date-wise-issue-type.csv").then(function (data) {

  // console.log(data);
  
  //////////
  // GENERAL //
  //////////

  // List of groups = header of the csv files
  const keys = data.columns.slice(3)

  // color palette
  const color = d3.scaleOrdinal()
    .domain(keys)
    .range(d3.schemeSet2);

  //stack the data?
  const stackedData = d3.stack()
    .keys(keys)
    (data)


  //////////
  // AXIS //
  //////////
  // console.log(data);
  // Add X axis
  const x = d3.scaleTime()
    .domain(d3.extent(data, function (d) { const xInput = formatDate(d.date); return new Date(xInput); }))
    .range([0, width]);
  console.log(d3.extent(data, function (d) { const xInput = formatDate(d.date); return xInput; }));
  const xAxis = svg.append("g")
    .attr("transform", `translate(0, ${height})`)
    .call(d3.axisBottom(x).ticks(5))

  // Add X axis label:
  svg.append("text")
    .attr("text-anchor", "end")
    .attr("x", width)
    .attr("y", height + 40)
    .text("Time (months)");

  // Add Y axis label:
  svg.append("text")
    .attr("text-anchor", "end")
    .attr("x", 0)
    .attr("y", -20)
    .text("")
    .attr("text-anchor", "start")
  
  // Add Y axis
  const y = d3.scaleLinear()
    .domain([0, 7000])
    .range([height, 0]);
  svg.append("g")
    .call(d3.axisLeft(y).ticks(5))



  //////////s
  // BRUSHING AND CHART //
  //////////

  // Add a clipPath: everything out of this area won't be drawn.
  const clip = svg.append("defs").append("svg:clipPath")
    .attr("id", "clip")
    .append("svg:rect")
    .attr("width", width)
    .attr("height", height)
    .attr("x", 0)
    .attr("y", 0);

  // Add brushing
  const brush = d3.brushX()                 // Add the brush feature using the d3.brush function
    .extent([[0, 0], [width, height]])
    .scaleExtent([0.1, 10]) // initialise the brush area: start at 0,0 and finishes at width,height: it means I select the whole graph area
    .on("end", updateChart) // Each time the brush selection changes, trigger the 'updateChart' function

  // Create the scatter variable: where both the circles and the brush take place
  const areaChart = svg.append('g')
    .attr("clip-path", "url(#clip)")

  // Area generator
  const area = d3.area()
    .x(function (d) { return x(new Date(d.data.date)); })
    .y0(function (d) { return y(d[0]); })
    .y1(function (d) { return y(d[1]); })
  
  // Show the areas
  areaChart
    .selectAll("mylayers")
    .data(stackedData)
    .join("path")
    .attr("class", function (d) { return "myArea " + d.key })
    .style("fill", function (d) { return color(d.key); })
    .attr("d", area)

  // Add the brushing
  areaChart
    .append("g")
    .attr("class", "brush")
    .call(brush);

  let idleTimeout
  function idled() { idleTimeout = null; }

  // A function that update the chart for given boundaries
  function updateChart(event, d) {

    const extent = event.selection

    // If no selection, back to initial coordinate. Otherwise, update X axis domain
    if (!extent) {
      if (!idleTimeout) return idleTimeout = setTimeout(idled, 350); // This allows to wait a little bit
      x.domain(d3.extent(data, function (d) { return new Date(d.date); }))
      // console.log("if", d3.extent(data, function (d) { return new Date(d.date); }));
      
    } else {
      x.domain([x.invert(extent[0]), x.invert(extent[1])])
      // console.log("else", extent);
      areaChart.select(".brush").call(brush.move, null) // This remove the grey brush area as soon as the selection has been done
    }

    // Update axis and area position
    xAxis.transition().duration(1000).call(d3.axisBottom(x).ticks(5))
    areaChart
      .selectAll("path")
      .transition().duration(1000)
      .attr("d", area)
  }



  //////////
  // HIGHLIGHT GROUP //
  //////////

  // What to do when one group is hovered
  const highlight = function (event, d) {
    // reduce opacity of all groups
    d3.selectAll(".myArea").style("opacity", .1)
    // expect the one that is hovered
    d3.select("." + d).style("opacity", 1)
  }

  // And when it is not hovered anymore
  const noHighlight = function (event, d) {
    d3.selectAll(".myArea").style("opacity", 1)
  }

  //////////
  // LEGEND //
  //////////

  // Add one dot in the legend for each name.
  const size = 20
  svg.selectAll("myrect")
    .data(keys)
    .join("rect")
    .attr("x", 400)
    .attr("y", function (d, i) { return 10 + i * (size + 5) }) // 100 is where the first dot appears. 25 is the distance between dots
    .attr("width", size)
    .attr("height", size)
    .style("fill", function (d) { return color(d) })
    .on("mouseover", highlight)
    .on("mouseleave", noHighlight)

  // Add one dot in the legend for each name.
  svg.selectAll("mylabels")
    .data(keys)
    .join("text")
    .attr("x", 400 + size * 1.2)
    .attr("y", function (d, i) { return 10 + i * (size + 5) + (size / 2) }) // 100 is where the first dot appears. 25 is the distance between dots
    .style("fill", function (d) { return color(d) })
    .text(function (d) { return d })
    .attr("text-anchor", "left")
    .style("alignment-baseline", "middle")
    .on("mouseover", highlight)
    .on("mouseleave", noHighlight)

})}

draw();

Following is the link for the csv: https://file.io/xlGjENteto4b

0

There are 0 best solutions below