引子
最近做公司的数据展示项目,用的核心插件是Echarts,但在雷达图的展示案列上,需求上出现了需要单轴轮播标签和数据,在看完github上的issue后,这个Echarts3不支持,看了一下源码,似乎有点复杂,改了改,只实现了多个series的轮播,和需求还是有差距,周末反正无聊,何不自己动手撸一个。
整理一下思路
想实现如上图这样一个建议雷达图,需要几步?个人总结,需要:
初始化一个canvas对象;
以画布中心,绘制三个同心圆;
根据输入的点数,计算3根轴在外径圆上的6个坐标点,并以中心点,绘制6根轴线;
根据输入的标签,集合前面6个坐标点,完成6个标签在画布的绘制;
根据输入的数据,结合前面的6个坐标,计算得输入数据的坐标点,连线完成输入数据展示;
要实现轮播,主要解决的就是根据输入数据坐标点计算此点在Dom元素的位置,然后轮播显示;
如果想触发echarts那种hover tooltip的效果,你只需要添加一个mousemove事件,获取位置,并计算其相对应的坐标点。
好像要达到的效果就完成了,似乎不难,来吧,实现它。
分布实现
初始化一块画布
首先引入一个canvas函数,并为其制定长高,然后获取这个元素,并得到一块平面画布,代码如下:
const draw = document.getElementById('canvas');const ctx = draw.getContext("2d");
为了后面更方便的计算,我们需要把坐标原点从画布的右上角移到画布的中心,所以我们首先获取画布的长宽,接着使用translate方法,转移坐标原点
const offset = {x:draw.offsetWidth,y:draw.offsetHeight};ctx.translate(offset.x/2,offset.y/2);
这样我们就初始化了一块画布,并将其坐标原点移到了画布的中心.
这里提示两点:
添加canvas为其指定宽高时,需要用width和height属性直接指定,而不能用style的宽高指定其宽高,如果是样式指定,画出来的线或圆是模糊且变形的;
虽然上面变换了坐标原点,但是canvas坐标系默认是向下,向右为正,所以我们改变了坐标原点,但这个特点并未改变,所以像(0,120)这个点是位于坐标原点正下方的,而非通常我们认知的正上方;
画同心圆
在echarts中,圆的个数可通过splitNum配置,其实这个实现也不难,可通过下面的代码实现:
const m &#61; Math; const PI &#61; m.PI;//这两个参数后面经常会用&#xff0c;这里提前简单申明/*输入参数*ctx 画布* radius 要画的最外层圆的半径* spiltNum 要画的圆的个数* */function drawArc(ctx,radius,splitNum){ctx.beginPath(); //开始画路径/*这个可通过stokeColor与fillColor设置圆边的颜色和圆的填充色*/const splitStep &#61;radius/splitNum;for(let i&#61;1;i<&#61;splitNum;i&#43;&#43;){ //按splitNum个数&#xff0c;计算并画出相应的圆ctx.moveTo(i*splitStep,0); //这一句很重要&#xff0c;你需要手动移动你的画笔&#xff0c;而不是任其在画布上连续移动&#xff0c;画出不应该出现的线&#xff1b;ctx.arc(0,0,i*splitStep,0,2*PI,false);}ctx.stroke();}drawArc(ctx,raduis,3);
这一步就提示一点&#xff1a;
ctx.beginPath()与ctx.stroke();总是成对成对出现&#xff0c;缺一个&#xff0c;这线必须出不来。
计算极坐标线与外侧圆的交点
首先回忆一个高中数学知识&#xff0c;已知一个圆的大小&#xff0c;其一条线与O度角直径成x度角&#xff0c;求其这条线沿Y轴正方向与圆的交点&#xff0c;好难&#xff0c;有没有&#xff0c;久了不摸&#xff0c;知识瞬间回到幼儿园&#xff0c;答案是&#xff1a;&#xff08;r*sin(x),r.cos(x)&#xff09;&#xff0c;答案怎么这么简单&#xff0c;不信&#xff0c;不信你可以用三个和四个点验证&#xff0c;我会说我就干过这事嘛。好了&#xff0c;进入正题&#xff0c;直接上函数&#xff0c;顺便说一句&#xff0c;我们在计算这个点的同时&#xff0c;也可以把标签的坐标点也一起算出来&#xff0c;直接上代码&#xff1a;
点计算好了&#xff0c;接下来我们需要连接每个点到圆心得连线 提示&#xff1a;其实绘制这几根极坐标也可以采用坐标变换rotate API来执行&#xff0c;比如这样 这样也很简单&#xff0c;但为了后面&#xff0c;我们需要知道这些极坐标也圆的交点&#xff0c;这在后面会很有用&#xff0c;但不得否认canvas的translate和retate是两个很好用的方法. canvas文字绘制&#xff0c;涉及到的属性和方法有font(设置字体样式),textAlign&#xff08;设置左右对齐&#xff09;,textBaseline&#xff08;设置基线&#xff0c;上下对齐&#xff09;,fillText&#xff08;文字绘制&#xff09;直接上代码&#xff1a; 提示&#xff1a;strokeText与fillText都可以为文本描边&#xff0c;但strokeText使用效果更好&#xff1b;另外使用fillText时&#xff0c;是使用fillStyle为文字设置颜色属性&#xff0c;而strokeText是采用strokeStyle设置文字颜色属性&#xff0c;很重要 其实这一步也相当简单&#xff0c;就是根据点计算在响应极坐标上的落点&#xff0c;然后一一连线&#xff0c;然后为封闭区域着色&#xff0c;直接上代码&#xff1a; Echarts怎么做的&#xff0c;Echarts可以配置拐点样式&#xff0c;那我们也为我们的雷达图加上拐点吧。 至此&#xff0c;我们基础的图形就画完了&#xff0c;现在我们要做最重要的一步&#xff0c;也就是轮播了。 还是直接上代码吧&#xff0c;下一篇文章再详说&#xff1a; 好了&#xff0c;至此我们就简单的实现了一个暂时没法定制的雷达轮播图&#xff0c;在下一篇文章我们将会根据这里提到的思想&#xff0c;开发一个与简易版的echarts radar插件。 本文系列&#xff1a; let l &#61; 6; //极坐标点的个数&#xff0c;也急速雷达的维度个数&#xff1b;const single &#61; 2*PI /l ;//计算偏转角度&#xff1b;let pointData&#61;[],labelData&#61;[];//声明两个数组&#xff0c;用于存储计算所得的坐标点&#xff1b;for(let i &#61; 0;i
/*输入画布&#xff0c;和要连接的点*/function drawLine(ctx,data){ctx.beginPath();ctx.strokeStyle &#61; &#39;blue&#39;;for(let i&#61; 0;i
rotate(single);
ctx.lingeTo(0,r);根据前面标签的计算点&#xff0c;绘制标签
let label&#61; [&#39;卫生&#39;,&#39;安全&#39;,&#39;交通&#39;,&#39;住宿&#39;,&#39;景点&#39;,&#39;吃88喝&#39;];
for(let i &#61; 0;i
}
function drawText(ctx,data,style) {const max &#61; data.length;ctx.font &#61; &#39;bold 14px Arial&#39;;ctx.textBaseline &#61;&#39;middle&#39;; //这个和textAlign都很重要&#xff0c;决定了整个图的美丑ctx.fillStyle &#61;&#39;red&#39;; //style.backgroundColorconsole.log(ctx);/*下面这些很重要&#xff0c;至于为什么&#xff0c;自己体会*/for(let i&#61;0;i
}
drawText(ctx,label);根据输入的点&#xff0c;绘制雷达闭合区域
let data &#61; [120,50,150,80,100,140];for(let i &#61; 0;i
也许拐点区域特殊处理&#xff0c;更有味道
如果你想用绘制一个线&#xff0c;并根据拐点圆的大小和切入角度&#xff0c;然后计算出这条线的长度&#xff0c;再绘制这个拐点&#xff0c;然后再接着画线&#xff0c;这样真的很难&#xff0c;有没有简单点的&#xff0c;有&#xff0c;就是投机取巧&#xff0c;绘完线后再绘制拐点&#xff0c;并将这个拐点圆填充&#xff0c;这样看起来就像是我们连着画的啦&#xff0c;但这样也有bug&#xff0c;如果我们没有填充颜色&#xff0c;或者填充的颜色有透明度&#xff0c;那么拐点下两条线相交就可见了&#xff0c;我们的连续绘制假象也就被自己拆穿了。还是上代码把&#xff1a; function drawPoint(ctx,data) {const max &#61; data.length;const r&#61; 3;ctx.fillStyle&#61;&#39;white&#39;; //rgba(10,50,200,0.5)ctx.beginPath();for(let i&#61;0;i
做一个简单的自动轮播
let step &#61;-1;function removeLabel(dom) {(dom.querySelectorAll(&#39;label&#39;).length)&&(dom.removeChild(dom.querySelector(&#39;label&#39;)));}function autoLabel(point){removeLabel();let label &#61;document.createElement(&#39;label&#39;);label.innerHTML &#61;&#39;show:0999&#39;; //这里先写成定值&#xff0c;后面会详述怎样定制轮播值label.style.position&#61;&#39;absolute&#39;;label.style.top&#61;point[1]&#43;offset.y/2 &#43;&#39;px&#39;;label.style.left&#61;point[0]&#43;offset.x/2&#43;&#39;px&#39; ;label.style.border&#61;&#39;1px solid yellowgreen&#39;;label.style.background &#61; &#39;gray&#39;;label.style.opacity &#61; &#39;0.5&#39;label.style.zIndex &#61; 999;dom.appendChild(label);} setInterval(function(){step &#61; (step&#43;1)%6;autoLabel(draw,pointData[step]);},1000)
如果发现有任何叙述不正确之处或有更好的想法&#xff0c;还请指正。
源码地址
从0开始撸一个支持单轴轮播的雷达图&#xff08;Ehcarts的单轴显示问题&#xff09;之中篇
从0开始撸一个支持单轴轮播的雷达图&#xff08;Ehcarts的单轴显示问题&#xff09;之末篇
本文首发于&#xff1a;http://closertb.site &#xff0c;转载请注明