继上一篇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. MySQL多表查询合并结果union all,内连接查询

    MySQL多表查询合并结果和内连接查询 1.使用union和union all合并两个查询结果:select 字段名 from tablename1 union select 字段名 from tab ...

  2. 关于编译GITHUB上的工程

    对于WINDOWS用户,很多人都不习惯使用cmake或makefile编译工程,对于GITHUB上的工程如何编译成熟悉的visual studio文件常常感到困难. 而且,GITHUB上的不少工程本身 ...

  3. Windows下使用python3 + selenium实现网页自动填表功能

    本文由博主(SunboyL)原创,转载请注明出处:https://www.cnblogs.com/SunboyL/p/11563345.html 因为工作原因,需要将xls文件的数据录入到网上.因为数 ...

  4. leetcode-mid-array-5. Longest Palindromic Substring

    mycode   12.51% class Solution(object): def longestPalindrome(self, s): """ :type s: ...

  5. Oracle redo与undo浅析

    http://liwenshui322.iteye.com/blog/1488949 一. 什么是redo(用于前滚数据) redo也就是重做日志文件(redo log file),Oracle维护着 ...

  6. 【C++进阶:STL常见性质2】

    一般STL函数接收迭代器参数的规则为:[it1, it2) 左闭右开区间: vector<int> scores; scores.erase(scores.begin(),scores.e ...

  7. Jmeter之事物控制器

    在我们需要统计一组取样器的统计数据,可以将这一组取样器放置在事物控制器下,进行统计. 一.界面显示 二.配置说明 1.名称:标识 2.注释:备注 3.Generate parent sample: 不 ...

  8. SpringBoot错误经验

    1.在application.properties 添加 debug=true,可以看见项目的执行流程有助于调bug 2.如果错误显示端口号被占用 cmd 步骤1 查看端口号应用情况:netstat ...

  9. ios平台appstore不支持网页内嵌app解决方案

    苹果一直拒绝 UIWebView 内嵌 HTML5 页面的 iPhone.iPad APP应用上架到 App Store,建议这样的APP去做成Safari的Web应用.但是,苹果的审核人员只从界面. ...

  10. Angular5 import interface 报错:XXX is not a module

    在项目里定义了一个interface,device.ts.然后在component.ts中要使用这个interface,import之后,VSCode报错:‘xxx/xxx/xxx/device.ts ...