一、功用结构
完成一个公共组件的时分,首要分析一下大概的完成结构以及开发思路,便利咱们少走弯路,也能够使组件愈加简单拓宽,维护性更强。然后我会把功用逐个拆开来讲,这样咱们才干学习到更详细的内容。下面简单论述下折线图组件的功用结构:
以上是根底的功用结构框架,包括一些比较简单的根底功用,后续还有点击触发、动画等功用也会规划进去。这一期咱们先完成上面这些根底的功用,后续再渐渐拓宽。
二、公共特点
1. 一个组件肯定会有一些公共的特点作为动态参数,便于组件之间的信息传递,咱们别离解说一下五个公共特点的作用:画布的宽度(cWidth)和高度(cHeight),这个是最基本的。可是我这儿操控是非必传,默许值都是100%就能够了。
2. 画布的内部留白距离(cSpace)。主要是用来操控内容区与画布外框的距离,防止绘画的内容被截掉。
3. 字体大小(fontSize)。主要是来操控整个绘画内容的字体大小,全局性,防止每个小功用都需求传字体大小。
4. 字体色彩(color)。与字体大小的功用共同。
5. 图表数据(data)。用来存储图表内容的数组,其间name与value是必传的。
以下是详细的代码:
//图表数据的特征接口
interfaceinterface_data{
name:string|number;
value:string|number;
[key:string]:any;
}
//图表的特征接口
interfaceinterface_option{
cWidth?:string|number,
cHeight?:string|number,
fontSize?:string|number,
color?:string,
cSpace?:number,
data?:interface_data[]
}
//option默许值
constdef_option:interface_option={
cWidth:'100%',
cHeight:'100%',
fontSize:10,
color:'#333',
cSpace:20,
data:[]
}
@Component
exportstructMcLineChart{
privatesettings:RenderingContextSettings=newRenderingContextSettings(true)
privatecontext:CanvasRenderingContext2D=newCanvasRenderingContext2D(this.settings)
@Stateoptions:interface_option={}
aboutToAppear(){
this.options=Object.assign({},def_option,this.options)
}
build(){
Canvas(this.context)
.width(this.options.cWidth)
.height(this.options.cHeight)
.onReady(()=>{
})
}
}
三、绘画坐标轴
绘画图表内容区部分,首要是绘画坐标轴,坐标轴分为X轴跟Y轴,咱们要先开始画Y轴,原因是:y轴上要显现文本标签,如果一开始没有得到文本标签对应的宽度最大值,那么Y轴跟X轴的起点坐标就会有偏差,会导致绘画全部错位,下图是完好的坐标轴的效果。
1.绘画Y轴
Y轴全体是由轴线、切割线、刻度线、文本标签四个部分组成的,四个部分都有先后联系,并且包括必定的算法逻辑,下面简单用一个概念图进行解说。
首要用500*500的矩形作为咱们这次的画布,咱们能够在图上看到Y轴全体包括了文本标签、Y轴线、切割线、刻度线四个部分。而canvas绘画基本都是经过坐标来定位的,Y轴全体的四个部分的起点与结束坐标都相互有联系,乃至需求把内部距离、切割距离、y轴线高度、文本最大的宽度四个特点核算在内。以上是概念与思路,接下来咱们逐个解说代码:
1、核算得到文本最长宽度(maxNameW),咱们能够从图中看到,不论是y轴线、刻度线还是切割线的起点坐标都是需求内容距离、文本标签、文本标签与切割线距离相加核算得到,而为了保持对齐,所以咱们需求核算出文本最长宽度。而y轴的文本一般都是数据(data)对应的数值,所以咱们需求得到传入数据(data)中的最大值。然后讲最大值切割成五等分。以下便是核算获取最大文本宽度的代码,部分逻辑我也会写在代码上:
build(){
Canvas(this.context)
.width(this.options.cWidth)
.height(this.options.cHeight)
.backgroundColor(this.options.backgroundColor)
.onReady(()=>{
constvalues:number[]=this.options.data.map((item)=>Number(item.value||0))
constmaxValue=Math.max(...values)
letmaxNameW=0
letcSpiltNum=5//切割等分
letcSpiltVal=maxValue/cSpiltNum//核算切割距离
for(vari=0;i<=this.options.data.length;i ){
//用最大值除于切割等分得到每一个文本的距离值,而每一次遍历用距离值乘于i就能得到每个刻度对应的数值了,核算得到得知需求保存整数且转成字符串
consttext=(cSpiltVal*i).toFixed(0)
consttextWidth=this.context.measureText(text).width;//获取文字的长度
maxNameW=textWidth>maxNameW?textWidth:maxNameW//每次进行最大值的匹配
}
})
}
2、绘画文本标签,咱们能够从图中看到文本标签的x坐标只跟内部距离有关,并且咱们从上面代码就已经得到每个刻度的切割距离了,然后能够得到每个文本的y轴。
.onReady(()=>{
....
for(vari=0;i<=this.options.data.length;i ){
...
//绘画文本标签
this.context.fillText(text,this.options.cSpace,cSpiltVal*(this.options.data.length-i) this.options.cSpace,0);
}
})
3、绘画刻度线。咱们能够从概念图得到,刻度线的起点x坐标算法是:内部距离(cSpace)加最长文本宽度(maxNameW)加上文本与刻度线的距离,起点y坐标则跟文本相同,经过切割距离与下角标的联系得到每个刻度的y坐标;而结尾x坐标则是刻度线的长度,结尾y坐标则跟起点的y坐标相同,我设置默许长度是5,这样就能得到咱们的刻度线了。代码如下:
.onReady(()=>{
....
constlength=this.options.data.length
for(vari=0;i<=length;i ){
...
}
//上面是获取最长文本宽度的代码
//画线的方法
functiondrawLine(x,y,X,Y){
this.context.beginPath();
this.context.moveTo(x,y);
this.context.lineTo(X,Y);
this.context.stroke();
this.context.closePath();
}
for(vari=0;i<=length;i ){
constitem=this.options.data[i]
//绘画文本标签
ctx.fillText(text,this.options.cSpace,cSpiltVal*(this.data.length-i) this.options.cSpace,0);
//内部距离 文本长度
constscaleX=this.options.cSpace maxNameW
//经过数据最大值算出等分距离,然后核算出每一个的结尾坐标
constscaleY=cSpiltVal*(length-i) this.options.cSpace
//这儿的5便是我设置文本跟刻度线的距离与刻度线的长度
drawLine(scaleX,scaleY,scaleX 5 5,scaleY);
}
})
4、绘画y轴线。持续分析概览图,从图中咱们能够得到:y轴线的起点x坐标的算法是:内部距离(cSpace)加最长文本宽度(maxNameW)加上文本与刻度线的距离以及刻度线长度,起点y坐标则是内部上距离;而结尾x坐标与起点x坐标相同,结尾y坐标算法是:画布高度减去上下两头的内部距离。经过以上核算联系就能绘画出y轴线了。代码如下:
.onReady(()=>{
...
//上面是绘画其他组成部分代码
conststartX=this.options.cSpace maxNameW 5 5
conststartY=this.options.cSpace
constendX=startX
constendY=this.context.height-(this.options.cSpace*2)
drawLine(startX,startY,endX,endY);//绘画y轴
})
5、绘画切割线。其实从图中能够看出切割线与刻度线差不多,起点x坐标算法是:在刻度线起点x坐标根底上加刻度线长度;起点y轴与刻度线相同。而结尾的x坐标算法:画布宽度减去起点x坐标;结尾的y坐标与起点的y坐标相同。详细代码如下:
.onReady(()=>{
....
//上面是获取最长文本宽度的代码
for(vari=0;i<=length;i ){
constitem=this.options.data[i]
//绘画文本标签跟刻度
...
//绘画切割线
constsplitX=scaleX 5 5
constsplitY=scaleY
drawLine(splitX,splitY,this.context.width-splitX-this.options.cSpace,splitY);
}
})
2.绘画X轴
绘画完Y轴之后,咱们接着绘画X轴,X轴与Y轴绘画逻辑共同,仅仅方向不同而已。详细的算法就不一一详解,能够参考一下概念图。
而与绘画Y轴不共同的在于:
1. 最长对象不相同。Y轴最长是文本宽度;而X轴需求获取的最长是文本高度。
2. 距离切割数不相同。Y轴是自定义的切割数;而X轴切割线是实践数据的长度。
3. 切割距离长度算法不相同。Y轴算法是用数据最大值处于自定义的切割数;而X轴算法是用画布宽度减去(左右两头的内部空隙以及Y轴宽度(文本最长宽度加上刻度线宽度)),再除去数据的长度,得到每个距离的长度。
除了上面三点需求留意的,其他的便是调换一下核算的位置。X轴全体的代码如下:
.onReady(()=>{
constcSpace=this.options.cSpace
//上面是制作y轴的代码
....
//制作x轴
//获取每个切割线的距离:this.context.width-20为x轴的长度
letxSplitSpacing=parseInt(String((this.context.width-cSpace*2-maxNameW)/this.options.data.length))
letx=0;
for(vari=0;i<=this.options.data.length;i ){
//绘画切割线
x=xSplitSpacing*(i 1)//核算每个数值的x坐标值
this.drawLine(x cSpace maxNameW,this.context.height-cSpace,x cSpace maxNameW,cSpace);
//制作刻度
this.drawLine(x cSpace maxNameW,this.context.height-cSpace,x cSpace maxNameW,this.context.height-cSpace);
//制作文字刻度标签
consttext=this.options.data[i].name
consttextWidth=this.context.measureText(text).width;//获取文字的长度
//这儿文本的x坐标需求减去本身文本宽度的一半,这样才干居中显现,y坐标这是画布高度减去内部距离即可
this.context.fillText(text,x cSpace maxNameW-textWidth/2,this.context.height-cSpace,0);
}
this.context.save();
this.context.rotate(-Math.PI/2);
this.context.restore();
})
四、绘画折线区
绘画完坐标轴之后,就能够来绘画折线区的内容了。也是整个画布要点的部分。折线区分为三个部分:绘画折线、绘画标点、绘画文本。1.绘画折线
从上面的图能够看出折线直接便是把实践数据的数值转成x跟y坐标,再经过连线连接起来。而每一个转折点的x坐标算法跟x轴的刻度或许文本是相同的,而y坐标是实践数值经过必定算法转成咱们需求的高度。x坐标咱们已经获取了,只要是霸占咱们的y坐标即可。能够经过图来观察一下在画布中与实践数据的联系:
首要Y轴的高度代表的是实践数据的最大值,这个咱们绘画Y轴的时分就得到的结果,那咱们则能够算出Y轴高度与实践数据的缩放倍数(scale),而折线的的每个y坐标对应的也是实践数值,需求把实践数值转换成画布中高度,那么就用实践数值乘与刚刚得到的缩放倍数(scale)就能得到转化后的高度了。
虽然咱们已经得到每个转折点缩放后的高度,可是如果要跟Y轴坐标一一对应的y坐标的画,还需求用画布的高度减去下边内部高度加x轴高度,再减去缩放后的实践高度。这样算出来的才是咱们想要的y坐标值,大概算法联系已经知道了,以下是终究代码:
.onReady(()=>{
...
//上面是制作x轴跟y轴的代码
//绘画折线
constySacle=(this.context.height-cSpace*2)/maxValue//核算出y轴与实践最大值的缩放倍数
//连线
this.context.beginPath();
for(vari=0;i<this.options.data.length;i ){
constdotVal=String(this.options.data[i].value);
constx=xSplitSpacing*(i 1) cSpace maxNameW//核算每个数值的x坐标值
consty=this.context.height-cSpace-parseInt(dotVal*ySacle);//画布的高度减去下边内部高度加x轴高度,再减去缩放后的实践高度
if(i==0){
//第一个作为起点
this.context.moveTo(x,y);
}else{
this.context.lineTo(x,y);
}
}
ctx.stroke();
})
2.绘画标点、文本标签
画完折线咱们基本能得到许多东西,比方折线上每个转折点的x跟y坐标值。这样对咱们绘画标点与文本标签就很便利了:
.onReady(()=>{
...
//上面是制作x轴跟y轴的代码
//绘画折线
constySacle=(this.context.height-cSpace*2)/maxValue//核算出y轴与实践最大值的缩放倍数
this.context.beginPath();
for(vari=0;i<this.options.data.length;i ){
//绘画折线代码
...
//制作标点
drawArc(x,y);
//制作文本标签
consttextWidth=this.context.measureText(dotVal).width;//获取文字的长度
consttextHeight=this.context.measureText(dotVal).height;//获取文字的长度
this.context.fillText(dotVal,x-textWidth/2,y-textHeight/2);//文字
}
functiondrawArc(x,y){
this.context.beginPath();
this.context.arc(x,y,3,0,Math.PI*2);
this.context.fill();
this.context.closePath();
}
this.context.stroke();
})
终究效果如下:
五、总结
以上是本次技术分析,期望能对咱们有所启发,也祝愿各位开发者能开宣布抱负的效果,后续咱们会把chart相联系列的组件封装到组件库发布到市场上,这样能够直接开箱即用了。敬请期待吧,后续还有许多技术的分享,不要错过!