Изменения в D3v4
В d3v4 были внесены изменения, которые могут нарушить график d3v3. Стеки в d3v3 имели x аксессоров и использовали y0
и y
для представления начальных и конечных значений для каждого сегмента стекового графика. Результат выглядит примерно так:
.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
Этот подход не работает с d3v4 +, но, похоже, он используется вами. На первый взгляд кажется, что ваш код может быть основан на этом v3 коде.
Стеки в d3v4 +
Давайте посмотрим, что d3.stack производит в d3v4 +.
У нас есть входные данные:
var data = [
{ country:"India", sales:100, profit:100, loss:10 },
{ country:"US", sales:100, profit:80, loss:40 },
{ country:"aus", sales:100, profit:70, loss:30 }
];
И генератор стека:
var stack = d3.stack()
.keys(["sales","profit","loss"])
.order(d3.stackOrderNone) // default value, does not need to be specified.
.offset(d3.stackOffsetNone) // default value, does not need to be specified
Когда мы передаем данные в генератор стека, мы получаем:
[/* India US AUS */
[[0, 100],[0, 100],[0, 100]], // Sales
[[100,200],[100,180],[100,170]], // Profit
[[200,210],[180,220],[170,200]] // Loss
]
Если упорядочить массив как таблицу, столбцы представляют каждый элемент в исходном массиве данных (ряд, представляющий страну), строки представляют каждый ключ. Каждый из двух массивов элементов представляет один сегментированный столбец.
Массивы, которые содержат все значения для данного ключа (например, [[0,100],[0,100],[0,100]]
для продаж), также содержат свойство "ключ", которое содержит имя этого ключа.
Каждый из массивов, представляющих один сегмент (например, [0,100]
), имеет свойство data
, которое является элементом в исходном массиве данных (например, {"country": "India","sales": 100,"profit": 100,"loss": 10}
).
Количество элементов в массиве стека (количество строк) не равно количеству серий (в данном случае страны), но равно количеству ключей.
Создание весов
Первая проблема, с которой вы сталкиваетесь, - как вы определяете шкалы:
var x = d3.scaleOrdinal()
.domain(series[0].map(function(d) { return d.x; }))
.range([10, width-10], 0.02);
var y = d3.scaleLinear()
.domain([0, d3.max(series, function(d) { return d3.max(d, function(d) { return d.y0 + d.y; }); })])
.range([height, 0]);
Поскольку стек не создает никаких свойств y0
, y
или x
, они не будут работать.
Масштаб x должен быть масштабом полосы (гистограммы обычно используют масштаб полосы с d3), и мы можем просто использовать исходный массив данных для создания домена:
var x = d3.scaleBand() // band scale
.domain(data.map(function(d) {
return d.country; })) // return the country.
.range([10, width-10]);
Шкала y должна иметь доступ к d [0] и d [1], а не к d.y0 и d.y:
var y = d3.scaleLinear()
.domain([0, d3.max(series, function(d) {
return d3.max(d, function(d) {
return d[0] + d[1]; }); })]) // access stack values (not d.y, d.y0)
.range([height, 0]);
Добавление прямоугольников
Теперь давайте перейдем к тому месту, куда добавляем прямоугольники:
.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.range())
Поскольку d.x не определено, мы можем использовать x(d.data.country)
. Там, где появляются d.y0
и d.y
, мы должны заменить d[0]
и d[1]
. Наконец, x.range()
- это почти полная ширина графика, вместо этого мы можем использовать x.bandwidth()
, то есть ширину одного столбца.
Ниже приведен ваш код (начиная с того, где вы определяете маржу) с этими обновлениями:
var margin = { top: 20, right: 160, bottom: 35, left: 30 };
var width = 500 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
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 data = [
{ country:"India", sales:100, profit:100, loss:10 },
{ country:"US", sales:100, profit:80, loss:40 },
{ country:"Aus", sales:100, profit:70, loss:30 },
{ country:"NZ", sales:100, profit:70, loss:30 }
];
var stack = d3.stack()
.keys(["sales","profit","loss"])
.order(d3.stackOrderNone)
.offset(d3.stackOffsetNone);
var series = stack(data);
var x = d3.scaleBand() // band scale
.domain(data.map(function(d) {
return d.country; })) // return the country.
.range([10, width-10])
.padding(0.1); // space between (value between 0 and 1).
var y = d3.scaleLinear()
.domain([0, d3.max(series, function(d) {
return d3.max(d, function(d) {
return d[0] + d[1]; }); })]) // access stack values (not d.y, d.y0)
.range([height, 0]);
// color scale:
var colors = d3.scaleOrdinal()
.range(["b33040", "#d25c4d", "#f2b447", "#d9d574"])
.domain(series.map(function(d) { return d.key; }));
var yAxis = d3.axisLeft(y)
.ticks(5)
.tickSize(-width, 0, 0)
.tickFormat( function(d) { return d } );
var xAxis = d3.axisBottom(x)
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
var groups = svg.selectAll("g.cost")
.data(series)
.enter().append("g")
.attr("class", "cost")
.style("fill", function(d) { return colors(d); });
// More changes:
var rect = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d) { return x(d.data.country); }) // Access country.
.attr("y", function(d) { return y(d[0] + d[1]); })
.attr("height", function(d) { return y(d[0]) - y(d[0] + d[1]); })
.attr("width", x.bandwidth())
// Minor changes:
var legend = svg.selectAll(".legend")
.data(series)
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(30," + i * 19 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", function(d) {
return colors(d.key);
});
legend.append("text")
.attr("x", width + 5)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "start")
.text(function(d) { return d.key; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
Для упрощения фрагмента я убрал взаимодействие с мышью
Создание легенды
Это можно немного упростить, это не является вопросом вопроса, тем не менее, я включил его здесь вместе с цветовой шкалой.