#d3.js
#d3.js
Вопрос:
Я пытаюсь получить D3.js (v5) столбчатая диаграмма с накоплением для быстрого изменения размера. У меня было что-то похожее, работающее со стандартной столбчатой диаграммой, но что-то в новых группах, содержащих сложенные столбцы, ставит меня в тупик… В настоящее время столбцы изменяются, но предыдущие остаются.
Вот CodePen моего разумно минимального примера кода.
Исходя из этого, вот renderBars()
функция, которая вызывается при загрузке страницы и при изменении размера страницы:
function renderBars() {
// Calculating the x, y, width and height of a bar:
var barX = function(d, i) { return xScale(d.data.label); };
var barY = function(d) { return yScale(d[1]); };
var barW = xScale.bandwidth();
var barH = function(d) { return yScale(d[0]) - yScale(d[1]); };
// Surrounds all of the barGroups:
var barGroupsContainer = inner.append("g");
// One barGroup per kind/colour of bar:
var barGroups = barGroupsContainer
.selectAll(".bargroup")
.data(seriesData)
.enter().append("g")
.classed("bargroup", true)
.attr("fill", function(d) { return colors(d.key); });
// Each bar is within its barGroup:
var bars = barGroups
.selectAll("rect")
.data(function(d) { return d; });
// ENTER
bars
.enter().append("rect")
.attr("x", barX)
.attr("y", barY)
.attr("width", barW)
.attr("height", barH);
// UPDATE
bars
.transition()
.attr("x", barX)
.attr("y", barY)
.attr("width", barW)
.attr("height", barH);
// EXIT
bars.exit().remove();
}
Я пробовал различные комбинации обновления и удаления barGroupsContainer
barGroups
, а также bars
, но без каких-либо улучшений.
(Любая другая критика всегда приветствуется!)
ОБНОВЛЕНИЕ: я не собираюсь делать это адаптивным с помощью preserveAspectRatio="xMinYMin meet"
viewBox
атрибута and на svg
-, который изменял бы размер всего на диаграмме, включая любые тексты, которые мне не нужны.
Ответ №1:
Пара проблем:
Во-первых, область действия barGroupsContainer
переменной (наличие ее в renderBars
функции) означает, что сразу же вы создаете родительский g
элемент, в который все остальное входит при каждом изменении размера. Это означает, что каждый элемент всегда был «входящим».
Во-вторых, даже исправляя то, что вы неправильно обрабатываете шаблон enter
/ update
в оболочке g
вокруг каждого набора rect
s . Вы только обработали enter
…
Вот быстрая переписка:
// Prepare data
var data = [
{"label": "2000", "dogs": 23, "cats": 30, "fish": 5},
{"label": "2001", "dogs": 27, "cats": 19, "fish": 8},
{"label": "2002", "dogs": 35, "cats": 25, "fish": 10},
];
var groupKeys = Object.keys(data[0]).slice(1);
var seriesData = d3.stack().keys(groupKeys)(data)
// Initial dimensions and scales
var margin = {top: 0, right: 0, bottom: 0, left: 0};
var chartW;
var chartH;
var xScale = d3.scaleBand()
.domain(
data.map(function(d) { return d.label; })
);
var yScale = d3.scaleLinear()
.domain([
0,
d3.max(seriesData, function(d) { return d3.max(d, function(d) { return d[1]})})
]);
var colors = d3.scaleOrdinal()
.domain(groupKeys)
.range(['#e41a1c','#377eb8','#4daf4a'])
// Prepare containing elements.
var container = d3.select(".container");
var svg = container.append("svg");
var inner = svg.append("g");
// Surrounds all of the barGroups:
var barGroupsContainer = inner.append("g");
// Do initial render, and re-render on resize:
render();
window.addEventListener("resize", render);
function render() {
sizeChart();
renderBars();
}
function sizeChart() {
var width = parseInt(container.style("width"), 10);
var height = parseInt(container.style("height"), 10);
chartW = width - margin.left - margin.right;
chartH = height - margin.top - margin.bottom;
xScale.range([0, chartW]).padding(0.1);
yScale.rangeRound([chartH, 0]);
svg
.transition()
.attr("width", width)
.attr("height", height)
inner.attr("transform", "translate(" margin.left "," margin.top ")");
}
function renderBars() {
// Calculating the x, y, width and height of a bar:
var barX = function(d, i) { return xScale(d.data.label); };
var barY = function(d) { return yScale(d[1]); };
var barW = xScale.bandwidth();
var barH = function(d) { return yScale(d[0]) - yScale(d[1]); };
// One barGroup per kind/colour of bar:
// ENTER
var barGroups = barGroupsContainer
.selectAll(".bargroup")
.data(seriesData);
// ENTER UPDATE
barGroups = barGroups
.enter().append("g")
.merge(barGroups)
.classed("bargroup", true)
.attr("fill", function(d) { return colors(d.key); });
// Each bar is within its barGroup:
var bars = barGroups
.selectAll("rect")
.data(function(d) {
return d;
});
// ENTER
bars = bars
.enter().append("rect")
.merge(bars);
//.attr("x", barX)
//.attr("y", barY)
//.attr("width", barW)
//.attr("height", barH);
// UPDATE
bars
.transition()
.attr("x", barX)
.attr("y", barY)
.attr("width", barW)
.attr("height", barH);
// EXIT
bars.exit().remove();
}
.container {
width: 100%;
height: 300px;
}
<script src="https://d3js.org/d3.v5.js"></script>
<div class="container"></div>
Комментарии:
1. Большое вам спасибо! Я не думаю, что наткнулся бы на это. Один вопрос: когда я использовал подобные шаблоны ранее, необходимость устанавливать x, y, ширину и высоту столбцов как при вводе, так и при обновлении кажется повторяющейся — это нормально и неизбежно? Или есть менее повторяющийся способ сделать то же самое?
2. @PhilGyford, ты можешь убрать двойное назначение. Единственным результатом этого является то, что вы увидите переход между столбцами при создании. См. Отредактированный фрагмент выше.
Ответ №2:
Добавьте viewBox в качестве атрибута к элементу SVG, а затем задайте ширину и высоту внутри viewbox.
Таким образом, это будет выглядеть примерно так, используя шаблонные литералы:
svg.attr('viewBox',`0 0 ${width} ${height}`)
Если бы вы хотели, чтобы ваши столбцы складывались в столбец из строки, я бы отрисовал 3 SVG, а затем использовал CSS flexbox для переключения со строки на столбец при изменении размера экрана.
Комментарии:
1. Спасибо, но это не устраняет проблему — предыдущие бары остаются. Я также должен был сказать, что я не собираюсь делать это отзывчивым, используя
preserveAspectRatio
иviewBox
в svg, что является одним из решений, потому что это также изменяет размеры любых текстовых элементов.