ECharts,一个纯 Javascript 的图表库,底层依赖轻量级的 Canvas 类库 ZRender,提供直观,生动,可交互,可高度个性化定制的数据可视化图表。当然我们自己可能有些需求,通过修改ECharts或者highcharts的option不能实现,比如说宽度不一致的柱状图图件。可以直接使用Canvas类库zrender开发图件,或者使用snap.svg.js开源项目开发图件。这里写了一个demo使用轻量级的Canvas类库zrender,自定义一个简单的图件。
ZRender 是二维绘图引擎,它提供 Canvas、SVG、VML 等多种渲染方式。ZRender 也是 ECharts 的渲染器。先来看看zrender的总体结构。
zrender使用起来非常方便,这里简单说几点用法,其他的用法可自行查看zRender的官方文档。
1、添加矩形,注意默认的填充颜色是黑色
var rect = new zrender.Rect({shape: {x: 0,y: 0,width: 100,height:100},style: {stroke:'#ffc8aa'},position: [10,10]});zr.add(rect );
2、矩形使用线性渐变色填充
var linearColor = new zrender.LinearGradient(0, 0, 0, 1, [{offset: 0,color: '#efe3ff'},{offset: 1,color: '#6cb3e9'}]);var rect = new zrender.Rect({shape: {x: 0,y: 0,width: 100,height:100},style: {fill:linearColor},position: [10,10]});zr.add(rect );
3、矩形添加动画,矩形的左上角从位置(10,10)移动到(10,100)。也就是纵向向下移动90px
var rect = new zrender.Rect({shape: {x: 0,y: 0,width: 100,height:100},style: {stroke:'#ffc8aa'},position: [10,10]});rect.animateTo({position: [10,100]}, 500, 0, 'linear');zr.add(rect );
4、绘制一条虚线,加上动画,在0.5秒的时间里绘制从0%到100%
var line = new zrender.Line({shape: {x1:10,y1:10,x2:100,y2:10,percent:0},style: {stroke:'#434348',lineDash:[5,5]}});line.animate('shape', false).when(500, {percent: 1}).start();zr.add(line);
lineDash属性,也就是虚线样式
opts.style.lineDash | number[] | null | 描边虚线样式,参考 SVG stroke-dasharray。 |
5、矩形添加添加鼠标事件
var rect = new zrender.Rect({shape: {x: 0,y: 0,width: 100,height:100},style: {stroke:'#ffc8aa'},position: [10,10]});rect.on('click',function(){console.log('单击了这个矩形');});zr.add(rect );
on(eventName, eventHandler, context)
绑定事件处理。
参数
名称 | 类型 | 默认值 | 描述 |
---|---|---|---|
eventName | string | 事件名称,支持: 'click' 、 'mousedown' 、 'mouseup' 、 'mousewheel' 、 'dblclick' 、 'contextmenu' 。 | |
eventHandler | Function | 事件处理的回调函数。 | |
context | Object | 函数上下文。 |
下面写了一个简单的demo,使用zrender开发的简单图件。同时将它封装成一个jQuery的插件,方便调用。按照不同的段绘制渐变色的柱状图,同时点击的时候能够动画显示数值。把它命名为demo.js。
demo.js
/*** Created by ChenCen on 2017/12/20*/(function (factory) {if (typeof define === 'function' && define.amd) {define('viewer', ['jquery'], factory);} else if (typeof exports === 'object') {factory(require('jquery'));} else {factory(jQuery);}
})(function ($) {'use strict';var $window = $(window);var $document = $(document);// Constantsvar NAMESPACE = 'dircard';var ELEMENT_VIEWER = document.createElement(NAMESPACE);function isUndefined(u) {return typeof u === 'undefined';}function isNumber(n) {return typeof n === 'number' && !isNaN(n);}function isString(s) {return typeof s === 'string';}function toArray(obj, offset) {var args = [];if (isNumber(offset)) { // It's necessary for IE8args.push(offset);}return args.slice.apply(obj, args);}//globlevar stroke="#C0D0E0";function dirCard(element, options) {this.element=element;this.$element = $(element);this.options =$.extend({}, dirCard.DEFAULTS, options);this.zr = zrender.init(this.element);this.w= this.zr.getWidth();this.h = this.zr.getHeight();this.disLeft=0.1;this.disTop=0.1;this.disBottom=0.1;this.disRight=0.05;this.zrEleArray=[];this.preZrEle='';this.originLinearColor='';this.init();}dirCard.DEFAULTS={data:[],wellSec:'段',//展示的列showCol:'',//柱颜色barColor:['#48c15e','#dff0d8'],//选中颜色checkColor:['#ff5454','#FF8053'],//背景色backgroundColor:'#fff',//绘制完成后的回调函数rowAfter:false};dirCard.prototype = {constructor: dirCard,//初始化init: function () {var options = this.options;var beginSec, endSec, length;var sec = [options.wellSec];var minData, maxData, i;var data = options.data;var showCol = options.showCol;this.length = length = data.length;if (!length || length == 0 || (!data[0][sec]))return;if (!data[0][showCol]) {console.log('provide the wrong clunmn name!');return}if ((data[0][sec]).split('-').length == 2) {this.split = "-";beginSec = (data[0][sec]).split('-')[0];endSec = (data[length - 1][sec]).split('-')[1];}else if ((data[0][sec]).split('~').length == 2) {this.split = "~";beginSec = (data[0][sec]).split('~')[0];endSec = (data[length - 1][sec]).split('~')[1];}if (beginSec && Number(beginSec) < Number(endSec)) {this.beginSec = Number(beginSec);this.endSec = Number(endSec);minData = Number(data[0][showCol]);maxData = Number(data[0][showCol]);for (i = 1; i < length; i++) {if (minData > Number(data[i][showCol]))minData = Number(data[i][showCol]);if (maxData < Number(data[i][showCol]))maxData = Number(data[i][showCol]);}this.maxData = maxData;this.minData = minData;console.log('max:' + this.beginSec + ' min:' + this.endSec);this.drawBG();if(maxData==0&&maxData==0){console.log('all the value is zero');}else if(maxData<0||minData<0){console.log('not handle this');}else{this.drawEle();}// callBack after drawif (options.rowAfter) {options.rowAfter();}}else {console.log('there must be something wrong!');}},//draw backgrounddrawBG: function () {var zr = this.zr;var w = this.w;var h = this.h;var showCol = this.options.showCol;var backgroundColor=this.options.backgroundColor;var disLeft=this.disLeft*w;var disRight=this.disRight*w;var disTop=this.disTop*h;var disBottom=this.disBottom*h;var i;var dis=this.dis=((this.endSec-this.beginSec)/4).toFixed(0);var wRadio=(w-disLeft-disRight)/(this.endSec-this.beginSec);console.log(dis);var bg = new zrender.Rect({shape: {cx: 0,cy: 0,width: w,height: h},style: {fill:backgroundColor}/*zlevel: -1*/});zr.add(bg);var roundRect = new zrender.Rect({shape: {cx: 0,cy: 0,width: 0.98*w,height:0.98*h},style: {stroke:stroke,fill:'#fff',},position: [0.01*w,0.01*h]});zr.add(roundRect);//axisvar xline =new zrender.Line({shape: {x1:disLeft,y1:h-disTop,x2:disLeft+wRadio*(4*dis),y2:h-disTop},style: {stroke:stroke}});var yline =new zrender.Line({shape: {x1:disLeft,y1:disTop,x2:disLeft,y2:h-disTop},style: {stroke:stroke}});zr.add(xline);zr.add(yline);for(i=0;i<5;i++){var smline =new zrender.Line({shape: {x1:0,y1:0,x2:0,y2:0.02*h},style: {stroke:stroke},position: [disLeft+wRadio*(i*dis), h-disBottom]});var smText = new zrender.Text({style: {stroke: '#434348',text:this.beginSec+(i*dis),fontSize: '11',textAlign:'center'},position: [disLeft+wRadio*(i*dis), h-disBottom+0.03*h]});zr.add(smline);zr.add(smText);}},//draw all eledrawEle: function () {var self = this;var options = this.options;var showCol = options.showCol;var sec=options.wellSec;var color=options.barColor;var zr = this.zr;var w = this.w;var h = this.h;var disLeft=this.disLeft*w;var disRight=this.disRight*w;var disTop=this.disTop*h;var disBottom=this.disBottom*h;var i;var wRadio=(w-disLeft-disRight)/(this.endSec-this.beginSec);var hRadio=(h-disTop-disBottom)/(this.maxData-this.minData);for (i = 0; i < this.length; i++) {var barValue=Number(options.data[i][showCol]);var bg = (options.data[i][sec]).split(this.split)[0];bg = Number(bg);var ed = (options.data[i][sec]).split(this.split)[1];ed = Number(ed);this.originLinearColor = new zrender.LinearGradient(0, 0, 0, 1, [{offset: 0,color: color[0]},{offset: 1,color: color[1]}]);var zrEle = new zrender.Rect({shape: {cx: 0,cy: 0,width: wRadio * (ed - bg),height:0},style: {fill: this.originLinearColor},position: [disLeft+wRadio*(bg-this.beginSec),h-disBottom]// silent: true 不响应鼠标事件});zrEle.rowIndex=i;//bind click eventself.clickEle(zrEle);zrEle.animateTo({shape: {cx: 0,cy: 0,width: wRadio * (ed - bg),height:hRadio*(barValue-this.minData)},position: [disLeft+wRadio*(bg-this.beginSec),h-disBottom-hRadio*(barValue-this.minData)]}, 500, i * 100, 'linear');zr.add(zrEle);this.zrEleArray.push(zrEle);}},//click and change style for one eleactiveEle: function (index) {var zr = this.zr;var options=this.options;var showCol = options.showCol;var sec=options.wellSec;var checkColor = options.checkColor;var w = this.w;var h = this.h;var disLeft=this.disLeft*w;var disRight=this.disRight*w;var disTop=this.disTop*h;var disBottom=this.disBottom*h;var wRadio=(w-disLeft-disRight)/(this.endSec-this.beginSec);var hRadio=(h-disTop-disBottom)/(this.maxData-this.minData);var barValue=Number(options.data[index][showCol]);var bg = (options.data[index][sec]).split(this.split)[0];bg = Number(bg);var ed = (options.data[index][sec]).split(this.split)[1];ed = Number(ed);if (index < this.length) {//恢复移除部分if (this.preZrEle) {this.preZrEle.attr({style: {stroke: null,lineWidth:0,fill: this.originLinearColor}});}if(this.tipLine){zr.remove(this.tipLine);}if(this.tipText){zr.remove(this.tipText);}//改变添加部分var checkZr = this.zrEleArray[index];if (checkZr) {this.preZrEle = checkZr;//设置元素属性checkZr.attr({style: {/* stroke: '#FF5454',*/lineWidth:4,fill: new zrender.LinearGradient(0, 0, 0, 1, [{offset: 0,color: checkColor[0]},{offset: 1,color: checkColor[1]}])}});//提示虚线var tipLine = new zrender.Line({shape: {x1:disLeft,y1:h-disBottom-hRadio*(barValue-this.minData),x2:disLeft+wRadio*(ed-this.beginSec),y2:h-disBottom-hRadio*(barValue-this.minData),percent:0},style: {stroke:'#434348',lineDash:[5,5]}});tipLine.animate('shape', false).when(500, {percent: 1}).start();zr.add(tipLine);this.tipLine=tipLine;//提示文字var tipText = new zrender.Text({style: {stroke: '#434348',text:barValue,fontSize: '10'},position: [disLeft+wRadio*(ed-this.beginSec),h-disBottom-hRadio*(barValue-this.minData)]});zr.add(tipText);this.tipText=tipText;}}else {console.log('该索引下没有zrender元素');}},clickEle:function(zrEle){var self=this;zrEle.on('click',function(){var rowIndex=zrEle.rowIndex;self.activeEle(rowIndex);});},//销毁实例dispose:function(){var zr = this.zr;zrender.dispose(zr);//移除反向绑定this.$element.removeData(NAMESPACE);}};// Register as jQuery plugin$.fn.dirCard = function (options) {var args = toArray(arguments, 1);var result;this.each(function () {//console.log(this);var $this = $(this);var data = $this.data(NAMESPACE);var fn;if (!data) {$this.data(NAMESPACE, (data = new dirCard(this, options)));}if (isString(options) && $.isFunction(fn = data[options])) {result = fn.apply(data, args);}});return isUndefined(result) ? this : result;};});
调用示例
<!--
Created by ChenCen on 2017/12/20
--><!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title><script type="text/javascript" src="jquery.js"></script><script type="text/javascript" src="zrender.js"></script><script type="text/javascript" src="demo.js"></script>
</head>
<body>
<div class="example-container" style="width:800px;height:400px"></div>
<script>str='[{"段":"794-810","value":5},' +'{"段":"815-823","value":5.6},' +'{"段":"840-849","value":3},' +'{"段":"855-860","value":7},' +'{"段":"877-893","value":2.6},' +'{"段":"920-945","value":8.3},' +'{"段":"955-962","value":4.5},' +'{"段":"970-977","value":6},' +'{"段":"980-988","value":7.1},' +'{"段":"993-1007","value":3.3},' +'{"段":"1008-1011","value":8.1},' +'{"段":"1020-1035","value":6.3}]';jsonData=JSON.parse(str);$('.example-container').dirCard({data:jsonData,wellSec:'段',showCol:'value',barColor:['rgba(114, 172, 209, 1)','rgba(114, 172, 209, 0.5)'],backgroundColor:'#ccc'});
</script>
</body>
</html>
效果图
以上只是一个简单的示例,可以尝试用zrender开发功能更全面的自定义图件。
修改后的demo:https://ccessl.github.io/zrender-use-chart/