我們的項目中有關於資料倉儲和挖掘,使用者要求UI的介面需要儀錶盤,我網上找了下,沒有發現免費的HTML儀錶盤,餅圖啥圖表的確很多,那就沒有辦法了,我和同事自己做了一個儀錶盤,結果如下。
之後我們就來討論下這個簡單的儀錶盤是怎麼做的。我們先大致有一個想法,設定一個寬高2:1的canvas,儀錶盤的半徑就是canvas的高度,儀錶盤需要的資料有上面分幾個地區(一般是低中高三個地區,為了測試我們準備了四個地區),刻度標記和文字,指標,和指標指向的值。
首先第一步自然是canvas的聲明
<body> <div> <canvas id="board" width="800" height="600"> </canvas> </div></body></html>
之後的工作都在javascript中完成,我們需要定義一些對象
//儀錶盤面板 var panel = function(id, option) { this.canvas = document.getElementById(id); //擷取canvas元素 this.cvs = this.canvas.getContext("2d"); //得到繪製上下文 this.width = this.canvas.width; //對象寬度 this.height = this.canvas.height; //對象高度 this.level = option.level; this.outsideStyle = option.outsideStyle; }
這個panel就是我們的儀錶盤對象,參數的id是canvas元素,option是我們需要提交給儀錶盤的一些參數值。這個option的定義如下:option對象可以擴充,你可以通過optin定製更加自由強大的儀錶盤對象的。
var panelOption = { maxLength: 30, interval: 5, level: [//儀錶盤需要呈現的資料隔離地區 {start: 0, color: "red" }, { start: 30, color: "yellow" }, { start: 40, color: "blue" }, { start: 100, color: "Lime" } ], outsideStyle: { lineWidth: 4, color: "rgba(100,100,100,1)" }};
在繪製元素的時候,我們常常需要儲存和恢複現場,特別當我們使用轉移,旋轉等方法的時候,一定要記得先save最後restore。為了方便,我們編寫了一個save函數提供這個方式,這個模式類似C#中的委託,和設計模式中的觀察著模式
panel.prototype.save = function(fn) { this.cvs.save(); fn(); this.cvs.restore();}
上面這個save可以方便的協助我們儲存和回複現場。
我們定義個用於畫半圓的方法,這個方法中,我們將需要畫半圓時做的translate放到save函數中,這樣畫半圓的變形操作不會對其他動作有影響。
panel.prototype.drawArc = function() { var p = this; var cvs = this.cvs; p.save(function() { cvs.translate(p.width / 2, p.height); //將座標點移到矩形的底部的中間 cvs.beginPath(); cvs.lineWidth = p.outsideStyle.lineWidth; cvs.strokeStyle = p.outsideStyle.color; cvs.arc(0, 0, p.height - cvs.lineWidth, 0, Math.PI / 180 * 180, true); //畫半圓 cvs.stroke(); });}
然後我們繪製中間顏色的填充地區
panel.prototype.drawLevelArea = function(level) { var p = this; var cvs = this.cvs; for (var i = 0; i < level.length; i++) { p.save(function() { cvs.translate(p.width / 2, p.height); cvs.rotate(Math.PI / 180 * level[i].start); p.save(function() { cvs.beginPath(); cvs.arc(0, 0, p.height - p.outsideStyle.lineWidth, Math.PI / 180 * 180, Math.PI / 180 * 360); cvs.fillStyle = level[i].color; cvs.fill(); }); }); }}
上面的代碼中,rotate很重要,我們每次都按level中的值進行旋轉,然後畫180°的顏色。這樣的過程是
第一次旋轉是0,沒有動,用紅色畫了180°的半圓
第二次旋轉了30°,你可以想想目前的畫布放斜了,然後以斜的畫了180°黃,這樣恰恰好吧紅色超過30°的顏色都覆蓋了
依次類推,我們不要對arc的起始和結束值做改變,我們只要再畫之前,rotate就可以了。當然,要放在save中哦。
之後是畫線,道理是一樣的,代碼如下
panel.prototype.drawLine = function() { var p = this; var cvs = this.cvs; for (var i = 0; i <= 180; i++) { p.save(function() { cvs.translate(p.width / 2, p.height); cvs.rotate(Math.PI / 180 * i); p.save(function() { cvs.beginPath(); cvs.lineTo(-(p.height - p.outsideStyle.lineWidth) + 10, 0); cvs.lineTo(-(p.height - p.outsideStyle.lineWidth) + 5, 0); cvs.stroke(); if (i % 10 == 0) { p.drawText(i); } }); }); }}
不過下面畫文字的代碼我沒有最佳化,你可以自己按上面的方式旋轉下
panel.prototype.drawText = function(val) { var p = this; var cvs = this.cvs; p.save(function() { cvs.lineWidth = 1; cvs.strokeText(val, -(p.height - p.outsideStyle.lineWidth) + 5, 0); });}
最後是畫指標,指標的角度值,是通過我們給的value來計算的。
panel.prototype.drawSpanner = function(value) { var p = this; var cvs = this.cvs; p.save(function() { cvs.translate(p.width / 2, p.height); cvs.moveTo(0, 0); cvs.arc(0, 0, p.height / 30, 0, (Math.PI / 180) * 360); cvs.lineWidth = 3; cvs.stroke(); }); p.save(function() { cvs.translate(p.width / 2, p.height); cvs.rotate(Math.PI / 180 * -90); p.save(function() { cvs.rotate(Math.PI / 180 * value); cvs.beginPath(); cvs.moveTo(5, -3); cvs.lineTo(0, -p.height * 0.7); cvs.lineTo(-5, -3); cvs.lineTo(5, -3); cvs.fill(); }); });}
現在主要的方法都完成了,我們實現對使用者的介面
panel.prototype.init = function(value) { var p = this; var cvs = this.cvs; cvs.clearRect(0, 0, this.width, this.height); p.drawArc(); p.drawLevelArea(p.level); p.drawLine(); p.drawSpanner(value);}
到此,panel的對象我們都編寫完成,以下是如何調用
window.onload = function() { var panelOption = { maxLength: 30, interval: 5, level: [//儀錶盤需要呈現的資料隔離地區 {start: 0, color: "red" }, { start: 30, color: "yellow" }, { start: 40, color: "blue" }, { start: 100, color: "Lime" } ], outsideStyle: { lineWidth: 4, color: "rgba(100,100,100,1)" } }; Panel = new panel("board", panelOption); Panel.init(15);}
大家可以測試這個簡單的儀錶盤了