Veer
Veer

Reputation: 166

Create a Speedometer like Semi Circle

I am creating speedometer like semi circle and I have have been successful creating semicircle with all the colors. See the design below:

The problem I am facing is creating the triangle which will act as pointer. It will move around value from 300 to 850. What ever the value will get in this range it will point there.

JS Fiddle Link

enter image description here

<canvas id="myCanvas" height="350" width="666">
                </canvas>

JS:

window.onload = function(){

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

context.font = "14px Trebuchet MS";
context.fillStyle = "#aaaaaa";
context.fillText("300", 60, 218);

context.font = "14px Trebuchet MS";
context.fillStyle = "#aaaaaa";
context.fillText("850", 280, 218);

// drawArcShadow   x    y   rad sAng eAng clockwise  line    fill
drawArcShadow(180, 200, 100, 0, 180, true,  "#eeeeee","white");

function drawArcShadow(xPos, yPos, radius, startAngle, endAngle, clockwise, lineColor, fillColor) {
    var startAngle = startAngle * (Math.PI/180);
    var endAngle   = endAngle   * (Math.PI/180);

    var radius = radius;

    context.strokeStyle = lineColor;
    context.fillStyle   = fillColor;
    context.lineWidth   = 20;
    context.beginPath();
    context.arc(xPos, yPos, radius, startAngle, endAngle, clockwise);
    context.fill();
    context.stroke();
}

// drawArc   x    y   rad sAng eAng clockwise  line    fill
drawArc(180, 200, 110, 0, 180, true,  "#c1634a","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 263, true,  "#ab5741","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 264, true,  "#e59636","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 288, true,  "#ce8631","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 289, true,  "#e8d932","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 302, true,  "#d0c52d","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 303, true,  "#aecd9c","rgba(255, 255, 255, 0)");
drawArc(180, 200, 110, 0, 320, true,  "#8db872","rgba(255, 255, 255, 0)");

function drawArc(xPos, yPos, radius, startAngle, endAngle, clockwise, lineColor, fillColor) {
    var startAngle = startAngle * (Math.PI/180);
    var endAngle   = endAngle   * (Math.PI/180);

    var radius = radius;

    context.strokeStyle = lineColor;
    context.fillStyle   = fillColor;
    context.lineWidth   = 20;
    context.beginPath();
    context.arc(xPos, yPos, radius, startAngle, endAngle, clockwise);
    context.fill();
    context.stroke();
}

};

Upvotes: 0

Views: 1908

Answers (1)

Kaiido
Kaiido

Reputation: 136658

This question already has been answered many times, but I felt you need some more corrections in your code's logic.

To rotate a shape around a point in canvas, use translate(x,y) and rotate(radian) methods. First you move your context to the rotation anchor point, then you rotate accordingly, and finally you translate again to give the desired offset.
Think of your context as a leaf of paper that you can move and rotate.

In your snippet, you had big antialiasing artifacts. This is because you made all your arcs start at angle 0. When drawing an arc, the browser will create semi-transparent pixels so the line looks smooth. But if you do draw multiple arcs at the same positions, these pixels are more and more opaque, leading to these visible artifacts.

Here is your code, a bit refactored :

var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");

function drawTriangle() {

  var ratio = Math.PI / (slider.max - slider.min);
  var angle = ((slider.value - slider.min) * ratio) + Math.PI;
  var radius = 110;
  // first move our cursor to the center of our arcs
  context.translate(180, 200);
  // then rotate the whole context
  context.rotate(angle);
  // move our cursor to our arc's radius
  context.translate(radius, 0);
  // draw the triangle
  context.fillStyle = 'black';
  context.beginPath();
  // this draws the triangle around its center point
  context.lineTo(10, -5);
  context.lineTo(10, 5)
  context.lineTo(-10, 0)
  context.closePath();
  context.fill();
  // place back our context's transfrom to its default
  context.setTransform(1, 0, 0, 1, 0, 0);
}

function drawArcShadow(xPos, yPos, radius, startAngle, endAngle, clockwise, lineColor, fillColor) {
  var startAngle = startAngle * (Math.PI / 180);
  var endAngle = endAngle * (Math.PI / 180);

  var radius = radius;

  context.strokeStyle = lineColor;
  context.fillStyle = fillColor;
  context.lineWidth = 20;
  context.beginPath();
  context.arc(xPos, yPos, radius, startAngle, endAngle, clockwise);
  context.fill();
  context.stroke();
}

function draw() {
  // clear the canvas
  context.clearRect(0, 0, canvas.width, canvas.height);
  // text
  context.font = "14px Trebuchet MS";
  context.fillStyle = "#aaaaaa";
  // since we don't change the properties, no need to tell the browser to do so
  context.fillText("300", 60, 218);
  context.fillText("850", 280, 218);

  // drawArcShadow   x    y   rad sAng eAng clockwise  line    fill
  drawArcShadow(180, 200, 100, 0, 180, true, "#eeeeee", "white");
  // drawArc   x    y   rad sAng eAng ANTIclockwise  line    fill
  // be careful, 6th argument of arc() is ANTI-clockwise boolean
  // here I changed all start angle to avoid anti-aliasing artifacts + I removed the transparent fill parameter
  drawArc(180, 200, 110, 263, 180, true, "#c1634a", null);
  drawArc(180, 200, 110, 264, 263, true, "#ab5741", null);
  drawArc(180, 200, 110, 288, 264, true, "#e59636", null);
  drawArc(180, 200, 110, 289, 288, true, "#ce8631", null);
  drawArc(180, 200, 110, 302, 289, true, "#e8d932", null);
  drawArc(180, 200, 110, 303, 302, true, "#d0c52d", null);
  drawArc(180, 200, 110, 320, 303, true, "#aecd9c", null);
  drawArc(180, 200, 110, 0, 320, true, "#8db872", null);

  drawTriangle();
}

function drawArc(xPos, yPos, radius, startAngle, endAngle, clockwise, lineColor, fillColor) {
  var startAngle = startAngle * (Math.PI / 180);
  var endAngle = endAngle * (Math.PI / 180);

  context.strokeStyle = lineColor;
  context.fillStyle = fillColor;
  context.lineWidth = 20;
  context.beginPath();
  context.arc(xPos, yPos, radius, startAngle, endAngle, clockwise);
  // instead of filling transparent, which produces nothing, just don't pass the fillColor parameter and don't call fill().
  fillColor && context.fill();
  context.stroke();
}
slider.oninput = draw;
draw();
<input type="range" id="slider" min="300" max="850">
<canvas id="myCanvas" height="350" width="666"></canvas>

Upvotes: 2

Related Questions