Wednesday, March 2, 2011

HTML5 Animated Graph / First Post

This blog is intended to be a repository of things I want to remember.

I typically use a journal for that purpose, but considering all I've gained from others sharing their experiences and code on the internets, I feel like I should share some back.

First up, here's a simple animated graph done using HTML5. I used to do a lot of graph writing in a former life but HTML5 beats the hell out of doing it in Java. I'm not sure which browsers besides Safari will render it (I know my version of Firefox didn't). Feel free to use it for whatever - there's better examples out there but this should be a decent starting place for anybody trying to use HTML5 to generate a graph. Just cut-n-paste the code below into an HTML file and it should run in Safari or other HTML5-enabled browsers.

Here's what the rendered graph should look like:



And here's the code:

<!--- Simple HTML5 Line Percentage Chart --->

<canvas width=320 height="170"></canvas>

<script>

var context = document.getElementsByTagName('canvas')[0].getContext('2d');

// fill the background (if desired)
//context.fillStyle = 'rgba(0,0,200,0.1)';
//context.fillRect(0, 0, context.canvas.width, context.canvas.height);

// sample data
var data = new Array();
for (i=0; i <= 11; i++){
        data[i] = 100 * Math.random();
}

// create some room for the margins
var xmargin=context.canvas.width * .12;
var ymargin=context.canvas.height * .15;

// create my own rect structure
function rect(x, y, w, h)
{
   this.x = x;
   this.y = y;
   this.w = w;
   this.h = h;
}

// create the "innerRect" where most of the drawing occurs
var ir = new rect(xmargin, ymargin, context.canvas.width-(1.5*xmargin), context.canvas.height - (2*ymargin));

// translate the drawing to be from the top left corner of the inner rect
context.translate(ir.x, ir.y);

// scale the Y to the Y axis (0 - 100)
context.scale(1, ir.h/100.0);

function doTitles() {
        var leftTitle = "Left Title";
        var topTitle = "The Top Title";

        context.fillStyle = 'rgba(0,0,0,.5)';
        context.strokeStyle = 'rgba(0,0,0,.5)';

        // left
        context.save();
        context.rotate((Math.PI/180.0) * -90.0);
        context.textAlign = "center";
        context.textBaseline = "middle";
        context.font = "10px arial";
        context.fillText(leftTitle, -50, -30);
        context.strokeText(leftTitle, -50, -30);
        context.restore();

        // top
        context.font = "12px arial";
        context.textAlign = "center";
        context.textBaseline = "bottom";
        context.fillText(topTitle,ir.w/2.0,0);
        context.strokeText(topTitle,ir.w/2.0,0);
}
doTitles();


// set up an inner margin so we don't crowd the left and right sides
var innermargin = 5;

// compute the x-offset between adjacent plotted points.
var offset = (ir.w-(2*innermargin)) / (data.length-1);

// initialize our data pointers
var ptr = 0;
var lastX = innermargin;
var lastY = 100-data[ptr];
ptr = 1;



function drawGrid() {
   context.save();

   var y = 100;

   // draw the outer border
   context.lineWidth = 1;
   context.strokeRect(0,0,ir.w,100);

   context.font = "10px arial";

   for (i=0; i <= 10;i++){
          context.beginPath();

        // draw Y axis tics.  For even-numbered tics, draw them longer and add a label
           context.strokeStyle = 'rgba(0,0,0,.3)';
        var x = -2;
        var x1 = 0;
        if ((i % 2) == 0){
           x = -5;
           x1 = 3;
        }
        context.moveTo(x, y);
        context.lineTo(x1, y);
           context.stroke();

        if ((i % 2) == 0){
                context.fillStyle = 'rgba(0,0,0,.5)';
                context.strokeStyle = 'rgba(0,0,0,.5)';
                context.textAlign = "right";
                context.textBaseline = "middle";
                context.fillText((i*10),0-7,y);
                context.strokeText((i*10),0-7,y);
        }

        // draw an alternating "band" effect.
        if ((i % 2) == 0 && i != 10){
             context.fillStyle = 'rgba(100,100,100,0.1)';
              context.fillRect(0, y-10, ir.w, 10);
        }
        y -= 10;
   }
        
   // now draw the x-axis tics and labels. 
   var x = innermargin;
   var y = 100;

   // if we have more than 20 tics, we're only going to show every 5th one (and also the first one)
   var domod5 = 0;
   if (data.length > 20)
        domod5 = 1;

   for (i=0; i < data.length;i++){
          context.beginPath();
        context.moveTo(x, y-2);
        context.lineTo(x, y+2);
           context.stroke();

        if (domod5 == 0 || domod5 && ((i+1)%5) == 0 || i == 0){
                context.fillStyle = 'rgba(0,0,0,.5)';
                context.strokeStyle = 'rgba(0,0,0,.5)';
                context.textAlign = "center";
                context.textBaseline = "top";
                var tx = (i+1)+"";

                context.fillText(tx,x,y+2);
                context.strokeText(tx,x,y+2);
        }
        x += offset;

    }
   context.restore();
}
drawGrid();



// draw a line segment - we'll use the setInterval to animate
function drawLine() {
   if (ptr == data.length){
        clearInterval(drawLine);
        return;
   }
   context.save();

   context.beginPath();
   context.lineWidth = 2;
   context.lineJoin = "miter";
   context.lineCap = "round";
   context.moveTo(lastX, lastY);
   lastX = lastX + offset;
   lastY = 100 - data[ptr];
   context.lineTo(lastX,lastY);
  // context.bezierCurveTo(lastX-(offset/2.0),lastY+25,lastX-(offset/2.0),lastY-25,lastX,lastY);

   context.strokeStyle = 'rgba(0,250,0,1)';
   context.shadowOffsetY = 0;
   context.shadowBlur = 12;
   context.stroke();
   context.restore();

   ptr++;
}
setInterval(drawLine, 50);

 

</script>

No comments:

Post a Comment