This site is a self-contained low down on what's going on in my life, what I'm working on, what I'm thinking about, and how I'm feeling about life in general.
Friday, 25 April 2008
I recently wrote about how to create a bar graph using the <canvas> tag in html. This was more of a theory article, so as promised, here is the substance.

Start by creating your html body as follows:
<body onload="buildChart();">
<div class="chart">
<div id="label_container" style="position: relative; margin: 20px auto; display: inline-block;">
<canvas id="canvas" width="400" height="200"></canvas>
</div>
</div>
<table id="mydata">
<tr> <th>Customers</th><th>Value</th> </tr>
<tr><td>Design</td> <td>2</td> </tr>
<tr><td>Enquiries</td> <td>5</td> </tr>
<tr><td>Home Screen</td> <td>1</td> </tr>
<tr><td> Navigation</td> <td> 4</td> </tr>
<tr><td> Projects</td> <td> 6</td> </tr>
<tr><td> To-do</td> <td> 9</td> </tr>
</table>
</body>
The table contains the data used to build the chart, this can be built dynamically which is really useful for reports etc.
Now lets start on the javascript to build our chart. Firstly, lets start working with our buildChart function.
function buildChart(){
// source data table and canvas tag
var data_table = document.getElementById('mydata');
var canvas = document.getElementById('canvas');
var td_index = 1; // which TD contains the data
var max_value = 0;
var ySegmentCount = 5;
Here, we've defined our variables for the table, the canvas and specified the column that contains the values for our graph. max_value will be set to the largest of the values, and ySegmentCount contains the number of values to be listed horizontally.
I've defined a fixed pallet for this example, however you can easily adapt this to use a dynamic pallet, I just like to control the colours used.
var myPallet = new Array();
myPallet[0] = "#2A416A";
myPallet[1] = "#39588E";
myPallet[2] = "#4572AD";
myPallet[3] = "#688BC3";
myPallet[4] = "#8EA6D2";
myPallet[5] = "#B2C3DF";
myPallet[6] = "#CCD8EA";
myPallet[7] = "#DDDBFB";
myPallet[8] = "#F0F0F0";
myPallet[9] = "#2A416A";
myPallet[10] = "#39588E";
myPallet[11] = "#4572AD";
myPallet[12] = "#688BC3";
myPallet[13] = "#8EA6D2";
myPallet[14] = "#B2C3DF";
myPallet[15] = "#CCD8EA";
myPallet[16] = "#DDDBFB";
myPallet[17] = "#F0F0F0";
Next we retrieve the chart values and calculate some parameters.
var tds, data = [], color, colors = [], value = 0;
var trs = data_table.getElementsByTagName('tr'); // all TRs
for (var i = 0; i < trs.length; i++) {
tds = trs[i].getElementsByTagName('td'); // all TDs
if (tds.length === 0) continue; // no TDs here, move on
// get the value, update total
value = parseFloat(tds[td_index].innerHTML);
if(value > max_value){ max_value = value; }
data[data.length] = value;
// Assign color
color = myPallet[i];
colors[colors.length] = color; // save for later
trs[i].style.backgroundColor = color; // color this TR
}
var itemCount = trs.length-1;
var yIncrement = max_value / ySegmentCount; //determine increment between y axis increments
var barWidth = (canvas.width - (10 * (itemCount * 2))) / itemCount;
var yIncrementPixels = (canvas.height - (2 * (ySegmentCount * 2))) / ySegmentCount;
We now know how wide to make the bars based on the canvas width and number of items, and also the distance between value markers.
We now need to prepare to draw our bar graph, we start by preparing the variables.
var ctx = canvas.getContext('2d');
var canvas_size = [canvas.width, canvas.height];
var backSoFar = 2;
var ySpanPosition = 0;
var yTotal = max_value;
backSoFar contains the vertical (y) start position of each background block. This starts at 2 to create a slight gap at the top. ySpanPosition contains the vertical position of the span containing the axis value.
Lets render the background.
for(i=0; i<ySegmentCount+1; i++){
if(i==ySegmentCount){
yTotal = canvas.height-8;
var labelContainer = document.getElementById('label_container');
var oSpan = document.createElement('span');
var oLabelText = document.createTextNode('0');
oSpan.setAttribute('style','left: -50px; top: '+yTotal+'px; position: absolute; font-size: 60%; z-index: 100; background: none; width: 45px; display: block; text-align: right;');
oSpan.appendChild(oLabelText);
labelContainer.insertBefore(oSpan, labelContainer.lastChild);
}else{
backSoFar += 2;
ySpanPosition = backSoFar -8;
ctx.fillStyle = '#E7EEF6';
ctx.fillRect(0,backSoFar,canvas.width,yIncrementPixels);
var labelContainer = document.getElementById('label_container');
var oSpan = document.createElement('span');
var oLabelText = document.createTextNode(yTotal.toFixed(2));
//var oLabelText = document.createTextNode('sdfsg');
oSpan.setAttribute('style','left: -50px; top: '+ySpanPosition+'px; position: absolute; font-size: 60%; z-index: 100; background: none; width: 45px; display: block; text-align: right;');
oSpan.appendChild(oLabelText);
labelContainer.insertBefore(oSpan, labelContainer.lastChild);
yTotal += -yIncrement;
backSoFar += yIncrementPixels;
backSoFar += 2;
}
}
Change the value of ctx.fillStyle to set the background colour. Now for our final block for javascript in this function, we render the bars themselves.
var sofar = 0; // keep track of progress
// loop the data[]
for (var piece in data) {
var percentHeight = (data[piece]/max_value)*100;
var pixelHeight = (canvas.height-4)*(percentHeight/100);
var barFloor = canvas.height - pixelHeight; //subtract bar height from canvas height to get ground level;
sofar += 10; //add left margin
//ctx.beginPath();
ctx.fillStyle = colors[piece]; // color
ctx.lineWidth = 4;
ctx.strokeStyle = '#FFF';
ctx.shadowBlur = 3;
ctx.shadowColor = "#999";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
ctx.strokeRect(sofar,barFloor,barWidth,pixelHeight);
ctx.shadowBlur = 0;
ctx.shadowColor = "#333";
ctx.fillRect(sofar,barFloor,barWidth,pixelHeight);
//Add labels
var xLabelPosition = sofar;
var yLabelPosition = canvas.height + 10; //add span height
var labelContainer = document.getElementById('label_container');
var oSpan = document.createElement('span');
var oLabelText = document.createTextNode(data[piece]);
//var oLabelText = document.createTextNode('sdfsg');
oSpan.setAttribute('style','left: '+xLabelPosition+'px; top: '+yLabelPosition+'px; position: absolute; font-size: 60%; z-index: 100; background: none; width: '+barWidth.toFixed(0)+'px; display: block; text-align: center;');
oSpan.appendChild(oLabelText);
labelContainer.insertBefore(oSpan, labelContainer.lastChild);
sofar += barWidth;
sofar += 10;
}
}
After calculating the positioning of the bar, we create 2 rectangles, the first is white and has drop shadow, the second is the coloured bar. Then we access the DOM to create the span labels and position them appropriately. The 10 value added to the sofar variable spaces the bars 10 pixels.
Finally, some css to make sure everything displays as required.
canvas { background: #fff; border-bottom: 1px solid #ccc; }
.chart {
position: relative;
display: block;
text-align: center;
background: #FFF;
padding-bottom: 10px;
padding-top: 10px;
}
span { font-family: Arial, Helvetica, sans-serif;}
So there we have it, a low bandwidth bar graph that looks great and is totally dynamic. I haven't had time to make the code as efficient as possible but it definitely does the job. I hope you find this useful.
Sunday, 13 April 2008
I recently wrote about how I'd modified a great example of creating a pie chart using the canvas tag and javascript. I've now written a bar graphing function based on the same principal of dynamically building the chart from tabulated data and building it to the size of the canvas tag.
This will form part of a charting component I'm writing, and although this example only builds a simple bar graph, the finished product will let you compare multiple bar values, plot trend lines, line graphs and scatter graphs. It's just a matter of doing it and making it more flexible.

I'll upload the source code once I've streamilined it a bit, the features are:
Source should be up later in the week, good night all.
Tuesday, 01 April 2008
I'm in the process of developing a project management web app for the iPhone / iPod Touch, and I wanted to build a canvas based charting component to reflect time spent, resource use, etc.
I knew I could code bar graphs quite easily, but pie charts were a different matter, it'd been a few years since I'd needed PI for anything, so I had a look around for some direction.
There are a couple of ready made solutions out there, notably plotKit, which I really liked the look of, but it is dependent on another framework. I wanted some bare bones code so that I could build upon it easily, and most importantly, understand what was happening at each stage.
I found this example, which was perfect, a well written article with clearly written code. I quickly incorporated it into my application, but wanted it to be more like the plotKit example in appearance, with some additional visuals that was absent from both examples. Only a small thing, but I wanted a line from just inside each segment of the pie, leading to the label. Below is the result of my labours.

It was quite a job to achieve but I'm happy to share how I got there.
Firstly though, a disclaimer, I have not gone through tidying up the code yet this is a rough and ready block of javascript. Now that's out the way, on to the code. You should insert this into the for loop where the segments are built.
//Attempt to get label position
var sliceStart = Math.PI * (2 * sofar);
var sliceEnd = Math.PI * (2 * (sofar + thisvalue));
var angle = (sliceStart + sliceEnd)/2;
// normalize the angle
var normalisedAngle = angle;
if (normalisedAngle > Math.PI * 2){
normalisedAngle = normalisedAngle - Math.PI * 2;
}else if (normalisedAngle < 0){
normalisedAngle = normalisedAngle + Math.PI * 2;
}
What I've done is define the start and end position of the slice, please note I've removed the -0.5 from the calculation for the labels, otherwise they appear at the wrong positions. I then add the two values and devide by two to find the mid point of the arc.
var labelx = center[0] + Math.sin(normalisedAngle) * (radius + 10);
var labely = center[1] - Math.cos(normalisedAngle) * (radius + 10);
var labelBack = '';
var labelAlign = '';
Now that I have the correct angle, I use sine and cosine to determine the x,y pixel reference for the labels.
if (normalisedAngle <= Math.PI * 0.5) { //Top Left
labelx = (labelx - 10) + "px";
labely = labely + "px";
labelBack = 'none';
labelAlign = 'right';
}
else if ((normalisedAngle > Math.PI * 0.5) && (normalisedAngle <= Math.PI)) { //Bottom Right Quarter
labelx = (labelx + 15) + "px";
labely = labely + 10 + "px";
labelBack = 'none';
labelAlign = 'left';
}
else if ((normalisedAngle > Math.PI) && (normalisedAngle <= Math.PI*1.5)) { //Bottom Left Quarter
labelx = (labelx - 30) + "px";
labely = (labely + 10 )+ "px";
labelBack = 'none';
labelAlign = 'right';
}
else { //Top Right
// text on top and align right
labelx = (labelx - 20) + "px";
labely = (labely) + "px";
labelBack = 'none';
labelAlign = 'left';
}
Depending on which quarter of the pie chart the label falls into the is some adjusting to be done, see comments to understand which quarter is being addressed. I also included a variable for background colour and another one for text direction.
//Create Label
var percentage = thisvalue * 100;
var labelContainer = document.getElementById('label_container');
var oSpan = document.createElement('span');
var oLabelText = document.createTextNode(percentage.toFixed(2)+'%');
oSpan.setAttribute('style','left: '+labelx+'; top: '+labely+'; position: absolute; font-size: 60%; z-index: 100; background:'+labelBack+'; width: 45px; display: block; text-align: '+labelAlign+';');
oSpan.appendChild(oLabelText);
labelContainer.insertBefore(oSpan, labelContainer.lastChild);
//End of create label
Finally I output the labels to a div that surrounds my canvas tag, and hey presto, the tags are on the form. Note that I've positioned the elements absolute, therefore the enclosing div tag must be set to position: relative. So that's the labels out of the way, now I render the pie segment. Now, after that the relatively simple task of adding the indicator lines.
// Draw label line
var labelLineFromX = center[0] + Math.sin(normalisedAngle) * (radius * 0.9);
var labelLineFromY = center[1] - Math.cos(normalisedAngle) * (radius * 0.9);
var labelLineToX = center[0] + Math.sin(normalisedAngle) * (radius + 10);
var labelLineToY = center[1] - Math.cos(normalisedAngle) * (radius + 10);
ctx.beginPath();
ctx.shadowBlur = 0;
ctx.moveTo(labelLineFromX,labelLineFromY);
ctx.lineTo(labelLineToX,labelLineToY);
ctx.lineWidth = 1;
ctx.strokeStyle = '#000';
ctx.stroke();
To get the start point, I go one tenth into the segment, and go 10 pixels outside of it, adjust the values to suit your needs.
That's it, simple looking back at it but took me a couple of days tinkering at it getting it the way I wanted it. Feel free to use this code as you will, and my sincerest thanks to the guy who came up with the original code, you saved me a lot of work, and this isn't meant as disrespect, merely a desire to customise and take your work further.