基于canvas使用贝塞尔曲线平滑拟合折线段的方法


Posted in HTML / CSS onJanuary 10, 2018

写在最前

本次分享一下在canvas中将绘制出来的折线段的棱角“磨平”,也就是通过贝塞尔曲线穿过各个描点来代替原有的折线图。

为什么要平滑拟合折线段

先来看下Echarts下折线图的渲染效果:

基于canvas使用贝塞尔曲线平滑拟合折线段的方法 

一开始我没注意到其实这个折线段是曲线穿过去的,只认为是单纯的描点绘图,所以起初我实现的“简(丑)易(陋)”版本是这样的:

基于canvas使用贝塞尔曲线平滑拟合折线段的方法

不要关注样式,重点就是实现之后才发现看起来人家Echarts的实现描点非常的圆滑,也由此引发了之后的探讨。怎么有规律的画平滑曲线?

效果图

先来看下最终模仿的实现:

因为我也不知道Echarts内部怎么实现的(逃

基于canvas使用贝塞尔曲线平滑拟合折线段的方法 

基于canvas使用贝塞尔曲线平滑拟合折线段的方法 

看起来已经非常圆润了,和我们最初的设想十分接近了。再看下曲线是否穿过了描点:

基于canvas使用贝塞尔曲线平滑拟合折线段的方法 

好的!结果很明显现在来重新看下我们的实现方式。

实现过程

  1. 绘制折线图
  2. 贝塞尔曲线平滑拟合

模拟数据

var data = [Math.random() * 300];
        for (var i = 1; i < 50; i++) { //按照echarts
            data.push(Math.round((Math.random() - 0.5) * 20 + data[i - 1]));
        }
        option = {
            canvas:{
                id: 'canvas'
            },
            series: {
                name: '模拟数据',
                itemStyle: {
                    color: 'rgb(255, 70, 131)'
                },
                areaStyle: {
                    color: 'rgb(255, 158, 68)'
                },
                data: data
            }
        };

绘制折线图

首先初始化一个构造函数来放置需要用到的数据:

function LinearGradient(option) {
    this.canvas = document.getElementById(option.canvas.id)
    this.ctx = this.canvas.getContext('2d')
    this.width = this.canvas.width
    this.height = this.canvas.height
    this.tooltip = option.tooltip
    this.title = option.text
    this.series = option.series //存放模拟数据
}

绘制折线图:

LinearGradient.prototype.draw1 = function() { //折线参考线
    ... 
    //要考虑到canvas中的原点是左上角,
    //所以下面要做一些换算,
    //diff为x,y轴被数据最大值和最小值的取值范围所平分的等份。
    this.series.data.forEach(function(item, index) {
        var x = diffX * index,
            y = Math.floor(self.height - diffY * (item - dataMin))
        self.ctx.lineTo(x, y) //绘制各个数据点
    })
    ...
}

贝塞尔曲线平滑拟合

贝塞尔曲线的关键点在于控制点的选择,这个网站可以动态的展现控制点不同而绘制的不同的曲线。而对于控制点的计算。。作者还是选择了百度一下毕竟数学不好:)。具体算法有兴趣的同学可以深入了解下,现在直接说下计算控制点的结论。

基于canvas使用贝塞尔曲线平滑拟合折线段的方法

上面的公式涉及到四个坐标点,当前点,前一个点以及后两个点,而当坐标值为下图展示的时候绘制出来的曲线如下所示:

基于canvas使用贝塞尔曲线平滑拟合折线段的方法

不过会有一个问题就是起始点和最后一个点不能用这个公式,不过那篇文章也给出了边界值的处理办法:

基于canvas使用贝塞尔曲线平滑拟合折线段的方法 

所以在将折线换成平滑曲线的时候,将边界值以及其他控制点计算好之后代入到贝塞尔函数中就完成了:

//核心实现
this.series.data.forEach(function(item, index) { //找到前一个点到下一个点中间的控制点
    var scale = 0.1 //分别对于ab控制点的一个正数,可以分别自行调整
    var last1X = diffX * (index - 1),
        last1Y = Math.floor(self.height - diffY * (self.series.data[index - 1] - dataMin)),
        //前一个点坐标
        last2X = diffX * (index - 2),
        last2Y = Math.floor(self.height - diffY * (self.series.data[index - 2] - dataMin)),
        //前两个点坐标
        nowX = diffX * (index),
        nowY = Math.floor(self.height - diffY * (self.series.data[index] - dataMin)),
        //当期点坐标
        nextX = diffX * (index + 1),
        nextY = Math.floor(self.height - diffY * (self.series.data[index + 1] - dataMin)),
        //下一个点坐标
        cAx = last1X + (nowX - last2X) * scale,
        cAy = last1Y + (nowY - last2Y) * scale,
        cBx = nowX - (nextX - last1X) * scale,
        cBy = nowY - (nextY - last1Y) * scale 
    if(index === 0) {
        self.ctx.lineTo(nowX, nowY)
        return
    } else if(index ===1) {
        cAx = last1X + (nowX - 0) * scale
        cAy = last1Y + (nowY - self.height) * scale 
    } else if(index === self.series.data.length - 1) {
        cBx = nowX - (nowX - last1X) * scale
        cBy = nowY - (nowY - last1Y) * scale
    } 
        self.ctx.bezierCurveTo(cAx, cAy, cBx, cBy, nowX, nowY);
        //绘制出上一个点到当前点的贝塞尔曲线
    })

由于我每次遍历的点都是当前点,但是文章中给出的公式是计算会知道下一个点的控制点算法,故在代码实现中我将所有点的计算挪前了一位。当index = 0时也就是初始点是不需要曲线绘制的,因为我们绘制的是从前一个点到当前点的曲线,没有到0的曲线需要绘制。从index = 1开始我们就可以正常开始绘制,从0到1的曲线,由于index = 1时是没有在他前面第二个点的故其属于边界值点,也就是需要特殊进行计算,以及最后一个点。其余均按照正常公式算出AB的xy坐标代入贝塞尔函数即可。

最后

源代码见这里

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

HTML / CSS 相关文章推荐
CSS3 media queries + jQuery实现响应式导航
Sep 30 HTML / CSS
CSS3 :nth-child()伪类选择器实现奇偶行显示不同样式
Nov 05 HTML / CSS
css3使用animation属性实现炫酷效果(推荐)
Feb 04 HTML / CSS
HTML5 语音搜索只需一句代码
Jan 03 HTML / CSS
html5的websockets全双工通信详解学习示例
Feb 26 HTML / CSS
html5小技巧之通过document.head获取head元素
Jun 04 HTML / CSS
一款html5 canvas实现的图片玻璃碎片特效
Sep 11 HTML / CSS
基于MUI框架使用HTML5实现的二维码扫描功能
Mar 01 HTML / CSS
HTML5实现自带进度条和滑块滑杆效果
Apr 17 HTML / CSS
详解使用postMessage解决iframe跨域通信问题
Nov 01 HTML / CSS
Html5 webRTC简单实现视频调用的示例代码
Sep 23 HTML / CSS
关于flex 上下文中自动 margin的问题(完整例子)
May 20 HTML / CSS
canvas实现高阶贝塞尔曲线(N阶贝塞尔曲线生成器)
Jan 10 #HTML / CSS
H5混合开发app如何升级的方法
Jan 10 #HTML / CSS
浅谈关于html5中图片抛物线运动的一些心得
Jan 09 #HTML / CSS
详解快速开发基于 HTML5 网络拓扑图应用
Jan 08 #HTML / CSS
浅谈HTML5 Web Worker的使用
Jan 05 #HTML / CSS
详解基于canvas的视频遮罩插件
Jan 04 #HTML / CSS
详解HTML5中的picture元素响应式处理图片
Jan 03 #HTML / CSS
You might like
解决中英文字符串长度问题函数
2007/01/16 PHP
php扩展ZF――Validate扩展
2008/01/10 PHP
[原创]解决wincache不支持64位PHP5.5/5.6的问题(提供64位wincache下载)
2016/06/22 PHP
PHP里面把16进制的图片数据显示在html的img标签上(实现方法)
2017/05/02 PHP
php实现文章评论系统
2019/02/18 PHP
JavaScript弹簧振子超简洁版 完全符合能量守恒,胡克定理
2009/10/25 Javascript
myFocus slide3D v1.1.0 使用方法与下载
2011/01/12 Javascript
jQuery实现拖动调整表格单元格大小的代码实例
2015/01/13 Javascript
JS实现控制表格单元格垂直对齐的方法
2015/03/30 Javascript
7个有用的jQuery代码片段分享
2015/05/19 Javascript
jQuery实现的指纹扫描效果实例(附演示与demo源码下载)
2016/01/26 Javascript
基于JQuery的$.ajax方法进行异步请求导致页面闪烁的解决办法
2016/05/10 Javascript
js实现功能比较全面的全选和多选
2017/03/02 Javascript
浅谈angular2路由预加载策略
2017/10/04 Javascript
jquery ajax异步提交表单数据的方法
2017/10/27 jQuery
js屏蔽退格键(backspace或者叫后退键与F5)
2019/02/10 Javascript
JavaScript数组、json对象、eval()函数用法实例分析
2019/02/21 Javascript
Webpack4+Babel7+ES6兼容IE8的实现
2019/04/10 Javascript
Vue中img的src是动态渲染时不显示的解决
2019/11/14 Javascript
[03:13]DOTA2-DPC中国联赛1月25日Recap集锦
2021/03/11 DOTA
Python使用Srapy框架爬虫模拟登陆并抓取知乎内容
2016/07/02 Python
Python3.6 Schedule模块定时任务(实例讲解)
2017/11/09 Python
Python如何快速上手? 快速掌握一门新语言的方法
2017/11/14 Python
Java与Python两大幸存者谁更胜一筹呢
2018/04/12 Python
django DRF图片路径问题的解决方法
2018/09/10 Python
python 将大文件切分为多个小文件的实例
2019/01/14 Python
浅谈Python爬虫原理与数据抓取
2020/07/21 Python
python实现模拟器爬取抖音评论数据的示例代码
2021/01/06 Python
HTML5仿微信聊天界面、微信朋友圈实例代码
2018/01/29 HTML / CSS
HTML5 WebSocket实现点对点聊天的示例代码
2018/01/31 HTML / CSS
美国最大的宠物药店:1-800-PetMeds
2016/10/02 全球购物
欧洲最大的滑雪假期供应商之一:Sunweb Holidays
2018/01/06 全球购物
初中班级口号
2014/06/09 职场文书
2014班子“三严三实”对照检查材料思想汇报
2014/09/18 职场文书
实验室安全管理制度
2015/08/05 职场文书
85句关于理想的名言警句大全
2019/08/22 职场文书