【帶着canvas去流浪(3)】繪製餅圖

圖片描述

示例代碼託管在: http://www.github.com/dashnowords/blogs

博客園地址:《大史住在大前端》原創博文目錄html

華爲雲社區地址:【你要的前端打怪升級指南】前端

[TOC]git

一. 任務說明

使用原生canvasAPI繪製餅圖(南丁格爾玫瑰)。(截圖以及數據來自於百度Echarts官方示例庫【查看示例連接】)。github

圖片描述

二. 重點提示

南丁格爾玫瑰圖的畫法有不少種,Echarts中提供的以半徑或面積兩種不一樣模式,本文中以面積比例畫法爲例,繪製算法以下:算法

  1. 肯定每一個扇區的角度。因爲全部扇區的角度加在一塊兒爲2π ,咱們先按照數據比例來計算角度:canvas

    $$ θ_i = \frac{data_i}{\sum_{i=0}^ndata_i}*2\pi $$數組

  2. 每一個扇區面積與總面積之間的比例即爲數值的比,將給定參數數組options.radius中的最大和最小數值做爲數值最大的一塊扇形的繪圖數據,代入以下公式便可求得總面積S

$$ \frac{\pi(R_{i}^2 - R_{min}^2)*θ_{i}/2\pi}{S} = \frac{data_i}{data_{sum}} $$瀏覽器

  1. 再利用上述公式分別計算出每一個扇形對應的外圓半徑,在canvas中繪製路徑並填充便可。

三. 示例代碼

南丁格爾玫瑰圖繪製示例代碼:echarts

//繪製餅圖
drawPieChart(options);
    
/**
 * 繪製餅圖
 * @param  {[type]} options [description]
 * @return {[type]}         [description]
 */
function drawPieChart(options) {
   //記錄最大數值以反求面積總和
   options.maxValue = 0;
   //求數據集總和以在後續計算每一個扇形的角度比例
   options.totalNum = options.data.reduce((pre,cur)=>{
     if (cur.value > options.maxValue) {
         options.maxValue = cur.value;
     }
     return pre+cur.value;
   },0);
    /*以最大值對應最大半徑來計算面積總和,並覆蓋原值
    *使得最大的一塊扇形外圓半徑爲options.radius[0]
    *內圓半徑爲options.radius[1]
    */
    let Rmin = options.radius[0];
    let Rmax = options.radius[1];
    let r = Math.sqrt((Rmax*Rmax - Rmin*Rmin)*options.totalNum / options.maxValue + Rmin*Rmin);
    options.radius[1] = r;
    //移動座標系原點至繪圖中心
   let paintingCenter={
     x:parseInt(options.center[0],10)/100 * (options.chartZone[2] - options.chartZone[0]) + options.chartZone[0],
     y:parseInt(options.center[1],10)/100 * (options.chartZone[3] - options.chartZone[1]) + options.chartZone[1]
   }
   context.translate(paintingCenter.x, paintingCenter.y);
    //繪製每一個扇形,過程當中累加旋轉角度
   let allAngle = options.data.reduce((prev,cur,index)=>{
       context.fillStyle = options.colorPool[index]
       let angle = calcPaintingData(cur,options);
       return prev + angle;
   },0);
   //繪製中空白色圓
   context.beginPath();
   context.fillStyle = 'white';
   context.arc(0,0,options.radius[0],0,2*Math.PI,false);
   context.fill();
}

/**
 * 計算每一個扇形所須要的繪圖參數
 */
function calcPaintingData(data,options) {
    let scale = data.value / options.totalNum; 
    let angle = scale * 2 * Math.PI;
    let Rmin = options.radius[0];
    let Rmax = options.radius[1];
    let r = Math.sqrt(scale * (Rmax*Rmax - Rmin*Rmin) + Rmin*Rmin);
    data.r = r;
    //繪製扇形
    paintFan({
        r:r,
        angle:angle,
        data:data,
        options:options
    });
    return angle;//將角度值返回給外層函數以供累加
}

//繪製扇形
function paintFan(opt) {
    context.beginPath();
    context.lineTo(opt.r,0);
    context.arc(0,0,opt.r,0,opt.angle,false);
    context.lineTo(0,0);
    context.closePath();
    context.fill();
    context.rotate(opt.angle);
}

瀏覽器中可查看效果:函數

圖片描述

四. hover高亮的實現思路

  1. 繪圖過程當中,將每一個扇區的繪圖數據(半徑,相對於圓心的起始轉角,扇區角度)均掛載在繪圖數據上。
  2. canvas標籤上監聽鼠標移動事件mousemove,並在回調函數中將鼠標移動事件event.clientXevent.clientY轉換爲相對於canvas座標的數值(mouseX,mouseY)
  3. 從圓心座標(paintingCenter.x,paintingCenter.y)(mouseX,mouseY)鏈接爲向量,根據該向量的角度和模便可判斷鼠標是否處於某個扇區之上。
  4. 若是處於扇區之上,則以過渡動畫來繪製關鍵幀使得hover效果表現出來。先修改context.fillStyle顏色爲對應扇區的高亮色,而後讓外圓繪圖半徑以線性的方式逐幀增長至目標大小(例如10%),每一幀中使用canvas繪圖上下文從新對繪圖區域進行封閉畫線,而後填充便可。
  5. hover效果出現時繪製高亮色的繪圖區域,hover效果消失時從外圓開始逐幀繪製白色外層扇區便可,最終再將數據扇區繪製爲原色。
相關文章
相關標籤/搜索