我有一个包含多个时间序列的数据文件,我想使用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}的部分(至少它似乎在调试模式下)。然后抛出未定义对象/变量的错误,脚本无法执行。对我来说,这绝对没有意义。此外,allLines
和allLines[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);
答案 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代码段中的相同代码:
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;