繼上一篇HTML5 Canvas(實戰:繪製餅圖)以後,筆者研究了一下如何給餅圖加鼠標停留時顯示的提示框。git
在開始Coding以前,筆者可以想到的最easy的方式,就是給餅圖的每個區域添加mousemove事件,鼠標在其上移動時則顯示對應的提示框,so easy!可事實不是這樣子滴~github
咱們肉眼上看上去是一塊一塊的東西,canvas並無真的把它們分紅一塊一塊的HTMLElement,咱們只能給canvas綁定事件。那麼如何得知鼠標當前停留在哪塊區域呢,能夠經過計算鼠標位置與圓心連線與基準線給的夾角是否在區域的起始角度與終止角度之間,爲此,咱們須要保存每一個區域的角度信息。
爲了方便保存,建立一個構造函數Plot。canvas
function Plot(start, end, color, data) { this.start = start; this.end = end; this.color = color; this.data = data; }
能夠將上一篇文章中的繪製圖例方法和繪製餅圖區域的方法都放進Plot的原型鏈中segmentfault
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(); }
在上一篇文章 HTML5 Canvas(實戰:繪製餅圖) 能夠看出,在咱們的最初設計中,Tooltip上顯示的內容是能夠定製化的,用戶能夠設定一個以下的模板:函數
Year: {{year}}, Data: {{data}}
咱們的目標是將上面的模板轉化成:工具
Year: 2017, Data: 3000
新建一個工具方法,接受template
字符串,以及鼠標當前停留plot
中的數據,返回實際顯示的字符串:this
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; }
注意,從代碼中能夠看出,不要習慣性的在{{
和}}
之間加入空格。spa
爲了判斷鼠標停留的區域,咱們須要完成以下兩步:prototype
angle
plots
,判斷angle
是否位於某一個plot
的startAngle
與endAngle
之間,若是找到了這個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; }
成品圖: