I read about HTML5 canvas. Handy element, but what if I do not have browser that support HTML5. So I played a bit with dom elements and absolute position. Lets see what we can do with those.
First thing is that we need object that know how to draw on something:
// div as canvas
var canvas = document.getElementById("canvas");
// draws point (a small div 1x1 px) with absolute position on x,y coordinate
// of canvas
var drawPoint = function(x,y){
var dot = document.createElement("DIV");
dot.className = "dot"; // this is usual stuff like line-height, height etc.
dot.style.top = x;
dot.style.left = y;
canvas.appendChild(dot);
}
At this point we need to define a css class for our dot and for canvas too.
.dot {
position: absolute;
display: block;
font-size: 1px;
line-height: 1px;
background: #000;
margin: 0;
padding: 0;
height: 1px;
width: 1px;
}
#canvas{
position: relative;
width: 310px;
height: 300px;
overflow: hidden;
border: 1px solid;
}
Now, we can draw a line, below is code that will draw line from point A(120,120) to point B(200,200).
document.load = function(){
// draw line from point A(120,120) to point B(200,200)
for(var i=120; i< 200; i++){
drawPoint(x,x);
}
};
Ok, that was easy! So lets make this script more flexibile, we will make extension methods for jQuery object, so it can contain drawPoint, drawLine, drawArc, drawCircle methods. We almoust have drawPoint method, and that will be our basic method for all other methods for our extension. second method, drawLine, is a bit comlicated, and I will try to explain my aproach below, but first, here is actual method for drawing point in canvas, and just to mention now, I will use String.format() method, I explaned in my earlier blog post Prototyping JavaSctipt, so don't panic if you see {0} in some string :). Now, the drawPoint method:
$.fn.extend({
drawPoint:function(x,y){
if (!this._validateCoords(x, y)){
return this;
}
var dot = this._dotTemplate.format(x,y);
$(this).append(dot);
return this;
},
/* private */
_dotTemplate : '<div class="dot" style="left:{0}px;top:{1}px;"> </div>',
_validateCoords: function(x,y){
if (x < 0 || y < 0) {
alert("Negative coordinares are not supported!");
return false;
}
return true;
}
});
I added private method _validateCoords, because it is insane to draw something which you can not see in browser. For any negative x or negative y, alert window will popup with message "Negative coordinares are not supported!". We could made this possible with extra script and another div wich owerflow is hidden and could contain canvas div. But I am to lazy right now :) Maybe some other time. Now, drawLine method:
$.fn.extend({
...
drawLine: function(fromX, fromY, toX, toY){
// only positive coords x and y are valid
if (!this._validateCoords(fromX, fromY)){
return this;
}
if (!this._validateCoords(toX, toY)){
return this;
}
// linear function is y = k*x + n
var k = (toY - fromY)/(toX - fromX);
var n = fromY - k * fromX;
// needed for lines that has |k| > 1 or you will got dots in line
var stepMultiplier = 1;
if ( isFinite(k) && Math.abs(k) > 1 ){
stepMultiplier = Math.round(k);
}
// for finite |k|
if (isFinite(k)){
var startFrom = (fromX < toX)? fromX: toX ;
var endTo = (fromX > toX)? fromX: toX ;
for(var x = startFrom * stepMultiplier; x <= endTo * stepMultiplier; x++){
var y = k * x/stepMultiplier + n;
this.drawPoint(x/stepMultiplier ,y);
}
}else{
// for infinite |k| x is calculated not y
var startFrom = (fromY < toY)? fromY: toY ;
for(var y = 0; y <= Math.abs(fromY - toY); y++){
this.drawPoint(fromX , y + startFrom );
}
}
return this;
},
...
});
As you can see, there is no much magic there. We used linear function to drow a line in canvas. Everyone who had a math in a school, have had see this expression y = k*x +n which is how we mathematicaly presents any linear function. In our case whe only have a scope of a line, which is defined from method input A(fromX, fromY) and B(toX, toY) points. So, first thing we need is to calculate k and n constants from those. We can do that with this expression k = (toY - fromY)/(toX - fromX) and n = fromY - k * fromX. Second, we need to check if k is finite number or not. If we don't, we will end up with one dot for infinite k. We need diffrent aproach of drawing points for this case. And final, in cases when |k| > 1, there is one issue with diffrence in progresion of x and y calculated coords, which are integer numbers don't forget that! we could end up with dotted line, which is not good for us, we need solid line. Pay antention to stepMultiplier variable, that is how we got those micro steps (0.1, 0.01 etc), so integer numbers are no problem for us anymore. In one of my next blog posts I will talk how to do Anti Aliasing, so our lines could be more smother when they are drawn in canvas. I hope, this has explaned code above, so we could cotinue to next method, drawArc.
$.fn.extend({
...
drawArc : function (centerX,centerY, radius, fromAngle, toAngle) {
if (!this._validateCoords(centerX, centerY)){
return this;
}
for (var angle = fromAngle * radius; angle <= toAngle * radius; angle++){
var x = Math.cos(angle/radius) * radius;
var y = Math.sin(angle/radius) * radius;
x = x + centerX ;
y = y + centerY ;
this.drawPoint(x,y);
}
return this;
},
drawCircle : function(centerX,centerY, radius) {
return this.drawArc(centerX, centerY, radius, 0, 2*Math.PI);
},
...
});
As you can see, drawCircle is same as drawArc, because we need second to draw arc for full 360 degrees. We used radisus as scale factor for same issue we had in drawLine method to get solid line. I think the rest of code is self explaining, so I will jump to example right a way.
$(document).ready(function(){
$("#canvas").drawCircle(80,120, 40)
.drawArc(80,120, 60, 0, 3 * Math.PI / 2 )
.drawLine(80,60,300,60)
.drawLine(300,60,140,120)
.drawLine(140,120,140,220)
.drawLine(20,120,20,220)
.drawLine(300,150,140,220)
.drawLine(300,60,300,150)
.drawArc(80,220, 60, 0, Math.PI );
});
Drop this code in html page and you will get drawing like below.
If you see blank box above, just enter to this blog entry to see drawing. Thant is all folks for tihs post, see you next time.