仿echarts写自己的插件之线性图

看了echarts的实例,感觉蛮溜的,但是源码看得我伤神,干脆自己写一个,就花了点时间自己整了个线性图,发出来大家看看,我个人觉得代码比框架大佬的更容易理解,更容易看懂,哈哈,有点自恋,是框架大佬的太高深了吧,看的我头痛。

首先来分析一下,这个图

从图中,我们可以大概将这个线性图分解成多个元素

  1.  xy坐标
  2. text文本
  3. 线段line
  4. 圆 Circle
  5. 提示框toast(图中没显示出来)

这里我们就可以想到,如果我们写5个构造函数,分别对应这个5种元素,需要什么元素就new一个,然后渲染出来,组合起来不就成了吗?

1.定义好5个构造函数。

2.根据传入的属性,设置好各种元素,并存储在一个数组中。

3.循环这个数组渲染出来。

XY坐标构造函数

function AxisXY(obj){
		this.init(obj);
	}
	
	AxisXY.prototype.init=function(obj){
		var axis = obj.echartsOption.getAxis()||{};
		this.x0=axis.x0,//x轴0处坐标
		this.y0=axis.y0,//y轴0处坐标
		this.x1=axis.x1,//x轴顶处坐标
		this.y1=axis.y1,//y轴顶处坐标
		this.out=axis.out;//坐标突出的地方
		this.ctx=obj.ctx;
	}
	AxisXY.prototype.render=function(){
		 var ctx=this.ctx;
		 ctx.save();
		 //先绘制X和Y轴
		 ctx.translate(this.x0,this.y0);
	     ctx.translate(0.5, 0.5);
	     _.drawLine(this.ctx,0,this.y1,0,this.out);
	     _.drawLine(this.ctx,-this.out,0,this.x1,0);
	     ctx.restore();
	}

init方法用来初始化 传入的属性,render方法用来最后的渲染。

文本Text构造函数

function Text(echarts,o){
		this.x=0,//x坐标
		this.y=0,//y坐标
		this.startX=0,//开始点x位置
		this.startY=0, //开始点y位置
		this.text='',//内容
		this.init(echarts,o);
	}
	Text.prototype.init=function(echarts,o){
		this.ctx = echarts.ctx;
		for(var key in o){
			this[key]=o[key];
		}
	}
	Text.prototype.render=function(){
		var ctx=this.ctx;
		ctx.save();
		ctx.translate(this.x,this.y);
		if(this.textAlign){
			ctx.textAlign=this.textAlign;
		}
		if(this.font){
			ctx.font=this.font;
		}
		ctx.fillText(this.text,this.startX,this.startY);
	  	ctx.restore();
	}

线段的构造函数

function Line(echarts,o){
		this.x=0,//x坐标
		this.y=0,//y坐标
		this.startX=0,//开始点x位置
		this.startY=0, //开始点y位置
		this.endX=0,//结束点x位置
		this.endY=0;//结束点y位置
		this.thin=false;//设置变细系数
		
		this.init(echarts,o);
	}
	Line.prototype.init=function(echarts,o){
		this.ctx = echarts.ctx;
		for(var key in o){
			this[key]=o[key];
		}
	}
	Line.prototype.render=function(){
		if(this.timeOut){
			var t = parseInt(this.timeOut);
			setTimeout(innerRender.bind(null,this),t)
		}else{
			innerRender(this);
		}
		
		function innerRender(obj){
			var ctx=obj.ctx;
			ctx.save()
			ctx.beginPath();
			ctx.translate(obj.x,obj.y);
			if(obj.thin){
				ctx.translate(0.5,0.5);
			}
			if(obj.lineWidth){
				ctx.lineWidth=obj.lineWidth;
			}
			if(obj.strokeStyle){
				ctx.strokeStyle=obj.strokeStyle;
			}
		  	ctx.moveTo(obj.startX, obj.startY);
		  	ctx.lineTo(obj.endX, obj.endY);
		  	ctx.stroke();
		  	ctx.restore();
		}
	  	
	  	return this;
	}
	Line.prototype.setTimeout=function(t){
		this.timeOut=t;
		return this;
	}

这里加多 一个setTimeout 方法,是用来延时渲染的,这个可以达到各个线段分别延时渲染,线条会一条条的渲染出来。

圆的构造函数

function Circle(echarts,o){
		this.x=0,//圆心x
		this.y=0,//圆心y
		this.r=0,//半径
		this.startAngle=0,//开始角度
		this.endAngle=0,//结束角度
		this.anticlockwise=false;//是否逆时针
		this.stroke=false;//绘制
		this.fill=false;//填充
		this.scaleX=1;//缩放X
		this.scaleY=1;//缩放y
		
		this.init(echarts,o);
	}
	Circle.prototype.init=function(echarts,o){
		this.echartsOption = echarts.echartsOption;
		this.ctx = echarts.ctx;
		for(var key in o){
			this[key]=o[key];
		}
	}
	//动画的更新函数
	Circle.prototype.update=function(o){
		var type = getType(o);
		if(type==='Function'){//直接传入的函数就执行
			o();
		}else if(type==='Array'){//是数组就迭代后执行
			_.each(o,function(fn){
				fn && fn();
			})
		}
	}
	//设定动画
	Circle.prototype.setAnimate=function(o){
		this.animation=o;
		return this;
	}
	//移出不要渲染的元素
	Circle.prototype.removeRender=function(){
		if(!this.renderArr) return ;
		var that=this;
		_.each(that.renderArr,function(item,i){
			if(item==that.toast){
				that.renderArr.splice(i,1);
				that.toast=undefined;
			}
		})
		
	}
	//是否在当前图形内
	Circle.prototype.isPoint=function(offset){
		var axis = this.echartsOption.getAxis()||{};
		var offsetX =offset.x ,
			offsetY	=offset.y ;
			
		//用勾股定理计算鼠标与圆心的距离
		var rDis = Math.sqrt((offsetX-this.x)**2+(offsetY-this.y)**2);
		if(rDis<=(this.r*this.scaleX)){
			if(this.isSeleted!=='1'){//防止在同一个图形里面重复移动
				this.isSeleted=1;//初次进入图形
			}else{
				this.isSeleted=2;//在图形里面移动
			}
		}else{
			this.isSeleted=3;//不在图形里面
		}
		return this.isSeleted;
	}
	//渲染图形
	Circle.prototype.render=function(){
		if(this.timeOut){
			var t = parseInt(this.timeOut);
			setTimeout(innerRender.bind(null,this),t)
		}else{
			innerRender(this);
		}
		
		function innerRender(obj){
			var ctx=obj.ctx;
			ctx.save();
			ctx.beginPath();
			ctx.translate(obj.x,obj.y);
			ctx.scale(obj.scaleX,obj.scaleY);
			ctx.arc(0,0,obj.r,obj.startAngle,obj.endAngle);
			if(obj.lineWidth){
				ctx.lineWidth=obj.lineWidth;
			}
			if(obj.fill){
				obj.fillStyle?(ctx.fillStyle=obj.fillStyle):null;
				ctx.fill();
			}
			if(obj.stroke){
				obj.strokeStyle?(ctx.strokeStyle=obj.strokeStyle):null;
				ctx.stroke();
			}	
			ctx.restore();
		}
	}
	//设置延时
	Circle.prototype.setTimeout=function(t){
		this.timeOut=t;
		return this;
	}

提示框toast的构造函数

function Toast(echarts,o){
		this.x=0,//x坐标
		this.y=0,//y坐标
		this.thin=false;//设置变细系数
		this.width=100,//宽
		this.height=40,//高
		this.thin=true,//线段薄一点
		this.init(echarts,o);
	}
	Toast.prototype.init=function(echarts,o){
		this.ctx = echarts.ctx;
		for(var key in o){
			this[key]=o[key];
		}
	}
	Toast.prototype.render=function(){
		var axis = this.parent.echartsOption.getAxis()||{};
		var x0=axis.x0,//x轴0处坐标
			y0=axis.y0,//y轴0处坐标
			x1=axis.x1,//x轴顶处坐标
			y1=axis.y1;//y轴顶处坐标
			
		var tranX = this.parent?this.parent.x:this.x,
			tranY = this.parent?this.parent.y:this.y;	
			
		if(this.timeOut){
			var t = parseInt(this.timeOut);
			setTimeout(innerRender.bind(null,this),t)
		}else{
			innerRender(this);
		}
			
		function innerRender(obj){
			var ctx=obj.ctx;
			ctx.save()
			ctx.beginPath();
			ctx.translate(tranX,tranY);
			
			if(obj.lineWidth){
				ctx.lineWidth=obj.lineWidth;
			}
			if(obj.strokeStyle){
				ctx.strokeStyle=obj.strokeStyle;
			}
			
			var x=y=0;
			if(tranX+obj.width>x1){//右边超界了
				x=-obj.width;
			}
			
			if(tranY+obj.height>y0){//下边超界了
				y=-obj.height;
			}
			
			ctx.translate(x,y);
			if(obj.thin){
				ctx.translate(0.5,0.5);
			}
			ctx.fillStyle='#FAFAFA';
			ctx.rect(0,0,obj.width,obj.height);
			ctx.fill();
		  	ctx.stroke();
		  	
		  	ctx.beginPath();
		  	ctx.font='13px san-serif';
		  	ctx.fillStyle='black';
			ctx.fillText(obj.type,10,obj.height/2);
			ctx.fillText(obj.value,obj.width/2,obj.height/2);
			
		  	ctx.restore();
		}
	  	
	  	return this;
	}
	Toast.prototype.setTimeout=function(t){
		this.timeOut=t;
		return this;
	}

渲染一个坐标和网格线,以及小格子分段线,都可以用构造函数Line,new Line 的方式,然后再把实例对象push到数组中,如下:

var line = new Line(this,{
				x:x0,
				y:y0,
			 	startX:x2,
			 	startY:0,
			 	endX:x2,
			 	endY:out,
			 	thin:true
			 })
			 this.renderArr.push(line);

x,y是原点坐标;startX、startY线段起点xy坐标;endX、endY线段结束点坐标;thin是否以细线来绘制。

绘制文本用到Text构造函数,new Text的方式,然后再把实例对象push到数组中,如下:

 var text = new Text(this,{
				x:x0,
				y:y0,
			 	startX:x3,
			 	startY:out*3,
			 	text:data[n-1],
			 	font:'13px san-serif',
			 	textAlign:'center'
			 })
			 this.renderArr.push(text);

其他的构造函数也不说明了一样的原理

等元素都组装完以后,就可以循环这个数组,调用每个子元素的render方法就可以渲染了

		_.each(this.renderArr,function(item){
			item && item.render();
		});

这当中有个要注意的,就是设置的动画函数,比如:

			c = new Circle(this,{
					x:axis.x0+pos.x,
					y:axis.y0+pos.y,
					r:3,
					startAngle:0,
					endAngle:2*Math.PI,
					fill:true,
					fillStyle:'#FFFFFF',
					stroke:true,
					strokeStyle:'#C74541',
					lineWidth:1.2
				}).setTimeout(pos.t);
				
				//设置动画
				c.setAnimate({
					original:[function(){this.scaleX=1,this.scaleY=1}.bind(c),function(){this.removeRender()}.bind(c)],
					animate:[function(){this.scaleX=2,this.scaleY=2}.bind(c),
							 this.toast.bind(this,{
									x:x1/2,
									y:-y1/2,
									value:pos.value,
									type:pos.type,
									strokeStyle:'#C74541'
								 },c)
							]
				})

这里绘制了一个圆,并且给这个原设置了动画对象,动画对象包含两个元素:original、animate,是什么意思呢,就是为了让执行动画的时候会调用animate对应的值

从图中可以看到,当鼠标移入的时候,会有两个事件触发(1.小圆变大了;2.弹出了一个提示框),是怎么做的呢?

就是这里的设置,触发动画后,会执行下面两个函数

1.  function(){this.scaleX=2,this.scaleY=2} 把小圆放大2倍

2. 创建了一个toast提示框

  this.toast.bind(this,{
                                    x:x1/2,
                                    y:-y1/2,
                                    value:pos.value,
                                    type:pos.type,
                                    strokeStyle:'#C74541'
                                 },c)

同理,鼠标离开的时候(圆会恢复之前的状态,toast也会被移除)

源码下载,不要积分的哦 

点这里

给个支持吧,谢谢拉!

猜你喜欢

转载自blog.csdn.net/dkm123456/article/details/113607875