HTML5 Canvas(實(shí)戰(zhàn):繪制餅圖2 Tooltip)

繼上一篇HTML5 Canvas(實(shí)戰(zhàn):繪制餅圖)之后,筆者研究了一下如何給餅圖加鼠標(biāo)停留時(shí)顯示的提示框。

事件綁定

在開始Coding之前怨愤,筆者能夠想到的最easy的方式,就是給餅圖的每一個(gè)區(qū)域添加mousemove事件孝赫,鼠標(biāo)在其上移動(dòng)時(shí)則顯示對應(yīng)的提示框,so easy!可事實(shí)不是這樣子滴~

我們?nèi)庋凵峡瓷先ナ且粔K一塊的東西,canvas并沒有真的把它們分成一塊一塊的HTMLElement黎做,我們只能給canvas綁定事件。那么如何得知鼠標(biāo)當(dāng)前停留在哪塊區(qū)域呢松忍,可以通過計(jì)算鼠標(biāo)位置與圓心連線與基準(zhǔn)線給的夾角是否在區(qū)域的起始角度與終止角度之間蒸殿,為此,我們需要保存每個(gè)區(qū)域的角度信息挽铁。
為了方便保存,創(chuàng)建一個(gè)構(gòu)造函數(shù)Plot敞掘。

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

可以將上一篇文章中的繪制圖例方法和繪制餅圖區(qū)域的方法都放進(jìn)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(實(shí)戰(zhàn):繪制餅圖) 可以看出叽掘,在我們的最初設(shè)計(jì)中,Tooltip上顯示的內(nèi)容是可以定制化的玖雁,用戶可以設(shè)定一個(gè)如下的模板:

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

我們的目標(biāo)是將上面的模板轉(zhuǎn)化成:

Year: 2017, Data: 3000

新建一個(gè)工具方法更扁,接受template字符串,以及鼠標(biāo)當(dāng)前停留plot中的數(shù)據(jù)赫冬,返回實(shí)際顯示的字符串:

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;
}

注意浓镜,從代碼中可以看出,不要習(xí)慣性的在{{}}之間加入空格劲厌。

鼠標(biāo)在哪

為了判斷鼠標(biāo)停留的區(qū)域膛薛,我們需要完成如下兩步:

  1. 計(jì)算鼠標(biāo)位置和圓心之間的弧度angle
  2. 遍歷plots,判斷angle是否位于某一個(gè)plotstartAngleendAngle之間补鼻,如果找到了這個(gè)plot哄啄,判斷這個(gè)plot是否是上一次的鼠標(biāo)所在的區(qū)域,如果是风范,說明沒有必要繪制Tooltip咨跌,如果不是,重繪圖表硼婿。假如沒有找到對應(yīng)的區(qū)域锌半,說明鼠標(biāo)不在canvas的餅圖區(qū)域,可能指向圖例寇漫、標(biāo)題或者空白區(qū)域刊殉,此時(shí)應(yīng)該清空全局變量currentPlot并重繪畫布殉摔。
    關(guān)于如何判斷鼠標(biāo)位置與圓心之間的弧度,小編畫了如下的一個(gè)餅圖冗澈,只能幫到這兒了...
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();
  }

現(xiàn)在我們知道了鼠標(biāo)當(dāng)前停留的位置钦勘,也可以定制要提示的文字,現(xiàn)在可以繪制提示框啦亚亲,以下代碼有些累贅彻采,計(jì)算過程也有些問題,筆者改天再重新算算~

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時(shí)都需要重繪餅圖捌归,而startAngle endAngle在每次繪制時(shí)都會(huì)修改肛响,因此繪制前需要重置。

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/canvas2

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末惜索,一起剝皮案震驚了整個(gè)濱河市特笋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌巾兆,老刑警劉巖猎物,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異角塑,居然都是意外死亡蔫磨,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門圃伶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來堤如,“玉大人,你說我怎么就攤上這事窒朋〔蟀眨” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵侥猩,是天一觀的道長榔至。 經(jīng)常有香客問我,道長欺劳,這世上最難降的妖魔是什么洛退? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮杰标,結(jié)果婚禮上兵怯,老公的妹妹穿的比我還像新娘。我一直安慰自己腔剂,他們只是感情好媒区,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般袜漩。 火紅的嫁衣襯著肌膚如雪绪爸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天宙攻,我揣著相機(jī)與錄音奠货,去河邊找鬼。 笑死座掘,一個(gè)胖子當(dāng)著我的面吹牛递惋,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播溢陪,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萍虽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了形真?” 一聲冷哼從身側(cè)響起杉编,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咆霜,沒想到半個(gè)月后邓馒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蛾坯,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年光酣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片偿衰。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挂疆,死狀恐怖改览,靈堂內(nèi)的尸體忽然破棺而出下翎,到底是詐尸還是另有隱情,我是刑警寧澤宝当,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布视事,位于F島的核電站,受9級(jí)特大地震影響庆揩,放射性物質(zhì)發(fā)生泄漏俐东。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一订晌、第九天 我趴在偏房一處隱蔽的房頂上張望虏辫。 院中可真熱鬧,春花似錦锈拨、人聲如沸砌庄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娄昆。三九已至佩微,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萌焰,已是汗流浹背哺眯。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留扒俯,地道東北人奶卓。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像陵珍,于是被迫代替她去往敵國和親寝杖。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 有了canvas之后互纯,我們可以很容易地創(chuàng)建一個(gè)簡單圖標(biāo)瑟幕,不需要任何插件,不過留潦,有的小伙伴覺得它很難只盹,筆者仔細(xì)思考一...
    Sue1024閱讀 6,308評(píng)論 1 3
  • 一:canvas簡介 1.1什么是canvas? ①:canvas是HTML5提供的一種新標(biāo)簽 ②:HTML5 ...
    GreenHand1閱讀 4,681評(píng)論 2 32
  • 一兔院、canvas簡介 1.1 什么是canvas殖卑?(了解) 是HTML5提供的一種新標(biāo)簽 Canvas是一個(gè)矩形區(qū)...
    Looog閱讀 3,942評(píng)論 3 40
  • 一、canvas簡介 1.1 什么是canvas坊萝?(了解) 是HTML5提供的一種新標(biāo)簽 Canvas是一個(gè)矩形區(qū)...
    J_L_L閱讀 1,516評(píng)論 0 4
  • 桃花在宴里盛開,留下一地嫣紅惦积,我在風(fēng)雨中飄零接校,內(nèi)心依然堅(jiān)定——趙曉旭 打開知乎、天涯狮崩、百度蛛勉,幾乎到處都是關(guān)于安全感...
    旭公子的半滿人生閱讀 937評(píng)論 0 0