需求
實現下圖所示的儀表盤的繪制。
分析
我們先來將儀表盤進行圖形拆分,並定義尺寸。
我們繪制的邏輯:
- 繪制中心圓
- 繪制環外圈圓
- 繪制環內圈圓
- 繪制刻度內圈圓
- 繪制刻度線
- 繪制刻度文字
- 繪制指針
定義圓
var circle = {
x: canvas.width / 2,
y: canvas.height / 2,
radius: 150
};
繪制中心圓
中心圓半徑是10,圓心是畫布中心。
const CENTROID_RADIUS = 10;
const CENTROID_STROKE_STYLE = 'rgba(0,0,0,.5)';
const CENTROID_FILL_STYLE = 'rgba(80,190,240,.6)';
// 畫儀表盤中心
function drawCentroid() {
context.beginPath();
context.save();
context.strokeStyle = CENTROID_STROKE_STYLE;
context.fillStyoe = CENTROID_FILL_STYLE;
context.arc(circle.x, circle.y, CENTROID_RADIUS, 0, 2 * Math.PI, false);
context.stroke();
context.fill();
context.restore();
}
繪制環
這里應用了剪紙效果
技巧,環外圈圓順時針繪制,環內圈圓逆時針順時針繪制,需要注意方向。
const RING_INNER_RADIUS = 35;
const RING_OUTER_RADIUS = 55;
// 繪制環外圈圓
function drawRingOuterCircle() {
context.shadowColor = 'rgba(0,0,0,.7)';
context.shadowOffsetX = 3;
context.shadowOffsetY = 3;
context.shadowBlur = 6;
context.strokeStyle = TRACKING_DIAL_STROKING_STYLE;
context.beginPath();
context.arc(circle.x, circle.y, circle.radius + RING_OUTER_RADIUS, 0, 2 * Math.PI, true);
context.stroke();
}
// 繪制環外圈圓
function drawRingInnerCircle() {
context.strokeStyle = 'rgba(0,0,0,.1)';
context.arc(circle.x, circle.y, circle.radius + RING_INNER_RADIUS, 0, 2 * Math.PI, false);
context.fillStyle = 'rgba(100,140,230,.1)';
context.fill();
context.stroke();
}
繪制效果:
繪制刻度內圈圓
const TICK_WIDTH = 10;
// 繪制刻度內圓
function drawTickInnerCircle() {
context.save();
context.beginPath();
context.strokeStyle = 'rgba(0,0,0,.1)';
context.arc(circle.x, circle.y, circle.radius + RING_INNER_RADIUS - TICK_WIDTH, 0, 2 * Math.PI, false);
context.stroke();
context.restore();
}
繪制效果:
繪制刻度線
每條刻度線,其實是一個短線段,需要確定Line的起始坐標和終止坐標。
const TICK_WIDTH = 10;
// 繪制刻度
function drawTicks() {
var radius = circle.radius + RING_INNER_RADIUS;
var ANGLE_MAX = 2 * Math.PI;
// var ANGLE_DELTA = Math.PI / 64;
var ANGLE_DELTA = Math.PI / 24;
var tickWidth;
context.save();
for (var angle = 0, count = 0; angle < ANGLE_MAX; angle += ANGLE_DELTA, count+=15) {
drawTick(angle, radius, count);
}
context.restore();
}
function drawTick(angle, radius, count) {
var tickWidth = count % 15 === 0 ? TICK_WIDTH : TICK_WIDTH / 2;
context.beginPath();
context.moveTo(circle.x + (radius - tickWidth) * Math.cos(angle), circle.y + (radius - tickWidth) * Math.sin(angle));
context.lineTo(circle.x + (radius) * Math.cos(angle), circle.y + (radius) * Math.sin(angle));
context.strokeStyle = TICK_SHORT_STROKE_STYLE;
context.stroke();
}
繪制刻度值
注意刻度值是每個2個刻度線,繪制一個text。
const ANNOTATIONS_FILL_STYLE = 'rgba(0,0,230,.9)';
const ANNOTATIONS_TEXT_SIZE = 12;
function drawAnnotations() {
var radius = circle.radius + RING_INNER_RADIUS;
// var deltaAngle = Math.PI /8;
var deltaAngle = Math.PI / 12;
context.save();
context.fillStyle = ANNOTATIONS_FILL_STYLE;
context.font = ANNOTATIONS_TEXT_SIZE + 'px Helvetica';
for (var angle = 0; angle < 2 * Math.PI; angle += deltaAngle) {
context.beginPath();
var degree = (angle * 180 / Math.PI).toFixed(0);
var pt = {
x: circle.x + (radius - TICK_WIDTH * 2) * Math.cos(angle),
y: circle.x - (radius - TICK_WIDTH * 2) * Math.sin(angle)
}
if (degree !== '360') {
context.fillText(degree, pt.x, pt.y);
}
}
context.restore();
}
效果:
繪制指針
這里沒有動畫,所以給的是固定角度。
// 繪制指針
function drawCentroidGuidewire(loc) {
var angle = -Math.PI / 4;
var radius = circle.radius + RING_OUTER_RADIUS;
var endpt;
if (loc.x > circle.x) {
endpt = {
x: circle.x + radius * Math.cos(angle),
y: circle.y + radius * Math.sin(angle)
};
} else {
endpt = {
x: circle.x - radius * Math.cos(angle),
y: circle.y - radius * Math.sin(angle)
};
}
context.save();
context.strokeStyle = GUIDEWIRE_STROKE_STYLE;
context.fillStyle = GUIDEWIRE_FILL_STYLE;
context.beginPath();
context.moveTo(circle.x, circle.y);
context.lineTo(endpt.x, endpt.y);
context.stroke();
context.beginPath();
context.strokeStyle = TICK_LONG_STROKE_STYLE;
context.arc(endpt.x, endpt.y, 5, 0, 2 * Math.PI, false);
context.fill();
context.stroke();
context.restore();
}
最后調用的時候,先繪制指針,再繪制中心點,這樣可以使指針在中心點下層,好看一些。
function drawDial() {
var loc = { x: circle.x, y: circle.y };
drawCentroidGuidewire(loc);
drawCentroid();
drawRingOuterCircle();
drawRingInnerCircle();
drawTickInnerCircle();
drawTicks();
drawAnnotations();
}
// Initialization
context.shadowColor = 'rgba(0,0,0,.4)';
context.shadowOffsetX = 2;
context.shadowOffsetY = 2;
context.shadowBlur = 4;
context.textAlign = 'center';
context.textBaseline = 'middle';
drawDial();