Apache ECharts 5.3.0 特性

Apache ECharts 5.3.0 在动画表达、渲染性能、服务端渲染等方面都带来了重要的增强。同时也新增了社区期待已久的坐标轴刻度对齐、提示框数值格式化、地图投影等功能。

关键帧动画

以前,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.apache.org/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 中,我们将这些过渡动画的配置扩展到了图形组件,并做了额外的增强。

如果你不想写出每个需要动画的属性,现在可以直接配置 transition: 'all' 来为所有属性添加动画。

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

新的 SVG 渲染器

在 5.3.0 中,我们重构了 SVG 渲染器,带来了 2 倍到 10 倍的性能提升,在一些特殊场景下甚至能达到几十倍。

以前,我们从渲染队列直接更新 SVG 渲染器到 DOM,但由于 ZRender 的图形属性与 DOM 不是一一对应的,我们不得不在中间实现非常复杂的 diff 逻辑,这很容易出错,并且在某些场景下性能也不是最好的。在这个版本中,我们首先将完整渲染重构为 VDOM,然后将 VDOM patch 到 DOM 来完成渲染。完整渲染避免了复杂 diff 逻辑可能带来的潜在错误,而 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 计算出合适的显示区域,并将经纬度线性地映射到该区域。这是最简单的地图投影方式。然而,简单的线性投影对于复杂的地图场景并不适用,比如使用阿尔伯斯投影来解决线性投影中的畸变问题,或者让世界地图以太平洋为中心等等。

因此,在 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]
    }
  ]
};
在线示例

禁用高亮(emphasis)和选中(select)状态

ECharts 中的 emphasis 状态在鼠标悬浮到元素上时为用户提供反馈,但在元素数量很多的图表中,高亮动画可能会导致性能问题。特别是通过 tooltiplegend 组件联动触发的高亮会同时高亮多个元素。

因此,在这个版本中我们增加了 emphasis.disabled 配置项。如果你不需要高亮反馈并且关心交互性,可以用这个选项禁用 emphasis 状态。

对于 select 状态,我们也增加了 select.disabled。这个选项可以用来配置部分数据为不可选中。

支持选中整个系列

从 5.3.0 开始,我们支持将 selectedMode 配置为 'series' 来启用选中一个系列中的所有数据。

提示框中数值的格式化

当用户悬停在数据项上时,提示框可以用来显示关于该数据项的更详细信息。ECharts 还提供了一个 formatter 回调函数,让开发者更灵活地自定义提示框的内容。

然而,我们发现大多数时候,开发者只需要格式化提示框的数值部分,比如固定精度、加上 $ 前缀等。以前,为了格式化数字,开发者不得不使用 formatter 重写整个提示框的内容。尤其是在 5.0 之后,ECharts 的提示框变得更加复杂和美观,所以重写它们的成本很高,而且很难达到默认的效果。

因此,在这个版本中,我们在提示框中增加了一个 valueFormatter 配置项,用于格式化数值部分。

就像在坐标轴对齐的例子中一样,我们可以在提示框的数值部分加上 °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

柱状图 large 模式优化

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

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

不兼容改动

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 上编辑此页

Oviliapissangplainheart