首页 > 解决方案 > d3 v4 从 CSV 堆叠到分组条形图

问题描述

参考Mike Bostick 的 Stacked to Grouped Bar Chart 示例,我正在修改它以使用 CSV 文件。我已经为此工作了几个星期,并查看了 Stack Overflow 和其他地方的无数示例,我被难住了。

堆积条形图有效。

堆积条形图:

堆积条形图

当我转换到分组条形图时,我只会在引用堆叠或分组的键或系列时遇到问题。现在所有的矩形都显示在彼此的顶部,而不是彼此相邻。

分组条形图:

分组条形图

在函数transitionStep2()中,我想乘以对应于系列或键的数字。我目前乘以这个函数中的数字 1 作为占位符 .attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; })

<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>

<form>
  <label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
  <label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>

<svg id = "bar" width = "500" height = "300"></svg>
<script>
  var svg = d3.select("#bar"),
    margin = {top: 20, right: 20, bottom: 20, left: 20},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var x = d3.scaleBand()
    .rangeRound([0, width])
    .padding(0.08);

  var y = d3.scaleLinear()
    .range([height, 0]);

  var color = d3.scaleOrdinal()
    .range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);

  d3.csv("data.csv", function(d, i, columns) {
    for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
    d.total = t;
    return d;
  }, function(error, data) {
    if (error) throw error;

  var keys = data.columns.slice(1);

  x.domain(data.map(function(d) { return d.Year; }));
  y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
  color.domain(keys);

  g.append("g")
    .selectAll("g")
    .data(d3.stack().keys(keys)(data))
    .enter().append("g")
      .attr("fill", function(d) { return color(d.key); })
    .selectAll("rect")
    .data(function(d) { return d; })
    .enter().append("rect")
      .attr("x", function(d) { return x(d.data.Year); })
      .attr("y", function(d) { return y(d[1]); })
      .attr("height", function(d) { return y(d[0]) - y(d[1]); })
      .attr("width", x.bandwidth());

  rect = g.selectAll("rect");
});

  d3.selectAll("input")
    .on("change", changed);

  function changed() {
    if (this.value === "step1") transitionStep1();
    else if (this.value === "step2") transitionStep2();
  }

  function transitionStep1() {
    rect.transition()
      .attr("y", function(d) { return y(d[1]); })
      .attr("x", function(d) { return x(d.data.Year); })
      .attr("width", x.bandwidth())
      .attr("stroke", "green");
  }

  function transitionStep2() {
    rect.transition()
      .attr("x", function(d) { return x(d.data.Year) + x.bandwidth() / 7 * 1; })
      .attr("width", x.bandwidth() / 7)
      .attr("y", function(d) { return y(d[1] - d[0]); })
      .attr("stroke", "red");
  }
</script></body></html>

和 csv 文件:

Year,A,B,C,D
1995,60,47,28,39
1996,29,56,99,0
1997,30,26,63,33
1998,37,16,48,0
1999,46,49,64,21
2000,78,88,81,57
2001,18,11,11,64
2002,91,76,79,64
2003,30,99,96,79

标签: javascriptcsvd3.jschartsvisualization

解决方案


您所指的示例具有可将图表分组的线性值,因此.attr("x", function(d, i) { return x(i) + x.bandwidth() / n * this.parentNode.__data__.key; })可以正常工作。

在您的情况下,列/键不是线性比例,而是您必须设置比例的序数值集:

参考简单的d3分组条形图

要做到这一点,即设置一个序数比例,这是可以做的:

var x1 = d3.scaleBand();
x1.domain(keys).rangeRound([0, x.bandwidth()]);

所以这个新的规模将有一个x规模的带宽范围,范围为["A", "B", "C", "D"]. 使用这个比例来设置 s 的x属性来对rect它们进行分组:

.attr("x", function(d) { 
  return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key); 
})

其中d3.select(this.parentNode).datum().key表示列名。

这是JSFIDDLE(我曾经d3.csvParse解析过数据,但我相信您会明白这一点。这只是x您需要重置的属性。

这是一个使用文件的Plunkr 。

这里还有一个代码片段:

  var svg = d3.select("#bar"),
    margin = {top: 20, right: 20, bottom: 20, left: 20},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom,
    g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  var x = d3.scaleBand()
    .rangeRound([0, width])
    .padding(0.08);

	var x1 = d3.scaleBand();
  
  var y = d3.scaleLinear()
    .range([height, 0]);

  var color = d3.scaleOrdinal()
    .range(["#7fc97f", "#beaed4", "#fdc086", "#ffff99"]);

var csv = 'Year,A,B,C,D\n1995,60,47,28,39\n1996,29,56,99,0\n1997,30,26,63,33\n1998,37,16,48,0\n1999,46,49,64,21\n2000,78,88,81,57\n2001,18,11,11,64\n2002,91,76,79,64\n2003,30,99,96,79';

var data = d3.csvParse(csv), columns = ["A", "B", "C", "D"];

data.forEach(function(d) {
    for (i = 1, t = 0; i < columns.length; ++i) t += d[columns[i]] = +d[columns[i]];
    d.total = t;
});

  var keys = columns;

  x.domain(data.map(function(d) { return d.Year; }));
  x1.domain(keys).rangeRound([0, x.bandwidth()]);
  y.domain([0, d3.max(data, function(d) { return d.total; })]).nice();
  color.domain(keys);

  g.append("g")
    .selectAll("g")
    .data(d3.stack().keys(keys)(data))
    .enter().append("g")
      .attr("fill", function(d) { return color(d.key); })
    .selectAll("rect")
    .data(function(d) { return d; })
    .enter().append("rect")
      .attr("x", function(d) { return x(d.data.Year); })
      .attr("y", function(d) { return y(d[1]); })
      .attr("height", function(d) { return y(d[0]) - y(d[1]); })
      .attr("width", x.bandwidth());

  rect = g.selectAll("rect");

  d3.selectAll("input")
    .on("change", changed);

  function changed() {
    if (this.value === "step1") transitionStep1();
    else if (this.value === "step2") transitionStep2();
  }

  function transitionStep1() {
    rect.transition()
      .attr("y", function(d) { return y(d[1]); })
      .attr("x", function(d) { return x(d.data.Year); })
      .attr("width", x.bandwidth())
      .attr("stroke", "green");
  }
  
	function transitionStep2() {
    rect.transition()
      .attr("x", function(d) { 
      	return x(d.data.Year) + x1(d3.select(this.parentNode).datum().key); 
    	})
      .attr("width", x.bandwidth() / 7)
      .attr("y", function(d) { return y(d[1] - d[0]); })
      .attr("stroke", "red");
  }
<!DOCTYPE html>
<script src="https://d3js.org/d3.v4.min.js"></script>
<html><body>

<form>
  <label><input type="radio" name="mode" style="margin-left: 10" value="step1" checked>1</label>
  <label><input type="radio" name="mode" style="margin-left: 20" value="step2">2</label>
</form>

<svg id = "bar" width = "500" height = "300"></svg>

希望有帮助。:)


推荐阅读