d3.js - D3 在堆积面积图中添加点
问题描述
我需要在下面的代码中为堆积面积图添加点。我已经用 .data(layers) 在代码中尝试了无数次迭代,但是在我尝试过的所有迭代中查找 cx 和 cy 时它会出错。主要问题是我不明白如何钻入图层变量以获得累积总和,以便圆圈与堆叠区域中的线条相匹配。
这是一个小提琴,这是一个片段:
var data = d3.csv.parse(d3.select("#dataset").text());
d3.select("#dataset").remove();
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.days);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
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 + ")");
var mygroups = d3.map(data, function(d){return(d.key)}).keys()
var color = d3.scale.ordinal()
.domain(mygroups)
.range(['#CA999A','#99A3B0','#9FBD9F'])
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return color(i); })
.style("stroke","black");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
//adds dots where original data would go but without error
/* svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
*/
//errors out with <circle> attribute cx: Expected length, "NaN".
//Tired various looping functions that would not work.
//I want the dots to follow the lines in the stack.
// it does at least put a dot on the graph
svg.selectAll("circle")
.data(layers)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.value); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<pre id = "dataset">key,value,date
Group1,37,04/23/12
Group2,12,04/23/12
Group3,46,04/23/12
Group1,32,04/24/12
Group2,19,04/24/12
Group3,42,04/24/12
Group1,45,04/25/12
Group2,16,04/25/12
Group3,44,04/25/12
Group1,24,04/26/12
Group2,52,04/26/12
Group3,64,04/26/12</pre>
解决方案
⚠️这个问题和答案都使用 d3v3 - d3v4+ 堆栈创建具有不同结构的数据数组 - 因此这个答案可能对 d3v4+ 没有用
data
即使缩放,使用变量绘制圆圈也不起作用,因为返回的值将用于非累积线/面积图。或者,在变量中绘制值layers
将不起作用,因为它每层仅包含一个值。中的每一层都layers
包含一个数组,其中包含属于该层的所有点,我们可以抓取所有这些点并将它们绘制为圆圈。
这是layers
结构:
[
{
"key": "Group1",
"values": [
{
"key": "Group1",
"value": 37,
"date": "2012-04-23T07:00:00.000Z",
"y0": 0,
"y": 37
},
...
{
"key": "Group1",
"value": 24,
"date": "2012-04-26T07:00:00.000Z",
"y0": 0,
"y": 24
}
]
},
{
"key": "Group2",
"values": [
{
"key": "Group2",
"value": 12,
"date": "2012-04-23T07:00:00.000Z",
"y0": 37,
"y": 12
},
... // an so on.
所以,我们可以循环遍历 的每一项layers
,也就是循环遍历每一层,收集所有的点。这样的事情就足够了:
// Grab all coordinates of all layers:
var points = [];
layers.forEach(function(d) {
return points.push(...d.values);
})
现在,让我们看看我们points
数组中的每个项目:
{
"key": "Group1",
"value": 37,
"date": "2012-04-23T07:00:00.000Z",
"y0": 0,
"y": 37
}
在这里,y
表示项目的高度,并y0
表示它的基础(该点的图层底部)。由于我们只想绘制每个点一次,并且我们不想绘制底层y0
属性的 0 值,我们应该只绘制最顶层的 y 值 ( y+y0
):
//adds dots where original data would go but without error
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.y0+d.y); });
看起来像这样:
var data = d3.csv.parse(d3.select("#dataset").text());
d3.select("#dataset").remove();
var format = d3.time.format("%m/%d/%y");
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.days);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
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 + ")");
var mygroups = d3.map(data, function(d){return(d.key)}).keys()
var color = d3.scale.ordinal()
.domain(mygroups)
.range(['#CA999A','#99A3B0','#9FBD9F'])
data.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter()
.append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return color(i); })
.style("stroke","black");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.selectAll("path","line")
.style({
fill: "none",
stroke: "#000",
"shape-rendering":"crispEdges"
});
// Grab all coordinates of all layers:
var points = [];
layers.forEach(function(d) {
return points.push(...d.values);
})
//adds dots where original data would go but without error
svg.selectAll("circle")
.data(points)
.enter()
.append("circle")
.attr("class", "dot")
.attr("r", 5)
.attr("cx", function(d) { return x(d.date); })
.attr("cy", function(d) { return y(d.y0+d.y); });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<pre id = "dataset">key,value,date
Group1,37,04/23/12
Group2,12,04/23/12
Group3,46,04/23/12
Group1,32,04/24/12
Group2,19,04/24/12
Group3,42,04/24/12
Group1,45,04/25/12
Group2,16,04/25/12
Group3,44,04/25/12
Group1,24,04/26/12
Group2,52,04/26/12
Group3,64,04/26/12</pre>
推荐阅读
- qt - 无法为 LiteIDE 设置 QTDIR 环境变量
- rx-java - Rxjava `2.x` 抛出异常时崩溃
- mongodb - Mongodb聚合从数组中获取所需的对象
- javascript - Mermaid.js 与框宽切割文本有关的问题
- python - 在饼图python上显示百分比
- c# - cshtml 和 cshtml.cs 部署后不起作用
- python - 我在链表中添加了4个元素,但是为什么只显示了三个而没有打印最后一个元素
- go - 如何解析用逗号分隔但不被数组包围的json对象字符串
- android - 使用 Android 中的 HttpUrlConnection 类在对 Django 服务器的 HTTP POST 请求中包含 CSRF 令牌
- javascript - 为什么用 JavaScript 为我的动画录制音频只能工作一次?