Создание D3.js отзывчивая столбчатая диаграмма с накоплением

#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, что является одним из решений, потому что это также изменяет размеры любых текстовых элементов.