Making a speedometer using HTML5’s Canvas

HTML5The canvas element is part of HTML5.  It allows for dynamic, scriptable rendering of 2D shapes.  It allows a developer to draw just as an artist would on their canvas; albeit, without the same creative flare an artist may have.  As I’m a developer I couldn’t resist having an attempt.  They first step was to come up with a project to do.  Not being that creative I decided to draw a speedometer – similar to the ones we see in most cars.  Initially it sounded quite simple, although, I did find parts quite tricky.  This was due to my less than impressive maths skills and inexperience with the Canvas implementation. I’ll start with the end result and then explain how the parts of the speedometer were rendered.  With any luck some skilled mathematician will be able to explain the maths I’ve used because I have managed to get it working without fully understanding the mathematical theory :-).

The End Result

The image below was drawn using HTML5’s Canvas implementation.


HTML5 Canvas Speedometer

You may notice the image above is not a canvas image!  It’s a traditional image included with the <img> tag.  I’m using images for illustrations rather than drawing each using the Canvas approach.  Each image that follows with also be paired with the JavaScript code used to draw the component.  The speedometer can be broken down into the following components:
  • the metallic outer arc;
  • the background semi-circle;
  • the coloured speed arc;
  • the speed tick marks and labels;
  • and, finally the needle and its base.

The combination of drawing each of these components results in the complete speedometer.

The HTML Source

The following code is stripped to the minimum in order to draw the speedometer above.

<html lang="en">
<head>
	<meta charset="utf-8" />
	<title>Speedometer HTML5 Canvas</title>
	<script src="speedometer.js"></script>
</head>
<body onload='draw(0);'>
	<canvas id="tutorial" width="440" height="220">Canvas not available.</canvas>
	<div>
		<form id="drawTemp">
			<input type="text" id="txtSpeed" name="txtSpeed" value="20" maxlength="2"/>
			<input type="button" value="Draw" onclick="drawWithInputValue();">
		</form>
	</div>	
</body>
</html>

The HTML is fairly straight forward. The items of interest are the canvas, the ‘onload’ event for the body and the script tag. For those who are not familiar with HTML, let me explain how the canvas turns into the speedometer. When any HTML page loads the browser will run the JavaScript code attached to the onload event of the body tag e.g.

<body onload='draw(0);'>

In this example the JavaScript function called ‘draw’ will be called. The next logical question is what and where is this function? In this case the draw function is defined in the script included in the header:

<script type="text/javascript" src="speedometer.js"></script>

The browser will load any JavaScript code included in a script tag prior to displaying the page to the user. Therefore, the content of my script “speedometer.js” will be loaded and thus the function called ‘draw’ will be available. If you were to look inside the aforementioned script you will be able to see the code e.g.

function draw(iSpeed)
{
	/* Main entry point for drawing the speedometer
 	* If canvas is not support alert the user.
 	*/

	var canvas = document.getElementById('tutorial');

	// Canvas good?
	if (canvas != null && canvas.getContext)
	{
		var options = buildOptionsAsJSON(canvas, iSpeed);

		// Clear canvas
		clearCanvas(options);

		// Draw the metallic styled edge
		drawMetallicArc(options);

		// Draw thw background
		drawBackground(options);

		// Draw tick marks
		drawTicks(options);

		// Draw labels on markers
		drawTextMarkers(options);

		// Draw speeometer colour arc
		drawSpeedometerColourArc(options);

		// Draw the needle and base
		drawNeedle(options);

	}
	else
	{
		alert("Canvas not supported by your browser!");
	}
}

The content of the function is quite readable and can be matched to the sections I mentioned above. The first of these is drawing the metallic edge. Of course, the code for this is defined in the aptly named JavaScript function ‘drawMetallicArc’:

function drawMetallicArc(options)
{
	/* Draw the metallic border of the speedometer
	 * by drawing two semi-circles, one over lapping
	 * the other with a bit of alpha transparency
	 */

	drawOuterMetallicArc(options);
	drawInnerMetallicArc(options);
}

The content of the function reveals the metallic edge of the speedometer is drawn by overlapping an outer semi-circle with a smaller inner one. Let’s look at the content:

function drawOuterMetallicArc(options)
{
	/* Draw the metallic border of the speedometer
	 * Outer grey area
	 */
	options.ctx.beginPath();

	// Nice shade of grey
	options.ctx.fillStyle = "rgb(127,127,127)";

	// Draw the outer circle
	options.ctx.arc(options.center.X,
		options.center.Y,
		options.radius,
		0,
		Math.PI,
		true);

	// Fill the last object
	options.ctx.fill();
}

function drawInnerMetallicArc(options)
{
	/* Draw the metallic border of the speedometer
	 * Inner white area
	 */

	options.ctx.beginPath();

	// White
	options.ctx.fillStyle = "rgb(255,255,255)";

	// Outer circle (subtle edge in white->grey)
	options.ctx.arc(options.center.X,
					options.center.Y,
					(options.radius / 100) * 90,
					0,
					Math.PI,
					true);

	options.ctx.fill();
}
If one was to run these function on their own we would get the following result:

The next step is to draw the background. The function ‘drawBackground’ performs this. The content of which is:
function drawBackground(options)
{
	/* Black background with alphs transparency to
	 * blend the edges of the metallic edge and
	 * black background
	 */

	options.ctx.globalAlpha = 0.2;
	options.ctx.fillStyle = "rgb(0,0,0)";

	// Draw semi-transparent circles
	for (var i = 170; i < 180 ; i++)
	{
		options.ctx.beginPath();

		options.ctx.arc(options.center.X,
			options.center.Y,
			1 * i, 0,
			Math.PI,
			true);

		options.ctx.fill();
	}
}
Again, executed on its own the result would be the following:

Background

The next step is to draw the speedometer tick marks. This is achieved by running the ‘drawTicks’ function. This contains the following JavaScript code:
function drawTicks(options)
{
	/* Two tick in the coloured arc!
	 * Small ticks every 5
	 * Large ticks every 10
	 */

	drawSmallTickMarks(options);
	drawLargeTickMarks(options);
}
The comments within the code explain this function quite well. This function calls two further functions. The first draws the small ticks every 5 MPH and the second draws the large ticks every 10 MPH. The source is:
function drawSmallTickMarks(options)
{
	/* The small tick marks against the coloured
	 * arc drawn every 5 mph from 10 degrees to
	 * 170 degrees.
	 */

	var tickvalue = options.levelRadius - 8;
	var iTick = 0;
	var gaugeOptions = options.gaugeOptions;
	var iTickRad = 0;

	applyDefaultContextSettings(options);

	// Tick every 20 degrees (small ticks)
	for (iTick = 10; iTick < 180; iTick += 20)
	{
		iTickRad = degToRad(iTick);

		/* Calculate the X and Y of both ends of the
		 * line I need to draw at angle represented at Tick.
		 * The aim is to draw the a line starting on the
		 * coloured arc and continueing towards the outer edge
		 * in the direction from the center of the gauge.
		 */

		var onArchX = gaugeOptions.radius - (Math.cos(iTickRad) * tickvalue);
		var onArchY = gaugeOptions.radius - (Math.sin(iTickRad) * tickvalue);
		var innerTickX = gaugeOptions.radius - (Math.cos(iTickRad) * gaugeOptions.radius);
		var innerTickY = gaugeOptions.radius - (Math.sin(iTickRad) * gaugeOptions.radius);

		var fromX = (options.center.X - gaugeOptions.radius) + onArchX;
		var fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + onArchY;

		var toX = (options.center.X - gaugeOptions.radius) + innerTickX;
		var toY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY;

		// Create a line expressed in JSON
		var line = createLine(fromX, fromY, toX, toY, "rgb(127,127,127)", 3, 0.6);

		// Draw the line
		drawLine(options, line);

	}
}

function drawLargeTickMarks(options)
{
	/* The large tick marks against the coloured
	 * arc drawn every 10 mph from 10 degrees to
	 * 170 degrees.
	 */

	var tickvalue = options.levelRadius - 8;
	var iTick = 0;
	var gaugeOptions = options.gaugeOptions;
	var iTickRad = 0;

	var innerTickY;
	var innerTickX;
	var onArchX;
	var onArchY;

	var fromX;
	var fromY;

	var toX;
	var toY;
	var line;

	applyDefaultContextSettings(options);

	tickvalue = options.levelRadius - 2;

	// 10 units (major ticks)
	for (iTick = 20; iTick < 180; iTick += 20)
	{
		iTickRad = degToRad(iTick);

		/* Calculate the X and Y of both ends of the
		 * line I need to draw at angle represented at Tick.
		 * The aim is to draw the a line starting on the
		 * coloured arc and continueing towards the outer edge
		 * in the direction from the center of the gauge.
		 */

		onArchX = gaugeOptions.radius - (Math.cos(iTickRad) * tickvalue);
		onArchY = gaugeOptions.radius - (Math.sin(iTickRad) * tickvalue);
		innerTickX = gaugeOptions.radius - (Math.cos(iTickRad) * gaugeOptions.radius);
		innerTickY = gaugeOptions.radius - (Math.sin(iTickRad) * gaugeOptions.radius);

		fromX = (options.center.X - gaugeOptions.radius) + onArchX;
		fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + onArchY;

		toX = (options.center.X - gaugeOptions.radius) + innerTickX;
		toY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY;

		// Create a line expressed in JSON
		line = createLine(fromX, fromY, toX, toY, "rgb(127,127,127)", 3, 0.6);

		// Draw the line
		drawLine(options, line);
	}
}
Running these functions results in the following:

Tick marks

The next step is to draw the text markers just above the ticks of the speedometer. This function is called ‘drawTextMarkers’. It consists of the following:
function drawTextMarkers(options)
{
	/* The text labels marks above the coloured
	 * arc drawn every 10 mph from 10 degrees to
	 * 170 degrees.
	 */

	var innerTickX = 0;
	var innerTickY = 0;
	var iTick = 0;
	var gaugeOptions = options.gaugeOptions;
	var iTickToPrint = 0;

	applyDefaultContextSettings(options);

	// Font styling
	options.ctx.font = 'italic 10px sans-serif';
	options.ctx.textBaseline = 'top';

	options.ctx.beginPath();

	// Tick every 20 (small ticks)
	for (iTick = 10; iTick < 180; iTick += 20)
	{
		innerTickX = gaugeOptions.radius - (Math.cos(degToRad(iTick)) * gaugeOptions.radius);
		innerTickY = gaugeOptions.radius - (Math.sin(degToRad(iTick)) * gaugeOptions.radius);

		// Some cludging to center the values (TODO: Improve)
		if(iTick 		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
		}
		if(iTick < 50)
		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX - 5,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
		}
		else if(iTick < 90)
		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY );
		}
		else if(iTick == 90)
		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 4,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY );
		}
		else if(iTick < 145)
		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 10,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY );
		}
		else
		{
			options.ctx.fillText(iTickToPrint, (options.center.X - gaugeOptions.radius - 12) + innerTickX + 15,
					(gaugeOptions.center.Y - gaugeOptions.radius - 12) + innerTickY + 5);
		}

		// MPH increase by 10 every 20 degrees
			iTickToPrint += 10;
	}

	options.ctx.stroke();

}

Text markers

The penultimate step is to draw the coloured arc. The coloured arc is drawn within the function ‘drawSpeedometerColourArc’. This contains the following:
function drawSpeedometerColourArc(options)
{
	/* Draws the colour arc.  Three different colours
	 * used here; thus, same arc drawn 3 times with
	 * different colours.
	 * TODO: Gradient possible?
	 */

	var startOfGreen = 10;
	var endOfGreen = 200;
	var endOfOrange = 280;

	drawSpeedometerPart(options, 1.0, "rgb(82, 240, 55)", startOfGreen);
	drawSpeedometerPart(options, 0.9, "rgb(198, 111, 0)", endOfGreen);
	drawSpeedometerPart(options, 0.9, "rgb(255, 0, 0)", endOfOrange);

}

The function ‘drawSpeedometerColourArc’ simply calls a helper function three times. The helper function draws separate parts of the the arc in different colours:


function drawNeedleDial(options, alphaValue, strokeStyle, fillStyle)
{
	/* Draws the metallic dial that covers the base of the
 	* needle.
 	*/

	options.ctx.globalAlpha = alphaValue
	options.ctx.lineWidth = 3;
	options.ctx.strokeStyle = strokeStyle;
	options.ctx.fillStyle = fillStyle;

	// Draw several transparent circles with alpha
	for (var i = 0;i < 30; i++)
	{
		options.ctx.beginPath();

		options.ctx.arc(options.center.X,
			options.center.Y,
			1*i,
			0,
			Math.PI,
			true);

		options.ctx.fill();

		options.ctx.stroke();
	}
}
If called on its own the following will be displayed:

Coloured arc

And, finally the needle of the speedometer is drawn within the ‘drawNeedle’ function:
function drawNeedle(options)
{
	/* Draw the needle in a nice read colour at the
 	* angle that represents the options.speed value.
 	*/

	var iSpeedAsAngle = convertSpeedToAngle(options);
	var iSpeedAsAngleRad = degToRad(iSpeedAsAngle);

	var gaugeOptions = options.gaugeOptions;

	var innerTickX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * 20);
	var innerTickY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * 20);

	var fromX = (options.center.X - gaugeOptions.radius) + innerTickX;
	var fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY;

	var endNeedleX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * gaugeOptions.radius);
	var endNeedleY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * gaugeOptions.radius);

	var toX = (options.center.X - gaugeOptions.radius) + endNeedleX;
	var toY = (gaugeOptions.center.Y - gaugeOptions.radius) + endNeedleY;

	var line = createLine(fromX, fromY, toX, toY, "rgb(255,0,0)", 5, 0.6);

	drawLine(options, line);

	// Two circle to draw the dial at the base (give its a nice effect?)
	drawNeedleDial(options, 0.6, "rgb(127, 127, 127)", "rgb(255,255,255)");
	drawNeedleDial(options, 0.2, "rgb(127, 127, 127)", "rgb(127,127,127)");

}
Called on its own this will display the following:

Speedometer needle

If all those layers are drawn sequentially we end up with this!

That concludes one’s experiment with HTML5’s canvas. I hope this helps someone out there. All the code above may be taken and used for anything – credit not required. Although, it would be nice to know. If you can explain any of the math I have stumbled through, or have a resource I could read please do post them below. A working version of this project can be viewed here. The source code for this project can be obtained from my Git Hub repository here

99 thoughts on “Making a speedometer using HTML5’s Canvas

  1. Hi Ray. I love this article of yours. I’ve been working with HTML5 as well, though haven’t worked on canvas yet. I wish to reblog this article, may i?

  2. Reblogged this on teknonics and commented:
    A great article on HTML5’s Canvas element by Ray Hammond. The HTML5 element is used to draw graphics, on the fly, via javascript.

  3. Hi,

    Thank you for your detailed explanation on how to draw this speed dial. Where can I find the reference to the function drawSpeedometerPart ? Thanks.

    Kumudini

  4. Ray, I Need to place two dials on a single canvas. How can I use the javacript in such a case?
    thanks

    1. Hi Jcole, The draw function obtains a handle to the canvas object and then uses this canvas to draw the speedometer. To have two speedometers on the same screen you could simply have two canvas elements with difference ids. Then if you modify the draw function to take the id of the canvas object it will then draw a speedometer on each.

  5. Is there any way to manage the speed of the needle? It is too fast. I am wishing for more dynamic effect like it slowly reaches to the value. (Pardon me for my ignorance, I am new to HTML).

    Thanks

    1. It should be simple to add the feature you require. I would simply add a timer that adjusts the current angle in small intervals until the needle reaches the desired location, at which point the timer should stop.

  6. hi ray..
    nice work..
    I want to caliberate the meter between values 10 to 40.
    and colors as: 10 to 18: red, 18 to 25 green, 25 to 30: yellow, and red again above 30
    please help to caliberate it…

    1. Hi KP, the calibration is simply a case of converting your values to the corresponding values along the semi-circle. For example the semi-circle cover 0-180 degrees and your range covers 0-30 (adjusted by 10 for the screen display). This, the 50% mark on your range would be 15 which is equivalent to the 90 degrees mark on the 180 degree arc – simple mathematical conversion.

      The colours are also easily altered, look at the 3 variable in the function drawSpeedometerColourArc.

    1. Yes, the needle can be changed dynamically. For example, you could connect to a server using an AJAX long wait, or, Web Socket – when the server has a new speed this can be passed from server to the client. Within the client you can simply called the function ‘draw(iSpeed)’.

  7. Hi, it’s a great article. Now, I’m making some alterations with needles. I want to change the length of needle – is there easy way to do that?

    1. Thanks for the feedback. The needle is drawn within the function drawNeedle(). This function references the variable gaugeOptions.radius. Adjusting the value of this will affect the length.

  8. Hey there. That article is really great, but i got a little problem. Always when i want to start it, i get the message “buildOptionsAsJSON is not defined”. What i have to do here to make it go away?

  9. Excellent blog you have here but I was wondering if you knew of
    any community forums that cover the same topics talked about
    in this article? I’d really like to be a part of community where I can get suggestions from other experienced individuals that share the same interest. If you have any suggestions, please let me know. Many thanks!

  10. Hi Ray, very informative article, thanks for sharing. I’ve managed to make the needle’s position change dynamically, but I’m having trouble recalibrating it to mange a higher range of speeds – say 10x what it is currently. Do you have any suggestions? The only real problem I seem to be having is with the math in the “convertSpeedToAngle” function, but I may be missing something somewhere else…. Anyway, great blog, thanks again!

  11. Hi! I could have sworn I’ve visited this web site before but after browsing through a few of the articles I realized it’s new to me.
    Anyhow, I’m definitely happy I stumbled upon it and I’ll
    be bookmarking it and checking back regularly!

  12. Regarding the positioning of the numbers… this can be done much easier.
    I set textBaseline to ‘middle’ and textAlign to ‘center’.

    1. To modify the range and thus the maximum value is simply a case of converting your values to the corresponding values along the semi-circle. For example the semi-circle cover 0-180 degrees and any new range covers 0-X (adjusted by 10 for the screen display). Thus, the 50% mark on your range would be x/2 which is equivalent to the 90 degrees mark on the 180 degree arc ā€“ simple mathematical conversion.

      1. thnx for reply. so if I need to make 80 to 2000, what values in the .js file I have to change?

    1. Simply modify the function drawTextMarkers to:

      function drawTextMarkers(options) {
      /* The text labels marks above the coloured
      * arc drawn every 10 mph from 10 degrees to
      * 170 degrees.
      */
      var innerTickX = 0,
      innerTickY = 0,
      iTick = 0,
      gaugeOptions = options.gaugeOptions,
      iTickToPrint = 80;

      applyDefaultContextSettings(options);

      // Font styling
      options.ctx.font = ‘italic 10px sans-serif’;
      options.ctx.textBaseline = ‘top’;

      options.ctx.beginPath();

      // Tick every 20 (small ticks)
      for (iTick = 10; iTick < 180; iTick += 20) {

      innerTickX = gaugeOptions.radius – (Math.cos(degToRad(iTick)) * gaugeOptions.radius);
      innerTickY = gaugeOptions.radius – (Math.sin(degToRad(iTick)) * gaugeOptions.radius);

      // Some cludging to center the values (TODO: Improve)
      if (iTick <= 10) {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY + 5);
      } else if (iTick < 50) {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX – 5,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY + 5);
      } else if (iTick < 90) {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY);
      } else if (iTick === 90) {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX + 4,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY);
      } else if (iTick < 145) {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX + 10,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY);
      } else {
      options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX + 15,
      (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY + 5);
      }

      // MPH increase by 10 every 20 degrees
      iTickToPrint += Math.round(2160 / 9);
      }

      options.ctx.stroke();
      }

  13. It is not working for me. What i might be missing here. I simply pasted the above code thats all. All i’m getting is an html form with default value 20 in it. Love your explanation btw.

    1. Thanks for the feedback. Rather than copy and pasting the code it would be better to download the code from my github repository. If you look at the last paragraph of the Blog you see the links to the source code and a working example. Hope that helps!

  14. Hey Ray thanx for the tutorial its a great and helped a lot to understand the canvas deeply but would like to know if there is a way to fit the arcs according to the reduction of the size.

    ie if the screen size is small the speedometer tends to fit itself to the screen.

    Thanx

  15. Thank you! I did a screen with 10 gauges, each one with different value source (ajax) and it works quickly, also the memory use is minimal.
    Thank you again and keep going.

      1. Hello,

        I need to covert this 180 degree semicircle to 270 degree circle , i tried doing the same via 1.5*math.pi in draw outer metallic and inner circle but it didnot work
        plz guide !!!!!!!

  16. Hi, I am sorry but I tried the codings but nothing comes out of my web page. So can I check with you if the tag are all in the script?

      1. for the Modified speedometer.js so the needle moves frame by frame between values…, do I just have to copy the all the codings into my document? But I dont understand the ‘+’ and ‘-‘ sign at the page as well as the different color. Please advise.
        Thanks.. =D

    1. The size is programmatically controlled. If you look at the main variables the width and height can be changed. But, I’ve not tested it to determine what side effects this will have!

    1. how do I change the color of the text markers from white to any other color?
      Also I am trying to make 4 speedometers. I changed the element id in both the html and Javascript page but it displays only the last speedometer. the other canvas remains blank.

    2. The function drawTextMarkers draws the text. You should add a line in this function to change the colour, I think ‘options.ctx.fillStyle = ‘blue’;’ should do it.

  17. I would like to change the text markers, so I modified:
    function drawTextMarkers(options)
    {
    var innerTickX = 0;
    var innerTickY = 0;
    var iTick = 0.0;
    var gaugeOptions = options.gaugeOptions;
    var iTickToPrint = 0.0;

    applyDefaultContextSettings(options);

    // Font styling
    options.ctx.font = ‘italic 10px sans-serif’;
    options.ctx.textBaseline = ‘top’;

    options.ctx.beginPath();

    // Tick every 20 (small ticks)
    for (iTick = 18; iTick < 90; iTick += 18)
    {
    innerTickX = gaugeOptions.radius – (Math.cos(degToRad(iTick)) * gaugeOptions.radius);
    innerTickY = gaugeOptions.radius – (Math.sin(degToRad(iTick)) * gaugeOptions.radius);

    options.ctx.fillText(iTickToPrint, (options.center.X – gaugeOptions.radius – 12) + innerTickX,
    (gaugeOptions.center.Y – gaugeOptions.radius – 12) + innerTickY + 5);

    // MPH increase by 10 every 20 degrees
    iTickToPrint += 18;
    }

    options.ctx.stroke();

    }

    But, it only draw the text markers until 54, instead of 90, why? how can I resolve it? thans

  18. I just started some web-development and needed a speedometer. I forked your git-hub project and modified your speedometer.js to support some of my needs (different sizes, more than one on the page, different numerical ranges (I’m using it as a percent visualizer so needed it to go from 0-100), I also wanted a larger color arc as I’m more interested in showing the needle is within a range than an actual value.) My modifications make it easier to customize (without changing the speedometer.js file so you can have different speedometers with different numerical ranges and only one copy of speedometer.js), scales to fit the canvas it is drawn in, centers itself within the canvas, and allows more than one speedometer on a page. My updated code is here:

    https://github.com/siannce/HTML5-canvas-projects/blob/master/speedometer/speedometer.js

    Thanks for the original!

  19. This very good article. I have some mathematics doubts. To calcule x and y i use the lines above. Why i need subtract in case of Y coordinate?

    Obs: I modify the code to try understand more.

    var x = options.center.X + Math.cos(iTickRad) * gaugeOptions.radius;
    var y = options.center.Y – Math.sin(iTickRad) * gaugeOptions.radius;
    var tox = options.center.X + Math.cos(iTickRad) * (gaugeOptions.radius – 18);
    var toy = options.center.Y – Math.sin(iTickRad) * (gaugeOptions.radius – 18);

    Thanks.

  20. what can be done to display the textmarkers inside instead of displaying above the ticks…

  21. Thanks for this post! This is awesome! You said that the code above could be used freely; would there be a note to the same effect somewhere for the code in the Git repository?

  22. Hi,
    I need help.

    I changed buildOptionsAsJSON
    function buildOptionsAsJSON(canvas,iSpeed,t) {
    /* Setting for the speedometer
    * Alter these to modify its look and feel
    */
    //var centerX = 100,
    alert(“in build”);
    var centerX = 0,
    centerY = 100,
    //radius = 140,
    radius = 70,
    //outerRadius = 200;
    outerRadius = 100;

    // Create a speedometer object using Javascript object notation
    return {
    ctx: canvas.getContext(‘2d’),
    speed: iSpeed,
    center: {
    X: outerRadius*t + 150,
    Y: centerY
    },
    levelRadius: radius – 10,
    gaugeOptions: {
    center: {
    X: outerRadius*t + 150,
    Y: centerY
    },
    radius: radius
    },
    radius: outerRadius
    };

    }

    but it not working.
    Please suggest.

  23. This is awesome. How can we make it where the speedometer isn’t adjusted 10 deg? I don’t get the point for that.

    1. To be honest I’m not sure what the point is either :-). I must have been feeling artistic the day I wrote the blog. The adjustment is present in the initialising loop variable. For example the function drawSmallTickMarks starts its loop at 10 degrees. Adjusting these will control were the arc starts

  24. Hi Ray,
    I need to make a speedometer which shows minutes from 0 to 5 and needle moves seconds by second.When I am putting my minutes variable in speedometer xval it is going directly from 0 to 1 to 2 and 3 and so on instead of going one by one from 1 to 2 i.e it is not moving in between

  25. Hey thanks for sharing it !
    Can we display to needles (with different colors, representing different data) on the same speedometer, at the same time ?

  26. Hi ! Thanks for sharing it šŸ™‚
    Is it possible to display two needles at te same time on the same speedometer (with different colors and data) ?

  27. Hello Sir,

    This thing already i implemented in android,but i wants to start the scale from 95 to 120.can you help..plz sir..

Leave a reply to geeksretreat Cancel reply