热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

从0开始撸一个支持单轴轮播的雷达图(Ehcarts的单轴显示问题)之头篇

引子最近做公司的数据展示项目,用的核心插件是Echarts,但在雷达图的展示案列上,需求上出现了需要单轴轮播标签和数据,在看

引子

最近做公司的数据展示项目,用的核心插件是Echarts,但在雷达图的展示案列上,需求上出现了需要单轴轮播标签和数据,在看完github上的issue后,这个Echarts3不支持,看了一下源码,似乎有点复杂,改了改,只实现了多个series的轮播,和需求还是有差距,周末反正无聊,何不自己动手撸一个。

整理一下思路

clipboard.png

想实现如上图这样一个建议雷达图,需要几步?个人总结,需要:
初始化一个canvas对象;

  1. 以画布中心,绘制三个同心圆;

  2. 根据输入的点数,计算3根轴在外径圆上的6个坐标点,并以中心点,绘制6根轴线;

  3. 根据输入的标签,集合前面6个坐标点,完成6个标签在画布的绘制;

  4. 根据输入的数据,结合前面的6个坐标,计算得输入数据的坐标点,连线完成输入数据展示;

  5. 要实现轮播,主要解决的就是根据输入数据坐标点计算此点在Dom元素的位置,然后轮播显示;

  6. 如果想触发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);

clipboard.png

这一步就提示一点&#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;

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;接下来我们需要连接每个点到圆心得连线

/*输入画布&#xff0c;和要连接的点*/function drawLine(ctx,data){ctx.beginPath();ctx.strokeStyle &#61; &#39;blue&#39;;for(let i&#61; 0;i

clipboard.png

提示&#xff1a;其实绘制这几根极坐标也可以采用坐标变换rotate API来执行&#xff0c;比如这样

rotate(single);
ctx.lingeTo(0,r);

这样也很简单&#xff0c;但为了后面&#xff0c;我们需要知道这些极坐标也圆的交点&#xff0c;这在后面会很有用&#xff0c;但不得否认canvas的translate和retate是两个很好用的方法.

根据前面标签的计算点&#xff0c;绘制标签

canvas文字绘制&#xff0c;涉及到的属性和方法有font(设置字体样式),textAlign&#xff08;设置左右对齐&#xff09;,textBaseline&#xff08;设置基线&#xff0c;上下对齐&#xff09;,fillText&#xff08;文字绘制&#xff09;直接上代码&#xff1a;

let label&#61; [&#39;卫生&#39;,&#39;安全&#39;,&#39;交通&#39;,&#39;住宿&#39;,&#39;景点&#39;,&#39;吃88喝&#39;];
for(let i &#61; 0;ilabel[i] &#61; {label:label[i],position:labelData[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&#61;-1)&&(x<&#61;1)){ //由于single值的问题&#xff0c;所以很多时候没有x&#61;0的出现&#xff0c;都是0.000001这种浮点数出现&#xff1b;ctx.textAlign &#61;&#39;center&#39;;}else if(x>1){ctx.textAlign &#61;&#39;left&#39;;}else{ctx.textAlign &#61;&#39;right&#39;;}ctx.fillText(data[i].label,...data[i].position);}
}
drawText(ctx,label);

clipboard.png

提示&#xff1a;strokeText与fillText都可以为文本描边&#xff0c;但strokeText使用效果更好&#xff1b;另外使用fillText时&#xff0c;是使用fillStyle为文字设置颜色属性&#xff0c;而strokeText是采用strokeStyle设置文字颜色属性&#xff0c;很重要

根据输入的点&#xff0c;绘制雷达闭合区域

其实这一步也相当简单&#xff0c;就是根据点计算在响应极坐标上的落点&#xff0c;然后一一连线&#xff0c;然后为封闭区域着色&#xff0c;直接上代码&#xff1a;

let data &#61; [120,50,150,80,100,140];for(let i &#61; 0;i

clipboard.png

也许拐点区域特殊处理&#xff0c;更有味道

Echarts怎么做的&#xff0c;Echarts可以配置拐点样式&#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

clipboard.png

至此&#xff0c;我们基础的图形就画完了&#xff0c;现在我们要做最重要的一步&#xff0c;也就是轮播了。

做一个简单的自动轮播

还是直接上代码吧&#xff0c;下一篇文章再详说&#xff1a;

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)

clipboard.png

好了&#xff0c;至此我们就简单的实现了一个暂时没法定制的雷达轮播图&#xff0c;在下一篇文章我们将会根据这里提到的思想&#xff0c;开发一个与简易版的echarts radar插件。
如果发现有任何叙述不正确之处或有更好的想法&#xff0c;还请指正。
源码地址

本文系列&#xff1a;
从0开始撸一个支持单轴轮播的雷达图&#xff08;Ehcarts的单轴显示问题&#xff09;之中篇
从0开始撸一个支持单轴轮播的雷达图&#xff08;Ehcarts的单轴显示问题&#xff09;之末篇
本文首发于&#xff1a;http://closertb.site &#xff0c;转载请注明



推荐阅读
  • IOS Run loop详解
    为什么80%的码农都做不了架构师?转自http:blog.csdn.netztp800201articledetails9240913感谢作者分享Objecti ... [详细]
  • Ihavetwomethodsofgeneratingmdistinctrandomnumbersintherange[0..n-1]我有两种方法在范围[0.n-1]中生 ... [详细]
  • 本文介绍如何使用线段树解决洛谷 P1531 我讨厌它问题,重点在于单点更新和区间查询最大值。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • WinMain 函数详解及示例
    本文详细介绍了 WinMain 函数的参数及其用途,并提供了一个具体的示例代码来解析 WinMain 函数的实现。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 在分析Android的Audio系统时,我们对mpAudioPolicy->get_input进行了详细探讨,发现其背后涉及的机制相当复杂。本文将详细介绍这一过程及其背后的实现细节。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 解决问题:1、批量读取点云las数据2、点云数据读与写出3、csf滤波分类参考:https:github.comsuyunzzzCSF论文题目ÿ ... [详细]
  • 本文提出了一种基于栈结构的高效四则运算表达式求值方法。该方法能够处理包含加、减、乘、除运算符以及十进制整数和小括号的算术表达式。通过定义和实现栈的基本操作,如入栈、出栈和判空等,算法能够准确地解析并计算输入的表达式,最终输出其计算结果。此方法不仅提高了计算效率,还增强了对复杂表达式的处理能力。 ... [详细]
  • 在C++程序中,文档A的每一行包含一个结构体数据,其中某些字段可能包含不同数量的数字。需要将这些结构体数据逐行读取并存储到向量中,随后不仅在控制台上显示,还要输出到新创建的文档B中。希望得到指导,感谢! ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • 本文将继续探讨 JavaScript 函数式编程的高级技巧及其实际应用。通过一个具体的寻路算法示例,我们将深入分析如何利用函数式编程的思想解决复杂问题。示例中,节点之间的连线代表路径,连线上的数字表示两点间的距离。我们将详细讲解如何通过递归和高阶函数等技术实现高效的寻路算法。 ... [详细]
  • 2018 HDU 多校联合第五场 G题:Glad You Game(线段树优化解法)
    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6356在《Glad You Game》中,Steve 面临一个复杂的区间操作问题。该题可以通过线段树进行高效优化。具体来说,线段树能够快速处理区间更新和查询操作,从而大大提高了算法的效率。本文详细介绍了线段树的构建和维护方法,并给出了具体的代码实现,帮助读者更好地理解和应用这一数据结构。 ... [详细]
  • 在 Vue 应用开发中,页面状态管理和跨页面数据传递是常见需求。本文将详细介绍 Vue Router 提供的两种有效方式,帮助开发者高效地实现页面间的数据交互与状态同步,同时分享一些最佳实践和注意事项。 ... [详细]
author-avatar
henrysong
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有