【问题标题】:D3.js Pie Chart Dynamic Update Transitions not Working as ExpectedD3.js 饼图动态更新转换未按预期工作
【发布时间】:2018-02-26 06:13:27
【问题描述】:

让我的饼图/甜甜圈图动态更新数据时遇到了很多麻烦。我对其进行了配置,以便用户滑动范围输入以选择他/她想要查看的数据月份,然后将数据传递给我的 d3 视觉对象。为了简单起见,我在示例中对数据进行了硬编码。您可以查看下面的sn-p:

var width = 450;
var height = 350;
var margins = { left: 0, top: 0, right: 0, bottom: 0 };

var svg = d3.select("body")
			.append("svg")
			.attr("width", width+margins.right)
			.attr("height", height+margins.top);

var period = ['JAN 2016','FEB 2016','MAR 2016', 'APR 2016', 'MAY 2016', 'JUN 2016',
'JUL 2016', 'AUG 2016', 'SEP 2016', 'OCT 2016', 'NOV 2016', 'DEC 2016'];

d3.select('#timeslide').on('input', function() {
	update(+this.value);
});

function update(value) {
	document.getElementById('range').innerHTML=period[value];
	create_pie(period[value]);
}

var pie_data = {
	'JAN2016': [16,4,1,30],
	'FEB2016': [17,4,0,30],
	'MAR2016': [16,5,1,29],
	'APR2016': [17,4,1,29],
	'MAY2016': [17,4,1,29],
	'JUN2016': [17,4,2,28],
	'JUL2016': [18,3,2,28],
	'AUG2016': [18,3,2,28],
	'SEP2016': [18,3,2,28],
	'OCT2016': [18,3,3,27],
	'NOV2016': [18,3,3,27],
	'DEC2016': [18,3,3,27]
}

function create_pie(month) {

		var radius = Math.min(width, height) / 4;

		var color = d3.scale.ordinal().range(['darkblue','steelblue','blue',  'lightblue']);

		var arc = d3.svg.arc()
		.innerRadius(radius - 50)
		.outerRadius(radius - 20);

		var sMonth = String(month).replace(' ','');
		var pData = pie_data[sMonth];

		var pie = d3.layout.pie()
		.padAngle(.05)
		.value(function(d) { return d; })
		.sort(null);

		var pieG = svg.append('g')
				.attr('transform', 'translate(' + 100 +',' + 75 + ')');

		var Ppath = pieG.datum(pData).selectAll(".pie")
			.data(pie);

	Ppath
		.enter().append("path").attr('class','pie');

	Ppath
			.attr("fill", function(d, i) { return color(i); })
			.attr("d", arc)
			.each(function(d) {
				this.x0 = d.x;
				this.dx0 = d.dx;
			});

	Ppath
			.transition()
			.duration(650)
			.attrTween("d", arcTweenUpdate);

	Ppath
			.exit().remove();

function arcTweenUpdate(a) {
	var i = d3.interpolate({x: this.x0, dx: this.dx0}, a);
	return function(t) {
		var b = i(t);
		this.x0 = b.x;
		this.dx0 = b.dx;
		return arc(b);
	};
}
}

create_pie('JAN 2016');
    
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="//d3js.org/d3.v3.min.js"></script>
</head>
<body>
	<div id='sliderContainer'>
		<input id='timeslide' type='range' min='0' max='11' value='0' step='1'/><br>
		<span id='range'>JAN 2016</span>
	</div>

好消息是饼图在每次更新月份时都会获取新数据,因为看起来饼图确实在移动。坏消息是饼图看起来很生涩,我的.transition().duration(650) 似乎根本不起作用。实际上我开始认为饼图是在自身之上一次又一次地绘制,因为饼图在每次更新数据时看起来更加模糊。我不确定为什么会这样,因为我非常小心地将Ppath.exit().remove(); 包含在大概正确的位置。最终,我觉得我对如何动态更新饼图数据的理解存在根本缺陷。

我很快意识到我并不是第一个遇到饼图转换问题的人。最棘手的似乎是arcTween 部分。我看着http://stackoverflow.com/questions/22312943/d3-sunburst-transition-given-updated-data-trying-to-animate-not-snap 并尽可能地紧跟其后。我使用了arcTweenUpdate 的实现,但不幸的是我的情况并没有改善。您可能会从 sn-p 注意到彩色弧线在移动,但“分割”或“切片”饼图的空白空间是静态的。这不是我想要的,它应该是过渡的,漂亮而流畅。不应该有像现在这样的静态部分或笨拙的过渡部分。

问题:我怎样才能保持视觉的动态特性(通过我的函数create_pie 以当前格式访问pie_data),同时又能像 M.博斯托克的classic donut

注意: M. Bostock 的块使用change() 函数来更新饼图。我更喜欢更正/增强/添加到我现有代码结构的答案(即Ppath.enter()...Ppath.transition()...Ppath.exit().remove())但是,我愿意接受类似于 M 的change() 函数。如果有人可以明确解释为什么我根据这篇文章的方法是不可能/非常不切实际的,那么 Bostock 的原创。

编辑

当我尝试动态更新数据时,另一个无法预料的问题是单选按钮。根据 Karan3112 的formatData() 功能:

function formatData(data) {
    if (document.getElementById("radioButton1").checked) {
        return data[Object.keys(data)[0]].slice(0,4).map(function(item, index) {
            let obj = {};
            Object.keys(data).forEach(function(key) {
                obj[key] = data[key][index] //JAN2016 : [index]
            })
            return obj;
        })
    }
     else if (document.getElementById("radioButton2").checked) {
        return data[Object.keys(data)[0]].slice(5,8).map(function(item, index) {
            let obj = {};
            Object.keys(data).forEach(function(key) {
                obj[key] = data[key][index] //JAN2016 : [index]
            })
            return obj;
        })
    }
}

基本上,在我的真实数据中,每个月都有一个长度为 8 的数组,如下所示:

'JAN2016': [17,4,1,29,7,1,1,42],

因此,根据选中的单选按钮,我希望根据 radioButton1 数组中的前 4 个项目或 radioButton2 数组中的最后 4 个项目绘制饼图。

我最初为我的 OP 省略了这部分任务,因为我认为它很容易适应,但在尝试了一段时间但进展甚微之后,我重新考虑了。我的切片似乎不起作用。我认为这是因为 formatData 函数只被调用一次。我尝试在change() 函数中放置一个formatData 调用,但这也不起作用。

【问题讨论】:

  • 我猜你的方法有问题。看看这个例子here。在更改滑块时,您只需更新饼图的数据,而不是重新绘制所有内容。
  • @karan3112 我知道那个街区,它是我灵感的一部分,非常漂亮。我不知道为什么,但我无法让 change() 函数处理我的月度数据。它不会抛出任何错误,但没有触发动画。我认为我正确地指出了所有内容,所以我采用了这种方法。我希望有一种方法可以挽救我在此处发布的内容,然后我可以在以后分享我的change() 函数问题。但我不确定,也许其他人会说change() 是让它工作的唯一方法,我得看看。
  • 另外,它不应该重绘所有内容。它应该在.enter() 期间绘制一次,然后在再次调用transition() 时绘制一次。但是,是的,出于某种原因,它正在重绘东西。
  • 示例中的数据格式是这样的,他们已经为 init d3.tsv("data.tsv" 上的每个数据生成了所有路径。但是只有在调用更改时才会显示具体路径。但是,在您的代码中,您正在生成更改滑块的路径。我想如果您在代码中更新该逻辑,它将正常工作。
  • @karan3112 我不确定您所说的特定路径是什么意思。你的意思是当我设置 var pData = pie_data[sMonth]; 时?我不确定是否有办法在 init 上生成路径并且仍然具有动态视觉效果。不过我可能是错的。

标签: javascript d3.js


【解决方案1】:

按照示例by Mike Bostock 已将您的代码更新如下。

  • 添加了一个数据格式函数,它将以{label : value} 格式返回数据。
  • 将代码逻辑从加载/重绘数据 onUpdate 更新为更新饼图值。

var width = 450;
		height = 350;
    radius = Math.min(width, height) / 4;

var color = d3.scale.ordinal().range(['darkblue','steelblue','blue',  'lightblue']);

var pie = d3.layout.pie()
    .value(function(d) { return d['JAN2016']; })
    .sort(null);

var arc = d3.svg.arc()
    .innerRadius(radius - 50)
	.outerRadius(radius - 20);

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g")
    .attr('transform', 'translate(' + 100 +',' + 75 + ')');

var period = ['JAN 2016','FEB 2016','MAR 2016', 'APR 2016', 'MAY 2016', 'JUN 2016',
'JUL 2016', 'AUG 2016', 'SEP 2016', 'OCT 2016', 'NOV 2016', 'DEC 2016'];
var pie_data = {
  'JAN2016': [16,4,1,30],
  'FEB2016': [17,4,0,30],
  'MAR2016': [16,5,1,29],
  'APR2016': [17,4,1,29],
  'MAY2016': [17,4,1,29],
  'JUN2016': [17,4,2,28],
  'JUL2016': [18,3,2,28],
  'AUG2016': [18,3,2,28],
  'SEP2016': [18,3,2,28],
  'OCT2016': [18,3,3,27],
  'NOV2016': [18,3,3,27],
  'DEC2016': [18,3,3,27]
};

var path = svg.datum(formatData(pie_data)).selectAll("path")
    .data(pie)
  .enter().append("path")
    .attr("fill", function(d, i) { return color(i); })
    .attr("d", arc)
    .each(function(d) { this._current = d; }); // store the initial angles
    
d3.select('#timeslide').on('input', function() {
  change(this.value);
});

function change(key) {
 var value =  period[key].replace(' ','');
  document.getElementById('range').innerHTML=period[key];
  pie.value(function(d) { return d[value]; }); // change the value function
  path = path.data(pie); // compute the new angles
  path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}

function arcTween(a) {
  var i = d3.interpolate(this._current, a);
  this._current = i(0);
  return function(t) {
    return arc(i(t));
  };
}

function formatData(data){
  return data[Object.keys(data)[0]].map(function(item, index){
    let obj = {};
    Object.keys(data).forEach(function(key){
      obj[key] = data[key][index] //JAN2016 : [index]
    })
    return obj;
  })
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<div id='sliderContainer'>
		<input id='timeslide' type='range' min='0' max='11' value='0' step='1'/><br>
		<span id='range'>JAN 2016</span>
	</div>

【讨论】:

  • 代码中的 cmets 非常有帮助。有时很难弄清楚var pathvar pie 在做什么。现在它更有意义了。我有点沮丧,我原来的方式似乎不起作用,但你已经展示了如何在这里很好地适应 M. Bostock 方式。我从来没有想过要放置一个formatData 函数并将其与var path = svg.datum() 一起使用。
  • 也许这是我没有包含更多代码的错;我只是想放一个简单的sn-p来说明我的动态数据更新问题。所以无论如何,现在的问题是我不知道如何为数据添加另一个维度。除了幻灯片界面,我还有两个单选按钮(类似于 M. Bostock 的示例)。根据单击的按钮,我想在不同的部分对数组进行切片。我会更新问题给你看。
  • @ArashHowaida 数据仅在饼图中加载一次,即在初始化时。我不认为你可以减少没有。饼图中的项目动态(前 4 个和后 4 个取决于单选值)。尽量保持简单并为每个收音机创建 2 个不同的饼图。有时重要的是要明白,我们无法在一个函数中实现所有功能。
  • 我觉得我现在已经很接近了。我采用了您建议的方法,在无线电选择上创建了一个新的饼图。我通过在无线电选择上调用drawPie() 函数来做到这一点。我还在帖子的 Edit 部分中使用了修改后的 formatData() 函数,该函数具有单选 if 子句以返回 .slice(0,4).slice(5,8)。奇怪的是两者都返回相同的数据:slice(0,4)。如果你能帮我找出切入你创建的formatData() 函数的正确方法,那么一切都会设置好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-12-10
  • 1970-01-01
  • 1970-01-01
  • 2017-10-08
  • 1970-01-01
  • 1970-01-01
  • 2019-05-28
相关资源
最近更新 更多