2

I have a data file with several time series and I want to plot them all in a single graph using Javascript/D3. I am fairly new to Javascript/D3 but after searching for examples I have managed to come up with a code that almost does the trick. There is still one issue remaining, namely I face a weird problem when I try to initialize and draw my line objects dynamically.

My three code files below contain a MWE with two series. In charts.js I load in some data (date column and two series). I initialize my line objects in two ways: first hard coding (e.g. line1 = d3.svg.line()) and secondly assigning them into an array (e.g. allLines[0] = d3.svg.line()) allowing for dynamic initialization of the series. I try to render the lines as

// Version 1: Harcoded lines
allPaths[0].attr('d', line1);
allPaths[1].attr('d', line2);   
allPaths[1].style("stroke", "red")

// Version 2: Lines from array
allPaths[0].attr('d', allLines[0]);
allPaths[1].attr('d', allLines[1]);   
allPaths[1].style("stroke", "red") 

Version 1 works just fine and both lines appear as they should. However, I encounter a weird problem when executing the lines in Version 2: when running line allPaths[0].attr('d', allLines[0]); the execution somehow jumps back in the code to the part where objects are allocated into allLines (or so it at least seems in debugging mode). This then throws an error of undefined object/variable and the script fails to execute. To me this makes absolutely no sense. Further, allLines[0] and line1 seem to be equivalent so I cannot figure out why they won't produce the same result.

In summary, does anybody have an idea what could cause this weird behavior? I am also open to other ways of obtaining the desired result, i.e. being able to plot all the series in the data file dynamically.

main.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width" />
  <title>D3 Line Chart</title>
  <link rel="stylesheet" href="style.css"> 
  <script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script>
</head>
<body>
  <div id="chart"></div>
  <script src="chart.js"></script>
</body>
</html>

chart.js

var Chart = (function(window,d3) {  

  // Declare global variables
  var svg, data, x, y, xAxis, yAxis, dim, chartWrapper, line1, line2, allLines, allPaths, margin = {}, width, height;

  // Create data
  var data = [{date: "1.1.2005", serie1: 10, serie2: 15},
              {date: "1.2.2005", serie1: 14, serie2: 16},
              {date: "1.3.2005", serie1: 12, serie2: 17},
              {date: "1.4.2005", serie1: 19, serie2: 21},
              {date: "1.5.2005", serie1: 7, serie2: 13},
              {date: "1.6.2005", serie1: 12, serie2: 16},             
              ];


    // Define how dates are presented in the source file
    var parseDate = d3.time.format("%d.%m.%Y").parse;

    // Format each series into its own, distinguishable elements
    color = d3.scale.category10();
    color.domain(d3.keys(data[0]).filter(function(key) {
      return key !== "date";
    }));    
    lineNames = color.domain().map(function(name) {
      return {
        name: name,
        values: data.map(function(d) {
          return {
            date: d.date,
          };
        })
      };
    }); 

    //Initialize scales. HACK: only selecting scales based on the 1st series
    xExtent = d3.extent(data, function(d,i) { return new Date(parseDate(d.date)) });
    yExtent = d3.extent(data, function(d,i) { return +d.serie1 });
    x = d3.time.scale().domain(xExtent);
    y = d3.scale.linear().domain(yExtent);

    //Initialize axes
    xAxis = d3.svg.axis().orient('bottom');
    yAxis = d3.svg.axis().orient('left');

    //Initialize svg
    svg = d3.select('#chart').append('svg');
    chartWrapper = svg.append('g');     
    chartWrapper.append('g').classed('x axis', true);
    chartWrapper.append('g').classed('y axis', true);   


    // Create lines:

        // Version 1: Hardcoded lines
        line1 = d3.svg.line()
          .x(function(d) { return x(new Date(parseDate(d.date))) })
          .y(function(d) { return y(+d.serie1) });

        line2 = d3.svg.line()
          .x(function(d) { return x(new Date(parseDate(d.date))) })
          .y(function(d) { return y(+d.serie2) });          


        // Version 2: Dynamic lines
        allLines =[];
        for (var i = 0; i <= 2; i++){
            allLines[i] = d3.svg.line()
                          .x(function(d) { return x(new Date(parseDate(d.date))) })
                          .y(function(d) { return y( d.lineNames[i].name  ) });                   
        };


    //Paths to all lines
    allPaths =[];
    for (var i = 0; i <= allLines.length - 1; i++){
        allPaths[i] = chartWrapper.append('path').datum(data).classed('line', true);
    };  

    //render the chart
    render(data);   


  function render(data) {

    margin.top = 100;
    margin.right = 300;
    margin.left = 50;
    margin.bottom = 100;

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



    // update x and y scales to new dimensions
    x.range([0, width]);
    y.range([height, 0]);

    // update svg elements to new dimensions
    svg
      .attr('width', width + margin.right + margin.left)
      .attr('height', height + margin.top + margin.bottom);
    chartWrapper.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

    // axes scales 
    xAxis.scale(x);
    yAxis.scale(y);

    // x-axis
    svg.select('.x.axis')
      .attr('transform', 'translate(0,' + height + ')')
      .call(xAxis);

    // y-axis
    svg.select('.y.axis')
      .call(yAxis);


    // Draw all lines:

        // Version 1: Harcoded lines
        allPaths[0].attr('d', line1);
        allPaths[1].attr('d', line2);   
        allPaths[1].style("stroke", "red")

        // Version 2: Lines from array. COMMENTED OUT SINCE DOESN'T WORK!
        //allPaths[0].attr('d', allLines[0]);
        //allPaths[1].attr('d', allLines[1]);   
        //allPaths[1].style("stroke", "red")        

  }
    }

)(window,d3);

style.css

body {
  font: 12px sans-serif;
  margin: 0;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.x.axis path {
  display: none;
}

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171
vvv
  • 145
  • 3
  • 9

1 Answers1

2

You have a closure problem. The i in the line generator function is not the i you think it is.

Instead of writing a long explanation, I'll link this question (with a lot of good answers), which I hope you read thoroughly: JavaScript closure inside loops – simple practical example

That being said, this should be your loop:

allLines = [];
for (let i = 0; i < 2; i++) {
    let thisName = lineNames[i].name;
    allLines[i] = d3.svg.line()
        .x(function(d) {
            return x(new Date(parseDate(d.date)))
        })
        .y(function(d) {
            return y(d[thisName])
        });
};

Pay attention to the let, to the < instead of <= and also to the bracket notation.

Here is your updated code: https://jsfiddle.net/60erppmL/

And the same code in the Stacked snippet:

var Chart = (function(window, d3) {

    // Declare global variables
    var svg, data, x, y, xAxis, yAxis, dim, chartWrapper, line1, line2, allLines, allPaths, margin = {},
      width, height;

    // Create data
    var data = [{
      date: "1.1.2005",
      serie1: 10,
      serie2: 15
    }, {
      date: "1.2.2005",
      serie1: 14,
      serie2: 16
    }, {
      date: "1.3.2005",
      serie1: 12,
      serie2: 17
    }, {
      date: "1.4.2005",
      serie1: 19,
      serie2: 21
    }, {
      date: "1.5.2005",
      serie1: 7,
      serie2: 13
    }, {
      date: "1.6.2005",
      serie1: 12,
      serie2: 16
    }, ];


    // Define how dates are presented in the source file
    var parseDate = d3.time.format("%d.%m.%Y").parse;

    // Format each series into its own, distinguishable elements
    color = d3.scale.category10();
    color.domain(d3.keys(data[0]).filter(function(key) {
      return key !== "date";
    }));
    lineNames = color.domain().map(function(name) {
      return {
        name: name,
        values: data.map(function(d) {
          return {
            date: d.date,
          };
        })
      };
    });

    //Initialize scales. HACK: only selecting scales based on the 1st series
    xExtent = d3.extent(data, function(d, i) {
      return new Date(parseDate(d.date))
    });
    yExtent = d3.extent(data, function(d, i) {
      return +d.serie1
    });
    x = d3.time.scale().domain(xExtent);
    y = d3.scale.linear().domain(yExtent);

    //Initialize axes
    xAxis = d3.svg.axis().orient('bottom');
    yAxis = d3.svg.axis().orient('left');

    //Initialize svg
    svg = d3.select('#chart').append('svg');
    chartWrapper = svg.append('g');
    chartWrapper.append('g').classed('x axis', true);
    chartWrapper.append('g').classed('y axis', true);


    // Create lines:

    // Version 1: Hardcoded lines
    line1 = d3.svg.line()
      .x(function(d) {
        return x(new Date(parseDate(d.date)))
      })
      .y(function(d) {
        return y(+d.serie1)
      });

    line2 = d3.svg.line()
      .x(function(d) {
        return x(new Date(parseDate(d.date)))
      })
      .y(function(d) {
        return y(+d.serie2)
      });


    // Version 2: Dynamic lines
    allLines = [];
    for (let i = 0; i < 2; i++) {
      let thisName = lineNames[i].name;
      allLines[i] = d3.svg.line()
        .x(function(d) {
          return x(new Date(parseDate(d.date)))
        })
        .y(function(d) {
          return y(d[thisName])
        });
    };


    //Paths to all lines
    allPaths = [];
    for (let i = 0; i <= allLines.length - 1; i++) {
      allPaths[i] = chartWrapper.append('path').datum(data).classed('line', true);
    };

    //render the chart
    render(data);


    function render(data) {

      margin.top = 100;
      margin.right = 300;
      margin.left = 50;
      margin.bottom = 100;

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



      // update x and y scales to new dimensions
      x.range([0, width]);
      y.range([height, 0]);

      // update svg elements to new dimensions
      svg
        .attr('width', width + margin.right + margin.left)
        .attr('height', height + margin.top + margin.bottom);
      chartWrapper.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

      // axes scales 
      xAxis.scale(x);
      yAxis.scale(y);

      // x-axis
      svg.select('.x.axis')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

      // y-axis
      svg.select('.y.axis')
        .call(yAxis);


      // Draw all lines:

      // Version 1: Harcoded lines
      //allPaths[0].attr('d', line1);
      //allPaths[1].attr('d', line2);
      //allPaths[1].style("stroke", "red")

      // Version 2: Lines from array. COMMENTED OUT SINCE DOESN'T WORK!
      allPaths[0].attr('d', allLines[0]);
      allPaths[1].attr('d', allLines[1]);
      allPaths[1].style("stroke", "red")

    }
  }

)(window, d3);
body {
  font: 12px sans-serif;
  margin: 0;
}

.axis path,
.axis line {
  fill: none;
  stroke: #000;
  shape-rendering: crispEdges;
}

.x.axis path {
  display: none;
}

.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 1.5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
Community
  • 1
  • 1
Gerardo Furtado
  • 100,839
  • 9
  • 121
  • 171