* ## **为何要改造uCharts?** * 并不是所有图表插件直接拿来就可以满足客户需求,如果您的UI设计师设计一个图表,如下图: :-: ![](https://img.kancloud.cn/c2/1f/c21f7f84c5ff42d323267323d7f48b05_468x337.gif) * 您会发现这个图表即使在echarts里也不是很好实现,那么就需要我们自己动手去实现。下面就让我们一起来完成,本文旨在抛砖引玉,希望各位朋友能够更好的应用uCharts来完成您的项目。 * ## **题外篇:学习canvas画布的教程** > [学习HTML5 Canvas这一篇文章就够了](https://blog.csdn.net/u012468376/article/details/73350998) * 此教程仅针对HTML的canvas,仅供入门参考。uCharts中使用的canvas的API是遵循小程序中的API,个别方法与此不一样,例如uCharts中要用context.setLineWidth(1)来指定线宽,而HTML中是用context.lineWidth=1,uCharts为兼容H5做了如下方法的兼容: ``` this.context = document.getElementById(opts.canvasId).getContext("2d"); this.context.setStrokeStyle = function(e){ return this.strokeStyle=e; } this.context.setLineWidth = function(e){ return this.lineWidth=e; } this.context.setLineCap = function(e){ return this.lineCap=e; } this.context.setFontSize = function(e){ return this.font=e+"px sans-serif"; } this.context.setFillStyle = function(e){ return this.fillStyle=e; } this.context.draw = function(){ } ``` * uCharts的画布API参考uniapp的画布API: > [uniapp的画布API](https://uniapp.dcloud.io/api/canvas/CanvasContext) * ## **uCharts程序结构介绍** * 很多朋友刚接触uCharts的时候,对于文档API部分不是很理解,先说一下入口函数: > var Charts = function Charts(opts) * 当我们在vue页面调用uCharts的时候,也就是 let xxx = new uCharts({});的时候,我们传入uCharts的参数,就是opts这个对象。所以文档里都是以opts.开头的API参数。 * 这个入口函数主要对一些默认配置进行了初始化,改造过程中不涉及改造这里,仅需了解opts是代表您传入的参数即可,另外后续opts这个对象也会起很大作用,一些计算好的数据也会挂载到这里。 > function drawCharts(type, opts, config, context) * `drawCharts`这个函数是主执行函数,所有的交互例如点击后出现toolTip、图例点击、拖拽滚动条等,都会调用此方法重新渲染图表。 * `type`是图表类型,`opts`是API参数,`config`是默认配置,`context`是画布的上下文。 > function draw...(xx,xx,xx) * 凡是涉及到function draw...开头的函数,都是绘制图表的主方法,如果E文好的话,根据方法名就可以看出来是做什么用的,这里不一一介绍了。其他get...(),find...(),cal...()开头的函数,都是辅助计算类函数。 ================================================= * 说了一堆没用的,下面说正题: * ## **图形分析** * 首先,我们会发现这个图形包含两部分: - 1外部的刻度与`仪表盘`的刻度类似,可以参考仪表盘的做法来改。 - 2外侧的进度条与`圆弧进度条`类似,把进度条部分复制过来,加到仪表盘里,剩下的就好办了。 * 这样,我们就需要把这两种图表进行一个结合,再绘制出其他辅助类的文字即可。 > function drawGaugeDataPoints(categories, series, opts, config, context) > function drawArcbarDataPoints(series, opts, config, context) * ## **仪表盘变量定义说明** * 我们先来看drawGaugeDataPoints这个方法里头部的几个变量,简单介绍一下用途: - var process:代表指针或者动画的进度,值的范围为[0-1],1为动画结束。 - var gaugeOption:这个继承了opts.extra.gauge的配置,便于后续读取opts中的扩展配置。 - categories:这个比较特殊,在仪表盘中用于定义刻度轴线的样式尺寸等配置。 - var centerPosition:顾名思义,这个是仪表盘的中心点。 - var radius:仪表盘半径,这里取了画布宽高的最小值作为基准依据。 - var innerRadius:仪表盘内部半径,根据外半径及刻度线宽度计算而来。 * ## **下面开始新增一个样式:** - 我们通过opts.extra.gauge.type的值来传入一个新的样式,原来的样式为`default`,新样式定义为`progress`,这样,我们判断gaugeOption.type的字符串来确定绘制哪个图表样式。 - 我们把这个图分为五部分来画: * ### 第一步画中心圆形背景和进度条背景: ``` //中心圆形背景 var pieRadius = radius - gaugeOption.width*3; context.beginPath(); let gradient = context.createLinearGradient(centerPosition.x, centerPosition.y-pieRadius, centerPosition.x , centerPosition.y+pieRadius); //配置渐变填充(起点:中心点向上减半径;结束点中心点向下加半径) gradient.addColorStop('0', hexToRgb(series[0].color, 0.3)); gradient.addColorStop('1.0',hexToRgb("#FFFFFF", 0.1)); context.setFillStyle(gradient); context.arc(centerPosition.x, centerPosition.y, pieRadius, 0, 2*Math.PI, false); context.fill(); //画进度条背景 context.setLineWidth(gaugeOption.width); context.setStrokeStyle(hexToRgb(series[0].color, 0.3)); context.setLineCap('round'); context.beginPath(); context.arc(centerPosition.x, centerPosition.y, innerRadius , gaugeOption.startAngle * Math.PI, gaugeOption.endAngle *Math.PI, false); context.stroke(); ``` * ### 第二步画刻度线: ``` totalAngle = gaugeOption.startAngle - gaugeOption.endAngle + 1; let splitAngle = totalAngle / gaugeOption.splitLine.splitNumber; let childAngle = totalAngle / gaugeOption.splitLine.splitNumber / gaugeOption.splitLine.childNumber; let startX = -radius - gaugeOption.width * 0.5 - gaugeOption.splitLine.fixRadius; let endX = -radius - gaugeOption.width - gaugeOption.splitLine.fixRadius + gaugeOption.splitLine.width; context.save(); context.translate(centerPosition.x, centerPosition.y); context.rotate((gaugeOption.startAngle - 1) * Math.PI); let len = gaugeOption.splitLine.splitNumber * gaugeOption.splitLine.childNumber + 1; let proc = series[0].data * process; for (let i = 0; i < len; i++) { context.beginPath(); //刻度线随进度变色 if(proc>(i/len)){ context.setStrokeStyle(hexToRgb(series[0].color, 1)); }else{ context.setStrokeStyle(hexToRgb(series[0].color, 0.3)); } context.setLineWidth(3 * opts.pixelRatio); context.moveTo(startX, 0); context.lineTo(endX, 0); context.stroke(); context.rotate(childAngle * Math.PI); } context.restore(); ``` * ### 第三步画进度条: ``` series = getArcbarDataPoints(series, gaugeOption, process); context.setLineWidth(gaugeOption.width); context.setStrokeStyle(series[0].color); context.setLineCap('round'); context.beginPath(); context.arc(centerPosition.x, centerPosition.y, innerRadius , gaugeOption.startAngle * Math.PI, series[0]._proportion_ *Math.PI, false); context.stroke(); ``` * ### 第四步画指针: ``` let pointerRadius = radius - gaugeOption.width*2.5; context.save(); context.translate(centerPosition.x, centerPosition.y); context.rotate((series[0]._proportion_ - 1) * Math.PI); context.beginPath(); context.setLineWidth(gaugeOption.width/3); let gradient3 = context.createLinearGradient(0, -pointerRadius*0.6, 0 , pointerRadius*0.6); gradient3.addColorStop('0', hexToRgb('#FFFFFF', 0)); gradient3.addColorStop('0.5', hexToRgb(series[0].color, 1)); gradient3.addColorStop('1.0', hexToRgb('#FFFFFF', 0)); context.setStrokeStyle(gradient3); context.arc(0, 0, pointerRadius , 0.85* Math.PI, 1.15 * Math.PI, false); context.stroke(); context.beginPath(); context.setLineWidth(1); context.setStrokeStyle(series[0].color); context.setFillStyle(series[0].color); context.moveTo(-pointerRadius-gaugeOption.width/3/2,-4); context.lineTo(-pointerRadius-gaugeOption.width/3/2-4,0); context.lineTo(-pointerRadius-gaugeOption.width/3/2,4); context.lineTo(-pointerRadius-gaugeOption.width/3/2,-4); context.stroke(); context.fill(); context.restore(); ``` * ### 第五步画中间文字(调用圆环图的Title方法): ``` drawRingTitle(opts, config, context, centerPosition); ```