Home > HTML5, JavaScript, Programming > HTML5 Canvas and Dashed Lines

HTML5 Canvas and Dashed Lines

September 7, 2010

I’ve been spending a lot of time with HTML5′s new Canvas tag, and I have to say, I’m quite excited with it. The gratification you can get with a small set of code rendering some images is great! It’s nothing like what you need to get going in C++ and OpenGL or even XNA – pure, simple, and of course, very limited.

Canvas is currently so limited that you cannot even draw a simple dashed line! Straight lines, no problem.

This is clearly a problem that needs fixing and luckily enough, I have just a solution. :)

So, how might we go about adding this functionality. Quite simply! Thanks to the ability for JavaScript’s prototypical nature, and maybe more accurately, it’s dynamic nature, we can extend the functionality of the Canvas object so that we can make our new functionality look like it’s built-in.

Ok, so how do we get started? First we need to define our protype code. I’ve created a new JavaScript file called canvas.js to where we’ll put our code.

CanvasRenderingContext2D.prototype.dashedLineTo = function (from, to, pattern) {}

That adds the ‘dashedLineTo’ function to our canvas protype.

Next we need to actually implement the function. I’m not going to actually go through and explain it all, but the idea of the code is to use the pattern parameter to define the length of each dash and each space, in alternating order, for your dashed line.

CanvasRenderingContext2D.prototype.dashedLineTo = function (fromX, fromY, toX, toY, pattern) {
  // Our growth rate for our line can be one of the following:
  //   (+,+), (+,-), (-,+), (-,-)
  // Because of this, our algorithm needs to understand if the x-coord and
  // y-coord should be getting smaller or larger and properly cap the values
  // based on (x,y).
  var lt = function (a, b) { return a <= b; };
  var gt = function (a, b) { return a >= b; };
  var capmin = function (a, b) { return Math.min(a, b); };
  var capmax = function (a, b) { return Math.max(a, b); };

  var checkX = { thereYet: gt, cap: capmin };
  var checkY = { thereYet: gt, cap: capmin };

  if (fromY - toY > 0) {
    checkY.thereYet = lt;
    checkY.cap = capmax;
  }
  if (fromX - toX > 0) {
    checkX.thereYet = lt;
    checkX.cap = capmax;
  }

  this.moveTo(fromX, fromY);
  var offsetX = fromX;
  var offsetY = fromY;
  var idx = 0, dash = true;
  while (!(checkX.thereYet(offsetX, toX) && checkY.thereYet(offsetY, toY))) {
    var ang = Math.atan2(toY - fromY, toX - fromX);
    var len = pattern[idx];

    offsetX = checkX.cap(toX, offsetX + (Math.cos(ang) * len));
    offsetY = checkY.cap(toY, offsetY + (Math.sin(ang) * len));

    if (dash) this.lineTo(offsetX, offsetY);
    else this.moveTo(offsetX, offsetY);

    idx = (idx + 1) % pattern.length;
    dash = !dash;
  }
};

To use this code, just include your new canvas.js script file in your .html file in a script tag, and voila!

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

If you know of a better way to do this, be sure to let me know!

Update: So, maybe I should have tested that version. :) Updated the code to actually work and not rely on some other pieces of a library I’m working on.

About these ads
Categories: HTML5, JavaScript, Programming
  1. jacorb effect
    September 21, 2010 at 2:33 pm

    I would be nice to see an example of a pattern instance and an explanation of it in more detail. Reading the code, I see pattern[idx] and pattern.length so I assume pattern is an array that contains numbers. Each number represents either a line or gap. So it would go something like…
    - draw a line of length pattern[0]
    - leave a gap of length pattern[1]
    - draw a line of length pattern[2]
    - leave a gap of length pattern[0] (assuming the pattern array had 3 elements)
    - draw a line of length pattern[1]
    - etc.

  2. jacorb effect
    September 21, 2010 at 2:48 pm

    Don’t you need a beginPath and stroke in there as well?

  3. jacorb effect
    September 21, 2010 at 2:50 pm

    I got this code working. Nice! Thanks.

    I didn’t attach it to the canvas object itself but I have a renderer class that I use instead. I attached it to that instead and replaced references of this to ctx and passed the ctx in as a parameter.

  4. October 16, 2010 at 1:20 pm

    Thanks, Works great! (thanks to @jacorb effect for the explanation!)

  5. Kraven
    October 21, 2010 at 11:39 pm

    Is there a license to the code you have written? I can’t find anything about if it is free to use and how it is, if I want to use that code for a commercial product.

  6. November 1, 2010 at 12:04 am

    here’s a very basic dotted arc function:

    CanvasRenderingContext2D.prototype.dottedArc = function(x,y,radius,startAngle,endAngle,anticlockwise) {
    var g = Math.PI / radius / 2, sa = startAngle, ea = startAngle + g;
    while(ea < endAngle) {
    this.beginPath();
    this.arc(x,y,radius,sa,ea,anticlockwise);
    this.stroke();
    sa = ea + g;
    ea = sa + g;
    }
    };

  7. Eric Juvet
    November 20, 2010 at 9:04 pm

    Yes, could you please show us what you used for the pattern parameter? Is it a byte-specified pattern, or is it an image?

  8. January 11, 2011 at 10:42 pm

    Thanks dude, You saved me an hour.I a using this code for my javascript library
    Thanks once again

  9. Markus Heinzer
    February 16, 2011 at 7:34 am

    use [length of dash in px, length of gap in px] for pattern.
    For example:
    ctx.dashedLineTo(100,100,780,178,[5,5]);

  10. April 16, 2011 at 2:51 am

    Thanks for posting that – works excellently. I made a minor modification: a default pattern if none is supplied.

    Hard to believe this isn’t part of the canvas standard.

  11. Per Holmäng
    May 20, 2011 at 5:29 am

    Note that this will crash IE versions < 9 since they lack canvas-support. To fix this, surround the function with the following if-statement:

    if (typeof CanvasRenderingContext2D !== 'undefined') {
    CanvasRenderingContext2D.prototype.dashedLineTo = function (fromX, fromY, toX, toY, pattern) {
    // code
    }
    }

  12. steven
    September 21, 2011 at 2:26 am

    thanks for the post – I needed to create a consistent dotted freehand line and this got me started…
    code in case anyone needs it:

    with(ctx) {
    beginPath();
    // draw dotted line if you’ve moved more then 3 pixels
    if((e.pageX-offset.left)>(this.X+3) || (e.pageY-offset.top)>(this.Y+3) || (e.pageX-offset.left+3)<this.X || (e.pageY-offset.top+3)<this.Y)
    {
    dashedLineTo(this.X, this.Y,e.pageX-offset.left, e.pageY-offset.top,[1,5]);
    strokeStyle = this.color;
    stroke();
    this.X = e.pageX-offset.left;
    this.Y = e.pageY-offset.top;
    }
    }

  13. October 4, 2011 at 4:53 pm

    My suggestion

    function dash(ctx, points) {

    var pattern = [20, 5];
    var cnt = 0;
    var b = 0;

    var prev = points.shift();

    ctx.moveTo(px, py);

    while (points.length > 0) {
    var cur = points.shift();

    var px = prev.x;
    var py = prev.y;

    var dx = cur.x – prev.x;
    var dy = cur.y – prev.y;

    var len = Math.sqrt(dx * dx + dy * dy);

    while (b < len) {

    b += pattern[cnt % pattern.length];

    var d = (b = len) { b -= d; break; }

    cnt += 1;
    }

    prev = cur;
    }
    }

  14. October 4, 2011 at 4:57 pm

    OK not sure of the magic to get code into a post .. email me and I give you the full contents

  15. aoeu
    May 31, 2012 at 12:28 pm

    jacorb effect :
    Don’t you need a beginPath and stroke in there as well?

    No, this should NOT have beginPath and stroke — a function like dashedLineTo() should behave just like a drop-in replacement to lineTo() and the user should manually issue the beginPath and stroke commands.

  16. October 5, 2012 at 9:15 am

    jacorb effect :
    I got this code working. Nice! Thanks.
    I didn’t attach it to the canvas object itself but I have a renderer class that I use instead. I attached it to that instead and replaced references of this to ctx and passed the ctx in as a parameter.

    This comment was almost helpful for this Javascript newbie. I think the solution is the renderer class. However, I don’t know how to define / use the renderer class. Can someone help?

  17. October 5, 2012 at 4:42 pm

    With experimentation and hints from jacorb, I found the answer. Here’s the modification of the function. First I changed the defininition to include “ctx” as the first parameter

      function (ctx, fromX, fromY, toX, toY, pattern) {
    

    In the body of the function definition, this.moveTo (2 places) and this.lineTo were changed to ctx.moveTo and ctx.lineTo.

    I called the function with:

            canvas.beginPath();
            canvas.dashedLineTo(canvas,
              [var.stub_width],[var.top],[var.stub_width],[var.bottom],[15,10]);
            canvas.stroke();
    

    I might note that the parameters to dashedLineTo are TBS (tinybutstrong) variables.
    HTH

  18. March 22, 2013 at 4:33 am

    The code can get into an infinite loop for near-horizontal lines.

    Here’s a possible fix:
    // make sure we don’t get an infinite loop drawing eg
    // y = -7.85046229341888E-17x
    var EPSILON = 0.00000001;

    var lt = function (a, b) { return a = b – EPSILON; };

    Many thanks for the original code!

  19. March 22, 2013 at 5:05 am

    oops,a bit got lost:

    var gt = function (a, b) { return a >= b – EPSILON; };

  1. No trackbacks yet.
Comments are closed.
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: