调整发散堆叠条形图以使用一般更新模式

时间:2018-06-08 07:59:31

标签: javascript d3.js

我一直在使用带有以下代码的堆叠条形图示例here

var data = [
  {month: "Q1-2016", apples: 3840, bananas: 1920, cherries: -1960, dates: -400},
  {month: "Q2-2016", apples: 1600, bananas: 1440, cherries: -960, dates: -400},
  {month: "Q3-2016", apples:  640, bananas:  960, cherries: -640, dates: -600},
  {month: "Q4-2016", apples:  320, bananas:  480, cherries: -640, dates: -400}
];

var series = d3.stack()
    .keys(["apples", "bananas", "cherries", "dates"])
    .offset(d3.stackOffsetDiverging)
    (data);

var svg = d3.select("svg"),
    margin = {top: 20, right: 30, bottom: 30, left: 60},
    width = +svg.attr("width"),
    height = +svg.attr("height");

var x = d3.scaleBand()
    .domain(data.map(function(d) { return d.month; }))
    .rangeRound([margin.left, width - margin.right])
    .padding(0.1);

var y = d3.scaleLinear()
    .domain([d3.min(series, stackMin), d3.max(series, stackMax)])
    .rangeRound([height - margin.bottom, margin.top]);

var z = d3.scaleOrdinal(d3.schemeCategory10);

svg.append("g")
  .selectAll("g")
  .data(series)
  .enter().append("g")
    .attr("fill", function(d) { return z(d.key); })
  .selectAll("rect")
  .data(function(d) { return d; })
  .enter().append("rect")
    .attr("width", x.bandwidth)
    .attr("x", function(d) { return x(d.data.month); })
    .attr("y", function(d) { return y(d[1]); })
    .attr("height", function(d) { return y(d[0]) - y(d[1]); })

svg.append("g")
    .attr("transform", "translate(0," + y(0) + ")")
    .call(d3.axisBottom(x));

svg.append("g")
    .attr("transform", "translate(" + margin.left + ",0)")
    .call(d3.axisLeft(y));

function stackMin(serie) {
  return d3.min(serie, function(d) { return d[0]; });
}

function stackMax(serie) {
  return d3.max(serie, function(d) { return d[1]; });
}

根据示例的代码不支持转换或重绘,所以我一直在尝试将general update pattern的原则应用到它,以便我可以在我的应用程序中使用它。

我不熟悉D3v4以及'追加'的性质。困惑我。我看到两个'进入'在同一个追加方法链中,所以我试图将它们分成自己的声明,如:

var join1 = .selectAll("g")
      .data(series)

var join2Update = .selectAll("rect")
      .data(function(d) { return d; })

var join2Enter= join2.enter()

join2Enter
 .append("rect")
 .merge(join2Update)
 .transition()
 .attr("width", x.bandwidth)
 .attr("x", function(d) { return x(d.data.month); })
 .attr("y", function(d) { return y(d[1]); })
 .attr("height", function(d) { return y(d[0]) - y(d[1]); })

但除了打破图表之外,它并没有做多少事情!我坦率地说,在整个事情上有点迷失,任何帮助都会非常感激。

1 个答案:

答案 0 :(得分:3)

我确实对代码进行了一些修改以使其更新,我会指出我所做的一些更改,如果还有其他任何不清楚的问题,你可以问一下。

我改变的主要内容是,在我们的数据集中,为简单起见,我们有两个类别,以便我们可以根据这样的输入更新我们的数据

var keys = ["apples" + input, "bananas" + input];

这里的输入变量最初是像这样选择的

var input = d3.selectAll(".opt").property("value");

当我们手动更新它时,我们会得到像这样的新数据

d3.selectAll(".opt").on("change", function() {
    update(data, this.value)
})

我们创建了一个包含series数据集的条形图组,然后将其与另一个变量一起引用,而不是直接在矩形上附加一个元素。

var barGroups = svg.selectAll("g.layer")
    .data(series);

barGroups.exit().remove();

barGroups.enter().insert("g", ".x-axis")
  .classed('layer', true);

这是您之前提到的代码的更新模式部分:

var bars = svg.selectAll("g.layer").selectAll("rect")
  .data(function(d) { return d; });

bars.exit().remove();

bars = bars
    .enter()
.append("rect")
    .attr("width", x.bandwidth())
    .attr("x", d => x(d.data.month))
  .merge(bars)

bars.transition().duration(750)
    .attr("y", d => y(d[1]))
    .attr("height", d => Math.abs(y(d[0])) - y(d[1]));

应该是它,看看下面的代码片段,看看它是如何运作的。

var data = [
  {month: "Q1-2016", apples_1: -400, bananas_1: 920, apples_2: -196, bananas_2: 840},
  {month: "Q2-2016", apples_1: -400, bananas_1: 440, apples_2: -960, bananas_2: 600},
  {month: "Q3-2016", apples_1: -600, bananas_1: 960, apples_2: -640, bananas_2: 640},
  {month: "Q4-2016", apples_1: -400, bananas_1: 480, apples_2: -640, bananas_2: 320}
];

var margin = {top: 35, right: 145, bottom: 35, left: 45},
    width = 650 - margin.left - margin.right,
    height = 450 - margin.top - margin.bottom;

var svg = d3.select("#chart")
	.attr("width", width + margin.left + margin.right)
	.attr("height", height + margin.top + margin.bottom)
.append("g")
	.attr("transform","translate(" + margin.left + "," + margin.top + ")");

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

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

var z = d3.scaleOrdinal()
  .range(["steelblue","darkorange"]);

svg.append("g")
  .attr("class","x-axis");

svg.append("g")
  .attr("class", "y-axis");

var input = d3.selectAll(".opt").property("value");

d3.selectAll(".opt").on("change", function() {
	update(data, this.value)
})

update(data, input);

function update(data, input) {

	var keys = ["apples" + input, "bananas" + input];
		
	var series = d3.stack()
		.keys(keys)
		.offset(d3.stackOffsetDiverging)
		(data);

	x.domain(data.map(d => d.month));

	y.domain([
		d3.min(series, stackMin), 
		d3.max(series, stackMax)
	]).nice();

    var barGroups = svg.selectAll("g.layer")
    	.data(series);

    barGroups.exit().remove();

    barGroups.enter().insert("g", ".x-axis")
      .classed('layer', true);
    
    svg.selectAll("g.layer")
    	.transition().duration(750)
    	.attr("fill", d => z(d.key));
    
    var bars = svg.selectAll("g.layer").selectAll("rect")
      .data(function(d) { return d; });
		
	bars.exit().remove();
    
    bars = bars
    	.enter()
    .append("rect")
    	.attr("width", x.bandwidth())
    	.attr("x", d => x(d.data.month))
      .merge(bars)

    bars.transition().duration(750)
    	.attr("y", d => y(d[1]))
    	.attr("height", d => Math.abs(y(d[0])) - y(d[1]));
	
	svg.selectAll(".x-axis").transition().duration(750)
		.attr("transform", "translate(0," + y(0) + ")")
		.call(d3.axisBottom(x));

	svg.selectAll(".y-axis").transition().duration(750)
		.call(d3.axisLeft(y));
	
	function stackMin(serie) {
		return d3.min(serie, function(d) { return d[0]; });
	}
	
	function stackMax(serie) {
	  return d3.max(serie, function(d) { return d[1]; });
	}

}
body {
	margin: auto;
	width: 850px;
}
<meta charset ="utf-8">

<script src="https://d3js.org/d3.v5.min.js"></script>
select something
<select class="opt">
	<option value="_1">1</option>
	<option value="_2">2</option>
</select><br>

<svg id="chart"></svg>