Saturday, February 27, 2010

Draw Lines, Circles and Dots with JavaScript

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.

Wednesday, February 24, 2010

News Feeds JavaScript Widget

I was playing with Jquery UI, again. Few time I have a chance to read about it and couple of times I made some simple widget, but never had a chance to make something useful!

So here is finally something that can be used in any web site. It is news feed, or should I say a billboard, like one we can see on streets. It has ability to automatically rotate feeds one by one or if you move mouse over it, then it will wait till you ether use mouse wheel or click on next/previous button.

It depends on two additional JavaScript libraries, except JQuery. Frist one is JQuery UI and second is jquery.mousewheel. Both are free and can be found at http://jquery.com/.

Finally, here it is:

Example title 1

slicica1 Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia

Example title 2

slicica1 Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia

Example title 3

slicica1 Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia

Example title 4

slicica1 Lorem ipsum dolar sit amet doloremque voluptatem veritatis sed aut iste quia aut sit quia sit consequuntur ut quae sit quia quia

If you like this widget, code is avalivable at google code web site http://code.google.com/p/jq-ui-widgets/ or click here

Tuesday, February 23, 2010

Prototyping JavaScript

Maybe most annoying thing in JavaScript is lack of string functions. Yes, there is functions but they are basic... give me char, replace it ... but first thing that came up to my mind was "Hey, it would be nice to have printf(...) function in JavaScript!". And next thing was ... you are guessing... googling :).

But that is few line of code, and for those of you who are used to C#, but not just for you ;), here is the snippet that can be helpful :

String.prototype.format = function(){
result = this;
for( var i = 0; i < arguments.length; i++ ) {
result = result.replace("{" + i + "}", arguments[i]);
}
return result;
};

And finally, how to use it:

"Hello, {0}, this is {1} formated message. See you later, {0}!".format("Peter", "JavaScript");