D3 PIE 图表在进入和更新 angular 应用程序时动画化
D3 PIE chart animate on enter and update angular app
我正在我的应用程序中使用 D3 饼图来可视化一些数据。我能够正确显示数据。我只想让图表在进入时进行动画处理。似乎当我更改数据时,它的动画效果很好。我在这里模拟了代码
主要问题是当我在 'enter' 中添加 arcTween 时它抛出错误
const data = pie().sort(null).value((d: any) => d.value)(rawData);
const { height, radius } = this.dimensions;
const slices = this.svg.select(`.slices${this.id}`);
// Inner Slices Enter/Update/Remove (for DOUGHNUTS)
const innerSliceUpdate = slices.selectAll(`.innerSlice${this.id}`).data(data);
const innerSliceEnter = innerSliceUpdate.enter().append('path').attr('class', `innerSlice${this.id}`);
innerSliceUpdate.exit().remove();
innerSliceEnter
.style('fill', (d: any) => hsl(d.data.color).darker(0.7).toString())
.style('transition', SLICE_ANIME)
.attr('d', d => this.pieInner(d, radius.x + 0.5, radius.y + 0.5, radius.z, height))
.each(d => { this.interpolators[d.data.label] = d; });
innerSliceUpdate
.transition().duration(ANIME_DURATION * 1e3)
.attrTween('d', d => (t, i = this.interpolatorFn(d)(t)) => this.pieInner(i, radius.x + 0.5, radius.y + 0.5, radius.z, height));
// Top Slices Enter/Update/Remove (facing up x/y axis)
const topSliceUpdate = slices.selectAll(`.topSlice${this.id}`).data(data);
const topSliceEnter = topSliceUpdate.enter().append('path').attr('class', `topSlice${this.id}`);
topSliceUpdate.exit().remove();
topSliceEnter
.style('fill', d => d.data.color)
.style('stroke', d => d.data.color)
.style('transition', SLICE_ANIME)
.attr('d', d => this.pieTop(d, radius.x, radius.y, radius.z))
.each(d => { this.interpolators[d.data.label] = d; });
// ****** TRYING TO ADD AN ARC TWEEN HERE *******
topSliceUpdate
.transition().duration(ANIME_DURATION * 1e3)
.attrTween('d', d => (t, i = this.interpolatorFn(d)(t)) => this.pieTop(i, radius.x, radius.y, radius.z));
D3 新手,如有任何帮助,我们将不胜感激
注意:寻找解决方案来学习和理解我做错了什么
实现动画的一种方法是使用一些不同于当前初始化它们的值来初始化图表切片,然后使用生命周期挂钩之一(如 ngAfterViewInit())来设置实际的初始值。根据您现有的代码,类似于:
export class AppComponent {
name = 'Angular ' + VERSION.major;
data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 0 },
{ key: 'completed', color: 'green', label: 'Completed', value: 0 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 0 },
];
ngAfterViewInit(){
this.data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 3 },
{ key: 'completed', color: 'green', label: 'Completed', value: 18 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 5 },
];
}
constructor(){
setTimeout(() => {
this.data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 5 },
{ key: 'completed', color: 'green', label: 'Completed', value: 5 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 15 },
]
}, 2000)
}
}
下面是另一个 hacky
解决方案,但实施起来要容易得多。它使用与@amishra
实现的相同的想法
在你的piechart.component.ts中改变ngAfterViewInit()
ngAfterViewInit(): void {
this.id = `__${Math.floor(Math.random() * 5e10)}`;
this.init();
if (this.data && this.data.length >= 0) {
this.draw(this.data.map(item => ({...item, value: 1}))); // <-- Simply add this line
this.draw(this.data);
}
}
这个想法是让用户看到动画到最终值的相等分区
这段代码
topSliceEnter
....
.attr("d", d => this.pieTop(d, radius.x, radius.y, radius.z))
明确表示新添加的项目将以完成状态呈现,这没有多大意义,如果你想为某些东西制作动画,那么你需要从哪里开始制作动画。
假设您想为 {startAngle: 0, endAngle: 0.1}
中的图表制作动画。希望你没想到我会重构你所有的代码,所以我修复了 topSlice
部分:
https://stackblitz.com/edit/angular-ivy-f7pvyz?file=src%2Fapp%2Fapp.component.ts
topSliceEnter
......
.attr("d", d => this.pieTop({startAngle: 0, endAngle: 0.1}, radius.x, radius.y, radius.z))
.each(d => {
this.interpolators[d.data.label] = {...d, startAngle: 0, endAngle: 0.1};
// I would refactor this. the interpolation logic looks strange, but still this will make your example animate fine.
});
topSliceUpdate.merge(topSliceEnter) // merge will make next transition to be applied on both entering and updating elements
.transition()
...
P.S。您的代码中存在一些错误,无法正确处理更新,例如颜色更改
我正在我的应用程序中使用 D3 饼图来可视化一些数据。我能够正确显示数据。我只想让图表在进入时进行动画处理。似乎当我更改数据时,它的动画效果很好。我在这里模拟了代码
主要问题是当我在 'enter' 中添加 arcTween 时它抛出错误
const data = pie().sort(null).value((d: any) => d.value)(rawData);
const { height, radius } = this.dimensions;
const slices = this.svg.select(`.slices${this.id}`);
// Inner Slices Enter/Update/Remove (for DOUGHNUTS)
const innerSliceUpdate = slices.selectAll(`.innerSlice${this.id}`).data(data);
const innerSliceEnter = innerSliceUpdate.enter().append('path').attr('class', `innerSlice${this.id}`);
innerSliceUpdate.exit().remove();
innerSliceEnter
.style('fill', (d: any) => hsl(d.data.color).darker(0.7).toString())
.style('transition', SLICE_ANIME)
.attr('d', d => this.pieInner(d, radius.x + 0.5, radius.y + 0.5, radius.z, height))
.each(d => { this.interpolators[d.data.label] = d; });
innerSliceUpdate
.transition().duration(ANIME_DURATION * 1e3)
.attrTween('d', d => (t, i = this.interpolatorFn(d)(t)) => this.pieInner(i, radius.x + 0.5, radius.y + 0.5, radius.z, height));
// Top Slices Enter/Update/Remove (facing up x/y axis)
const topSliceUpdate = slices.selectAll(`.topSlice${this.id}`).data(data);
const topSliceEnter = topSliceUpdate.enter().append('path').attr('class', `topSlice${this.id}`);
topSliceUpdate.exit().remove();
topSliceEnter
.style('fill', d => d.data.color)
.style('stroke', d => d.data.color)
.style('transition', SLICE_ANIME)
.attr('d', d => this.pieTop(d, radius.x, radius.y, radius.z))
.each(d => { this.interpolators[d.data.label] = d; });
// ****** TRYING TO ADD AN ARC TWEEN HERE *******
topSliceUpdate
.transition().duration(ANIME_DURATION * 1e3)
.attrTween('d', d => (t, i = this.interpolatorFn(d)(t)) => this.pieTop(i, radius.x, radius.y, radius.z));
D3 新手,如有任何帮助,我们将不胜感激
注意:寻找解决方案来学习和理解我做错了什么
实现动画的一种方法是使用一些不同于当前初始化它们的值来初始化图表切片,然后使用生命周期挂钩之一(如 ngAfterViewInit())来设置实际的初始值。根据您现有的代码,类似于:
export class AppComponent {
name = 'Angular ' + VERSION.major;
data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 0 },
{ key: 'completed', color: 'green', label: 'Completed', value: 0 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 0 },
];
ngAfterViewInit(){
this.data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 3 },
{ key: 'completed', color: 'green', label: 'Completed', value: 18 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 5 },
];
}
constructor(){
setTimeout(() => {
this.data = [
{ key: 'inprogress', color: 'yellow', label: 'In Progress', value: 5 },
{ key: 'completed', color: 'green', label: 'Completed', value: 5 },
{ key: 'delayed', color: 'red', label: 'Delayed', value: 15 },
]
}, 2000)
}
}
下面是另一个 hacky
解决方案,但实施起来要容易得多。它使用与@amishra
在你的piechart.component.ts中改变ngAfterViewInit()
ngAfterViewInit(): void {
this.id = `__${Math.floor(Math.random() * 5e10)}`;
this.init();
if (this.data && this.data.length >= 0) {
this.draw(this.data.map(item => ({...item, value: 1}))); // <-- Simply add this line
this.draw(this.data);
}
}
这个想法是让用户看到动画到最终值的相等分区
这段代码
topSliceEnter
....
.attr("d", d => this.pieTop(d, radius.x, radius.y, radius.z))
明确表示新添加的项目将以完成状态呈现,这没有多大意义,如果你想为某些东西制作动画,那么你需要从哪里开始制作动画。
假设您想为 {startAngle: 0, endAngle: 0.1}
中的图表制作动画。希望你没想到我会重构你所有的代码,所以我修复了 topSlice
部分:
https://stackblitz.com/edit/angular-ivy-f7pvyz?file=src%2Fapp%2Fapp.component.ts
topSliceEnter
......
.attr("d", d => this.pieTop({startAngle: 0, endAngle: 0.1}, radius.x, radius.y, radius.z))
.each(d => {
this.interpolators[d.data.label] = {...d, startAngle: 0, endAngle: 0.1};
// I would refactor this. the interpolation logic looks strange, but still this will make your example animate fine.
});
topSliceUpdate.merge(topSliceEnter) // merge will make next transition to be applied on both entering and updating elements
.transition()
...
P.S。您的代码中存在一些错误,无法正确处理更新,例如颜色更改