Wechart 餅圖

預覽

Pie

衆所周知 Cax 既能開發遊戲、又能開發圖表。本文將從餅圖開始 Wechart 的圖表之旅。
Wechart 徹底基於 Group 體系構建(自定義 Element) ,易維護,可擴展,任何場景可插拔使用。javascript

快速開始

建立餅圖實例:css

const pie = new Pie([
    { name: 'WeChat', value: 10 },
    { name: 'Canvas', value: 15 },
    { name: 'Cax', value: 23 },
    { name: 'Tencent', value: 7 },
    { name: 'Wepay', value: 22 }
], {
        processing: (item) => { 
            return item.value 
        },
        x: 200,
        y: 200,
        r: 160,
        circleColor: 'white',
        textOffsetY: -12,
        font: '20px Arial',
        color: (index) => {
            return ['#4BC0C0', '#FF6485', '#FFA07A', '#ADACB9', '#A37AC1'][index]
        },
        label: (item) => {
            return item.name
        },
        tooltip: (item) => {
            return item.name + '<br/>' + item.value
        }
    }
)

上面各項配置項很清晰明瞭,不作解釋,開發者可自行修改參數看餅圖的變化,下面把餅圖添加到舞臺:java

const stage = new cax.Stage(640, 400, 'body')
stage.add(pie)
stage.update()

stage 是最大的容器,經過 add 方法往裏面加對象,而後 update 舞臺就能顯示。git

顯示和隱藏餅圖:github

pie.show()
pie.hide()

實現原理

看到上面的 DEMO 能夠會有幾方面技術須要講解:canvas

  • Pie 對象和 Group 的關係
  • Cax 扇形繪製
  • 展開和收縮動畫實現
  • 文字和文字走線顯示在對應扇形的中間
  • 顯示兼容 PC 和 Mobile
  • 交互兼容 PC 和 Mobile
  • 漸變和點擊彈出和移除收縮實現
  • Tooltip 實現

Pie 對象和 Group 的關係

先看 cax 內置的 Group 對象, Group 用於分組, group 也能夠嵌套 group,父容器的屬性會疊加在子屬性上, 好比:ide

  • group 的 x 是 100, group 裏的 bitmap 的 x 是 200, 最後 bitmap 渲染到 stage 上的 x 是 300
  • group 的 alpha 是 0.7, group 裏的 bitmap 的 alpha 是 0.6, 最後 bitmap 渲染到 stage 上的 alpha 是 0.42
const group = new cax.Group()
const rect = new cax.Rect(100, 100 {
  fillStyle: 'black'
})
group.add(rect)
stage.add(group)
stage.update()

Pie 對象正是自定義 Element,繼承自 Group:動畫

class Pie extends Group {
  constructor (data, option) {
    super()

通常狀況下,稍微複雜組合體都建議使用繼承自 Group,這樣利於擴展也方便管理自身內部的元件。
能夠看到小遊戲的 DEMO 裏的 Player、Bullet、Enemy、Background 全都是繼承自 Group。3d

扇形繪製

Cax 內置 Graphics,可使用連綴 Canvas API 的方式繪製圖形:code

const sector = new cax.Graphics()
sector
    .beginPath()
    .moveTo(0, 0)
    .arc(0, 0, 30, 0, Math.PI/2)
    .closePath()
    .fillStyle('green')
    .fill()
    .strokeStyle('red')
    .lineWidth(2)
    .stroke()

stage.add(sector)

這裏假設你已經建立好了舞臺。效果以下:

sector.png

因此一個餅圖就是把圓分紅若干個扇形。怎麼分? arc 方法傳入動態數據:

let current = 0

data.forEach((item, index) => {
    const sector = new cax.Graphics()
    sector
        .beginPath()
        .moveTo(0, 0)
        .arc(0, 0, 30, current, current += Math.PI * 2 * item.value / totalValue)
        .closePath()
        .fillStyle('green')
        .fill()
        .strokeStyle('red')
        .lineWidth(2)
        .stroke()
})

其中 totalValue 爲全部 item.value 的和。能夠看到上面是平分一個圓。那麼怎麼平分一個扇形?能運動平分的角度嗎?

展開和收縮動畫實現

看這行代碼:

.arc(0, 0, 30, current, current += Math.PI * 2 * item.value / totalValue)

把 Math.PI * 2 改爲 totalAngle 動態變量就能夠!

let totalAngle = 0
...
...
.arc(0, 0, 30, current, current += totalAngle * item.value / totalValue)

運動 totalAngle 而且進行重繪:

cax.To.get(option)
      .to({ totalAngle: Math.PI * 2 }, option.duration, option.easing)
      .progress((object) => {
        current = option.begin
        sectorGroup.forEach((item, index) => {
          item
            .clear()
            .beginPath()
            .moveTo(0, 0)
            .arc(0, 0, r, current, current += object.totalAngle * option.processing(item) / totalValue)
            .closePath()
            .fillStyle(option.color(index))
            .fill()
            .strokeStyle(option.circleColor)
            .lineWidth(2)
            .stroke()
            .closePath()
        })
      })
    ...
    ...
    ...

使用 cax 內置的 to2to 運動能力。這裏須要提醒的是,progress 方法會不斷地執行,爲了防止 sector 的 graphics path 不斷疊加,在循環執行的代碼裏必定要調用 clear 來清除 graphics 的之前的 Canvas 繪製命令。

文字和文字走線

文字和走線分四種狀況:

if (angle >= 0 && angle < Math.PI / 2) {
        
} else if (angle >= Math.PI / 2 && angle < Math.PI) {
    
} else if (angle >= Math.PI && angle < Math.PI + Math.PI / 2) {
    
} else 

}

須要注意的是:

  • 落在左邊的文字的 x 座標須要減去文件的寬度。 Cax 內置的 Text 可使用 getWidth() 方法獲取到文字的寬度
  • 走線的第一根線角度也分兩種狀況,一、3象限平行,二、4象限平行,走線的第二根先角度都是平行於 y 軸(如上圖所示,相同顏色圈中的線是平行的)

顯示與交互兼容 PC 和 Mobile

從 javascript 裏會發現 canvas 的寬高是 640*400:

const stage = new cax.Stage(640, 400, 'body')

就和咱們平時使用兩倍圖同樣,在移動端經過 media 把 canvas 變成一半寬度:

@media screen and (max-width: 500px) {  
        canvas {
            width : 320px
        }
}

這個時候會出現一個問題!由於 cax 會把 canvas 上的事件過分給 cax 內置對象,事件發生的座標由於 canvas 寬高的變化而變化了, 移動端點擊事件觸發位置不許確了!這個時候須要 scaleEventPoint 方法來校訂座標:

if (window.innerWidth <= 500) {
    stage.scaleEventPoint(0.5, 0.5)
}

搞定!這樣無論是在 PC 鼠標仍是移動 Mobile 觸摸都能精準觸發事件。

漸變和點擊彈出和移除收縮實現

function fadeIn(obj) {
  obj.alpha = 0
  To.get(obj).to({ alpha: 1 }, 600).start()
}

function fadeOut(obj) {
  obj.alpha = 1
  To.get(obj).to({ alpha: 0 }, 600).start()
}

function bounceIn(obj, from, to) {
  from = from || 0
  obj.from = from
  To.get(obj).to({ scaleX: to || 1, scaleY: to || 1 }, 300, cax.easing.bounceOut).start()
}

function bounceOut(obj, from, to) {
  from = from || 1
  obj.from = from
  To.get(obj).to({ scaleX: to || 0, scaleY: to || 0 }, 300, cax.easing.bounceOut).start()
}

基於 cax 內置的 to2to 動畫引擎封裝了四個方法。

Tooltip 實現

sector.hover(function (evt) {
    bounceIn(sector, 1, 1.1)
    tooltip.style.left = (evt.pureEvent.pageX + 5) + 'px'
    tooltip.style.top = (evt.pureEvent.pageY + 5) + 'px'
    tooltip.innerHTML = option.tooltip(data[index])
    tooltip.style.display = 'block'
}, function (evt) {
    bounceOut(sector, 1.1, 1)
    tooltip.style.display = 'none'
}, function (evt) {
    tooltip.style.left = (evt.pureEvent.pageX + 5) + 'px'
    tooltip.style.top = (evt.pureEvent.pageY + 5) + 'px'
})

Cax 內置對象擁有 hover(over, out, move) 方法來監聽鼠標或者手指 over、out 和 move。

Tooltip 也是徹底基於 DOM 來實現的,這樣能夠浮在 Canvas 外面,而不會限制在 Canvas 裏面。

Star && Follower

誰在使用?

Tencent Wechat Tencent QQ

License

MIT

相關文章
相關標籤/搜索