需求
实现下图所示的仪表盘的绘制。
分析
我们先来将仪表盘进行图形拆分,并定义尺寸。
我们绘制的逻辑:
- 绘制中心圆
- 绘制环外圈圆
- 绘制环内圈圆
- 绘制刻度内圈圆
- 绘制刻度线
- 绘制刻度文字
- 绘制指针
定义圆
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();