echarts整体脉络梳理

311 阅读16分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第16天,点击查看活动详情

echarts是一个很庞大的图标库,有非常多的配置项和API,每次使用都要反复查询文档,因此非常有必要对echarts的使用在整体上做了一个梳理。

图标容器

在 HTML 中定义有宽度和高度的父容器(推荐)

通常来说,需要在 HTML 中先定义一个 <div> 节点,并且通过 CSS 使得该节点具有宽度和高度。初始化的时候,传入该节点,图表的大小默认即为该节点的大小。

<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
  var myChart = echarts.init(document.getElementById('main'));
</script>

监听图表容器的大小并改变图表大小

在有些场景下,我们希望当容器大小改变时,图表的大小也相应地改变。

var myChart = echarts.init(document.getElementById('main'));
window.onresize = function() {
   myChart.resize();
};

容器节点被销毁以及被重建时

假设页面中存在多个tabs,每个标签页都包含一些图表。当选中一个标签页的时候,其他标签页的内容在 DOM 中被移除了。这样,当用户再选中这些标签页的时候,就会发现图表"不见"了。

本质上,这是由于图表的容器节点被移除导致的。即使之后该节点被重新添加,图表所在的节点也已经不存在了。

正确的做法是,在图表容器被销毁之后,调用 echartsInstance.dispose 销毁实例,在图表容器重新被添加后再次调用 echarts.init 初始化。

在容器节点被销毁时,总是应调用 echartsInstance.dispose 以销毁实例释放资源,避免内存泄漏。

封装一个vue的图标组件

  • class: 'echarts'设置为height: 100% width: 100%,这样就继承父级的宽高,父级一定要设置一个宽高,否则图标就没有宽高了
  • 这里需要保证有一个唯一的class,因为这个组件是一个通用组件,当一个页面中有很多图标的时候,如果class都是一样的,那么就会出现异响不到的bug
<template>
  <div :class="[className, 'echarts']"></div>
</template>

<script>
import { watch, onMounted } from 'vue'
// 全量引入
import * as echarts from 'echarts'
import { v4 as uuidv4 } from 'uuid'

export default {
  name: 'VueEcharts',
  props: {
    options: Object,
    theme: [String, Object]
  },
  setup(props) {
    let dom
    let chart
    // 因为是通用组件,所以必须要满足有一个唯一的class
    let className = `echarts${uuidv4()}`
    const initChart = () => {
      if (!chart) {
        dom = document.getElementsByClassName(className)[0]
        chart = echarts.init(dom, props.theme)
      }
      if (props.options) {
        chart.setOption(props.options)
      }
    }
    onMounted(() => {
      initChart()
    })
    watch(
      () => props.options,
      () => {
        initChart()
      }
    )
    return {
      className
    }
  }
}
</script>

<style lang="scss" scoped>
.echarts {
  width: 100%;
  height: 100%;
}
</style>

使用:

<vue-echarts :options="options" />

样式设置

颜色主题(Theme)

最简单的更改全局样式的方式,是直接采用颜色主题(theme)。ECharts5 除了一贯的默认主题外,还内置了'dark'主题。可以像这样切换成深色模式:

var chart = echarts.init(dom, 'dark');

其他的主题,没有内置在 ECharts 中,需要自己加载。这些主题可以在 主题编辑器 里访问到,具体使用参见文档。

调色盘

调色盘,可以在 option 中设置。它给定了一组颜色,图形、系列会自动从其中选择颜色。 可以设置全局的调色盘,也可以设置系列自己专属的调色盘。

option = {
  // 全局调色盘。
  color: [
    '#c23531',
    '#2f4554',
    '#61a0a8',
    '#d48265'
  ],
  series: [
    {
      type: 'bar',
      // 此系列自己的调色盘。
      color: [
        '#dd6b66',
        '#759aa0',
        '#e69d87',
      ]
    }
]

比如下面我们对柱状图设置了一个调色盘:

const color = ['rgb(116,166,49)', 'rgb(190,245,99)', 'rgb(202,252,137)', 'rgb(251,253,142)']
options = {
    color: color
}

柱状图的颜色就是上面设置的颜色:

image.png

直接的样式设置 itemStyle, lineStyle, areaStyle, label, ...

直接的样式设置是比较常用设置方式。纵观 ECharts 的 option 中,很多地方可以设置 itemStylelineStyleareaStylelabel 等等。这些的地方可以直接设置图形元素的颜色、线宽、点的大小、标签的文字、标签的样式等等。

一般来说,ECharts 的各个系列和组件,都遵从这些命名习惯,虽然不同图表和组件中,itemStylelabel 等可能出现在不同的地方。

高亮的样式:emphasis

在鼠标悬浮到图形元素上时,一般会出现高亮的样式。默认情况下,高亮的样式是根据普通样式自动生成的。但是高亮的样式也可以自己定义,主要是通过 emphasis 属性来定制。emphsis 中的结构,和普通样式的结构相同,例如:

option = {
  series: {
    type: 'scatter',
    // 普通样式。
    itemStyle: {
      // 点的颜色。
      color: 'red'
    },
    label: {
      show: true,
      // 标签的文字。
      formatter: 'This is a normal label.'
    },

    // 高亮样式。
    emphasis: {
      itemStyle: {
        // 高亮时点的颜色。
        color: 'blue'
      },
      label: {
        show: true,
        // 高亮时标签的文字。
        formatter: 'This is a emphasis label.'
      }
    }
  }
};

注意:在 ECharts4 以前,高亮和普通样式的写法,是这样的:

option = {
  series: {
    type: 'scatter',

    itemStyle: {
      // 普通样式。
      normal: {
        // 点的颜色。
        color: 'red'
      },
      // 高亮样式。
      emphasis: {
        // 高亮时点的颜色。
        color: 'blue'
      }
    }
};

这种写法 仍然被兼容,但是,不再推荐。

数据集

数据集dataset)是专门用来管理数据的组件。虽然每个系列都可以在 series.data 中设置数据,但是从 ECharts4 支持 数据集 开始,更推荐使用 数据集 来管理数据。因为这样,数据可以被多个组件复用,也方便进行 “数据和其他配置” 分离的配置风格。毕竟,在运行时,数据是最常改变的,而其他配置大多并不会改变。

在系列中设置数据

如果数据设置在 系列series)中,例如:

option = {
  xAxis: {
    type: 'category',
    data: ['Matcha Latte', 'Milk Tea', 'Cheese Cocoa', 'Walnut Brownie']
  },
  series: [
    {
      type: 'bar',
      name: '2015',
      data: [89.3, 92.1, 94.4, 85.4]
    }
  ]
};

这种方式的优点是,适于对一些特殊的数据结构(如“树”、“图”、超大数据)进行一定的数据类型定制。 但是缺点是,常需要用户先处理数据,把数据分割设置到各个系列(和类目轴)中。此外,不利于多个系列共享一份数据,也不利于基于原始数据进行图表类型、系列的映射安排。

在数据集中设置数据

数据设置在 数据集dataset)中,会有这些好处:

  • 能够贴近数据可视化常见思维方式:(I)提供数据,(II)指定数据到视觉的映射,从而形成图表。
  • 数据和其他配置可以被分离开来。数据常变,其他配置常不变。分开易于分别管理。
  • 数据可以被多个系列或者组件复用,对于大数据量的场景,不必为每个系列创建一份数据。
  • 支持更多的数据的常用格式,例如二维数组、对象数组等,一定程度上避免使用者为了数据格式而进行转换。 下面是一个最简单的 dataset 的例子:
option = {
  dataset: {
    // 提供一份数据。
    source: [
      ['product', '2015', '2016', '2017'],
      ['Matcha Latte', 43.3, 85.8, 93.7],
      ['Milk Tea', 83.1, 73.4, 55.1],
      ['Cheese Cocoa', 86.4, 65.2, 82.5],
      ['Walnut Brownie', 72.4, 53.9, 39.1]
    ]
  },
  // 声明一个 X 轴,类目轴(category)。默认情况下,类目轴对应到 dataset 第一列。
  xAxis: { type: 'category' },
  // 声明一个 Y 轴,数值轴。
  yAxis: {},
  // 声明多个 bar 系列,默认情况下,每个系列会自动对应到 dataset 的每一列。
  series: [{ type: 'bar' }, { type: 'bar' }, { type: 'bar' }]
};

image.png

或者也可以使用常见的“对象数组”的格式:

option = {
  legend: {},
  tooltip: {},
  dataset: {
    // 用 dimensions 指定了维度的顺序。直角坐标系中,如果 X 轴 type 为 category,
    // 默认把第一个维度映射到 X 轴上,后面维度映射到 Y 轴上。
    // 如果不指定 dimensions,也可以通过指定 series.encode
    dimensions: ['product', '2015', '2016', '2017'],
    source: [
      { product: 'Matcha Latte', '2015': 43.3, '2016': 85.8, '2017': 93.7 },
      { product: 'Milk Tea', '2015': 83.1, '2016': 73.4, '2017': 55.1 },
      { product: 'Cheese Cocoa', '2015': 86.4, '2016': 65.2, '2017': 82.5 },
      { product: 'Walnut Brownie', '2015': 72.4, '2016': 53.9, '2017': 39.1 }
    ]
  },
  xAxis: { type: 'category' },
  yAxis: {},
  series: [{ type: 'bar' }, { type: 'bar' }, { type: 'bar' }]
};

把数据集( dataset )的行或列映射为系列(series)

有了数据表之后,使用者可以灵活地配置:数据如何对应到轴和图形系列。 用户可以使用 seriesLayoutBy 配置项,改变图表对于行列的理解。seriesLayoutBy 可取值:

  • 'column': 默认值。系列被安放到 dataset 的列上面。
  • 'row': 系列被安放到 dataset 的行上面。
option = {
  legend: {},
  tooltip: {},
  dataset: {
    source: [
      ['product', '2012', '2013', '2014', '2015'],
      ['Matcha Latte', 41.1, 30.4, 65.1, 53.3],
      ['Milk Tea', 86.5, 92.1, 85.7, 83.1],
      ['Cheese Cocoa', 24.1, 67.2, 79.5, 86.4]
    ]
  },
  xAxis: [
    { type: 'category', gridIndex: 0 },
    { type: 'category', gridIndex: 1 }
  ],
  yAxis: [{ gridIndex: 0 }, { gridIndex: 1 }],
  grid: [{ bottom: '55%' }, { top: '55%' }],
  series: [
    // 这几个系列会出现在第一个直角坐标系中,每个系列对应到 dataset 的每一行。
    { type: 'bar', seriesLayoutBy: 'row' },
    { type: 'bar', seriesLayoutBy: 'row' },
    { type: 'bar', seriesLayoutBy: 'row' },
    // 这几个系列会出现在第二个直角坐标系中,每个系列对应到 dataset 的每一列。
    { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 },
    { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 },
    { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 },
    { type: 'bar', xAxisIndex: 1, yAxisIndex: 1 }
  ]
};

image.png

维度的概念

常用图表所描述的数据大部分是"二维表"结构,上述的例子中,我们都使用二维数组来容纳二维表。现在,当我们把系列(series)对应到"列"的时候,那么每一列就称为一个"维度(dimension)",而每一行称为数据项(item)。反之,如果我们把系列(series)对应到表行,那么每一行就是"维度( dimension )",每一列就是数据项( item )(维度的概念不是很好理解)。比如下面的数据集:

source: [
      ['product', '2012', '2013', '2014', '2015'],
      ['Matcha Latte', 41.1, 30.4, 65.1, 53.3],
      ['Milk Tea', 86.5, 92.1, 85.7, 83.1],
      ['Cheese Cocoa', 24.1, 67.2, 79.5, 86.4]
]

如果系列对应的是列,那么每一列就是一个维度,维度有'product', '2012', '2013', '2014', '2015',也就是我们看数据就从这几个维度来分析数据。比如,我从2012年这个维度来分析产品的销量情况。

如果系列对应的是行,那么每一行就是一个维度,维度有'product', 'Matcha Latte', 'Milk Tea', 'Cheese Cocoa',那么分析数据的时候就从这几个维度来分析,比如,我从Matcha Latte这个产品的维度来分析2012 2013 2014等这几年的销售情况。

维度的定义,也可以使用单独的 dataset.dimensions 或者 series.dimensions 来定义,这样可以同时指定维度名,和维度的类型( dimension type )。大多数情况下,我们并不需要去设置维度类型,因为 ECharts 会自动尝试判断。

数据到图形的映射( series.encode )

了解了维度的概念后,我们就可以使用 series.encode 来做映射。总体是这样的感觉:

var option = {
  dataset: {
    source: [
      ['score', 'amount', 'product'],
      [89.3, 58212, 'Matcha Latte'],
      [57.1, 78254, 'Milk Tea'],
      [74.4, 41032, 'Cheese Cocoa'],
      [50.1, 12755, 'Cheese Brownie'],
      [89.7, 20145, 'Matcha Cocoa'],
      [68.1, 79146, 'Tea'],
      [19.6, 91852, 'Orange Juice'],
      [10.6, 101852, 'Lemon Juice'],
      [32.7, 20112, 'Walnut Brownie']
    ]
  },
  xAxis: {},
  yAxis: { type: 'category' },
  series: [
    {
      type: 'bar',
      encode: {
        // 将 "amount" 列映射到 X 轴。
        x: 'amount',
        // 将 "product" 列映射到 Y 轴。
        y: 'product'
      }
    }
  ]
};

image.png

series.encode 声明的基本结构如下。其中冒号左边是坐标系、标签等特定名称,如 'x''y''tooltip' 等,冒号右边是数据中的维度名(string 格式)或者维度的序号(number 格式,从 0 开始计数),可以指定一个或多个维度(使用数组)。可以参考示例,可以看看series.encode是如何写的。

多个 dataset 以及如何引用他们

可以同时定义多个 dataset。系列可以通过 series.datasetIndex 来指定引用哪个 dataset。例如:

var option = {
  dataset: [
    {
      // 序号为 0 的 dataset。
      source: []
    },
    {
      // 序号为 1 的 dataset。
      source: []
    },
    {
      // 序号为 2 的 dataset。
      source: []
    }
  ],
  series: [
    {
      // 使用序号为 2 的 dataset。
      datasetIndex: 2
    },
    {
      // 使用序号为 1 的 dataset。
      datasetIndex: 1
    }
  ]
};

注意:ECharts 4 之前一直以来的数据声明方式仍然被正常支持,如果系列已经声明了 series.data, 那么就会使用 series.data 而非 dataset

其实,series.data 也是种会一直存在的重要设置方式。一些特殊的非 table 格式的图表,如 treemapgraphlines 等,现在仍不支持在 dataset 中设置,仍然需要使用 series.data。另外,对于巨大数据量的渲染(如百万以上的数据量),需要使用 appendData 进行增量加载,这种情况不支持使用 dataset

数据转换

Apache ECharts5 开始支持了"数据转换"( data transform )功能。在 echarts 中,"数据转换"这个词指的是,给定一个已有的"数据集"和一个"转换方法",echarts 能生成一个新的"数据集",然后可以使用这个新的"数据集"绘制图表,这些工作都可以声明式地完成。

抽象地来说,数据转换是这样一种公式:outData = f(inputData)f 是转换方法,例如:filtersortregressionboxplotclusteraggregate(todo) 等等。有了数据转换能力后,我们就至少可以做到这些事情:

  • 把数据分成多份用不同的饼图展现。
  • 进行一些数据统计运算,并展示结果。
  • 用某些数据可视化算法处理数据,并展示结果。
  • 数据排序。
  • 去除或直选择数据项。

数据转换基础使用

在 echarts 中,数据转换是依托于数据集来实现的. 我们可以设置dataset.transform来表示,此 dataset 的数据,来自于此 transform 的结果。

var option = {
  dataset: [
    {
      // 这个 dataset 的 index 是 `0`。
      source: [
        ['Product', 'Sales', 'Price', 'Year'],
        ['Cake', 123, 32, 2011],
        ['Cereal', 231, 14, 2011],
        ['Tofu', 235, 5, 2011],
        ['Dumpling', 341, 25, 2011],
        ['Biscuit', 122, 29, 2011],
        ['Cake', 143, 30, 2012],
        ['Cereal', 201, 19, 2012],
        ['Tofu', 255, 7, 2012],
        ['Dumpling', 241, 27, 2012],
        ['Biscuit', 102, 34, 2012],
        ['Cake', 153, 28, 2013],
        ['Cereal', 181, 21, 2013],
        ['Tofu', 395, 4, 2013],
        ['Dumpling', 281, 31, 2013],
        ['Biscuit', 92, 39, 2013],
        ['Cake', 223, 29, 2014],
        ['Cereal', 211, 17, 2014],
        ['Tofu', 345, 3, 2014],
        ['Dumpling', 211, 35, 2014],
        ['Biscuit', 72, 24, 2014]
      ]
      // id: 'a'
    },
    {
      // 这个 dataset 的 index 是 `1`。
      // 这个 `transform` 配置,表示,此 dataset 的数据,来自于此 transform 的结果。
      transform: {
        type: 'filter',
        config: { dimension: 'Year', value: 2011 }
      }
      // 我们还可以设置这些可选的属性: `fromDatasetIndex` 或 `fromDatasetId`。
      // 这些属性,指定了,transform 的输入,来自于哪个 dataset。例如,
      // `fromDatasetIndex: 0` 表示输入来自于 index 为 `0` 的 dataset 。又例如,
      // `fromDatasetId: 'a'` 表示输入来自于 `id: 'a'` 的 dataset。
      // 当这些属性都不指定时,默认认为,输入来自于 index 为 `0` 的 dataset 。
    },
    {
      // 这个 dataset 的 index 是 `2`。
      // 同样,这里因为 `fromDatasetIndex` 和 `fromDatasetId` 都没有被指定,
      // 那么输入默认来自于 index 为 `0` 的 dataset 。
      transform: {
        // 这个类型为 "filter" 的 transform 能够遍历并筛选出满足条件的数据项。
        type: 'filter',
        // 每个 transform 如果需要有配置参数的话,都须配置在 `config` 里。
        // 在这个 "filter" transform 中,`config` 用于指定筛选条件。
        // 下面这个筛选条件是:选出维度( dimension )'Year' 中值为 2012 的所有
        // 数据项。
        config: { dimension: 'Year', value: 2012 }
      }
    },
    {
      transform: {
        type: 'filter',
        config: { dimension: 'Year', value: 2013 }
      }
    }
  ],
  series: [
    {
      type: 'pie',
      radius: 50,
      center: ['25%', '50%'],
      // 这个饼图系列,引用了 index 为 `1` 的 dataset 。也就是,引用了上述
      // 2011 年那个 "filter" transform 的结果。
      datasetIndex: 1
    },
    {
      type: 'pie',
      radius: 50,
      center: ['50%', '50%'],
      datasetIndex: 2
    },
    {
      type: 'pie',
      radius: 50,
      center: ['75%', '50%'],
      datasetIndex: 3
    }
  ]
};

image.png

具体transform的使用请参照使用 transform 进行数据转换

坐标轴

x 轴、y 轴

x 轴和 y 轴都由轴线、刻度、刻度标签、轴标题四个部分组成。部分图表中还会有网格线来帮助查看和计算数据。

image.png

x 轴常用来标示数据的维度,维度一般用来指数据的类别,是观察数据的角度,例如"销售时间" "销售地点" "产品名称"等。y 轴常常用来标示数据的数值,数值是用来具体考察某一类数据的数量值,也是我们需要分析的指标,例如"销售数量"和"销售金额"等。

option = {
  xAxis: {
    type: 'time',
    name: '销售时间'
    // ...
  },
  yAxis: {
    type: 'value',
    name: '销售数量'
    // ...
  },
  dataZoom: [
    // ...
  ]
};

当 x 轴(水平坐标轴)跨度很大,可以采用区域缩放方式dataZoom灵活显示数据内容。

一个坐标系有多个轴

option = {
  tooltip: {
    trigger: 'axis',
    axisPointer: { type: 'cross' }
  },
  legend: {},
  xAxis: [
    {
      type: 'category',
      axisTick: {
        alignWithLabel: true
      },
      data: [
        '1月',
        '2月',
        '3月',
         ...
        '10月',
        '11月',
        '12月'
      ]
    }
  ],
  yAxis: [
    {
      type: 'value',
      name: '降水量',
      min: 0,
      max: 250,
      // 执行轴的摆放位置
      position: 'right',
      axisLabel: {
        formatter: '{value} ml'
      }
    },
    {
      type: 'value',
      name: '温度',
      min: 0,
      max: 25,
      position: 'left',
      axisLabel: {
        formatter: '{value} °C'
      }
    }
  ],
  series: [
    {
      name: '降水量',
      type: 'bar',
      // 对应哪个y轴
      yAxisIndex: 0,
      data: [6, 32, 70, 86, 68.7, 100.7, 125.6, 112.2, 78.7, 48.8, 36.0, 19.3]
    },
    {
      name: '温度',
      type: 'line',
      smooth: true,
      yAxisIndex: 1,
      data: [
        6.0,
        10.2,
        10.3,
        11.5,
        10.3,
        ...
        5.2
      ]
    }
  ]
};

image.png

多个坐标轴

image.png

参考配置

var sizeValue = '57%';
grid: [
   { right: sizeValue, bottom: sizeValue },
   { left: sizeValue, bottom: sizeValue },
   { right: sizeValue, top: sizeValue },
   { left: sizeValue, top: sizeValue }
],
xAxis: [
   {
      type: 'value',
      gridIndex: 0,
   },
   {
      type: 'category',
      gridIndex: 1,
   },
   ...
],
series: [
   {
      type: 'scatter',
      symbolSize: symbolSize,
      xAxisIndex: 0,
      yAxisIndex: 0, 
   }
   ...
]

视觉映射

ECharts 的每种图表本身就内置了这种映射过程,比如折线图把数据映射到“线”,柱状图把数据映射到“长度”。一些更复杂的图表,如关系图、事件河流图、树图也都会做出各自内置的映射。

此外,ECharts 还提供了 visualMap 组件 来提供通用的视觉映射。visualMap 组件中可以使用的视觉元素有:

  • 图形类别(symbol)、图形大小(symbolSize)
  • 颜色(color)、透明度(opacity)、颜色透明度(colorAlpha)、
  • 颜色明暗度(colorLightness)、颜色饱和度(colorSaturation)、色调(colorHue)

visualMap多用在多维度的数据展示中。在图表中,往往默认把dataset的前一两个维度进行映射,比如取第一个维度映射到 x 轴,取第二个维度映射到 y 轴。如果想要把更多的维度展现出来,可以借助 visualMap。最常见的情况,散点图(scatter)使用半径展现了第三个维度。

visualMap 组件定义了把数据的哪个维度映射到什么视觉元素上。

事件与行为

在 Apache ECharts 的图表中用户的操作将会触发相应的事件。开发者可以监听这些事件,然后通过回调函数做相应的处理,比如跳转到一个地址,或者弹出对话框,或者做数据下钻等等。

在 ECharts 中事件分为两种类型,一种是用户鼠标操作点击,或者 hover 图表的图形时触发的事件,还有一种是用户在使用可以交互的组件后触发的行为事件,例如在切换图例开关时触发的 'legendselectchanged' 事件,数据区域缩放时触发的 'datazoom' 事件等等。

以上就是对echarts的整体脉络的一个梳理,对echarts整体有个了解后,我们就可以比较高效的开发数据可视化项目了。