工作解决方案:现在,我正在研究样式并解决与创建由多个数据系列和值跟踪组成的图表有关的一些问题。我会尽快为您提供一个工作代码示例,如果有人遇到与我相同或类似的问题,可以将其作为基础。目前我使用的大部分技巧都在下面的评论中。
这将是我在 StackOverflow 上提出的第一个问题,我期待看到您对我的问题有什么答案。
最近我得到了一个项目,其中我必须编写 Javascript 代码来生成图表,并且我能够同时从图表的每一行读取 Y 值。我对 D3 框架非常陌生,现在我能够读取 csv 数据、创建多系列图表以及跟踪和读取 Y 值,但仅限于从单个数据系列创建图表时。我试图创建多个类似的函数来跟踪不同数据系列中的数据,但它不起作用,并且在控制台中我看到 Y 根据我的理解显示为空。我使用 D3 网站上的示例来尝试学习它,现在代码将与这些示例非常相似。
后来我需要用它做一些其他的事情,但我认为解决这个问题后我将能够继续下去。会有这样的:
- 通过代码减少 csv 中的数据,因为我需要删除 header 信息
- 更改图表的视觉样式并编辑轴缩放
现在我有类似的东西。抱歉,如果有点困惑,但我仍在学习和尝试很多不同的东西。我还添加了我的屏幕截图以及我可以获得的一些控制台信息。我希望它能帮助你们了解我做错了什么以及我需要学习什么。另外,这不是我唯一的方法,要全部展示它们会太长。
编辑:我正在尝试一种不同的方法。在页面底部我将显示我现在所做的事情。
编辑2:抱歉,如果我对我的目标不够精确。我想要做的是我希望能够在一个 X 轴值上同时读取绘制线的所有 Y 轴值(将是其中的 4 条)。我添加了第二个代码的屏幕截图,其中我只能读取一个 Y 轴值,而无法读取超过一个值。
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.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;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="d3.min.js"></script>
<script>
var margin = {top: 20, right: 80, bottom: 30, left: 200},
//-margin.left
width = 960 - margin.right,
height = 750 - margin.top - margin.bottom;
var parseDate = d3.time.format("%Y-%M-%d %H:%M").parse,
//dodane do sledzenia myszy i rysowania kuleczek
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return "$" + formatValue(d); };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.interpolate("basis")
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.transfers); });
var svg = d3.select("body").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 + ")");
d3.csv("data2.csv", function(error, data) {
color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
data.forEach(function(d) {
d.date = parseDate(d.date);
});
var bitrates = color.domain().map(function(name) {
return {
name: name,
values: data.map(function(d) {
return {date: d.date, transfers: +d[name]};
})
};
});
console.log(bitrates);
//data.sort(function(a, b) {
//return a.date - b.date;
//});
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([
d3.min(bitrates, function(c) { return d3.min(c.values, function(v) { return v.transfers; }); }),
d3.max(bitrates, function(c) { return d3.max(c.values, function(v) { return v.transfers; }); })
]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Transfers");
var chart = svg.selectAll(".chart")
.data(bitrates)
.enter().append("g")
.attr("class", "chart");
chart.append("path")
.attr("class", "line")
.attr("d", function(d) { return line(d.values); })
//.attr("d", line);
.style("stroke", function(d) { return color(d.name); });
chart.append("text")
.datum(function(d) { return {name: d.name, value: d.values[d.values.length - 1]}; })
.attr("transform", function(d) { return "translate(" + x(d.value.date) + "," + y(d.value.transfers) + ")"; })
.attr("x", 3)
.attr("dy", ".35em");
//.text(function(d) { return d.name; });
//sledzenie myszy i rysowanie kuleczek
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("g").append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.date) + "," + y(d.value) + ")");
focus.select("text").text(formatCurrency(d.value));
}
});
</script>
对我来说,它看起来像这样: Generated chart CSV 数据文件如下所示:
date,Średni wych.:,Średni wch.:,Maks. wych.:,Maks. wch.:
2014-02-14 15:40,86518717581,101989990772,88304882317,108036052338
2014-02-14 16:00,85739038102,98312113056,87060053514,107154908503
我在尝试了解问题所在时检查的一些信息:
[Object, Object, Object, Object]
0: Object
name: "Średni wych.:"
values: Array[504]
__proto__: Object
1: Object
2: Object
name: "Maks. wych.:"
values: Array[504]
[0 … 99]
[100 … 199]
100: Object
date: Thu Jan 16 2014 01:00:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 49305177944
__proto__: Object
101: Object
date: Thu Jan 16 2014 01:20:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 42169641572
__proto__: Object
102: Object
date: Thu Jan 16 2014 01:40:00 GMT+0100 (Środkowoeuropejski czas stand.)
transfers: 39400112189
__proto__: Object
103: Object
104: Object
105: Object
106: Object
107: Object
108: Object
109: Object
110: Object
我非常感谢您的任何帮助。我了解一些面向对象编程、HTML、CSS,但目前我并没有真正使用任何框架,学习很有趣,但另一方面,在试图弄清楚我到底在做什么时可能会非常令人沮丧错了。
编辑
现在我正在尝试分别绘制两条线。它工作得很好,可以让我以后更轻松地更改线条样式。现在我需要对每一行使用 mousemove 函数。然后,将读取的值传递给某些变量并将它们显示在某个框或其他内容中将相当容易。
这是我第二次尝试的代码(抱歉帖子太长了):
Screenshot for the second code is called Chart2.jpg
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.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;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: none;
stroke: steelblue;
}
</style>
<body>
<script src="d3.js"></script>
<script>
var margin = {top: 20, right: 50, bottom: 30, left: 100},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse,
bisectDate = d3.bisector(function(d) { return d.date; }).left,
formatValue = d3.format(",.2f"),
formatCurrency = function(d) { return "$" + formatValue(d); };
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var line = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.close); });
var valueline2 = d3.svg.line()
.x(function(d) { return x(d.date); })
.y(function(d) { return y(d.open); });
var svg = d3.select("body").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 + ")");
d3.csv("data.csv", function(error, data) {
data.forEach(function(d) {
d.date = parseDate(d.date);
d.close = +d.close;
d.open = +d['open data'];
});
data.sort(function(a, b) {
return a.date - b.date;
});
x.domain([data[0].date, data[data.length - 1].date]);
y.domain([0, d3.max(data, function(d) { return Math.max(d.close, d.open); })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Price ($)");
svg.append("path")
.datum(data)
.attr("class", "line")
.attr("d", line);
svg.append("path")
.datum(data)
.attr("class", "line")
.style("stroke", "red")
.attr("d", valueline2);
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("circle")
.attr("r", 4.5);
focus.append("text")
.attr("x", 9)
.attr("dy", ".35em");
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove1)
.on("mousemove", mousemove2);
function mousemove1() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.close) + ")");
focus.select("text").text(formatCurrency(d.close));
}
function mousemove2() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
focus = focus.attr("transform", "translate(" + x(d.date) + "," + y(d.open) + ")");
focus.select("text").text(formatCurrency(d.open));
}
});
</script>
最佳答案
您已经获得了所有基本代码,只需让它们同时运行即可。
第一个问题是您在同一元素上设置两个不同的“mousemove”事件处理程序。 Unless you use namespaces to distinguish them, the second function just replaces the first ,所以你的第一个函数永远不会被调用。将所有事件处理代码放入一个函数中比创建两个具有不同命名空间的事件处理程序要容易得多。
第二个问题是您只有一个“焦点”元素,因此即使您运行这两个函数来设置两个不同的工具提示内容和位置,也只会显示第二个版本,因为它只是替换第一个版本.
因此,回顾一下,您需要:为每个路径创建一个工具提示/焦点元素,然后使用一个事件处理函数来根据路径的相应列设置所有值和位置。数据文件。
为了保持代码简洁,并允许您快速从两行切换到四行或更多行,我建议您将焦点元素创建为数据连接选择,其中数据是一个数组列名称:
var columnNames = d3.keys( data[0] ) //grab the key values from your first data row
//these are the same as your column names
.slice(1); //remove the first column name (`date`);
var focus = svg.selectAll("g")
.data(columnNames)
.enter().append("g") //create one <g> for each columnName
.attr("class", "focus")
.style("display", "none");
focus.append("circle") //add a circle to each
.attr("r", 4.5);
focus.append("text") //add a text field to each
.attr("x", 9)
.attr("dy", ".35em");
现在,当您在鼠标悬停/鼠标悬停事件中显示或隐藏焦点时,它将显示或隐藏所有工具提示:
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
但是你应该在 mousemove 函数中做什么呢?第一部分,计算最近的 x 值(日期)是相同的。但是,您必须根据正确列中的值设置每个焦点工具提示的文本和位置。您可以这样做,因为每个焦点元素都有一个作为数据对象绑定(bind)到它的列名称,并且 d3 会将该数据对象作为第一个参数传递给您传递给 d3 方法的任何函数:
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.date > d1.date - x0 ? d1 : d0;
//d is now the data row for the date closest to the mouse position
focus.attr("transform", function(columnName){
return "translate(" + x( d.date ) + "," + y( d[ columnName ] ) + ")";
});
focus.select("text").text(function(columnName){
//because you didn't explictly set any data on the <text>
//elements, each one inherits the data from the focus <g>
return formatCurrency(d[ columnName ]);
});
}
顺便说一句,您可以使用相同的结构 - 使用列名称作为数据,然后在函数中使用该名称来获取正确的数据值 - 使用相同的代码创建所有行,而无需创建单独的数据数组。如果您在实现时遇到困难,请告诉我。
关于javascript - 带 Y 值跟踪的 D3.js 多系列图表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22064083/