继上一篇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. glm初试,关于行矩阵列矩阵问题

    /*** * glm中矩阵是行优先存储的,这不同于opengl默认的以列优先存储的方式??,以下面矩阵mat为例 * 它是用四个行向量来模拟存储四个行:vec4 value[4],其中 * value ...

  2. 最近使用的两个工具 winscp和xshell

    最近在编译MONO源码时用到了这两个工具,感觉挺好,记录备忘 WINSCP 长这样 用于可视化的查看和操作远程服务器上的文件 xshell用于命令行下操作远程服务器,服务器的同学好多在用这个.命令行是 ...

  3. C# 防火墙操作之创建规则

    对于某些程序,我们只允许它使用某些特定端口.网络类型或者特定IP类型等信息.这时候,需要使用到防火墙里面的“高级设置”,创建某些特定的入站或者出栈规则,以规避其程序使用允许端口等意外的信息. 下面以创 ...

  4. 后台管理模板ACE

    要做系统,界面展示效果较简单,1个开发人员,无美工,无前端! 以前一直用bootstrap来做界面.这次也走的老路!同样适用的bootstrap界面.做完之后,功能上,我还是比较满意的,兼容IE8+, ...

  5. [转载]Parsing X.509 Certificates with OpenSSL and C

    Parsing X.509 Certificates with OpenSSL and C Zakir Durumeric | October 13, 2013 While OpenSSL has b ...

  6. VGA时序及其原理(转载)

    显示器扫描方式分为逐行扫描和隔行扫描:逐行扫描是扫描从屏幕左上角一点开始,从左像右逐点扫描,每扫描完一行,电子束回到屏幕的左边下一行的起始位置,在这期间,CRT对电子束进行消隐,每行结束时,用行同步信 ...

  7. 【MM系列】SAP MM模块-组织结构介绍

    公众号:SAP Technical 本文作者:matinal 原文出处:http://www.cnblogs.com/SAPmatinal/ 原文链接:[MM系列]SAP MM模块-组织结构介绍   ...

  8. unity编辑器Hierarchy添加图标

    效果 素材 using UnityEditor; using UnityEngine; using System.Collections.Generic; [InitializeOnLoad] cla ...

  9. idea的热部署

    1:先找到你要热部署的tomcat之后 ,在设置tomcat时  先选择 server,里面有On 'Update' action ()  和 On frame deactivation 这两项  都 ...

  10. 介绍一款代理端口管理工具--Proxfier

    官网下载地址: https://www.proxifier.com/download/ProxifierSetup.exe 用户名随意填注册码下边 5EZ8G-C3WL5-B56YG-SCXM9-6Q ...