继上一篇HTML5 Canvas(实战:绘制饼图)之后,笔者研究了一下如何给饼图加鼠标停留时显示的提示框。

Plot对象

在开始Coding之前,笔者能够想到的最easy的方式,就是给饼图的每一个区域添加mousemove事件,鼠标在其上移动时则显示对应的提示框,so easy!可事实不是这样子滴~

我们肉眼上看上去是一块一块的东西,canvas并没有真的把它们分成一块一块的HTMLElement,我们只能给canvas绑定事件。那么如何得知鼠标当前停留在哪块区域呢,可以通过计算鼠标位置与圆心连线与基准线给的夹角是否在区域的起始角度与终止角度之间,为此,我们需要保存每个区域的角度信息。
为了方便保存,创建一个构造函数Plot。

function Plot(start, end, color, data) {
this.start = start;
this.end = end;
this.color = color;
this.data = data;
}

可以将上一篇文章中的绘制图例方法和绘制饼图区域的方法都放进Plot的原型链中


Plot.prototype.drawLegend = function() {
ctx.fillRect(legend_posX, legend_posY, legend_width, legend_height);
ctx.font = 'bold 12px Arial';
var percent = this.data.label + ' : ' + (this.data.portion * 100).toFixed(2) + '%';
ctx.fillText(percent, legend_textX, legend_textY);
}
Plot.prototype.drawPlot = function() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.moveTo(center.x, center.y);
ctx.arc(center.x, center.y, radius, this.start, this.end, false);
ctx.closePath();
ctx.fill();
}

定制的Tooltip

在上一篇文章 HTML5 Canvas(实战:绘制饼图) 可以看出,在我们的最初设计中,Tooltip上显示的内容是可以定制化的,用户可以设定一个如下的模板:

Year: {{year}}, Data: {{data}}

我们的目标是将上面的模板转化成:

Year: 2017, Data: 3000

新建一个工具方法,接受template字符串,以及鼠标当前停留plot中的数据,返回实际显示的字符串:

function replaceAttr(text, data) {
while (text.indexOf("{{") != -1) {
var start = text.indexOf("{{"),
end = text.indexOf("}}"),
attr = text.substring(start + 2, end);
text = text.replace("{{" + attr + "}}", data[attr]);
}
return text;
}

注意,从代码中可以看出,不要习惯性的在{{}}之间加入空格。

鼠标在哪

为了判断鼠标停留的区域,我们需要完成如下两步:

  1. 计算鼠标位置和圆心之间的弧度angle
  2. 遍历plots,判断angle是否位于某一个plotstartAngleendAngle之间,如果找到了这个plot,判断这个plot是否是上一次的鼠标所在的区域,如果是,说明没有必要绘制Tooltip,如果不是,重绘图表。假如没有找到对应的区域,说明鼠标不在canvas的饼图区域,可能指向图例、标题或者空白区域,此时应该清空全局变量currentPlot并重绘画布。

关于如何判断鼠标位置与圆心之间的弧度,小编画了如下的一个饼图,只能帮到这儿了...

function getAngle(cx, cy, mx, my) {
var x = Math.abs(cx - mx),
y = Math.abs(cy - my),
z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)),
cos = y / z,
radina = Math.acos(cos); if (mx > cx && my > cy) {
return radina;
} else if (mx < cx && my > cy) {
return Math.PI / 2 + radina;
} else if (mx > cx && my < cy) {
return 3 * Math.PI / 2 - radina
} else {
return 3 * Math.PI / 2 + radina
}
}
function onMouseMove(e) {
var ex = e.pageX - cv.offsetLeft,
ey = e.pageY - cv.offsetTop;
var angle = getAngle(center.x, center.y, ex, ey);
for (let i = 0; i < plots.length; i++) {
if (plots[i].start < angle && plots[i].end > angle) {
if (currentPlot != plots[i]) {
currentPlot = plots[i];
draw();
}
return;
}
}
currentPlot = null;
draw();
}

现在我们知道了鼠标当前停留的位置,也可以定制要提示的文字,现在可以绘制提示框啦,以下代码有些累赘,计算过程也有些问题,笔者改天再重新算算~

Plot.prototype.drawTooltip = function() {
var text = replaceAttr(op.tooltip.template, this.data);
var width_tooltipText = ctx.measureText(text).width,
height_tooltipText = parseInt(op.tooltip.font.size, 10),
angle = (this.start + this.end) / 2 / (2 * Math.PI) *360;
var tan = Math.tanh(angle),
x = 0,
y = 0; if (angle < 90)((x = radius / 2 * tan + center.x) || true) && ((y = -radius / 2 + center.y) || true)
else if (angle > 90 && angle < 180)((x = radius / 2 * tan + center.x) || true) && ((y = radius / 2 + center.y) || true)
else if (angle > 180 && angle < 270)((x = -radius / 2 * tan + center.x) || true) && ((y = radius / 2 + center.y) || true)
else if (angle > 270 && angle < 360)((x = -radius / 2 * tan + center.x) || true) && ((y = -radius / 2 + center.y) || true)
var tooltip_box_x = x - radius / 4,
tooltip_box_y = y,
tooltip_box_width = width_tooltipText + 10,
tooltip_box_height = height_tooltipText + 10,
tooltip_text_x = x - radius / 4 + 5,
tooltip_text_y = y + 10 + 2;
ctx.fillStyle = 'white';
ctx.fillRect(tooltip_box_x, tooltip_box_y, tooltip_box_width, tooltip_box_height);
ctx.fillStyle = '#000';
ctx.fillText(text, tooltip_text_x, tooltip_text_y);
}

每次重绘Tooltip时都需要重绘饼图,而startAngle endAngle在每次绘制时都会修改,因此绘制前需要重置。

function clear() {
ctx.clearRect(0, 0, cv.width, cv.height);
startAngle = 0;
endAngle = 0;
cv.onmousemove = null;
}

最终我们的draw方法~

function draw() {
clear();
title_text = op.title.text;
ctx.font = op.title.font.weight + " " + op.title.font.size + "px " + op.title.font.family;
title_width = ctx.measureText(title_text).width;
title_height = op.title.font.size;
title_position = {
x: (width, title_width) / 2,
y: 20 + title_height
};
ctx.fillText(title_text, title_position.x, title_position.y);
radius = (height - title_height - title_position.y - 20) / 2;
center = {
x: radius + 20,
y: radius + 30 + title_position.y
};
legend_width = op.legend.font.size * 2.5;
legend_height = op.legend.font.size * 1.2;
legend_posX = center.x * 2 + 20;
legend_posY = 80;
legend_textX = legend_posX + legend_width + 5;
legend_textY = legend_posY + op.legend.font.size * 0.9;
ctx.strokeStyle = 'grey';
ctx.lineWidth = 3;
ctx.strokeRect(0, 0, width, height); for (var i = 0, len = data_c.length; i < len; i++) {
endAngle += data_c[i].portion * 2 * Math.PI;
var plot = new Plot(startAngle, endAngle, data_c[i].color, data_c[i])
plots.push(plot);
plot.drawPlot();
startAngle = endAngle;
legend_posY += (10 + legend_height);
legend_textY += (10 + legend_height);
plot.drawLegend();
}
if (currentPlot) {
currentPlot.drawTooltip();
}
cv.onmousemove = onMouseMove;
}

成品图:

源码地址:https://github.com/Sue1024/ca...

HTML5 Canvas(实战:绘制饼图2 Tooltip)的更多相关文章

  1. HTML5 canvas标签绘制正三角形 鼠标按下点为中间点,鼠标抬起点为其中一个顶点

    用html5的canvas标签绘制圆.矩形比较容易,绘制三角形,坐标确定相当于前面两种难点,这里绘制的是正三角形,比较容易,我们只需要把鼠标刚按下去的点设置为三角形的中心点,鼠标抬起的点设置为三角形右 ...

  2. [js高手之路] html5 canvas教程 - 绘制七巧板

    七巧板长什么样? 用canvas把他画出来,其实就是把这7个区域的图形,每个点的坐标找出来,再用moveTo, lineTo连线,设置不同的颜色即可. <head> <meta ch ...

  3. HTML5 Canvas实战之刮奖效果

    近年来由于移动设备对HTML5的较好支持,经常有活动用刮奖的效果,最近也在看H5方面的内容,就自己实现了一个,现分享出来跟大家交流. 1.效果 2.原理 原理很简单,就是在刮奖区添加两个canvas, ...

  4. [HTML5 Canvas学习]绘制矩形

    1.使用strokeRect和fillRect方法绘制矩形 a.strokeRect是绘制一个不填充的矩形 b.fillRect是绘制一个填充的矩形 代码: <script> var ca ...

  5. HTML5 canvas图像绘制方法与像素操作属性和方法

    图像绘制方法 drawImage()        向画布上绘制图像.画布或视频 像素操作属性和方法 width                                返回 ImageData ...

  6. HTML5 Canvas中绘制椭圆的几种方法

    1.canvas自带的绘制椭圆的方法 ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)是后来 ...

  7. html5 canvas 鼠标绘制

    <!doctype html> <html> <head> <meta charset="utf-8"> <title> ...

  8. html5 canvas路径绘制2

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  9. HTML5 Canvas ( 图片绘制 转化为base64 ) drawImage,toDataURL

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

随机推荐

  1. php部署调优

    转自Laravel学院,  作者:学院君 最近刚好看到一些php.ini优化问题处理. 很多文章都是把配置全部翻译. (内容翻译太多和流程结构写的不是很清晰,看起来也头大.但是建议全部内容看几遍了解一 ...

  2. Linux中相关知识(atexit(),fork(),粘滞位)

    1.atexit()函数 函数名: atexit 头文件:#include<stdlib.h> 功 能: 注册终止函数(即main执行结束后调用的函数) 用 法: int atexit(v ...

  3. 利用 TensorFlow 入门 Word2Vec

    利用 TensorFlow 入门 Word2Vec 原创 2017-10-14 chen_h coderpai 博客地址:http://www.jianshu.com/p/4e16ae0aad25 或 ...

  4. 关于openGL、GPUImage、ios直播相关不错的博客

    http://www.jianshu.com/users/815d10a4bdce/latest_articles

  5. sql数据库收缩

    回收步骤: 1.查看日志文件大小[一般回收比较大的] --适用于RDS For SQL Server2012\2016 SELECT DB_NAME(database_id) AS [Database ...

  6. ssh连接MAC服务器显示No route to host解决方法

    首先MAC操作系统是10 其它电脑通过ssh访问一台mac服务器时,有时候可以登录进去,有显示显示no route to host 并且通过浏览器访问布署在mac上的jenkins,反应奇慢,后来做了 ...

  7. 2019 Nanchang Onsite

    D.Interesting Series F(n)实际上是一个等比数列的和,将它从递推式转变为通项公式(a^n-1)/(a-1),这里只需要确定n就可以. 题目要求选取k大小的所有子集的答案求和,可以 ...

  8. 从进度条和alert的出现顺序来了解浏览器 UI 渲染 & JS进程

    项目里有一个需求是在上传文件的时候需要显示进度条,那么理所当然的在上传完成后就需要提示用户上传完毕并且更新进度条. 之前的预期表现是,上传完毕后,先更新进度条到100%,再alert出提示,所以代码如 ...

  9. git总览

    git客户端官网:https://git-scm.com/ 下载对应版本安装 服务器安装git 安装依赖:yum install -y curl-devel expat-devel gettext-d ...

  10. Monte Carlo Policy Evaluation

    Model-Based and Model-Free In the previous several posts, we mainly talked about Model-Based Reinfor ...