【带着canvas去流浪(4)】绘制散点图

栏目: Html5 · 发布时间: 5年前

内容简介:[TOC]使用原生

【带着canvas去流浪(4)】绘制散点图

[TOC]

一. 任务说明

使用原生 canvasAPI 绘制散点图。(截图以及数据来自于百度Echarts官方示例库 【查看示例链接】 )。

【带着canvas去流浪(4)】绘制散点图

二. 重点提示

学习过折线图的绘制后,如果数据点只有坐标数据,则通过基本的坐标转换在对应的点上绘制出散点并不难实现。而在气泡图中,当我们直接将百度 Echarts 示例中的数据拿来经过一定的线性缩小后作为半径直接绘制散点时,就会出现一些问题,数据集的范围跨度较大,导致大部分点呈现后都非常小,这个时候就需要使用某种方法从真实数据值映射到散点圆半径进行映射,来缩小它们之间的差异,否则一旦数据集中有一个偏离度较大的点,就会造成其他点所对应的散点半径都很大或者都很小,对数据呈现来说是不可取的。例如在下面的示例中,当使用几种不同的映射方法来处理数据后,可以看到绘制的散点图是不一样的。

//求散点半径时所使用的公式
//1.直接数值
r = value * 5 / 100000000;
//2.求对数
r = Math.log(value);
//3.求指数
r = Math.pow(value,0.4) / 100;

所绘制出的散点图如下所示:

【带着canvas去流浪(4)】绘制散点图

坐标映射 的实现思路其实并不算复杂, 它的概念可以参考算法的时间复杂度来进行理解 ,挑选一个增长更快的映射函数来区分相近的点,或者挑选一个增长更慢的映射函数来减小大跨度数据之间的差异,在数据可视化中是非常实用的技巧。本文示例中的效果是笔者自己手动调的,如果要实现根据数据集自动挑选适当的映射函数,还需要设计一些计算方法,感兴趣的读者可以自行研究。

三. 示例代码

气泡散点图绘制示例代码(坐标轴的绘制过程在前述博文中已经出现过很多次,故不再赘述,有需要的小伙伴可以直接翻看这个系列之前的博文或者查看本篇的demo):

/*数据点来自于百度Echarts官方示例库,每个数值分别表示[横坐标,纵坐标,数值,国家,年份]
*[28604,77,17096869,'Australia',1990]
*/

/**
 * 绘制数据
 */
function drawData(options) {
    let data = options.data;//获取数据集
    let xLength = (options.chartZone[2] - options.chartZone[0]);
    let yLength = (options.chartZone[3] - options.chartZone[1]);
    let gap = xLength / options.xAxisLabel.length;

    //遍历两个年份
    for(let i = 0; i < data.length ;i++){
        let x,y,r,c;
        context.fillStyle = options.colorPool[i];//从颜色池中选取颜色
        context.globalAlpha = 0.8;//为避免点覆盖,采取半透明绘制
        //遍历各个数据点
        for(let j = 0; j < data[i].length ; j++){
            //计算坐标
             x = options.chartZone[0] + xLength * data[i][j][0] / 70000;
             y = options.chartZone[3] - yLength * (data[i][j][4] - 55) / (85 - 55);

             //直接数值
             r = data[i][j][5] * 5 / 100000000;
             //求对数
             r = Math.log(data[i][j][6]);
             //开根号
             r = Math.pow(data[i][j][7],0.4) / 100;
             //绘制散点
             context.beginPath();
             context.arc(x, y , r , 0 , 2*Math.PI,false);
             context.fill();
             context.closePath();
        }
    }
}

浏览器中可查看效果:

【带着canvas去流浪(4)】绘制散点图

四.散点hover交互效果的实现

4.1 基本算法

在散点图上实现hover交互效果的基本算法如下:

  1. canvas 元素上监听鼠标移动事件,将鼠标坐标转换为canvas坐标系的坐标值。
  2. 遍历数据点查看是否存在当前鼠标点距离某个数据中心点的距离小于其散点的绘制半径,如果有则认为鼠标在该点之上。
  3. 利用之前缓存的该点绘图数据,调整绘图样式,增大数据点的绘图半径覆盖式绘图即可。
  4. 当鼠标距离任何数据点的距离都大于该点的绘图半径,或鼠标从一个hover数据点移动到另一个hover点时,均需要调用一次 resetHover( ) 方法清除之前的hover状态。
  5. 为了恢复hover前的状态,可以使用 【离屏canvas技术】 缓存首次绘图后的结果,然后使用 drawImage( ) 方法将对应区域恢复到hover前的状态。

4.2 参考代码

hover效果的关键代码如下,完整示例代码请在demo中获取,或访问 【我的github仓库】

/*简单hover效果*/
canvas.onmousemove = function (event) {
    //转换鼠标坐标为相对canvas
    let pos = {
        x: event.clientX - rect.left,
        y: event.clientY - rect.top
    }
    //获取当前hover点坐标
    let hoverPoint = checkHover(options, pos);
    /**
     * 如果当前有聚焦点
     */
    if (hoverPoint) {
        //如果当前点和上一次记录的hover点是不同的点,则先调一次reset方法,然后把hover点更改为当前的点
        let samePoint = options.hoverData === hoverPoint ? true : false; 
        if (!samePoint) {
            resetHover();
            options.hoverData = hoverPoint;
        }
        //绘制当前点的hover状态
        paintHover();
    } else{
        //第一次尝试手动恢复
        // resetHover();
        //使用离屏canvas恢复
        resetHoverWithOffScreen();
    }
} 


/*检测是否hover在散点之上*/
function checkHover(options,pos) {
    let data = options.paintingData;
    let found = false;
    for(let i = 0; i < data.length; i++){
        found = false;
        for(let j = 0; j < data[i].length; j++){
            if (Math.sqrt(Math.pow(pos.x - data[i][j].x , 2) + Math.pow(pos.y - data[i][j].y , 2)) < data[i][j].r) {
                found = data[i][j];
                break;
            }
        }
        if (found) break;
    }
    return found;
}

/*绘制hover状态*/
function paintHover() {
    let {x,y,r,c} = options.hoverData;
    let step = 0.5;
    context.globalAlpha = 1;
    context.fillStyle = c;
    //逐帧增加hover点的绘图半径,重新绘制hover状态的散点
    for(let i = 0 ; i < 30; i++){
       context.beginPath();
       context.arc(x,y,r + i * step, 0 , 2*Math.PI,false);
       context.fill();
       context.closePath();
    }
}

/*首次尝试的取消高亮状态的函数*/
function resetHover() {
    if (!options.hoverData) return;
    let {x,y,r,c} = options.hoverData;
    let step = 0.5;
    context.globalAlpha = 1;
    for(let i = 29; i>0; i--){
       context.save();
       //绘制外圆范围
       context.beginPath();
       context.arc(x,y,r + 30 * step, 0 , 2*Math.PI,false);
       context.closePath();
       //设置剪裁区域
       context.clip();
       //用全局背景色绘制剪裁区背景
       context.globalAlpha = 1;
       context.fillStyle = options.globalGradient;
       context.fill();
       //绘制内圆
       context.beginPath();
       context.arc(x,y,r + i * step, 0 , 2*Math.PI,false);
       context.closePath();
       context.fillStyle = c;
       context.globalAlpha = 0.8;
       //填充内圆
       context.fill();
       context.restore();
    }
    options.hoverData = null;
    console.log('清除hover效果');
}

//利用离屏canvas恢复hover前的状态
function  resetHoverWithOffScreen() {
    if (!options.hoverData) return;
    let {x,y,r,c} = options.hoverData;
    let step = 0.5;
    context.globalAlpha = 1;
    for(let i = 29; i>0; i--){
       context.save();
       //将hover状态下数据点圆所在的正方形范围恢复为hover前的状态
       context.drawImage(canvas2, x - r - 30 * step, y - r - 30 * step , 2 * (r + 30 * step),2*(r + 30 * step),x - r - 30 * step, y - r - 30 * step , 2*(r + 30 * step),2*(r + 30 * step));
       //绘制内圆
       context.beginPath();
       context.arc(x,y,r + i * step, 0 , 2*Math.PI,false);
       context.closePath();
       context.fillStyle = c;
       context.globalAlpha = 0.8;
       //填充内圆
       context.fill();
       context.restore();
    }
    options.hoverData = null;
    console.log('清除hover效果');
}

4.3 Demo中的小问题

context.clip( )

【带着canvas去流浪(4)】绘制散点图

所以最终采用离屏canvas的方法,将初次绘制后的数据点先暂存下来,然后在清除hover状态时,使用 context.drawImage( ) 方法将有关区域的数据复制粘贴过来,以替代原来的使用背景图填充该区域的做法,这样就可以在数据点之间有重叠时重现hover前的状态。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

浪潮式发售

浪潮式发售

[美] 杰夫.沃克(Jeff Walker) / 李文远 / 广东人民出版社 / 2016-3-1 / 39.80元

10天时间,4种发售路径, 让你的产品一上架就被秒杀 投资失败的个体户,怎样让长期积压的库存,变成众人抢购的稀缺品,最终敲开财富之门? 只有一腔热血的大学毕业生,怎样将原本无人问津的网球课程,发售成价值45万美元的专业教程? 长期脱离社会的全职主妇,如何白手起家,创造出自己的第一款爆品,并挽救即将破碎的家庭? 改变上述人士命运的是同一件法宝——产品发售方程式。互......一起来看看 《浪潮式发售》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器