Apache ECharts 5.2.0 特性介绍

全局过渡动画

在 Apache ECharts 中我们一直把自然流畅的过渡动画作为一个重要特性。通过避免数据带来的突变,不仅仅可以改善视觉效果,更为表达数据的关联和演变提供了可能。因此,在 5.2.0 中,我们进一步将过渡动画从表现系列内部数据的变化,泛化到全局能力。接下来,我们会看到这种全局过渡动画 Universal Transition是如何为图表增加表现力和叙事能力的。

在之前的版本中,过渡动画有一定的局限性:只能用于相同类型的图形的位置、尺寸、形状,而且只能作用在相同类型的系列上。比如,下面例子就是通过饼图中扇区形状的变化反映了数据分布的变化:

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

而从 5.2.0 开始,我们引入了更强大的全局过渡动画能力,让过渡动画不再局限于相同类型的系列之间。现在,我们可以使用这种跨系列的形变,为任意类型的系列和任意类型的图形做形变动画。

这会有多酷呢?我们一起来感受一下!

跨系列的形变动画

在设置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',
      // 通过 id 关联需要过渡动画的系列
      id: 'Score',
      radius: [0, '50%'],
      universalTransition: true,
      animationDurationUpdate: 1000
    }
  ]
};
const barOption = {
  dataset: [dataset],
  xAxis: {
    type: 'category'
  },
  yAxis: {},
  series: [
    {
      type: 'bar',
      // 通过 id 关联需要过渡动画的系列
      id: 'Score',
      // 每个数据都是用不同的颜色
      colorBy: 'data',
      encode: { x: 'name', y: 'score' },
      universalTransition: true,
      animationDurationUpdate: 1000
    }
  ]
};

option = barOption;

setInterval(() => {
  option = option === pieOption ? barOption : pieOption;
  // 使用 notMerge 的形式可以移除坐标轴
  myChart.setOption(option, true);
}, 2000);
live

更多的常见基础图表之间的过渡:

这样的动画过渡不再仅仅局限于基础的折、柱、饼中,在柱状图和地图之间:

或者旭日图和矩形树图之间,甚至非常灵活的自定义系列之间都可以进行动画的过渡。

注意需要配置系列的 id 来保证需要动画过渡的系列之间能够一一对应。

按需引入的代码需要单独引入该特性

import { UniversalTransition } from 'echarts/features';
echarts.use([UniversalTransition]);

数据的分裂和合并动画

除了常见的数据值的更新,有时候我们还会碰到数据的聚合、下钻等交互后的更新,这个时候我们就不能直接应用一对一的动画过渡,而需要使用更多像分裂、合并这样的动画效果,来通过正确的图形动画表达出数据的变换。

为了能够表达数据之间可能存在的多对多联系,在 5.2.0 中我们新引入了一个数据组groupId的概念,我们可以通过 series.dataGroupId 设置整个系列所属的组,或者更细粒度的通过 series.data.groupId 设置每个数据所属的组。如果你使用了dataset管理数据则更方便了,可以使用encode.itemGroupId来指定一个维度编码成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]
    ]
  },
  {
    dataGroupId: 'fruits',
    data: [
      ['Apples', 4],
      ['Oranges', 2]
    ]
  },
  {
    dataGroupId: 'cars',
    data: [
      ['Toyota', 4],
      ['Opel', 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);
          }
        }
      ]
    });
  }
});
live

通过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'
    }
  ]
};
live

或者在饼图中统一使用一个颜色:

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'
      }
    ]
  }
};
live

这一配置项可以让我们避免了去找调色盘颜色然后去一一设置的麻烦,可能在特定的场景需求中提供便捷。后续我们会对这个配置项做进一步的增强,提供更多的调色盘取色的策略。

极坐标柱状图的标签

这个版本中我们实现了极坐标上的柱状图的标签,并且支持丰富的标签定位配置。下面是一个最常见的标签显示在端点的进度图:

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,
        // 试试改成 'insideStart'
        position: 'start',
        formatter: '{b}'
      },
      coordinateSystem: 'polar'
    }
  ]
};
live

更多标签位置的配置:

这一灵活的标签位置配置项大大丰富了极坐标柱状图的表达能力,让文字清晰地匹配对应的数据。

空数据的饼图样式

在之前的版本中,如果饼图没有数据,画面中可能就是完全空白的。因为没有任何的视觉元素,所以用户会疑惑是不是出现了 bug 导致图中没有内容。

为了解决这个问题,这个版本我们会默认在无可显示数据的时候显示一个灰色的占位圆以防止画面中完全空白。我们可以通过emptyCircleStyle配置这个占位圆的样式。

option = {
  series: [
    {
      type: 'pie',
      data: [],
      // showEmptyCircle: false,
      emptyCircleStyle: {
        // 将样式改为空心圆
        color: 'transparent',
        borderColor: '#ddd',
        borderWidth: 1
      }
    }
  ]
};
live

如果不想要显示这个灰色的圆,也可以设置showEmptyCircle: false关闭。

高维数据的性能增强

我们从 4.0 开始引入了 dataset 用来管理图表的数据,通常情况下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 };
  })
};

产生这个性能问题是因为我们在底层每个系列都会按照其需要处理一遍这个 dataset,然后保存一份处理过后的数据以及维度等元信息。这意味着刚才那个例子中需要处理并保存1000 x 1000个维度的信息,这带来了巨大的内存和垃圾回收的压力,从而导致了高维度的性能的急剧下降。

在新版本中我们对这个问题做了优化,所有系列都尽可能共享 dataset 的数据(能否共享取决于系列怎么使用这份数据)存储而非每个系列都处理并存储一次,并且只处理和存储了使用到的维度。这些优化保证了内存不会随着 dataset 维度和系列的增长而爆炸,大幅度的提升了极端场景下的初始化性能。刚才例子的渲染耗时也从卡住降低到了可接受的 300 毫秒以下。

这次优化带来收益的还不只是这种高维的场景,在使用维度不高但是数据量很大的 dataset 的时候,因为数据的共享所以多个系列只处理了一遍数据,因此也可以带来显著的性能提升。

自定义系列的类型优化

自定义系列提供了非常灵活的创建系列图形的方式,相对于其它系列,自定义系列的学习曲线可能有些陡峭,而且容易出错。因此在这个版本中,我们进一步的优化了自定义系列中的核心方法renderItem的类型,对于renderItem的参数和返回值类型做了更精确的推断,从而可以根据返回的图形类型推断出可以设置该图形的哪些属性:

series = {
  type: 'custom',
  renderItem(params) {
    return {
      type: 'group',
      // group 类型使用 children 存储其它类型的子元素
      children: [
        {
          type: 'circle',
          // circle 拥有下面这些可以配置的 shape 属性
          shape: { r: 10, cx: 0, cy: 0 },
          // 可以配置的样式
          style: { fill: 'red' }
        },
        {
          type: 'rect',
          // rect 拥有下面这些可以配置的 shape 属性
          shape: { x: 0, y: 0, width: 100, height: 100 }
        },
        {
          type: 'path',
          // 自定义路径图形
          shape: { d: '...' }
        }
      ]
    };
  }
};

小结

以上我们介绍了 5.2.0 中的新特性以及优化,如果你对其中的一些特性感兴趣,不妨更新到最新版本的 Apache ECharts 亲自体验一下。

如果你对 Apache ECharts 接下来的计划感兴趣,也可以在 GitHub Milestone 关注我们的开发计划。也非常欢迎加入我们的贡献者行列(在 Wiki 中了解更多)。

完整更新记录

查看版本更新

本文贡献者 在 GitHub 上编辑本页

pissang pissangOvilia Ovilia