Navigation Menu
loading content ...
Error Message
Macro 'toolbar' not defined, or not allowed to call [restricted_mode=False]
Rated /5.00 | Created 06 November 2008
I've been doing some work with the canvas object and wanted to briefly share my experience. Basically I have been drawing polygons and trying to make each of those polygons clickable something which canvas does not support out of the box.

Do this I accepted I would need a way of identifying a polygon. To do this I just wrapped the fundamental data used to draw the polygon - the coordinates - into a javascript object which also carried an id. The id would also represent the order in which it was drawn - ie. the first polygon to be drawn has id 0, the 5th has id 4 and so on... eg..  {coords:[x1,y1,....], id:0}  - these will also need to be stored in an object memory (more on this later) so they are all accessible and referencable.

The first method I tried then to make the canvas clickable was using the ancient tag. This allows you to add tags to specify clickable areas on an image. So if I have one flat image with 2 buttons, one of which I want to take to a contact me page and another I want to take the user to the homepage, I define these areas and associate them with different clicks. Well this was a tried and tested method, so I gave it a go with canvas. To do this, I popped a transparent image over the top of the canvas and associated it with an image map which I created dynamically as I drew the polygon. I'd recommend NOT doing this. Since the tag is ancient - it gives a big kick to performance, however if your wanted something relative simple ie. 4 polygons that you want clickable feel free to give it ago.

Without the map tag rendering was much quicker however, it just wasn't clickable which begged the question - how do I make it clickable without producing a big dent in my performance. Well it turns out that the canvas supports a bizarrely named function "isPointInPath" which well tell you if a given x and y coordinate is in the polygon last drawn. Only downside of this is you can only run this test as you draw it. No big deal.. this is doable. We just have to iterate through all the polygons we have drawn on the screen - which we can do as we have that lovely variable memory from earlier.

However now after drawing the polygon we run a test on a given mouse x and y coordinate to see if the coordinate is present in the polygon.
function drawPolygon(poly,x,y){
    this.canvas = document.getElementById(canvas.id);
    this.ctx = this.canvas.getContext('2d');
            for(var i=0; i < poly.transformedCoords.length-1; i+=2){
                var x =parseFloat(poly.coords[i]);
                var y=parseFloat(poly.coords[i+1]);
                this.ctx.lineTo(x,y);
            }
            //connect last to first
            this.ctx.lineTo(poly.coords[0],poly.coords[1], poly.coords[poly.coords.length-2],poly.coords[poly.coords.length-1]);
            this.ctx.closePath();
            
            this.ctx.stroke();

return this.ctx.isPointInPath(x,y);
}

Using the above operation we can thus find the polygon where the x any y coordinate sit in, and return the polygon and do something else with it. We could change the fill colour for instance and just redraw that polygon. However this is still not ideal. Imagine 1000 polygons - we'd have to check each of those 1000 till we find a hit. We're going to need some optimisations.

The easiest one I can think of is by using grids. Each polygon, no matter what shape can be contained by a box. The box can be made of 4 values - x1, x2 and y1 and y2, where x1 is the furthest point on the left for a given polygon and x2 is the furthest to the right. When drawing a polygon, we can keep a record of this and store it into our polygon object as an attribute for instance poly.grid.x1, poly.grid.x2... etc..
So our polygon object now looks a bit like this {id: 0, coords:[x1,y1...xn,yn], grid: {x1:0, x2:50 y1:10 y2:50}.
It is now much quicker to iterate through all the shapes and return only the shapes where for a given x and y mouse coordinate x >= g.x1 && x <= g.x2 && y >=  g.y1 && y <=g.y2  where g is the grid attribute of the shape. We now only have to redraw in instances where two polygons are in close proximity that their "grids" overlap.

It's the best solution I've come up with so far but it's not without it's weaknesses.. it seems the ExplorerCanvas does not support the isPointInPath operation which means less accuracy in IE (well until I work out an algorithm that does isPointInPath for me).

Watch this space and I'll share some code on this soon...

no ie support

Thanks for reading this far. Did I bore you or interest you? Let me get better at doing the latter and focus more on working on the good stuff...
Error Message
Macro 'ratemytiddler' not defined, or not allowed to call [restricted_mode=False]
Comments
Error Message
Macro 'tiddlyWebComments' not defined, or not allowed to call [restricted_mode=False]
a list of older blog posts can be found at here

Please ignore the following if you are using a text browser (this is part of the TiddlyWiki application used to run this software):