看了echarts的实例,感觉蛮溜的,但是源码看得我伤神,干脆自己写一个,就花了点时间自己整了个线性图,发出来大家看看,我个人觉得代码比框架大佬的更容易理解,更容易看懂,哈哈,有点自恋,是框架大佬的太高深了吧,看的我头痛。
首先来分析一下,这个图
从图中,我们可以大概将这个线性图分解成多个元素
- xy坐标
- text文本
- 线段line
- 圆 Circle
- 提示框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)