课程表

Echarts 基础教程

Echarts 案例教程

工具箱
速查手册

Echarts 如何自定义图表

当前位置:免费教程 » JS/JS库/框架 » Echarts

自定义系列(custom series),是一种系列的类型。它把绘制图形元素这一步留给开发者去做,从而开发者能在坐标系中自由绘制出自己需要的图表。

echarts 为什么会要支持 自定义系列 呢?echarts 内置支持的图表类型是最常见的图表类型,但是图表类型是难于穷举的,有很多小众的需求 echarts 并不能内置的支持。那么就需要提供一种方式来让开发者自己扩展。另一方面,所提供的扩展方式要尽可能得简单,例如图形元素创建和释放、过渡动画、tooltip、数据区域缩放(dataZoom)、视觉映射(visualMap)等功能,尽量在 echarts 中内置得处理,使开发者不必纠结于这些细节。综上考虑形成了 自定义系列(custom series)。

例如,下面的例子使用 custom series 扩展出了 x-range 图:

  1. var data = [];
  2. var dataCount = 10;
  3. var startTime = +new Date();
  4. var categories = ['categoryA', 'categoryB', 'categoryC'];
  5. var types = [
  6.     {name: 'JS Heap', color: '#7b9ce1'},
  7.     {name: 'Documents', color: '#bd6d6c'},
  8.     {name: 'Nodes', color: '#75d874'},
  9.     {name: 'Listeners', color: '#e0bc78'},
  10.     {name: 'GPU Memory', color: '#dc77dc'},
  11.     {name: 'GPU', color: '#72b362'}
  12. ];
  13.  
  14. // Generate mock data
  15. echarts.util.each(categories, function (category, index) {
  16.     var baseTime = startTime;
  17.     for (var i = 0; i < dataCount; i++) {
  18.         var typeItem = types[Math.round(Math.random() * (types.length - 1))];
  19.         var duration = Math.round(Math.random() * 10000);
  20.         data.push({
  21.             name: typeItem.name,
  22.             value: [
  23.                 index,
  24.                 baseTime,
  25.                 baseTime += duration,
  26.                 duration
  27.             ],
  28.             itemStyle: {
  29.                 normal: {
  30.                     color: typeItem.color
  31.                 }
  32.             }
  33.         });
  34.         baseTime += Math.round(Math.random() * 2000);
  35.     }
  36. });
  37.  
  38. function renderItem(params, api) {
  39.     var categoryIndex = api.value(0);
  40.     var start = api.coord([api.value(1), categoryIndex]);
  41.     var end = api.coord([api.value(2), categoryIndex]);
  42.     var height = api.size([0, 1])[1] * 0.6;
  43.  
  44.     var rectShape = echarts.graphic.clipRectByRect({
  45.         x: start[0],
  46.         y: start[1] - height / 2,
  47.         width: end[0] - start[0],
  48.         height: height
  49.     }, {
  50.         x: params.coordSys.x,
  51.         y: params.coordSys.y,
  52.         width: params.coordSys.width,
  53.         height: params.coordSys.height
  54.     });
  55.  
  56.     return rectShape && {
  57.         type: 'rect',
  58.         shape: rectShape,
  59.         style: api.style()
  60.     };
  61. }
  62.  
  63.  
  64. option = {
  65.     tooltip: {
  66.         formatter: function (params) {
  67.             return params.marker + params.name + ': ' + params.value[3] + ' ms';
  68.         }
  69.     },
  70.     title: {
  71.         text: 'Profile',
  72.         left: 'center'
  73.     },
  74.     dataZoom: [{
  75.         type: 'slider',
  76.         filterMode: 'weakFilter',
  77.         showDataShadow: false,
  78.         top: 400,
  79.         height: 10,
  80.         borderColor: 'transparent',
  81.         backgroundColor: '#e2e2e2',
  82.         handleIcon: 'M10.7,11.9H9.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z', // jshint ignore:line
  83.         handleSize: 20,
  84.         handleStyle: {
  85.             shadowBlur: 6,
  86.             shadowOffsetX: 1,
  87.             shadowOffsetY: 2,
  88.             shadowColor: '#aaa'
  89.         },
  90.         labelFormatter: ''
  91.     }, {
  92.         type: 'inside',
  93.         filterMode: 'weakFilter'
  94.     }],
  95.     grid: {
  96.         height:300
  97.     },
  98.     xAxis: {
  99.         min: startTime,
  100.         scale: true,
  101.         axisLabel: {
  102.             formatter: function (val) {
  103.                 return Math.max(0, val - startTime) + ' ms';
  104.             }
  105.         }
  106.     },
  107.     yAxis: {
  108.         data: categories
  109.     },
  110.     series: [{
  111.         type: 'custom',
  112.         renderItem: renderItem,
  113.         itemStyle: {
  114.             normal: {
  115.                 opacity: 0.8
  116.             }
  117.         },
  118.         encode: {
  119.             x: [1, 2],
  120.             y: 0
  121.         },
  122.         data: data
  123.     }]
  124. };

在线运行实例

下面来介绍开发者怎么使用 自定义系列(custom series)


(一)renderItem 方法

开发者自定义的图形元素渲染逻辑,是通过书写 renderItem 函数实现的,例如:

  1. var option = {
  2.     ...,
  3.     series: [{
  4.         type: 'custom',
  5.         renderItem: function (params, api) {
  6.             // ...
  7.         },
  8.         data: data
  9.     }]
  10. }

在渲染阶段,对于 series.data 中的每个数据项(为方便描述,这里称为 dataItem),会调用此 renderItem 函数。这个 renderItem 函数的职责,就是返回一个(或者一组)图形元素定义,图形元素定义 中包括图形元素的类型、位置、尺寸、样式等。echarts 会根据这些 图形元素定义 来渲染出图形元素。如下的示意:

  1. var option = {
  2.     ...,
  3.     series: [{
  4.         type: 'custom',
  5.         renderItem: function (params, api) {
  6.             // 对于 data 中的每个 dataItem,都会调用这个 renderItem 函数。
  7.             // (但是注意,并不一定是按照 data 的顺序调用)
  8.  
  9.             // 这里进行一些处理,例如,坐标转换。
  10.             // 这里使用 api.value(0) 取出当前 dataItem 中第一个维度的数值。
  11.             var categoryIndex = api.value(0);
  12.             // 这里使用 api.coord(...) 将数值在当前坐标系中转换成为屏幕上的点的像素值。
  13.             var startPoint = api.coord([api.value(1), categoryIndex]);
  14.             var endPoint = api.coord([api.value(2), categoryIndex]);
  15.             // 这里使用 api.size(...) 获得 Y 轴上数值范围为 1 的一段所对应的像素长度。
  16.             var height = api.size([0, 1])[1] * 0.6;
  17.  
  18.             // shape 属性描述了这个矩形的像素位置和大小。
  19.             // 其中特殊得用到了 echarts.graphic.clipRectByRect,意思是,
  20.             // 如果矩形超出了当前坐标系的包围盒,则剪裁这个矩形。
  21.             // 如果矩形完全被剪掉,会返回 undefined.
  22.             var rectShape = echarts.graphic.clipRectByRect({
  23.                 // 矩形的位置和大小。
  24.                 x: startPoint[0],
  25.                 y: startPoint[1] - height / 2,
  26.                 width: endPoint[0] - startPoint[0],
  27.                 height: height
  28.             }, {
  29.                 // 当前坐标系的包围盒。
  30.                 x: params.coordSys.x,
  31.                 y: params.coordSys.y,
  32.                 width: params.coordSys.width,
  33.                 height: params.coordSys.height
  34.             });
  35.  
  36.             // 这里返回为这个 dataItem 构建的图形元素定义。
  37.             return rectShape && {
  38.                 // 表示这个图形元素是矩形。还可以是 'circle', 'sector', 'polygon' 等等。
  39.                 type: 'rect',
  40.                 shape: rectShape,
  41.                 // 用 api.style(...) 得到默认的样式设置。这个样式设置包含了
  42.                 // option 中 itemStyle 的配置和视觉映射得到的颜色。
  43.                 style: api.style()
  44.             };
  45.         },
  46.         data: [
  47.             [12, 44, 55, 60], // 这是第一个 dataItem
  48.             [53, 31, 21, 56], // 这是第二个 dataItem
  49.             [71, 33, 10, 20], // 这是第三个 dataItem
  50.             ...
  51.         ]
  52.     }]
  53. }

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 中定义的样式信息,以及视觉映射的样式信息。也可以用这种方式覆盖这些样式信息:api.style({fill: 'green', stroke: 'yellow'})。

书写完 renderItem 方法后,自定义系列的 90% 工作就做完了。剩下的是一些精化工作。


(二)使坐标轴的范围自适应数据范围

在 直角坐标系(grid)、极坐标系(polar) 中都有坐标轴。坐标轴的刻度范围需要自适应当前显示出的数据的范围,否则绘制出的图形会超出去。所以,例如,在 直角坐标系(grid) 中,使用自定义系列的开发者,需要设定,data 中的哪些维度会对应到 x 轴上,哪些维度会对应到 y 轴上。这件事通过 encode 来设定。例如:

  1. option = {
  2.     series: [{
  3.         type: 'custom',
  4.         renderItem: function () {
  5.             ...
  6.         },
  7.         encode: {
  8.             // data 中『维度1』和『维度2』对应到 X 轴
  9.             x: [1, 2],
  10.             // data 中『维度0』对应到 Y 轴
  11.             y: 0
  12.         },
  13.         data: [
  14.             // 维度0  维度1  维度2  维度3
  15.             [   12,   44,   55,   60   ], // 这是第一个 dataItem
  16.             [   53,   31,   21,   56   ], // 这是第二个 dataItem
  17.             [   71,   33,   10,   20   ], // 这是第三个 dataItem
  18.             ...
  19.         ]
  20.     }]
  21. };

(三)设定 tooltip

当然,使用 tooltip.formatter 可以任意定制 tooltip 中的内容。但是还有更简单的方法,通过encode 和 dimensions 来设定:

  1. option = {
  2.     series: [{
  3.         type: 'custom',
  4.         renderItem: function () {
  5.             ...
  6.         },
  7.         encode: {
  8.             x: [1, 2],
  9.             y: 0,
  10.             // 表示『维度2』和『维度3』要显示到 tooltip 中。
  11.             tooltip: [2, 3]
  12.         },
  13.         // 表示给『维度2』和『维度3』分别取名为『年龄』和『满意度』,显示到 tooltip 中。
  14.         dimensions: [null, null, '年龄', '满意度'],
  15.         data: [
  16.             // 维度0  维度1  维度2  维度3
  17.             [   12,   44,   55,   60   ], // 这是第一个 dataItem
  18.             [   53,   31,   21,   56   ], // 这是第二个 dataItem
  19.             [   71,   33,   10,   20   ], // 这是第三个 dataItem
  20.             ...
  21.         ]
  22.     }]
  23. };

上面,一个简单的 custome series 例子完成了。

下面介绍几个其他细节要点。


(四)超出坐标系范围的截取

与 dataZoom 结合使用的时候,常常使用会设置 dataZoom.filterMode 为 'weakFilter'。这个设置的意思是:当 dataItem 部分超出坐标系边界的时候,dataItem 不会整体被过滤掉。例如:

  1. option = {
  2.     dataZoom: {
  3.         xAxisIndex: 0,
  4.         filterMode: 'weakFilter'
  5.     },
  6.     series: [{
  7.         type: 'custom',
  8.         renderItem: function () {
  9.             ...
  10.         },
  11.         encode: {
  12.             // data 中『维度1』和『维度2』对应到 X 轴
  13.             x: [1, 2],
  14.             y: 0
  15.         },
  16.         data: [
  17.             // 维度0  维度1  维度2  维度3
  18.             [   12,   44,   55,   60   ], // 这是第一个 dataItem
  19.             [   53,   31,   21,   56   ], // 这是第二个 dataItem
  20.             [   71,   33,   10,   20   ], // 这是第三个 dataItem
  21.             ...
  22.         ]
  23.     }]
  24. };

在这个例子中,『维度1』和『维度2』对应到 X 轴,dataZoom 组件控制 X 轴的缩放。假如在缩放的过程中,某个 dataItem 的『维度1』超出了 X 轴的范围,『维度2』还在 X 轴的范围中,那么只要设置 dataZoom.filterMode = 'weakFilter',这个 dataItem 就不会被过滤掉,从而还能够使用 renderItem 绘制图形(可以使用上面提到过的 echarts.graphic.clipRectByRect 把图形绘制成被坐标系剪裁过的样子)。


(五)关于 dataIndex

开发者如果使用到的话应注意,renderItem.arguments.params 中的 dataIndex 和 dataIndexInside 是有区别的:

  • dataIndex 指的 dataItem 在原始数据中的 index。

  • dataIndexInside 指的是 dataItem 在当前数据窗口(参见 dataZoom)中的 index。

renderItem.arguments.api 中使用的参数都是 dataIndexInside 而非 dataIndex,因为从 dataIndex 转换成 dataIndexInside 需要时间开销。


(六)事件监听

  1. chart.setOption({
  2.     // ...
  3.     series: {
  4.         type: 'custom',
  5.         renderItem: function () {
  6.             // ...
  7.             return {
  8.                 type: 'group',
  9.                 children: [{
  10.                     type: 'circle'
  11.                     // ...
  12.                 }, {
  13.                     type: 'circle',
  14.                     name: 'aaa',
  15.                     // 用户指定的信息,可以在 event handler 访问到。
  16.                     info: 12345,
  17.                     // ...
  18.                 }]
  19.             };
  20.         }
  21.     }
  22. });
  23. chart.on('click', {element: 'aaa'}, function (params) {
  24.     // 当 name 为 'aaa' 的图形元素被点击时,此回调被触发。
  25.     console.log(params.info);
  26. });

(七)自定义矢量图形

自定义系列能支持使用 SVG PathData 定义矢量路径。从而可以使用矢量图工具中做出的图形。参见官方的文档:path ,以及例子:icons 和 shapes

转载本站内容时,请务必注明来自W3xue,违者必究。
 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号