继上一篇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. C++用参数返回结果与用返回值返回结果的思考

    /*** *xvkBuffer作为参数比写为返回值的好处是: *1,xvkBuffer可以是堆变量或栈变量,若写为返回值则只能是堆上申请,因为必须保证它的永久性 *2,xvkBuffer或作为栈变量返 ...

  2. loj#6157 A ^ B Problem

    分析 用并查集维护 每次一个连通块的每个点记录它到当前连通块的根的异或值 对于不符合的情况容易判断 最后判断是否都在一个连通块内然后记录答案即可 代码 #include<bits/stdc++. ...

  3. Netty 系列之 Netty 高性能之道 高性能的三个主题 Netty使得开发者能够轻松地接受大量打开的套接字 Java 序列化

    Netty系列之Netty高性能之道 https://www.infoq.cn/article/netty-high-performance 李林锋 2014 年 5 月 29 日 话题:性能调优语言 ...

  4. 小程序框架MpVue踩坑日记(二)

    数据嵌套超过三层或者等于三层的时候 父组件传值给子组件后,如果子组件内的值需要改变 通过this.emit()传值后,父组件的值虽然会改变,但是视图并不会重新渲染 原因就是数据嵌套太多,没有触发ren ...

  5. history历史记录在AJAX下出现异常跳转 [解决]

    事情是这样的,在一个历史记录指针应该在[1, 2, 3, 4]的[3]位置的情况下,出现了历史记录指针指向了[4]的情况,而且是在正常后退事件发生之后,(据我所知)没有代码操作的情况发生的. 这是一个 ...

  6. 006-unity3d GUI初识、贴图、自定义鼠标指针

    一.gui概念 无论摄像机拍摄到的图像怎么变换,GUI永远显示在屏幕上,不受变形.碰撞.光照的影响.对话框.战斗值.能量等.示例:用手机录像,摄像的参数不会随着拍摄场景变换.GUI基础GUI部分是每帧 ...

  7. Linux-档案权限概念

    第一欄代表這個檔案的類型與權限(permission): 這個地方最需要注意了!仔細看的話,你應該可以發現這一欄其實共有十個字元:(圖5.2.1及圖5.2.2內的權限並無關係) 圖5.2.2.檔案的類 ...

  8. mysql5.7插入数据报错 Incorrect integer value

    mysql5.7插入字符串为空的时候取出来的值设置为null

  9. 医院里的CR、DR、CT、磁共振、B超都是什么?

    转自 百度知道MR CT CR DR DSA X线 都事医学影像疾病诊断的一种. MRI 是磁共振影像检查,可以获得横断面,矢状面和冠状面的影像.空间分辩率好. CT 是一种X线诊断设备,是一种复杂的 ...

  10. html—倒计时demo

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