动态绘制线时出错

时间:2017-05-14 19:05:43

标签: javascript d3.js

我有一个包含多个时间序列的数据文件,我想使用Javascript / D3将它们全部绘制在一个图表中。我对Javascript / D3相当新,但在搜索了一些例子后,我设法提出了一个几乎可以解决问题的代码。还有一个问题,即当我尝试动态初始化和绘制线对象时,我遇到了一个奇怪的问题。

下面我的三个代码文件包含一个包含两个系列的MWE。在 charts.js 中,我加载了一些数据(日期列和两个系列)。我以两种方式初始化我的线对象:第一次硬编码(例如line1 = d3.svg.line()),然后将它们分配到一个数组(例如allLines[0] = d3.svg.line()),允许系列的动态初始化。我尝试将这些行渲染为

// 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") 

版本1工作得很好,两行都按照应有的方式显示。但是,在执行第2版中的行时遇到一个奇怪的问题:当运行行allPaths[0].attr('d', allLines[0]); 时,执行会以某种方式跳回代码到将对象分配到{{1}的部分(至少它似乎在调试模式下)。然后抛出未定义对象/变量的错误,脚本无法执行。对我来说,这绝对没有意义。此外,allLinesallLines[0]似乎是等价的,因此我无法弄清楚为什么它们不会产生相同的结果。

总之,是否有人知道可能导致这种奇怪行为的原因是什么?我也对获得所需结果的其他方式持开放态度,即能够动态地绘制数据文件中的所有系列。

main.html中

line1

chart.js之

<!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>

的style.css

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

1 个答案:

答案 0 :(得分:2)

您遇到closure问题。行生成器函数中的i不是您认为的i

我没有写出长篇解释,而是将这个问题(有很多好的答案)联系起来,我希望你们仔细阅读:JavaScript closure inside loops – simple practical example

话虽如此,这应该是你的循环:

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])
        });
};

注意let<而非<=以及括号表示法。

以下是您的更新代码:https://jsfiddle.net/60erppmL/

Stacked代码段中的相同代码:

&#13;
&#13;
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);
&#13;
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;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
&#13;
&#13;
&#13;