更多内容请访问:与华为官方共建的鸿蒙技术社区https://harmonyos.51cto.com前言鸿蒙有图提供了组件来实现数据可视化需求,那么我们如何自定义一个图表组件来实现数据可视化呢?本文将使用canvas自定义一个简单的直方图组件。直方图在一切开始之前,我们首先需要创建一个画布。所有对画布的操作都会在这个画布上进行,所以我们需要给画布绑定绘图函数。
我们知道直方图是由坐标轴、坐标轴内容、数据内容和箭头组成的。显然,我们绘制直方图的函数也应该由绘制这些部分的函数组成。draw(){constel=this.$element('the-canvas');constcontext=el.getContext('2d');this.drawAxis()this.drawYLables()this.drawXLables()this.drawData()this.drawData2()this.drawArrowX()this.drawArrowY()},我们还可以定义一些绘制直方图需要的数据。option:{chartZone:[],//图表左上角和右下角坐标数组yAxisLable:[],//Y轴内容yMax:'',//Y轴最大值guideLine:'',//是否有辅助线xAxisLable:[],//x轴内容data:[],//数据内容barStyle:{width:'',//数据条宽度color:''//颜色databar},axisArrow:{size:'',//箭头因子大小color:''//箭头填充颜色}}绘制坐标轴绘制X轴和Y轴我们需要定义一个绘图区chartZone来确定在哪里我们要开始在画布上绘制我们的内容,chartZone的四个值分别是图表左上角和右下角的坐标。注意,在canvas中,默认的坐标原点将不再是我们常规理解的左下角的坐标原点,而是画布的左上角。//绘制X、Y轴drawAxis(){letchartZone=this.option.chartZoneconstel=this.$element('the-canvas');constcontext=el.getContext('2d');context.lineWidth=2context.strokeStyle='#353535'context.moveTo(chartZone[0],chartZone[1])context.lineTo(chartZone[0],chartZone[3])context.lineTo(chartZone[2],chartZone[3]])context.stroke()},在Y轴上绘制内容我们先在Y轴上绘制内容。我们在绘制字体之前使用context.measureText(label).width来计算字体的长度,加上一个距离来存储带有偏移量的值,并用它来计算数值内容在Y轴上的x坐标.在绘制Y轴的时候,我们一般不会使用Y轴的真实长度来平分我们的Y轴数据。我们需要在顶部留一段,保持美观,后面用来画箭头。所以我们实际上是用Y轴的长度作为总长度乘以0.98。(当然我们可以自定义这个值)代码如下:constcontext=el.getContext('2d');context.save()letlabels=this.option.yAxisLable;让yLength=(this.option.chartZone[3]-this.option.chartZone[1])*0.98让gap=yLength/(labels.length-1)让option=this.optionlabels.forEach(function(label,index){//绘制坐标文本//offset是y轴上内容的起点到y轴的距离letoffset=context.measureText(label).width+20context.strokeStyle='#eaeaea';context.font="16px"context.fillText(label,option.chartZone[0]-offset,option.chartZone[3]-index*gap);context.restore()//绘制一个小区间(坐标轴的原点不需要画小区间)if(index==0){return}else{context.beginPath();context.strokeStyle='#353535';//这里我们设置了长度小间隔到10pxcontext.moveTo(option.chartZone[0]-10,option.chartZone[3]-index*gap);context.lineTo(option.chartZone[0],option.chartZone[3]-index*gap)context.stroke()}//绘制辅助线if(option.guideLine==true){//x轴坐标不需要画辅助线if(index==0){return}else{context.beginPath()context.strokeStyle='grey'context.lineWidth=2context.moveTo(option.chartZone[0],option.chartZone[3]-index*gap)context.lineTo(option.chartZone[2],option.chartZone[3]-index*gap)context.stroke()}}})},如果你结合代码,这样会更容易理解X轴上的内容。这部分其实和Y轴很像,不需要做辅助线。注意,我们为了让X轴上的内容与小间隔的点对齐,还使用了context.measureText(label).width。将这个值除以二,放入横坐标小区间运算,使得小区间的点在文本中居中。我们需要记住这个时候我们存储的偏移量offsetXLabel,绘制数据的时候会用到。drawXLabels(){constel=this.$element('the-canvas');constcontext=el.getContext('2d');让标签=this.option.xAxisLable;//和Y轴实际使用的长度类似,我们只使用X轴的96%letxLength=(this.option.chartZone[2]-this.option.chartZone[0])*0.96letgap=xLength/(labels.length)letoption=this.optionlabels.forEach(function(label,index){//绘制坐标文字letoffset=context.measureText(label).widthcontext.save()context.strokeStyle='#eaeaea';context.font="18px"context.fillText(label,option.chartZone[0]+(index+1)*gap-offset,option.chartZone[3]+20);//绘制小区间上下文.beginPath();context.strokeStyle='#353535';context.moveTo(option.chartZone[0]+(index+1)*gap-offset/2,option.chartZone[3]);context.lineTo(选项.chartZone[0]+(index+1)*gap-offset/2,option.chartZone[3]+5)context.stroke()//存储偏移量option.offsetXLabel=offset/2})},如果结合代码看截图,会更容易理解图的绘制数据并绘制数据条难点应该在于矩形的高度和位置。这两点应该怎么计算呢?我们可以通过比率计算出第i个数据条的高度。我们将需要的数据条的高度设置为height,H为y轴实际使用的高度,y显然:所以我们需要绘制数据条的y0坐标值等于chartZone[3]-高度。那么我们如何计算出绘制数据条所需的x0呢?还记得我们是如何在x轴上制作小间隔的吗?x0显然是小间隔的x坐标减去我们希望数据条宽度的一半。计算的时候请记得使用我们刚才保存的offsetXLabel,计算方法见代码。//直方图constel=this.$element('the-canvas');constcontext=el.getContext('2d');context.save()letdata=this.option.dataletxLength=(this.option.chartZone[2]-this.option.chartZone[0])*0.96letylength=(this.option.chartZone[3]-this.option.chartZone[1])*0.98letgap=xLength/this.option.xAxisLable.length;让option=this.optiondata.forEach(function(item,index){context.fillStyle=option.barStyle.color||'#1abc9c';让x0=option.chartZone[0]+(index+1)*gap-option.barStyle.width/2-option.offsetXLabel;letheight=item/option.yMax*ylengthlety0=option.chartZone[3]-heightletwidth=option.barStyle.widthcontext.fillRect(x0,y0,width,height)})context.restore()绘制箭头绘制箭头就简单多了,我们可以设置一个箭头尺寸因子,在箭头形状不变的情况下,我们可以直接通过Vary箭头因子的尺寸来改变箭头的大小。drawArrowX(){//绘制x轴箭头constel=this.$element('the-canvas');constcontext=el.getContext('2d');letfactor=this.option.axisArrow.size;context.save()context.beginPath()context.moveTo(this.option.chartZone[2],this.option.chartZone[3])context.lineTo(this.option.chartZone[2]+2*factor,这个.option.chartZone[3]-3*factor)context.lineTo(this.option.chartZone[2]+10*factor,this.option.chartZone[3])context.lineTo(this.option.chartZone[2]+2*因子,this.option.chartZone[3]+3*factor)context.lineTo(this.option.chartZone[2],this.option.chartZone[3])context.globalAlpha=0.7context.fillStyle=this.option.axisArrow.colorcontext.fill()context.restore()},drawArrowY(){//绘制y轴箭头constel=this.$element('the-canvas');constcontext=el.getContext('2d');让因子=this.option.axisArr低尺寸;context.save()context.beginPath()context.moveTo(this.option.chartZone[0],this.option.chartZone[1])context.lineTo(this.option.chartZone[0]-3*因子,this.option.chartZone[1]-2*factor)上下文。lineTo(this.option.chartZone[0],this.option.chartZone[1]-10*factor)上下文。lineTo(this.option.chartZone[0]+3*factor,this.option.chartZone[1]-2*factor)context.lineTo(this.option.chartZone[0],this.option.chartZone[1])context.globalAlpha=0.7context.fillStyle=this.option.axisArrow.colorcontext.fill()context.restore()}组件的使用首先是指我们的组件,例如: