调整发散堆叠的条形图以使用常规更新模式 [英] Adapting diverging stacked bar chart to use general update pattern

查看:61
本文介绍了调整发散堆叠的条形图以使用常规更新模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我一直在使用此处中可用的堆积条形图示例,其代码如下:

  var data = [
{month: Q1-2016,苹果:3840,香蕉:1920,樱桃:-1960,日期:-400},
{月: Q2-2016,苹果:1600,香蕉:1440,樱桃:-960,日期:-400},
{月: Q3-2016,苹果:640,香蕉:960,樱桃:-640,日期:-600},
{月: Q4-2016,苹果:320,香蕉:480,樱桃:-640,日期:-400}
];

变量系列= d3.stack()
.keys([ apples, bananas, cherry, dates])
.offset(d3。 stackOffsetDiverging)
(数据);

var svg = d3.select( svg),
保证金= {顶部:20,右侧:30,底部:30,左侧:60},
width = + svg.attr( width),
height = + svg.attr( height);

var x = d3.scaleBand()
.domain(data.map(function(d){return d.month;}))
.rangeRound([margin。左,宽度-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));

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

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

根据示例的代码不支持过渡或重绘,所以我有一直尝试对其应用常规更新模式的原理,以便在我的产品中使用它



我对D3v4不熟悉,附加的性质使我感到困惑。我在同一append方法链中看到两个输入项,所以我试图将它们分成自己的声明,例如:

  var join1 = .selectAll( g)
.data(系列)

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]);})

但除了打破图表外,它并没有做其他事情!坦白地说,我在整个过程中有些失落,对您的帮助将不胜感激。

解决方案

我确实安静了一些修改代码以使其可更新,如果有其他不清楚的地方,我可以指出我所做的一些修改。



我已经更改了,为简单起见,在我们的数据集中有两个类别,以便我们可以根据这样的输入来更新数据

  var键= [苹果 +输入,香蕉 +输入]; 

此处最初选择输入变量的方式是这样

  var输入= d3.selectAll(。opt)。property( value); 

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

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

我们创建了一个钢筋组,而不是与rect一起直接附加aeglemnt包含系列数据集,并随后使用另一个变量对其进行引用。

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

barGroups.exit()。remove();

barGroups.enter()。insert( g, .x轴)
.classed( layer,true);

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

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

bars.exit()。remove();

条=条
.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]));

就应该了,看看下面的代码片段,看看它们是如何工作的。 / p>

  var data = [{月: Q1-2016,apples_1:- 400,香蕉_1:920,苹果_2:-196,香蕉_2:840},{月: Q2-2016,苹果_1:-400,香蕉_1:440,苹果_2:-960,香蕉_2:600},{月: Q3- 2016,苹果_1:-600,香蕉_1:960,苹果_2:-640,香蕉_2:640},{月: Q4-2016,苹果_1:-400,香蕉_1:480,苹果_2:-640,香蕉_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( + marg in.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轴 ); svg.append( g).attr( class, y轴);变量输入= 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,香蕉 +输入]; 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轴).classed(图层,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轴).transition()。duration(750).attr( transform, translate(0, + y(0)+)).call(d3.axisBottom( X)); svg.selectAll(。y-axis)。transition()。duration(750).call(d3.axisLeft(y));函数stackMin(serie){返回d3.min(serie,function(d){返回d [0];}); }函数stackMax(serie){返回d3.max(serie,function(d){返回d [1];}); }}  

  body {margin:auto;宽度:850像素;}  

 <元字符集= utf -8>< script src = https://d3js.org/d3.v5.min.js< / script>选择某些内容< select class = opt> < option value = _ 1> 1< / option> < option value = _ 2> 2< / option>< / select>< br>< svg id = chart>< / svg>  


I have been working with a stacked bar chart example available here with the following code:

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]; });
}

The code as per the example won't support transitions or redraws so I have been trying to apply the principles of the general update pattern to it so I can use it in my application.

I am not familiar with D3v4 and the nature of the 'appends' confuses me. I see two 'enters' in the same append method chain so I attempted to seperate them into their own declarations like:

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]); })

But it doesn't do much except break the chart! I'm frankly a bit lost in the whole thing and any help would be very much appreciated.

解决方案

I did quiet a few changes in your code to make it updateable, I'll point out some of the changes I made, if there's anything else that's unclear you can just ask.

The main thing I've changed is that in our dataset we have two categories for the sake of simplicity so that we can update our data based on input like this

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

And the input variable here being initially selected like this

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

and when we manually update it we get the new data like this

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

Instead of appending a g elemnt dirctly in conjunction with the rect, we create a bar group that holds the series dataset and later refernce it with yet another variable.

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

barGroups.exit().remove();

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

Here's the Update pattern part of the code that you mentioned earlier:

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]));

And that should be it, have a look at the snippet below to see how it all works.

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>

这篇关于调整发散堆叠的条形图以使用常规更新模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆