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
getData(){letcurTime=Date.now()if(localStorage.getItem('aoyun')){let{ list, time }=JSON.parse(localStorage.getItem('aoyun'))console.log(curTime-time,'查看时间差')if(curTime-time<=24*60*60*60){this.data=list}else{this.fetchData()}}else{this.fetchData()}}fetchData(){fetch('http://localhost:3030/data').then((res)=>res.json()).then((res)=>{const{ errcode, list }=JSON.parse(res.body)if(errcode===100){alert('接口请求太频繁')}elseif(errcode===0){this.data=listconstobj={
list,time: Date.now(),}localStorage.setItem('aoyun',JSON.stringify(obj))}}).catch((err)=>{console.log(err)})}
drawXlabel(){constlength=this.data.slice(0,10).lengththis.ctx.textAlign='center'for(leti=0;i<length;i++){const{ country }=this.data[i]consttotalWidth=this.cWidth-20constxMarker=parseInt(this.originX+totalWidth*(i/length)+this.rectWidth)constyMarker=this.originY+15this.ctx.fillText(country,xMarker,yMarker,40)// 文字 }}
前言
2020东京奥运会已经开幕很多天了,还记得小时候看奥运会的是在2008年的北京奥运会,主题曲是北京欢迎你, 那个时候才上小学吧,几乎有中国队的每场必看,当时也是热血沸腾了, 时间转眼已经到了2021年而我也从小学生变成了一个每天不断敲代码的程序员👩💻,看奥运的时间又少,但是又想出分力,既然是程序员,想着能为奥运会搞点什么?第一时间想到了就是给奥运奖牌数🏅做可视化,因为单看表格数据,不能体现出我们中国的牛逼🐂, 废话不多说,直接开写。
数据获得
我们先看下奥运奖牌数的表格,这东西肯定是接口获得的吧,我不可能手写吧,而且每天都是更新的,难道我要每天去改,肯定不是这样的,我当时脑子里就想着去做爬虫,去用puppeteer 去模拟浏览器的行为然后获取页面的原生dom,然后将表格的数据搞出来, 然后我就很兴奋的去搞了,写了下面的代码:
然后当我很兴奋的想要去结果的时候,结果发现是空。百度是不是做了反爬虫协议, 毕竟我是爬虫菜鸟,搞了很久。还是没搞出来。如果有大佬会,欢迎指点我下哦!
不过这个puppeteer,这个库有点牛皮的,可以实现网页截图、生成pdf、拦截请求,其实有点自动化测试的感觉。感兴趣的同学可以自行了解一下,这不在本篇文章介绍的重点。
接口获得
然后这时候就开始疯狂百度,开始寻找有没有现成的api, 真是踏破铁鞋无觅处,得来全不费工夫。被我找到了,原来是有大佬已经开始做了, 这时候我本地直接去请求那个接口是有问题的,前端不得不处理的问题—— 跨域。 看着东西我头疼哇, 不过没关系, 我直接node起一个服务器, 我node去请求那个接口,然后后台在配置下跨域, 搞定接口数据就直接获得了, 后台服务我是用的express, 搭建的服务器直接随便搞搞的。代码如下:
这样我就是实现了接口转发,也搞定了跨域问题,前台我直接用 fetch去请求数据然后做一层数据转换,但是这个接口不能频繁请求,动不动就crash, 是真的烦, OK所以直接做了一个操作, 将数据 存到localstorage中,然后做一个定时刷新,时间大概是一天一刷。这样就保证数据的有效性。代码如下:
数据如下图所示 :
柱状图的表示
其实我想了很多表达中国金牌数的方式,最终我还是选择用2d柱状图去表示,并同时做了动画效果,显得每一快金牌🏅来的并不容易。我还是用原生手写柱状图不去使用Echarts 库, 我们首先先看下柱状图:
从图中可以分析出一些元素
画布初始化
在页面上创建canvas和获取canvas的一些属性,并对canvas绑上移动事件。代码如下:
画坐标轴
坐标轴本质上也是一个直线,直线对应的两个点,不同的直线其实就是对应的端点不同,所以我直接封装了一个画直线的方法:
可能有的人对canvas不熟悉,这里我还是大概说下, 开启一段路径, 移动画笔到开始的点, 然后画直线到末尾的点,然后描边 这一步是canvas做渲染, 很重要,很多小白不写, 直线就不出来, 然后闭合路径。 结束over!
画坐标轴我们首先先确定原点在哪里,我们首先给画布向内缩一个padding距离,然后呢,算出画布实际的宽度和高度。
代码如下:
有了原点我们就可以画X轴和Y轴了, 只要加上实际画布对应的宽度和高度 就好了 。 代码如下:
第一个 函数就是设置canvas画笔的样式的,其实这东西没什么。 我们看下效果:
很多人以为到这里就结束了哈哈哈, 那你想太多了, canvas我设置的画线宽度是1px 为什么看图片的线的宽度像是2px?不仔细观察根本发现不了这个问题, 所以我们要学会思考这到底是什么问题?其实这个问题也是我看Echarts源码发现的, 学而不思则罔,思而不学则殆哇!
彩蛋——canvas如何画出1PX的直线
在这里我举一个例子, 你就明白了, 假设我要画从(50,10) 到 (200,10)这样的一条直线。为了画这条线,浏览器首先到达初始起点(50,10)。这条线宽1px,所以两边各留0.5px。所以基本上初始起点是从(50,9.5)延伸到(50,10.5)。现在浏览器不能在屏幕上显示0.5像素——最小阈值是1像素。浏览器别无选择,只能将起点的边界延伸到屏幕上的实际像素边界。它会在两边再加0.5倍的“垃圾”。所以现在,最初的起点是从(50,9)扩展到(50,11),所以看起来有2px宽。情况如下:
现在你就应该明白了原来浏览器不能显示0.5像素哇, 四舍五入了, 知道了 问题我们就一定有解决方案
平移canvas
ctx.translate (x,y ) 这个方法:
translate()
方法, 将 canvas 按原始 x点的水平方向、原始的 y点垂直方向进行平移变换如图:
说的更直白点, 你对canvas做了translate变化后, 你之前所有画的点,都会相对偏移。 所以呢,回到我们这个问题上来, 解决办法就是什么呢?就我将画布 整体向下偏移 0.5 , 所以原本坐标 (50,10) 变成了(50.5,10.5) 和(200.5, 10.5)ok 然后浏览器的再去画的 他还是要预留像素, 所以就是从(50.5, 10) 到(50.5, 11) 这个区间去画OK, 就是1px了。我们来try it.
代码如下:
偏移完之后还是要恢复过去的, 还是要十分注意的。 我画了两张图作比对:
不多说了, 看到这里,如果觉得对你有帮助的话, 或者学到了话, 我是希望你给我点赞👍、评论、加收藏。
画标尺
我们现在只有X轴和Y轴, 光秃秃的,我给X轴和Y轴底部增加一些标尺,X轴对应的标尺,肯定就是每个国家的名字,大概的思路就是数据的数量去做一个分段, 然后去填充就好了。
代码如下:
这里的话我截取了排名前10的国家, 分断的思路, 首先两边留白20px, 我们首先先定义每一个柱状图的宽度 假设是 30 对应上文的 this.rectWidth, 然后每个文字的坐标 其实就很好算了, 起初的x + 所占的分端数 + 矩形宽度就可以画出来了
如图:
x轴画完了,我们开始画Y轴, Y轴的大概思路就是 以最多的奖牌数去做分段, 这里我就分成6段吧。
// 定义Y轴的分段数this.ySegments = 6//定义字体最大宽度this.fontMaxWidth = 40
接下啦我们就开始计算Y轴每个点的Y坐标, X坐标其实很好计算 只要原点坐标的X向左平移几个距离就好了,主要是计算Y轴的坐标, 这里一定要注意的是, 我们从坐标是相对于左上角的, 所以呢, Y轴的坐标应该是向上递减的。
最大的数据就是数组的第一个数据, 然后每个标尺就是所占的比例就好了, Y轴的坐标由于我们是递减的所以 对应的坐标应该是 1- 所占的份额, 由于这只是算的图标的实际高度 ,换算到画布里面, 还要加上原先我们设置的内边距,由于又加上了文字, 文字也占有一定像素, 所以有加上了20。 OK Y轴画结束了, 有了Y轴每个分断的坐标, 同时就画出背后的对应的几条实线。
代码如下:
最终呈现的效果图如下:
画矩形
everything isReady, 下面开始画矩形, 还是同样的方式 先封装画矩形的方法, 然后我们只要传入对应的数据就OK了。
这里用到了,canvas原生的rect 方法。参数理解如下:
矩形宽度 我们自定义的, 矩形的高度就是对应的奖牌数在画布中的高度, 所以我们只要确定 矩形的起点就搞定了, 这里矩形的(x,y) 其实是左上角的点。
代码如下:
第一步我们先做一个点的映射, 我们在画Y轴的时候,将Y轴的上的画布的所有的点都放在一个数组中, 注意记得将原点的Y放进去。所以只要计算出每个奖牌数在总部的比例是多少? 然后再用原点的Y值做一个相减就可以得到真正的Y轴坐标了。X轴的坐标就比较简单了,原点的X坐标加上 ( 所占的比例 / 总长度 ) 然后在加上 一半的矩形宽度就好了。 这个道理和画文字是一样的, 只不过文字要居中嘛。
代码如下:
画出的效果图如下:
矩形交互优化
黑秃秃的也丑了吧,一个不知道的人根本不知道这是哪一个国家获得多少快金牌。
现在画矩形的基础上加一些文字吧,代码如下:
渐变就设计到Canvas一个api了,createLinearGradient
那我就开始了首先肯定创建渐变:
然后呢我们就改造drawReact下 ,这里用了 restore 和save 这个方法, 防止污染文字的样式。
如图所示:
添加动画效果
光一个静态的不能看出我们的牛皮🐂,所以得有动画的效果慢慢的增加对吧。其实我们可以思考🤔下整个动画过程,变化的其实就两个, 柱状图的高度和文字, 其实坐标轴, 以及柱状图的x坐标是不变的, 所以我只要定义两个变量一个开始的值 ,和一个总共的值,高度和文字的大小 其实在每一帧去乘以对应的高度就可以了。
代码如下:
我们改造下drawBars 这个方法:
每一次都加一,直到比总数大, 然后不断重画。 就可以形成动画效果了。我们看下gif图吧:
总结
本篇文章写到这里也算结束了,我大概总结下:
本篇文章算是canvas实现可视化图表的第二篇吧,后面我会持续分享、饼图、树状图、K线图等等各种可视化图表,我自
己在写文章的同时也在不断地思考,怎么去表达的更好。如果你对可视化感兴趣,点赞收藏关注👍吧!,可以关注我下
面的数据可视化专栏, 每周分享一篇 文章, 要么是2d、要么是three.js的。我会用心创作每一篇文章,绝不水文。
我们一起为中国🇨🇳奥运加油! 奥利给!!!
源码获得
关注公众号【前端图形】, 回复【奥运】 两个字,就可以获得所有源码。
The text was updated successfully, but these errors were encountered: