ECharts 自定义系列
什么是 ECharts 自定义系列?
自定义系列(custom series)是一种系列的类型,它能够让用户定制渲染逻辑,在已有的坐标系中创造新的图表;并且增强了极坐标柱状图、自定义维度映射、dataZoom 等 。
自定义系列(custom series)它把绘制图形元素这一步留给开发者去做,从而开发者能在坐标系中自由绘制出自己需要的图表。
ECharts 为什么会支持自定义系列?
由于图表的类型多种多样,要让 ECharts 内置支持所有类型的图表是很难的,有很多小众的需求 ECharts 并不能内置的支持。那么就需要提供一种方式来让开发者自己扩展。另一方面,所提供的扩展方式要尽可能得简单,例如图形元素创建和释放、过渡动画、tooltip、数据区域缩放(dataZoom)、视觉映射(visualMap)等功能,尽量在 ECharts 中内置得处理,使开发者不必纠结于这些细节。综上考虑形成了 自定义系列(custom series)。
例如,下面的例子使用自定义系列( custom series)扩展出了 x-range 图:
下面来介绍开发者怎么使用自定义系列(custom series)。
(一)renderItem 方法
通过书写 renderItem 函数能够让开发者实现自定义的图形元素渲染逻辑,例如:
var option = {
...,
series: [{
type: 'custom',
renderItem: function (params, api) {
// ...
},
data: data
}]
}
在渲染阶段,对于 series.data 中的每个数据项(为方便描述,这里称为 dataItem),会调用此 renderItem 函数。
renderItem 函数的作用:返回一个(或者一组)图形元素定义,图形元素定义了图形元素的类型、位置、尺寸、样式等。
ECharts 会根据这些 图形元素定义 来渲染出图形元素。如下述所示:
var option = {
...,
series: [{
type: 'custom',
renderItem: function (params, api) {
// 对于 data 中的每个 dataItem,都会调用这个 renderItem 函数。
// (但是注意,并不一定是按照 data 的顺序调用)
// 这里进行一些处理,例如,坐标转换。
// 这里使用 api.value(0) 取出当前 dataItem 中第一个维度的数值。
var categoryIndex = api.value(0);
// 这里使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值。
var startPoint = api.coord([api.value(1), categoryIndex]);
var endPoint = api.coord([api.value(2), categoryIndex]);
// 这里使用 api.size(...) 获得 Y 轴上数值范围为 1 的一段所对应的像素长度。
var height = api.size([0, 1])[1] * 0.6;
// 这里返回为这个 dataItem 构建的图形元素定义。
return {
// 表示这个图形元素是矩形。还可以是 'circle', 'sector', 'polygon' 等等。
type: 'rect',
// shape 属性描述了这个矩形的像素位置和大小。
// 其中特殊得用到了 echarts.graphic.clipRectByRect,意思是,
// 如果矩形超出了当前坐标系的包围盒,则剪裁这个矩形。
shape: echarts.graphic.clipRectByRect({
// 矩形的位置和大小。
x: startPoint[0],
y: startPoint[1] - height / 2,
width: endPoint[0] - startPoint[0],
height: height
}, {
// 当前坐标系的包围盒。
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height
}),
// 用 api.style(...) 得到默认的样式设置。这个样式设置包含了
// option 中 itemStyle 的配置和视觉映射得到的颜色。
style: api.style()
};
},
data: [
[12, 44, 55, 60], // 这是第一个 dataItem
[53, 31, 21, 56], // 这是第二个 dataItem
[71, 33, 10, 20], // 这是第三个 dataItem
...
]
}]
}
renderItem 函数中包含两个参数:
- params:包含了当前数据信息(如 seriesIndex、dataIndex 等等)和坐标系的信息(如坐标系包围盒的位置和尺寸)。
- api:是一些开发者可调用的方法集合(如 api.value()、api.coord())。
renderItem 函数须返回根据此 dataItem 绘制出的图形元素的定义信息,参见 renderItem.return。
一般来说,renderItem 函数的主要逻辑,是将 dataItem 里的值映射到坐标系上的图形元素。这一般需要用到 renderItem.arguments.api 中的两个函数:
- api.value(...),意思是取出 dataItem 中的数值。例如 api.value(0) 表示取出当前 dataItem 中第一个维度的数值。
- api.coord(...),意思是进行坐标转换计算。例如 var point = api.coord([api.value(0), api.value(1)]) 表示 dataItem 中的数值转换成坐标系上的点。
有时候还需要用到 api.size(...) 函数,表示得到坐标系上一段数值范围对应的长度。
返回值中样式的设置可以使用 api.style(...) 函数,他能得到 series.itemStyle.normal 中定义的样式信息,以及视觉映射的样式信息。也可以用这种方式覆盖这些样式信息:api.style({fill: 'green', stroke: 'yellow'})。
renderItem 方法写完后,我们就完成了自定义系列 90% 的工作,接下来该对工作进行优化了。
(二)使坐标轴的范围自适应数据范围
直角坐标系(grid)和极坐标系(polar)中的坐标轴的刻度范围需要自适应当前显示出的数据的范围,否则绘制出的图形会超出去。
例如,在直角坐标系(grid)中,开发者如果使用自定义系列的话,就需要设定,data 中的哪些维度会对应到 x 轴上,哪些维度会对应到 y 轴上。
上述内容通过 encode 来设定。例如:
option = {
series: [{
type: 'custom',
renderItem: function () {
...
},
encode: {
// data 中『维度1』和『维度2』对应到 X 轴
x: [1, 2],
// data 中『维度0』对应到 Y 轴
y: 0
},
data: [
// 维度0 维度1 维度2 维度3
[ 12, 44, 55, 60 ], // 这是第一个 dataItem
[ 53, 31, 21, 56 ], // 这是第二个 dataItem
[ 71, 33, 10, 20 ], // 这是第三个 dataItem
...
]
}]
};
(三)设定 tooltip
当然,使用 tooltip.formatter 可以任意定制 tooltip 中的内容。
但是,还有更简单的方法,即通过 encode 和 dimensions 来设定:
option = {
series: [{
type: 'custom',
renderItem: function () {
...
},
encode: {
x: [1, 2],
y: 0,
// 表示『维度2』和『维度3』要显示到 tooltip 中。
tooltip: [2, 3]
},
// 表示给『维度2』和『维度3』分别取名为『年龄』和『满意度』,显示到 tooltip 中。
dimensions: [null, null, '年龄', '满意度'],
data: [
// 维度0 维度1 维度2 维度3
[ 12, 44, 55, 60 ], // 这是第一个 dataItem
[ 53, 31, 21, 56 ], // 这是第二个 dataItem
[ 71, 33, 10, 20 ], // 这是第三个 dataItem
...
]
}]
};
(四)其他注意事项
(1)与 dataZoom 结合使用的时候,常常使用会设置 dataZoom.filterMode 为 'weakFilter'。这个设置的意思是:当 dataItem 部分超出坐标系边界的时候,dataItem 不会整体被过滤掉。例如:
option = {
dataZoom: {
xAxisIndex: 0,
filterMode: 'weakFilter'
},
series: [{
type: 'custom',
renderItem: function () {
...
},
encode: {
// data 中『维度1』和『维度2』对应到 X 轴
x: [1, 2],
y: 0
},
data: [
// 维度0 维度1 维度2 维度3
[ 12, 44, 55, 60 ], // 这是第一个 dataItem
[ 53, 31, 21, 56 ], // 这是第二个 dataItem
[ 71, 33, 10, 20 ], // 这是第三个 dataItem
...
]
}]
};
在上述例子中,维度1和维度2对应到 X 轴,dataZoom 组件控制 X 轴的缩放。
假如在缩放的过程中,某个 dataItem 的维度1超出了 X 轴的范围,维度2还在 X 轴的范围中,那么只要设置 dataZoom.filterMode = 'weakFilter',这个 dataItem 就不会被过滤掉,从而还能够使用 renderItem 绘制图形(可以使用上面提到过的 echarts.graphic.clipRectByRect 把图形绘制成被坐标系剪裁过的样子)。参见上面提到过的例子:Profile
(2)此外,开发者还应注意,renderItem.arguments.params 中的 dataIndex 和 dataIndexInside 是有区别的:
- dataIndex 指的 dataItem 在原始数据中的 index。
- dataIndexInside 指的是 dataItem 在当前数据窗口(参见 dataZoom)中的 index。
renderItem.arguments.api 中使用的参数都是 dataIndexInside 而非 dataIndex,因为从 dataIndex 转换成 dataIndexInside 需要时间开销。
参考阅读
更多的自定义系列的例子参见:custom examples