Datacrawler
Datacrawler

Reputation: 2876

Recreate D3 Multi-Line Chart on Resize (responsive)

In order to have a responsive D3 multi-line chart I have added a resize function but it doesn't seem to work although the function gets called:

  var data = [{
    Date: "2016-10-10",
    ValueOne: 1,
    ValueTwo: 0
  }, {
    Date: "2016-10-17",
    ValueOne: 23,
    ValueTwo: 2
  }, {
    Date: "2016-10-24",
    ValueOne: 32,
    ValueTwo: 17
  }, {
    Date: "2016-10-31",
    ValueOne: 57,
    ValueTwo: 40
  }, {
    Date: "2016-11-07",
    ValueOne: 74,
    ValueTwo: 56
  }];


var  margin = {top: 10, right: 50, bottom: 100, left: 50},
// Set default width and height
    widther = (window.innerWidth),
    width = widther - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

    
// Determine current size, which determines vars
function set_vars() {

    var width = window.innerWidth - margin.left - margin.right,
        height = 500 - margin.top - margin.bottom;
        
}

function drawGraphic() {

    var svg = d3.select('#charts')
      .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 + ")");

    //Parses date for correct time format
    var formatTime = d3.timeFormat("%Y-%m-%d");

    data.forEach(function(d) {
      d.Date = new Date(d.Date)
    });

    var valueOneData = data.map(function(d) {
      return {
        date: d.Date,
        value: d.ValueOne
      }
    });

    var valueTwoData = data.map(function(d) {
      return {
        date: d.Date,
        value: d.ValueTwo
      }
    });

    var xScale = d3.scaleTime()
      .range([0, width])
      .domain(d3.extent(data, function(d) {
        return d.Date
      }));

    var yScale = d3.scaleLinear()
      .range([height, 0])
      .domain([0, d3.max(data, function(d) {
        return d.ValueOne
      }) * 1.05]);

    var lineGenerator = d3.line()
      .x(function(d) {
        return xScale(d.date)
      })
      .y(function(d) {
        return yScale(d.value)
      });

    var gX = svg.append("g")
      .attr("transform", `translate(0,${height})`)
      .call(d3.axisBottom(xScale).tickFormat(function(d) {
        return formatTime(d)
      }).tickValues(data.map(function(d) {
        return d.Date
      })))
      .selectAll("text")
      .style("text-anchor", "end")
      .attr("transform", "rotate(-65)")
      .attr("y", 4)
      .attr("x", -10)
      .attr("dy", ".35em");

    var gY = svg.append("g")
      .call(d3.axisLeft(yScale));

    var valueOneLine = svg.append("path")
      .datum(valueOneData)
      .attr("d", lineGenerator)
      .style("fill", "none")
      .style("stroke", "#124");

    var valueTwoLine = svg.append("path")
      .datum(valueTwoData)
      .attr("d", lineGenerator)
      .style("fill", "none")
      .style("stroke", "#c7003b");

  //RESPONSIVENESS ATTEMPT NO1
  d3.select(window).on("resize", resized);

}

//Resize function
function resized() {
    d3.select("svg").remove();
    set_vars();
    drawGraphic();
    console.log("FUNCTION IS BEING CALLED")
}

set_vars();
drawGraphic();

 //RESPONSIVENESS ATTEMPT NO2
 window.addEventListener("resize", function(){ d3.select("svg").remove(); set_vars(); drawGraphic(); });
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="charts"></div>

In the snippet, I have tried two ways to do that. None of them make the chart recreate from scratch. The same issue applied in this jsfiddle.

Upvotes: 0

Views: 555

Answers (1)

Shashank
Shashank

Reputation: 5660

The issue was with the parsing of the data every time the window was resized.

As the date within the data is parsed the first time, calling parseDate(d.date) will fail on every other call as it's already been parsed to a valid date. Do you get it?

Hence, moving the parsing code so that it's executed just once:

// parse data just once
data.forEach(function(d) {
  d.date = parseDate(d.date);
  d.value = +d.value;
});

Fiddle link: https://jsfiddle.net/a5rqt0L1/

Suggestion: I feel this isn't the right way to make a responsive chart i.e. removing SVG and re-appending to the body with all the configuration done multiple times. Here's how I'd do it:

  1. Parse the data, append svg with initial height and width, append X, Y axes just once but move drawBars (to draw the actual bars) to a separate function that will use d3's own enter, exit and update selection logic.
  2. On window resize, just change the SVG's height and width, re-render the axes by .call(xAxis)... and just call the drawBars function.

Hope this helps.

Upvotes: 1

Related Questions