Apache ECharts 5.3.0 新特性

Apache ECharts 5.3.0 包含了对动画表现力、渲染性能以及服务端渲染的显著增强。它还引入了社区期待已久的功能,例如多轴刻度的自动对齐、tooltip 数值格式化以及地图投影等。

关键帧动画

此前,ECharts 的动画主要聚焦于创建、更新和移除元素的过渡动画,通常只有起始状态和结束状态。为了表达更复杂的动画效果,我们在 5.3.0 中为自定义系列和图形组件引入了全新的关键帧动画。

以下是通过关键帧动画实现的呼吸效果的简易示例

option = {
  graphic: {
    type: 'circle',
    shape: { r: 100 },
    left: 'center',
    top: 'center',
    keyframeAnimation: [
      {
        duration: 3000,
        loop: true,
        keyframes: [
          {
            percent: 0.5,
            easing: 'sinusoidalInOut',
            scaleX: 0.1,
            scaleY: 0.1
          },
          {
            percent: 1,
            easing: 'sinusoidalInOut',
            scaleX: 1,
            scaleY: 1
          }
        ]
      }
    ]
  }
};
在线示例

在关键帧动画中,你可以配置动画的时长、延迟、缓动函数、是否循环、每帧的位置、缓动以及图形属性。还可以为同一元素同时设置多个关键帧动画,并采用不同的配置。灵活的配置使我们能够实现非常复杂的动画效果,下面列出了一些关键帧动画的典型应用场景。

自定义加载动画

ECharts 默认内置了加载动画,可以通过调用 showLoading 来显示。社区中对更多加载动画效果的需求非常频繁。现在借助关键帧动画,我们可以使用 graphic 组件配合关键帧动画,打造任意想要的加载动画效果。

以下是文本描边动画的示例。

option = {
  graphic: {
    elements: [
      {
        type: 'text',
        left: 'center',
        top: 'center',
        style: {
          text: 'Apache ECharts',
          fontSize: 40,
          fontWeight: 'bold',
          lineDash: [0, 200],
          lineDashOffset: 0,
          fill: 'transparent',
          stroke: '#000',
          lineWidth: 1
        },
        keyframeAnimation: {
          duration: 3000,
          loop: true,
          keyframes: [
            {
              percent: 0.7,
              style: {
                fill: 'transparent',
                lineDashOffset: 200,
                lineDash: [200, 0]
              }
            },
            {
              // Stop for a while.
              percent: 0.8,
              style: {
                fill: 'transparent'
              }
            },
            {
              percent: 1,
              style: {
                fill: 'black'
              }
            }
          ]
        }
      }
    ]
  }
};
在线示例

或对柱形进行动画。

const columns = [];
for (let i = 0; i < 7; i++) {
  columns.push({
    type: 'rect',
    x: i * 20,
    shape: {
      x: 0,
      y: -40,
      width: 10,
      height: 80
    },
    style: {
      fill: '#5470c6'
    },
    keyframeAnimation: {
      duration: 1000,
      delay: i * 200,
      loop: true,
      keyframes: [
        {
          percent: 0.5,
          scaleY: 0.1,
          easing: 'cubicIn'
        },
        {
          percent: 1,
          scaleY: 1,
          easing: 'cubicOut'
        }
      ]
    }
  });
}
option = {
  graphic: {
    elements: [
      {
        type: 'group',
        left: 'center',
        top: 'center',
        children: columns
      }
    ]
  }
};
在线示例

在图表中扩展更丰富的动画效果

带动画效果的散点图一直是 ECharts 的一大亮点。开发者可以使用 effectScatter 系列实现带涟漪效果的动态图形散点图,让图表更具趣味并突出用户关注点。与加载动画一样,开发者也常常希望拥有更多动画效果。现在我们可以通过在 custom 系列中使用关键帧动画,实现更为复杂的效果。

例如,下面的示例为自定义系列在 SVG 地图上绘制的图钉添加了跳动效果,并配合涟漪动画。

fetch(
  'https://echarts.org.cn/examples/data/asset/geo/Map_of_Iceland.svg'
)
  .then(response => response.text())
  .then(svg => {
    echarts.registerMap('iceland_svg', { svg: svg });
    option = {
      geo: {
        map: 'iceland_svg',
        left: 0,
        right: 0
      },
      series: {
        type: 'custom',
        coordinateSystem: 'geo',
        geoIndex: 0,
        zlevel: 1,
        data: [
          [488, 459, 100],
          [770, 757, 30],
          [1180, 743, 80],
          [894, 1188, 61],
          [1372, 477, 70],
          [1378, 935, 81]
        ],
        renderItem(params, api) {
          const coord = api.coord([
            api.value(0, params.dataIndex),
            api.value(1, params.dataIndex)
          ]);

          const circles = [];
          for (let i = 0; i < 5; i++) {
            circles.push({
              type: 'circle',
              shape: {
                cx: 0,
                cy: 0,
                r: 30
              },
              style: {
                stroke: 'red',
                fill: 'none',
                lineWidth: 2
              },
              // Ripple animation
              keyframeAnimation: {
                duration: 4000,
                loop: true,
                delay: (-i / 4) * 4000,
                keyframes: [
                  {
                    percent: 0,
                    scaleX: 0,
                    scaleY: 0,
                    style: {
                      opacity: 1
                    }
                  },
                  {
                    percent: 1,
                    scaleX: 1,
                    scaleY: 0.4,
                    style: {
                      opacity: 0
                    }
                  }
                ]
              }
            });
          }
          return {
            type: 'group',
            x: coord[0],
            y: coord[1],
            children: [
              ...circles,
              {
                type: 'path',
                shape: {
                  d:
                    'M16 0c-5.523 0-10 4.477-10 10 0 10 10 22 10 22s10-12 10-22c0-5.523-4.477-10-10-10zM16 16c-3.314 0-6-2.686-6-6s2.686-6 6-6 6 2.686 6 6-2.686 6-6 6z',
                  x: -10,
                  y: -35,
                  width: 20,
                  height: 40
                },
                style: {
                  fill: 'red'
                },
                // Jump animation.
                keyframeAnimation: {
                  duration: 1000,
                  loop: true,
                  delay: Math.random() * 1000,
                  keyframes: [
                    {
                      y: -10,
                      percent: 0.5,
                      easing: 'cubicOut'
                    },
                    {
                      y: 0,
                      percent: 1,
                      easing: 'bounceOut'
                    }
                  ]
                }
              }
            ]
          };
        }
      }
    };

    myChart.setOption(option);
  });
在线示例

加载 Lottie 动画

为充分发挥全新关键帧动画的威力,ECharts 团队的沈毅编写了一个 Lottie 动画解析库,能够将 Lottie 动画文件解析为 ECharts 的图形格式进行渲染。结合 Lottie 本身的表现力,我们可以在项目中引入更多惊艳的动画。

图形组件过渡动画

我们在 5.0 中已经为自定义系列返回的元素提供了更灵活的过渡动画配置。transitionenterFromleaveTo 配置项可以指定每个元素的哪些属性需要过渡动画,以及在图形创建和销毁时的动画方式。下面是一个示例。

function renderItem() {
  //...
  return {
    //...
    x: 100,
    // 'style', 'x', 'y' will be animated
    transition: ['style', 'x', 'y'],
    enterFrom: {
      style: {
        // Fade in
        opacity: 0
      },
      // Fly in from the left
      x: 0
    },
    leaveTo: {
      // Fade out
      opacity: 0
    },
    // Fly out to the right
    x: 200
  };
}

在 5.3.0 中我们将这些过渡动画的配置扩展到了 graphic 组件,并作了进一步的增强。

如果不想逐一写出需要动画的属性,现在可以直接配置 transition: 'all' 来对所有属性开启动画。

我们还新增了 enterAnimationupdateAnimationleaveAnimation,分别用于配置每个图形的进入、更新、退出动画的 durationdelayeasing。渐变颜色现在也支持动画。

全新 SVG 渲染器

在 5.3.0 中我们对 SVG 渲染器进行了重构,性能提升约为 2 倍至 10 倍,在部分特殊场景甚至可提升数十倍。

此前,SVG 渲染器直接从渲染队列更新到 DOM,但由于 zrender 的图形属性与 DOM 并非一一对应,需要在中间实现非常复杂的 diff 逻辑,既容易出错,又在某些场景下无法提供最佳性能。本次版本我们首先将完整渲染构建为 VDOM,然后再把 VDOM patch 到真实 DOM 完成渲染。完整渲染避免了复杂 diff 逻辑可能导致的 Bug,VDOM 与 DOM 的一一对应保证了 patch 时的最小化更新,从而带来巨大的性能提升。

此示例 能让你直观感受到性能的提升。相比之前的版本,在 SVG 模式下拖拽图表时新版本更加流畅。

5.2.2(之前) 5.3.0(之后)
before after

除了性能提升之外,我们还能对渲染得到的 VDOM 做更多事情,例如服务端渲染,下面将进行详述。

零依赖的服务端渲染

在之前的版本中,ECharts 也可以实现服务端渲染,但必须依赖 node-canvas,或者在使用 SVG 模式时依赖 JSDOM 来模拟 DOM 环境。这些依赖不仅会增大体积并带来额外的使用门槛,还会影响性能。

全新 SVG 渲染器使我们能够直接从中间渲染的 VDOM 获取字符串,实现了完全零依赖的服务端渲染,并输出集成了 CSS 动画的更精细的 SVG 字符串。

const echarts = require('echarts');

// In SSR mode the first parameter does not need to be passed in as a DOM object
const chart = echarts.init(null, null, {
  renderer: 'svg', // must use SVG mode
  ssr: true, // enable SSR
  width: 400, // need to specify height and width
  height: 300
});

// setOption as normal
chart.setOption({
  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'
    }
  ]
});

// Output string
const svgStr = chart.renderToSVGString();

自定义地图投影

地图一直是 ECharts 中使用最广泛的组件。通常,地图组件使用带有经纬度信息的 GeoJSON 数据,ECharts 会计算合适的显示范围并将经纬度线性映射到该范围内。这是最简单的投影方式。但线性投影在复杂的地图场景下会出现失真,例如使用 Albers 投影来解决线性投影的失真问题,或在世界地图中将太平洋放在中间等需求。

因此在 5.3.0 中我们引入了扩展的地图投影机制。它通过 projectunproject 方法告诉 ECharts 如何将坐标投影以及如何从投影坐标反算经纬度。下面展示了使用墨卡托投影的简单示例。

series = {
  type: 'map',
  projection: {
    project: point => [
      (point[0] / 180) * Math.PI,
      -Math.log(Math.tan((Math.PI / 2 + (point[1] / 180) * Math.PI) / 2))
    ],
    unproject: point => [
      (point[0] * 180) / Math.PI,
      ((2 * 180) / Math.PI) * Math.atan(Math.exp(point[1])) - 90
    ]
  }
};

除了自己实现投影公式外,也可以使用诸如 d3-geo 等第三方库提供的投影实现。

const projection = d3.geoConicEqualArea();
// ...
series = {
  type: 'map',
  projection: {
    project: point => projection(point),
    unproject: point => projection.invert(point)
  }
};

结合 5.2 中新增的全局过渡动画特性,我们可以对不同投影效果之间的切换进行动画演示: !

map-projection-animation

除了地图投影外,本次发布对地图还做了以下两项增强。

  • 为 GeoJSON 数据提供了对 'LineString''MultiLineString' 的支持。
  • 将默认标签位置的计算方式从包围盒中心改为最大面积多边形的质心,以获得更精确的结果。

多轴刻度对齐

多轴刻度对齐是社区长期的需求,社区中有很多关于在 ECharts 中实现轴对齐的文章,通常实现起来既繁琐又受限。

在 5.3.0 中,我们终于在 'value''log' 轴上实现了刻度对齐功能。只需在需要对齐的轴上配置 alignTicks: true,该轴会自动依据第一根轴的刻度进行调整,实现自动对齐。

option = {
  tooltip: {
    trigger: 'axis'
  },
  legend: {},
  xAxis: [
    {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
      axisPointer: {
        type: 'shadow'
      }
    }
  ],
  yAxis: [
    {
      type: 'value',
      name: 'Precipitation',
      alignTicks: true,
      axisLabel: {
        formatter: '{value} ml'
      }
    },
    {
      type: 'value',
      name: 'Temperature',
      axisLabel: {
        formatter: '{value} °C'
      }
    }
  ],
  series: [
    {
      name: 'Evaporation',
      type: 'bar',
      // prettier-ignore
      data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3]
    },
    {
      name: 'Precipitation',
      type: 'bar',
      // prettier-ignore
      data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
    },
    {
      name: 'Temperature',
      type: 'line',
      yAxisIndex: 1,
      data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2]
    }
  ]
};
在线示例

禁用强调和选择状态

ECharts 中的 emphasis 状态用于在鼠标悬停元素时给用户反馈,但在元素数量庞大的图表中,高亮动画可能导致性能问题。特别是由 tooltiplegend 组件联动触发的高亮,往往会一次性高亮多个元素。

因此本次发布新增了 emphasis.disabled 配置。如果你不需要高亮反馈并且在意交互性能,可以通过该选项关闭 emphasis 状态。

对于 select 状态,我们同样新增了 select.disabled,该选项可用于将部分数据设为不可被选中。

支持整系列选择

从 5.3.0 起,我们支持将 selectedMode 配置为 'series',从而一次性选中整个系列的所有数据。

Tooltip 中数值的格式化

Tooltip 用于在用户悬停时展示数据项的更详细信息。ECharts 也提供了 formatter 回调函数,让开发者可以灵活定制 Tooltip 的内容。

然而我们发现,大多数情况下开发者仅需对 Tooltip 中的数值部分进行格式化,例如固定小数位、添加 $ 前缀等。过去若要格式化数字,需要在 formatter 中重新拼装整个 Tooltip 内容。尤其在 5.0 以后,Tooltip 的样式变得更为复杂和美观,重写成本变高且难以保持默认效果。

因此本版本在 Tooltip 中新增了 valueFormatter 配置项,专门用于对数值部分进行格式化。

如同轴对齐示例一样,我们可以在 Tooltip 的数值部分追加 °C、ml 等后缀。

option = {
  tooltip: {
    trigger: 'axis'
  },
  legend: {},
  xAxis: [
    {
      type: 'category',
      data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
      axisPointer: {
        type: 'shadow'
      }
    }
  ],
  yAxis: [
    {
      type: 'value',
      name: 'Precipitation',
      alignTicks: true,
      axisLabel: {
        formatter: '{value} ml'
      }
    },
    {
      type: 'value',
      name: 'Temperature',
      axisLabel: {
        formatter: '{value} °C'
      }
    }
  ],
  series: [
    {
      name: 'Evaporation',
      type: 'bar',
      tooltip: {
        valueFormatter: value => value + ' ml'
      },
      // prettier-ignore
      data: [2.0, 4.9, 7.0, 23.2, 25.6, 76.7, 135.6, 162.2, 32.6, 20.0, 6.4, 3.3]
    },
    {
      name: 'Precipitation',
      type: 'bar',
      tooltip: {
        valueFormatter: value => value + ' ml'
      },
      // prettier-ignore
      data: [2.6, 5.9, 9.0, 26.4, 28.7, 70.7, 175.6, 182.2, 48.7, 18.8, 6.0, 2.3]
    },
    {
      name: 'Temperature',
      type: 'line',
      yAxisIndex: 1,
      tooltip: {
        valueFormatter: value => value + ' °C'
      },
      data: [2.0, 2.2, 3.3, 4.5, 6.3, 10.2, 20.3, 23.4, 23.0, 16.5, 12.0, 6.2]
    }
  ]
};
在线示例

每个系列都可以根据自身的数值格式单独配置 valueFormatter

更灵活的扇形圆角半径

在 5.0 中我们已经为扇形(如饼图、旭辉图)加入了圆角配置,使其显示更具趣味性。此前只支持分别设置内外半径的圆角,本次我们进一步支持对扇形的四个角分别指定不同的圆角半径,从而提供更灵活的展示效果。

option = {
  tooltip: {
    trigger: 'item'
  },
  legend: {
    top: '5%',
    left: 'center'
  },
  series: [
    {
      name: 'Access From',
      type: 'pie',
      radius: ['30%', '70%'],
      roseType: 'angle',
      itemStyle: {
        borderRadius: [20, 5, 5, 10],
        borderColor: '#fff',
        borderWidth: 2
      },
      label: {
        show: false
      },
      data: [
        { value: 800, name: 'Search Engine' },
        { value: 735, name: 'Direct' },
        { value: 580, name: 'Email' },
        { value: 484, name: 'Union Ads' },
        { value: 400, name: 'Video Ads' }
      ]
    }
  ]
};
在线示例

饼图复杂标签优化

饼图一直是 ECharts 中标签展示最为复杂的图表之一。我们长期致力于优化饼图标签的布局与显示。

此次我们对使用换行、背景色、富文本等复杂布局的饼图标签进行了深度优化。在自适应宽度、容器溢出、引导线计算等方面相较之前都有了更好的效果。

5.2.2(之前) 5.3.0(之后)
before after
before after

柱状图大数据模式优化

在数据量较大(> 2k)的情况下,我们支持通过开启 large 模式来加速柱状图的渲染并提升交互性能。但之前 large 模式下的布局比较简单,且不支持多系列堆叠后的布局。5.3.0 中我们对 large 模式的布局进行了优化,使其行为与普通模式保持一致。通过开启 large,我们可以在更多场景下提升柱状图的性能。

此外,优化后的柱状图布局还修复了在对数轴等非线性坐标轴上堆叠错误的 bug。

不兼容改动

registerMap 和 getMap 方法需在导入地图图表后才能使用

为减少最小化包体积,我们将地图数据管理方法 getMapregisterMap 从核心模块中剔除。

如果你 仅按需导入图表和组件,则需要确保在使用 registerMap 注册地图数据前已导入 GeoComponentMapChart

import * as echarts from 'echarts/core';
import { MapChart } from 'echarts/charts';

echarts.use([MapChart]);

// You must import the MapChart with the `use` method before you can register the map with registerMap
echarts.registerMap('world', worldJSON);

如果你是通过 import * as echarts from 'echarts' 整体导入整个包,则此改动不会对你产生任何影响。

移除折线图默认的加粗强调效果

我们在 5.0 中为折线图引入了默认的加粗强调效果,但社区反馈在很多场景下并不适合。因此本版本将该效果的默认状态从开启改为关闭。你可以通过以下方式重新启用:

series = {
  type: 'line',
  //...
  emphasis: {
    lineStyle: {
      width: 'bolder'
    }
  }
};

完整更新日志

查看 更新日志

贡献者 在 GitHub 上编辑此页

Ovilia Oviliapissang pissangplainheart plainheart