Apache ECharts 5.2.0 新特性
万能过渡动画
自然流畅的过渡动画一直是 Apache ECharts 的重要特色。通过避免数据更新带来的突变,不仅能提升视觉效果,也为表达数据的关联、演变提供了可能。因此,在 5.2.0 中,我们进一步增强了这项动画的能力。接下来,我们将看到这项「**万能过渡动画**」(Universal Transition)是如何为图表增添表现力和叙事能力的。
在以前的版本中,过渡动画有一定的局限性:只能用于同一个系列(series)内相同类型的图形的位置、尺寸等属性的过渡,不能跨系列,也不能跨图表类型。比如下面这个例子,通过饼图中扇形的变化,反映了数据百分比的变化。
function makeRandomData() { return [ { value: Math.random(), name: 'A' }, { value: Math.random(), name: 'B' }, { value: Math.random(), name: 'C' } ]; } option = { series: [ { type: 'pie', radius: [0, '50%'], data: makeRandomData() } ] }; setInterval(() => { myChart.setOption({ series: { data: makeRandomData() } }); }, 2000);
而从 5.2.0 开始,我们引入了万能过渡动画这一更强大的动画功能。有了它,过渡不再局限于同一个系列内部。现在,我们可以利用这种跨系列的形变(morphing)动画,在任何类型的系列、任何类型的图形之间进行变换。
这能有多酷呢?让我们一起来看一看!
跨系列图表的形变(morphing)过渡
通过配置项 universalTransition: true
开启万能过渡动画特性后,从饼图切换到柱状图,或者从柱状图切换到散点图,甚至在更复杂的旭日图和矩形树图之间切换,都可以实现自然的形变效果。
如下所示,在饼图和柱状图之间切换。
const dataset = { dimensions: ['name', 'score'], source: [ ['Hannah Krause', 314], ['Zhao Qian', 351], ['Jasmin Krause ', 287], ['Li Lei', 219], ['Karle Neumann', 253], ['Mia Neumann', 165], ['Böhm Fuchs', 318], ['Han Meimei', 366] ] }; const pieOption = { dataset: [dataset], series: [ { type: 'pie', // associate the series to be animated by id id: 'Score', radius: [0, '50%'], universalTransition: true, animationDurationUpdate: 1000 } ] }; const barOption = { dataset: [dataset], xAxis: { type: 'category' }, yAxis: {}, series: [ { type: 'bar', // associate the series to be animated by id id: 'Score', // Each data will have a different color colorBy: 'data', encode: { x: 'name', y: 'score' }, universalTransition: true, animationDurationUpdate: 1000 } ] }; option = barOption; setInterval(() => { option = option === pieOption ? barOption : pieOption; // Use the notMerge form to remove the axes myChart.setOption(option, true); }, 2000);
更多常见图表间的过渡效果。
这种过渡不仅限于基本的折线图、柱状图、饼图,还可以在柱状图和地图之间切换
或者在旭日图和矩形树图之间切换,甚至在非常灵活的自定义系列之间也可以进行过渡。
注意,你需要配置系列的 id,以确保需要进行过渡动画的系列之间有一一对应的关系。
最小化包需要手动引入该特性。
import { UniversalTransition } from 'echarts/features'; echarts.use([UniversalTransition]);
数据的分裂、合并动画
除了常见的数据值的更新,有时我们也会遇到数据聚合、下钻等交互后的更新,这时我们无法直接应用一对一的过渡,而是需要用分裂、合并等更多的动画效果来表达数据的变换。
为了能够表达数据之间可能的多对多联系,在 5.2.0 中我们引入了一个新的概念 groupId
。我们可以通过 series.dataGroupId 给整个系列设置分组,或者更细粒度地通过 series.data.groupId 给每个数据设置所属的分组。如果你使用 dataset
管理数据,那就更方便了,可以使用 encode.itemGroupId
指定一个维度编码为 groupId
。
例如,我们要实现一个柱状图的下钻动画,我们可以将下钻后的整个系列的数据都设置为相同的 groupId
,这个 groupId
对应于下钻前的数据
option = { xAxis: { data: ['Animals', 'Fruits', 'Cars'] }, yAxis: {}, dataGroupId: '', animationDurationUpdate: 500, series: { type: 'bar', id: 'sales', data: [ { value: 5, groupId: 'animals' }, { value: 2, groupId: 'fruits' }, { value: 4, groupId: 'cars' } ], universalTransition: { enabled: true, divideShape: 'clone' } } }; const drilldownData = [ { dataGroupId: 'animals', data: [ ['Cats', 4], ['Dogs', 2], ['Cows', 1], ['Sheep', 2], ['Pigs', 1], ['Cows', 1], ['Sheep', 2], ['Pigs', 1] ] }, { dataGroupId: 'fruits', data: [ ['Apples', 4], ['Oranges', 2], ['Oranges', 2] ] }, { dataGroupId: 'cars', data: [ ['Toyota', 4], ['Opel', 2], ['Volkswagen', 2], ['Volkswagen', 2] ] } ]; myChart.on('click', event => { if (event.data) { const subData = drilldownData.find(data => { return data.dataGroupId === event.data.groupId; }); if (!subData) { return; } myChart.setOption({ xAxis: { data: subData.data.map(item => { return item[0]; }) }, series: { type: 'bar', id: 'sales', dataGroupId: subData.dataGroupId, data: subData.data.map(item => { return item[1]; }), universalTransition: { enabled: true, divideShape: 'clone' } }, graphic: [ { type: 'text', left: 50, top: 20, style: { text: 'Back', fontSize: 18 }, onclick: function() { myChart.setOption(option, true); } } ] }); } });
借助 groupId
,我们还可以实现更丰富的聚合、下钻动画。
数据的聚合。
单个系列下钻成两个系列
万能过渡动画将表达数据关联和演变的能力提升到了一个新的高度,为你的可视化创作灵感插上翅膀。
看到这里,我们知道你已经迫不及待想尝试了。不过别急,5.2.0 还有更多值得一看的新特性。
调色盘颜色拾取策略
在上面的万能过渡动画例子中,你可能已经注意到了,我们用了一个之前版本没有的配置项 colorBy
。这个配置项也是我们在这个版本新增的特性,用于为系列配置不同粒度的调色盘颜色拾取。该配置项目前支持两种策略。
'series'
会按系列分配调色盘中的颜色,同一系列中的所有数据都是一个颜色。'data'
会按数据项分配调色盘中的颜色,每个数据项都会使用不同的颜色。
以前,我们为每种类型的系列固定了这种策略,例如,柱状图固定为 'series'
,饼图固定为 'data'
。
而现在有了这个新功能,我们可以给柱状图的每个数据项分配不同的颜色。
option = { xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] }, yAxis: { type: 'value' }, series: [ { data: [120, 200, 150, 80, 70, 110, 130], type: 'bar', colorBy: 'data' } ] };
或者在饼图中统一使用一种颜色。
option = { series: { type: 'pie', colorBy: 'series', radius: [0, '50%'], itemStyle: { borderColor: '#fff', borderWidth: 1 }, data: [ { value: 335, name: 'Direct Visit' }, { value: 234, name: 'Union Ad' }, { value: 1548, name: 'Search Engine' } ] } };
这个配置项可以让我们避免去寻找调色盘颜色并逐个设置的麻烦,在特定场景下或许能提供便利。我们后续会进一步增强这个配置项,提供更多的策略。
极坐标系柱状图的标签
在这个版本中,我们实现了极坐标系上柱状图的标签,并支持丰富的标签定位配置。下面是一个在起始点显示标签的进度图。
option = { angleAxis: { show: false, max: 10 }, radiusAxis: { show: false, type: 'category', data: ['AAA', 'BBB', 'CCC', 'DDD'] }, polar: {}, series: [ { type: 'bar', data: [3, 4, 5, 6], colorBy: 'data', roundCap: true, label: { show: true, // Try changing it to 'insideStart' position: 'start', formatter: '{b}' }, coordinateSystem: 'polar' } ] };
更多标签位置的配置。
这个灵活的标签位置配置项极大地丰富了极坐标系柱状图的表现力,让文字能够清晰地与对应的数据匹配。
饼图无数据时的样式
在以前的版本中,如果饼图中没有数据,屏幕可能完全是空白的。因为没有任何视觉元素,用户可能会怀疑是不是出现了 bug。
为了解决这个问题,在这个版本中,我们在没有数据时会默认显示一个灰色的占位圆,以防止屏幕完全空白。我们可以通过 emptyCircleStyle
配置这个占位圆的样式。
option = { series: [ { type: 'pie', data: [], // showEmptyCircle: false, emptyCircleStyle: { // change the style to empty circle color: 'transparent', borderColor: '#ddd', borderWidth: 1 } } ] };
如果你不希望显示这个灰色的圆,也可以设置 showEmptyCircle: false
将其关闭。
高维数据的性能增强
我们从 4.0 版本开始引入了 dataset 来管理图表数据。然而,在一些特别高维(>100)数据的极端场景下,我们可能会遇到一些急剧的性能下降,比如下面这个通过一千个系列来可视化一千维数据的场景(#11907),甚至可能导致卡死。
const indices = Array.from(Array(1000), (_, i) => {
return `index${i}`;
});
const option = {
xAxis: { type: 'category' },
yAxis: {},
dataset: {
// dimension: ['date', . . indices],
source: Array.from(Array(10), (_, i) => {
return {
date: i,
... .indices.reduce((item, next) => {
item[next] = Math.random() * 100;
return item;
}, {})
};
})
},
series: indices.map(index => {
return { type: 'line', name: index };
})
};
这个性能问题的原因是,我们会在每个系列的底层根据需要处理高维数据集,并保存一份处理后的数据和关于数据维度的元信息。这意味着,在上面的例子中,1000 x 1000
个维度的数据需要被处理和保存,这给内存和垃圾回收带来了巨大的压力,导致高维情况下性能急剧下降。
在新版本中,我们优化了这个问题,让所有系列尽可能地共享数据集的存储(是否共享取决于系列如何使用数据)。这个优化确保了内存不会随着数据集维度和系列数量的增长而爆炸,显著提升了在这种极端场景下的初始化性能。刚才描述的例子的渲染时间也减少到了可接受的 300 毫秒以内。
从这个优化中受益的不仅仅是这种高维场景。当使用大数据量的 dataset 时,多个系列因为数据共享而只需处理一次数据,因此也能带来显著的性能提升。
自定义系列的类型优化
自定义系列提供了一种非常灵活的方式来创建系列图表。相比其他系列,自定义系列的学习曲线可能有点陡峭。因此,在这个版本中,我们进一步优化了自定义系列中核心方法 renderItem
的类型,通过对 renderItem
的参数和返回值的类型进行更精确的推断,从而可以根据返回的类型推断出可以设置哪些元素的属性
series = {
type: 'custom',
renderItem(params) {
return {
type: 'group',
// The group type uses children to store children of other types
children: [
{
type: 'circle',
// circle has the following configurable shape attributes
shape: { r: 10, cx: 0, cy: 0 },
// Configurable styles
style: { fill: 'red' }
},
{
type: 'rect',
// rect has the following configurable shape properties
shape: { x: 0, y: 0, width: 100, height: 100 }
},
{
type: 'path',
// Custom path shapes
shape: { d: '...' }
}
]
};
}
};
总结
如果你对 5.2.0 中的一些功能和优化感兴趣,不妨更新到最新版本的 Apache ECharts 并亲自尝试一下。
如果你对 Apache ECharts 的未来发展感兴趣,也可以在 GitHub Milestone 关注我们的开发计划。欢迎加入我们成为贡献者(在 Wiki 了解更多)。
完整更新日志
查看更新日志