Drawing the speedometer arrow in canvas
I want to make an animation of the speedometer using canvas. But I need the speedometer needle to be triangular in shape and when changing the value, it points to the desired value, but its base always remains in the center. Tell me what formula or algorithm you need to apply for this. The code is shown below.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<canvas id="canvas" width="500" height="500"></canvas>
<script>
const canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
// general settings
middleX = canvas.width / 2,
middleY = canvas.height / 2,
radius = 240,
counterClockwise = false,
// ticks settings
tickWidth = canvas.width / 100,
// tickColor = "#746845";
tickOffsetFromArc = canvas.width / 40,
// Center circle settings
centerCircleRadius = canvas.width / 20,
centerCircleColor = "#ccc",
centerCircleBorderWidth = canvas.width / 100,
// Arrow settings
arrowValueIndex = .73,
arrowColor = "#464646",
arrowWidth = canvas.width / 50,
// numbers
digits = [0, 20, 40, 50, 60, 70, 80, 90, 100],
digitsColor = "#0a0a0a",
digitsFont = "bold 20px Tahoma",
digitsOffsetFromArc = canvas.width / 15,
//zones
zonesCount = digits.length - 1;
// beginning and ending of our arc. Sets by radius*pi
let startAngleIndex = .75,
endAngleIndex = 2.25,
step = (endAngleIndex - startAngleIndex) / zonesCount;
/*draw zones*/
let DrawZones = function () {
const greyZonesCount = zonesCount / 1.6;
greenZonesCount = zonesCount - greyZonesCount,
startAngle = (startAngleIndex - 0.02) * Math.PI,
endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI,
endGreenAngle = (endAngleIndex + 0.02) * Math.PI,
//zones' options
sectionOptions = [
{
startAngle: startAngle,
endAngle: endGreyAngle,
color: "#e7e7e7",
zoneLineWidth: 2
},
{
startAngle: endGreyAngle,
endAngle: endGreenAngle,
color: "#13b74b",
zoneLineWidth: 5
},
];
this.DrawZone = function (options) {
ctx.beginPath();
ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise);
ctx.lineWidth = options.zoneLineWidth;
ctx.strokeStyle = options.color;
ctx.lineCap = "round";
ctx.stroke();
};
sectionOptions.forEach(options => this.DrawZone(options));
};
/*draw dots*/
let DrawTicks = function () {
startAngleIndex = .73,
endAngleIndex = 2.27,
step = (endAngleIndex - startAngleIndex) / zonesCount;
this.DrawTick = function (angle,count) {
let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle),
fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle),
toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle),
toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle),
centerOfDotX=(fromX+toX)/2,
centerOfDotY=(fromY+toY)/2;
ctx.beginPath();
ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true);
if (count<6){
switch (count) {
case 1:
case 2:
case 3:
ctx.fillStyle="#FF0000";
break;
default:
ctx.fillStyle="#F9AF00";
break;
}
}else{
ctx.fillStyle="#FFF";
ctx.strokeStyle="#13B74B";
ctx.shadowColor = "#a8bbaa";
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
}
ctx.fill();
ctx.closePath();
ctx.shadowBlur =0;
};
let count=0;
for (let i = startAngleIndex; i <= endAngleIndex; i += step) {
let angle = i * Math.PI;
count++;
this.DrawTick(angle,count);
}
};
//draw numbers
let DrawDigits = function () {
let angleIndex = startAngleIndex;
digits.forEach(function (digit) {
let angle = angleIndex * Math.PI,
x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle),
y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle);
angleIndex += step;
ctx.font = digitsFont;
ctx.fillStyle = digitsColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(digit, x, y);
});
};
/*draw arrow РИСОВАНИЕ СТРЕЛКИ*/
let DrawArrow = function () {
let arrowAngle = arrowValueIndex * Math.PI;
let toX = middleX + (radius) * Math.cos(arrowAngle)+50;
let toY = middleY + (radius) * Math.sin(arrowAngle)-50;
ctx.beginPath();
ctx.moveTo(middleX, middleY);
ctx.lineTo(toX, toY);
ctx.strokeStyle = arrowColor;
ctx.lineWidth = arrowWidth;
ctx.stroke();
ctx.closePath();
};
window.onload=()=>{
DrawZones();
DrawTicks();
DrawDigits();
DrawArrow();
};
</script>
</body>
</html>
1
3 answers
Here is another option, I did not destroy the old one, let there be a second answer:
let canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
// general settings
middleX = canvas.width / 2,
middleY = canvas.height / 2,
radius = 240,
counterClockwise = false,
// ticks settings
tickWidth = canvas.width / 100,
// tickColor = "#746845";
tickOffsetFromArc = canvas.width / 40,
// Center circle settings
centerCircleRadius = canvas.width / 20,
centerCircleColor = "#ccc",
centerCircleBorderWidth = canvas.width / 100,
// Arrow settings
arrowValueIndex = 0,
arrowColor = "#464646",
arrowWidth = canvas.width / 150,
// numbers
digits = [0, 20, 40, 50, 60, 70, 80, 90, 100],
digitsColor = "#0a0a0a",
digitsFont = "bold 20px Tahoma",
digitsOffsetFromArc = canvas.width / 15,
//zones
zonesCount = digits.length - 1;
// beginning and ending of our arc. Sets by radius*pi
let startAngleIndex = .75,
endAngleIndex = 2.25,
step = (endAngleIndex - startAngleIndex) / zonesCount;
/*draw zones*/
let DrawZones = function () {
const greyZonesCount = zonesCount / 1.6;
greenZonesCount = zonesCount - greyZonesCount,
startAngle = (startAngleIndex - 0.02) * Math.PI,
endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI,
endGreenAngle = (endAngleIndex + 0.02) * Math.PI,
//zones' options
sectionOptions = [{
startAngle: startAngle,
endAngle: endGreyAngle,
color: "#e7e7e7",
zoneLineWidth: 2
},{
startAngle: endGreyAngle,
endAngle: endGreenAngle,
color: "#13b74b",
zoneLineWidth: 5
}];
this.DrawZone = function (options) {
ctx.beginPath();
ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise);
ctx.lineWidth = options.zoneLineWidth;
ctx.strokeStyle = options.color;
ctx.lineCap = "round";
ctx.stroke();
};
sectionOptions.forEach(options => this.DrawZone(options));
};
/*draw dots*/
let DrawTicks = function () {
startAngleIndex = .73,
endAngleIndex = 2.27,
step = (endAngleIndex - startAngleIndex) / zonesCount;
this.DrawTick = function (angle,count) {
let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle),
fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle),
toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle),
toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle),
centerOfDotX=(fromX+toX)/2,
centerOfDotY=(fromY+toY)/2;
ctx.beginPath();
ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true);
if (count<6){
switch (count) {
case 1:
case 2:
case 3:
ctx.fillStyle="#FF0000";
break;
default:
ctx.fillStyle="#F9AF00";
break;
}
} else {
ctx.fillStyle="#FFF";
ctx.strokeStyle="#13B74B";
ctx.shadowColor = "#a8bbaa";
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
}
ctx.fill();
ctx.closePath();
ctx.shadowBlur =0;
};
let count=0;
for (let i = startAngleIndex; i <= endAngleIndex; i += step) {
let angle = i * Math.PI;
count++;
this.DrawTick(angle,count);
}
};
//draw numbers
let DrawDigits = function () {
let angleIndex = startAngleIndex;
digits.forEach(function (digit) {
let angle = angleIndex * Math.PI,
x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle),
y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle);
angleIndex += step;
ctx.font = digitsFont;
ctx.fillStyle = digitsColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(digit, x, y);
});
};
/*draw arrow РИСОВАНИЕ СТРЕЛКИ*/
let DrawArrow = function () {
ctx.beginPath();
ctx.moveTo(middleX-17, middleY-47);
ctx.lineTo(middleX, middleY-180);
ctx.lineTo(middleX+17, middleY-47);
ctx.strokeStyle = arrowColor;
ctx.lineWidth = arrowWidth;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(middleX, middleY, 50, Math.PI/8- Math.PI/2, 2 * Math.PI-Math.PI/8- Math.PI/2);
ctx.stroke();
};
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height);
DrawZones();
DrawTicks();
DrawDigits();
ctx.translate(middleX,middleY);
ctx.rotate(arrowValueIndex);
ctx.translate(-middleX,-middleY);
DrawArrow();
ctx.translate(middleX,middleY);
ctx.rotate(-arrowValueIndex);
ctx.translate(-middleX,-middleY);
}
window.onload = draw
function val(value) {
let sector = Math.PI*0.385
if (value < 40)
arrowValueIndex = value/40*sector - sector*2;
else
arrowValueIndex = (value-40)/60*sector*3 - sector;
document.querySelector('span').textContent = value;
draw();
}
<input type="range" value="60" onmousemove="val(this.value)"><span></span><br>
<canvas id="canvas" width="500" height="500"></canvas>
3
Author: Stranger in the Q, 2019-04-10 20:06:05
It is sad that the distribution of the scale is uneven, if it were uniform, you could do something like this
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height) //очистка канвы
ctx.translate(middleX,middleY); // сдвиг в центр
ctx.rotate(arrowValueIndex); // поворот табло
ctx.translate(-middleX,-middleY); // сдвиг обратно
DrawZones();
DrawTicks(); // рисует табло
DrawDigits();
ctx.translate(middleX,middleY); // сдвиг в центр
ctx.rotate(-arrowValueIndex); // обратный поворот табло
ctx.translate(-middleX,-middleY); // сдвиг обратно
DrawArrow(); // стрелка
}
let canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
// general settings
middleX = canvas.width / 2,
middleY = canvas.height / 2,
radius = 240,
counterClockwise = false,
// ticks settings
tickWidth = canvas.width / 100,
// tickColor = "#746845";
tickOffsetFromArc = canvas.width / 40,
// Center circle settings
centerCircleRadius = canvas.width / 20,
centerCircleColor = "#ccc",
centerCircleBorderWidth = canvas.width / 100,
// Arrow settings
arrowValueIndex = 0,
arrowColor = "#464646",
arrowWidth = canvas.width / 150,
// numbers
digits = [0, 20, 40, 50, 60, 70, 80, 90, 100],
digitsColor = "#0a0a0a",
digitsFont = "bold 20px Tahoma",
digitsOffsetFromArc = canvas.width / 15,
//zones
zonesCount = digits.length - 1;
// beginning and ending of our arc. Sets by radius*pi
let startAngleIndex = .75,
endAngleIndex = 2.25,
step = (endAngleIndex - startAngleIndex) / zonesCount;
/*draw zones*/
let DrawZones = function () {
const greyZonesCount = zonesCount / 1.6;
greenZonesCount = zonesCount - greyZonesCount,
startAngle = (startAngleIndex - 0.02) * Math.PI,
endGreyAngle = (startAngleIndex + greyZonesCount * step) * Math.PI,
endGreenAngle = (endAngleIndex + 0.02) * Math.PI,
//zones' options
sectionOptions = [
{
startAngle: startAngle,
endAngle: endGreyAngle,
color: "#e7e7e7",
zoneLineWidth: 2
},
{
startAngle: endGreyAngle,
endAngle: endGreenAngle,
color: "#13b74b",
zoneLineWidth: 5
},
];
this.DrawZone = function (options) {
ctx.beginPath();
ctx.arc(middleX, middleY, radius, options.startAngle, options.endAngle, counterClockwise);
ctx.lineWidth = options.zoneLineWidth;
ctx.strokeStyle = options.color;
ctx.lineCap = "round";
ctx.stroke();
};
sectionOptions.forEach(options => this.DrawZone(options));
};
/*draw dots*/
let DrawTicks = function () {
startAngleIndex = .73,
endAngleIndex = 2.27,
step = (endAngleIndex - startAngleIndex) / zonesCount;
this.DrawTick = function (angle,count) {
let fromX = middleX + (radius - tickOffsetFromArc) * Math.cos(angle),
fromY = middleY + (radius - tickOffsetFromArc) * Math.sin(angle),
toX = middleX + (radius + tickOffsetFromArc) * Math.cos(angle),
toY = middleY + (radius + tickOffsetFromArc) * Math.sin(angle),
centerOfDotX=(fromX+toX)/2,
centerOfDotY=(fromY+toY)/2;
ctx.beginPath();
ctx.arc(centerOfDotX,centerOfDotY,6,0,Math.PI*2,true);
if (count<6){
switch (count) {
case 1:
case 2:
case 3:
ctx.fillStyle="#FF0000";
break;
default:
ctx.fillStyle="#F9AF00";
break;
}
}else{
ctx.fillStyle="#FFF";
ctx.strokeStyle="#13B74B";
ctx.shadowColor = "#a8bbaa";
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.stroke();
}
ctx.fill();
ctx.closePath();
ctx.shadowBlur =0;
};
let count=0;
for (let i = startAngleIndex; i <= endAngleIndex; i += step) {
let angle = i * Math.PI;
count++;
this.DrawTick(angle,count);
}
};
//draw numbers
let DrawDigits = function () {
let angleIndex = startAngleIndex;
digits.forEach(function (digit) {
let angle = angleIndex * Math.PI,
x = middleX + (radius - digitsOffsetFromArc) * Math.cos(angle),
y = middleY + (radius - digitsOffsetFromArc) * Math.sin(angle);
angleIndex += step;
ctx.font = digitsFont;
ctx.fillStyle = digitsColor;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(digit, x, y);
});
};
/*draw arrow РИСОВАНИЕ СТРЕЛКИ*/
let DrawArrow = function () {
ctx.beginPath();
ctx.moveTo(middleX-17, middleY-47);
ctx.lineTo(middleX, middleY-180);
ctx.lineTo(middleX+17, middleY-47);
ctx.strokeStyle = arrowColor;
ctx.lineWidth = arrowWidth;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(middleX, middleY, 50, Math.PI/8- Math.PI/2, 2 * Math.PI-Math.PI/8- Math.PI/2);
ctx.stroke();
};
function draw() {
ctx.clearRect(0,0,canvas.width,canvas.height)
ctx.translate(middleX,middleY);
ctx.rotate(arrowValueIndex);
ctx.translate(-middleX,-middleY);
DrawZones();
DrawTicks();
DrawDigits();
ctx.translate(middleX,middleY);
ctx.rotate(-arrowValueIndex);
ctx.translate(-middleX,-middleY);
DrawArrow();
}
window.onload = draw;
function val(value) {
let sector = Math.PI*0.385
if (value < 40)
arrowValueIndex = value/40*sector - sector*2;
else
arrowValueIndex = (value-40)/60*sector*3 - sector;
document.querySelector('span').textContent = value;
draw()
}
<input type="range" value="60" onmousemove="val(this.value)"><span></span><br>
<canvas id="canvas" width="500" height="500"></canvas>
1
Author: Stranger in the Q, 2019-04-10 20:08:29
Didn't work with canvas, but look at the implementation data, maybe it will help: http://www.knowstack.com/html5-canvas-speedometer/
0
Author: Андрей, 2019-04-10 16:33:37