You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
drawLineWithDiscrete(start,end,n=5){// 由于 x 轴上的 y 都是相同的constpoints=[]conststartX=start.xconstendX=end.xpoints.push(start)constsegmentValue=(endX-startX)/nfor(leti=1;i<=n-1;i++){points.push(newPoint2d(startX+i*segmentValue,start.y))}points.push(end)// 生成线段points.forEach((point)=>{this.drawLine(point,point.clone().add(newPoint2d(0,5)))})}
// 生成文字this.clearFillColor()textPoints.forEach((info)=>{const{ text, point }=infothis.xPoints.push(point.x)this.ctx.fillText(text,point.x,point.y,20)})
前言
终于又到周末了,上一周的一篇3d文章 带你入门three.js——从0到1实现一个3d可视化地图很开心😺收到了这么多小伙伴的喜欢,这是对我知识输出的肯定。再次感谢大家!这周我又来了,这次给大家分享一下可视化图表比较简单的图表📈但同时我们又不得不学会的 那就是————折线图。读完本篇文章你可以学到什么
直线折线图
我们先去非常有名的Echarts 官网看一看,他的折线图是什么样子的?如图:
从图中可以得到以下2d图形元素:
好像仔细分析一下也没什么嘛,其实就是画直线和加文字。OK, 问下自己canvas如何画直线?是不是有一个ctx.LineTo的方法,但是他画出来的是直线没有端点的所以呢? 我们以此基础进行封装,并且直线的端点的图形可控, 同时还有文字位于直线的位置是不是可以画出这样的图形呢? 我们接下来进行实操环节。
画布的创建
第一步我们肯定是进行画布的创建,这里没什么好讲的。这里我在html 新建一个canvas, 我新建了一个类叫lineChart 直接上代码:
上面代码没什么好讲的,然后我在为canvas 画布设置背景色。代码如下:
canvas绘图操作复习
其实折线图,本质上就是一个画直线,只不过在原有画直线的能力上,给他做一些增强。我用一个画三角形的例子: 带你熟悉一下画线操作。
先看下api:
直线一般是由两个点组成的,该方法有两个参数:x以及y ,代表坐标系中直线结束的点。开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,等等。。。开始点也可以通过
moveTo()
函数改变。moveTo 是什么就在画布中移动笔触, 也就是你开始画的第一个点,或者你可以想象一下在纸上作业,一支钢笔或者铅笔的笔尖从一个点到另一个点的移动过程。
介绍完毕, 开始实战环节:
我们先移动一个点, 然后再画条直线, 然后再画条直线。 如果写到你认为结束了,你就错了
你还差一个很重要的一步就是画布描边或者是填充, 我刚开始学也会忘记这个。
这里給大家整理下canvas 的整个画图流程
也就是我们刚才所做的一切只是在准备路径,所以我们需要描边或者填充来渲染图形, 我们来看下这两个api。
我们把填充加上去: 看下效果:
我们看下描边效果:
你会发现为什么没有闭合?,代码是这样的:
这就说明了一个重要问题就是什么呢?
既然发现了问题,我们就需要解决问题,那么canvas 如何闭合路径呢??
代码如下:
这时候效果图已经出来了:
有closePath? 难道没有开始路径? 答案是当然有的:
这里会问这个有什么作用呢?
首先 生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。
closePath 其实也不是必须的,如果图形已经是闭合的,就不需要调用, 到这里canvas的基本绘图操作复习就到这里,后面还有一些实战api : 我就例子中给大家讲解, 不然会显得很生硬。
封装画直线方法
再次之前,我把canvas中每一个点的位置都用一个point2d 点去表示并且写了一些方法,我在之前的文章都有仔细讲过这里我就不展开说了: 3千字长文canvas实现任意正多边形的移动(点、线、面) 这一篇文章。 这里我就直接放上代码:
分别对应的是一些静态方法、叉乘、 两个点之间求距离哇等等。
我们先在画布上画一条基础的直线, 我们先用random, 在画布上重新生成两个点,然后画出一条随机的直线, 代码如下:
js实现直线方程
第一步: 实现直线方程
我们先看下直线方程的几种表达方式:
一般式: Ax+By+C=0(A、B不同时为0)【适用于所有直线】
点斜式: y-y0=k(x-x0) 【适用于不垂直于x轴的直线】 表示斜率为k,且过(x0,y0)的直线
截距式:x/a+y/b=1【适用于不过原点或不垂直于x轴、y轴的直线】
两点式:表示过(x1,y1)和(x2,y2)的直线 【适用于不垂直于x轴、y轴的直线】 (x1≠x2,y1≠y2)
这里很明显我们适合第四种:已经知道直线的起始点和结束点可以求出直线方程。我给出以下代码:
p0、p1、 对应的两个直线点 t 就是参数,对应直线的x,我们求出y,返回新的点就好了 。我们默认以开始点和结束点的 x 位置分别 减去或者加一个固定的值 , 求得圆心。直接看下图吧:
这个图已经很明显了, 1和2 之间的距离就是半径, 所以我们只要求出点1 和点4 好像 就OK了, canvas 中是怎么画圆呢有一个arc 这个api :
画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。
圆肯定就是从0-360度, 代码如下:
准备工作都做好了, 我们就开始实现话带圆的直线吧。 画图的步骤就是
画开始圆和画结束圆其实可以封装成一个方法: 他们最主要的区别其实就是起始点的不同,代码如下:
这样我们就可以画圆了。先看下效果图:
到这里我们就已经结束了折线图的第一个部分, 紧接着进入第二部分:
画XY坐标轴
坐标轴本质上就是两条直线,所以第一步确定坐标原点,然后以坐标原点画出垂直和水平的两条直线。 我们设置坐标原点离画布的左内边距和底部内边距,这样我们可以通过画布的高度减去底部内边距得到 原点的y, 然后通过画布的宽度减去左内边距得到x, 有了坐标原点画坐标轴就没什么大问题了。代码如下:
这里要特别提示的是 首先整个画布的 坐标轴 是在整个屏幕的左上方, 但是我们显示的坐标原点是在 左下方, 然后 画Y轴的时候是由原点向上减去, 是向量点的减法。
效果图如下 :
但是和echarts 那个不太一样, 他的x轴是有线段的和文字的,接下来我们就开始改造 x轴。就是将X轴分几段嘛,
然后生成一个点的集合,这些点的y都是相同的, 然后 x是不相同的。代码如下:
这里要注意的就是循环的个数,因为起始点和终止点是有的。 看下效果图:
这时候还差文字,canvas 绘制文字的api
所以说白了还是去计算文字点的坐标,首先在项目初始化的定义X轴和Y轴的数据。代码如下:
文字我们统一放在线段的中点处其实只要计算每个分段数的长度然后在端点处+分段数长度的一半就可以得到。代码如下:
效果图如下:
但是看着图好像文字并没有处于居中的位置, 胖虎思考了🤔一下, 其实因为文字也有长度, 所以每一个文字的坐标要减去文字长度的一半值就对了。这时候this.ctx.fillText 的第三个参数就显得十分重要了, 限制文字的长度, 这样我们就可以处理了, 代码 重新修改下:
直接看效果图:
这下看一下就是完美。
X轴的处理好了,我们处理Y轴,Y轴其实相对比较简单就是每个数据对应的一条直线。
Y轴的话也是要计算每个线段的长度的值,然后画出直线, 这里要特别注意的是就是文字的放置, 在每个端点还要进行微调。使得文字和直线居中对齐。代码如下:
因为过程和X轴十分相似, 提醒一下描边 设置后,要将它恢复默认,不然会引用上一个颜色哦。
如图:
整个画布就差最后一步了, 生成折线图, 我们在上面已经封装了,带圆的直线, 所以只要找到所有的点去画折线图就好了。首先每个点的X坐标没什么问题对应的就是每个文字的中点, 主要是Y轴的坐标: 回忆一下之前我们是怎么去计算Y轴的坐标的是, 长度/ 除以分段数 去计算的。 这样就导致一个问题,出来的结果可能是一个小数,因为我们实际的数据 可能是223 这种这样导致画出来的图形点误差太大, 所以为了减少误差, 我换一个计算模式,就是进行等分,这样在区间里面的点都可以表达, 误差可以稍微小点, 其实在实际项目中, 容差问题是计算肯定存在的问题,js 本身就有0.1+0.2 这样的问题, 所以或者说在容差范围内我们可以认为这两个点是等价的 代码如下:
然后我这时候引入真实的数据:
分别对应的是真实的数据, xPoints是什么文字的中点坐标代码如下:
yPoints其实也就比较简单了, 真实数据 * 每一份的距离就好了。
数据准备好了,我们就开始调用方法去画折线图:
这段代码需要注意的是默认找一个开始点, 然后 不断地去更改开始点, 然后注意下标位置。
如图:
目前存在的问题:
到这里打大家可以这么去思考,为什么圆和直线要捆绑在一起? 单独画不就没有这样的问题了。说干就干,
这里注意会少一个开始圆,我们在开头的直接补上就好了, 圆的半径我都统一设置了。
如图:
至此到这里, 这折线图全部完成,为了做的更完美一点,我还是增加的提示和虚线。
显示tooltip
这里我看大多数图表都在鼠标移动的时候都会显示一个虚线和提示,不然我怎么清除的看数据对吧。 我们还是初始化一个div将它的样式设置为隐藏。
为canvas 增加监听事件:
其实我们要做的事情非常简单首先我们就是去比较鼠标的点 和 实际的点在某个范围内我就显示,类似于吸附, 从用户的角度不可能完全移动到那里才显示。
代码如下:
这里其实只要比较x的位置就好了,容差可以自定义设置。
画垂直的虚线
我看了很多图表他们都有垂直的虚线,这里就涉及到一个问题canvas 如何画虚线, 我在用canvas 实现矩形的移动(点、线、面)(1)这篇文章有介绍, 我就直接拿过来,不过多解释了,感兴趣的小伙伴可以看下这篇文章。 代码如下:
我们对onMouseMove 再一次进行改造:
增加了以下代码, 但是这样是有问题的,就是我们鼠标不停的移动, 所以上一次绘制的虚线不会取消。会出现下面这种情况:
所以我做了一个数据清除同时清除画布上的东西重新画:
整体代码如下:
restore和save的妙用
再给出一个小技巧**, 其实canvas 中 画图如果某次的样只想在某一个绘制中起作用:有save 和 restore方法
使用
save()
方法保存当前的状态,使用restore()
进行恢复成一开始的样子所以我们可以重新改写下画虚线的方法,在一开始的时候svae 一下, 然后结束在 restore , 有点像栈的感觉,先进去,然后画结束,弹出来。 每一项都有自己的独特的画图状态,不影响其他项。
至此整个折线图我想给大家讲解的已经结束了,我们看下效果吧:
最后
本篇文章算是canvas实现可视化图表的第一篇吧,后面我会持续分享、饼图、树状图、K线图等等各种可视化图表,我自
己在写文章的同时也在不断地思考,怎么去表达的更好。如果你对可视化感兴趣,点赞收藏关注👍吧!,可以关注我下
面的数据可视化专栏, 每周分享一篇 文章, 要么是2d、要么是three.js的。我会用心创作每一篇文章,绝不水文。
最后一句话: 大家和我一起做一个Api的创造者而不是调用者!
源码下载
本篇文章例子的所有代码都在我的github上,欢迎star☆😯! 如果你对图形感兴趣,可以关注我的公众号【前端图形】,领取可视化学习资料哦!!我们下期再见👋
The text was updated successfully, but these errors were encountered: