{"id":589,"date":"2011-01-26T20:30:21","date_gmt":"2011-01-26T20:30:21","guid":{"rendered":"http:\/\/www.nickbennett.co.uk\/?p=589"},"modified":"2025-06-11T15:41:34","modified_gmt":"2025-06-11T15:41:34","slug":"html5-dynamic-charting","status":"publish","type":"post","link":"https:\/\/blog.nickbennett.co.uk\/index.php\/2011\/01\/26\/html5-dynamic-charting\/","title":{"rendered":"HTML5 Dynamic Charting"},"content":{"rendered":"<p>I recently attended an HTML5 workshop held by<br \/>\n  <a\n    title=\"HTML 5 Guru\"\n    href=\"http:\/\/html5doctor.com\/\"\n    target=\"_blank\"\n    rel=\"noopener\"\n    >Mr Remy Sharp<\/a\n  >. I was the only honest schmo in the room to admit to having already<br \/>\n  purchased the HTML5 book on offer. I was waiting for the free T-shirt that<br \/>\n  never transpired \ud83d\ude41 The workshop itself was really interesting and I left with<br \/>\n  the usual &#8216;I&#8217;m going to change the world with this new knowledge&#8217; excitement.<br \/>\n  A few months have passed and I&#8217;ve finally got around to writing this blog (the<br \/>\n  changing the world part will have to wait).<\/p>\n<p>The part of the day that really got my juices flowing was the Canvas tag<br \/>\n  stuff. It&#8217;s perhaps embarrassing to admit but I&#8217;d not had any dealings with it<br \/>\n  beforehand. The bit that really blew my top was the traversing through each<br \/>\n  pixel of a transposed image on the canvas and altering each one. Simply<br \/>\n  amazing. During my lunch hours at work, I wanted to investigate the Canvas tag<br \/>\n  in more depth. I wanted to create a simple table of data, that using<br \/>\n  JavaScript, could be used to generate a simple line graph. I wasn&#8217;t even sure<br \/>\n  if it was possible.<\/p>\n<p>Just a quick mention of<br \/>\n  <a\n    title=\"Zen Coding\"\n    href=\"http:\/\/code.google.com\/p\/zen-coding\/\"\n    target=\"_blank\"\n    rel=\"noopener\"\n    >Zen-Coding<\/a\n  ><br \/>\n  which I was also introduced to at the Workshop. Imagine being able to write<br \/>\n  entire blocks of HTML shorthand and then convert them at the press of a<br \/>\n  button. So<\/p>\n<p><code>div.class-name<\/code> <strong>becomes<\/strong><br \/>\n<code>&lt;div class=\"class-name\"&gt;&lt;\/div&gt;<\/code>and<\/p>\n<p><code>ul&gt;li*3<\/code> <strong>becomes<\/strong><br \/>\n<code>&lt;ul&gt;&lt;li&gt;&lt;\/li&gt;&lt;li&gt;&lt;\/li&gt;&lt;li&gt;&lt;\/li&gt;&lt;\/ul&gt;<\/code><br \/>\n(Tabbed in reality)Back to the point&#8230;<\/p>\n<p><strong>Generating a line graph on the fly<\/strong><\/p>\n<p>The final example can be seen<br \/>\n  <a\n    title=\"View the final result\"\n    href=\"http:\/\/www.nickbennett.co.uk\/custom\/canvas_chart2.html\"\n    target=\"_blank\"\n    rel=\"noopener\"\n    >here<\/a\n  >. I started off with a basic table of data within my HTML5 document.<\/p>\n<table id=\"data\">\n<tbody>\n<tr>\n<th>Day<\/th>\n<th>Jokes Learnt<\/th>\n<th>Girls Numbers<\/th>\n<\/tr>\n<tr>\n<td>Day 1<\/td>\n<td>5<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>Day 2<\/td>\n<td>7<\/td>\n<td>1<\/td>\n<\/tr>\n<tr>\n<td>Day 3<\/td>\n<td>10<\/td>\n<td>3<\/td>\n<\/tr>\n<tr>\n<td>Day 4<\/td>\n<td>20<\/td>\n<td>4<\/td>\n<\/tr>\n<tr>\n<td>Day 5<\/td>\n<td>40<\/td>\n<td>7<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>Here we are studying the highly scientific effect of comedy on the fairer sex.<br \/>\n  The next thing I added was the container for my X and Y labels and a key<br \/>\n  holder which explained what the line represented.<\/p>\n<pre>\n<code>\n  &lt;div id=\"y-axis\"&gt;\n    &lt;ul id=\"y-axis\"&gt; \n      &lt;li id=\"label-1\"&gt;&lt;\/li&gt; \n      &lt;li id=\"label-2\"&gt;&lt;\/li&gt; \n      &lt;li id=\"label-3\"&gt;&lt;\/li&gt; \n      &lt;li id=\"label-4\"&gt;&lt;\/li&gt; \n    &lt;\/ul&gt;\n  &lt;\/div&gt; \n    &lt;div id=\"canvas-holder\"&gt;&lt;\/div&gt; \n    &lt;div id=\"key-holder\"&gt; \n      &lt;table id=\"key\"&gt; \n        &lt;tbody&gt; \n          &lt;tr&gt;\n            &lt;th&gt;\n              Key\n            &lt;\/th&gt; \n            &lt;th&gt;\n              Value\n            &lt;\/th&gt; \n          &lt;\/tr&gt; \n        &lt;\/tbody&gt;\n      &lt;\/table&gt; \n    &lt;\/div&gt;\n  &lt;div id=\"x-axis\"&gt; &lt;\/div&gt;\n<\/code>\n<\/pre>\n<p>I learnt at the workshop about <strong>document.querySelector<\/strong> and<br \/>\n  <strong>document.querySelectorAll<\/strong>. Both return the corresponding DOM<br \/>\n  elements of the queried parameters. I wanted this HTML file to be<br \/>\n  self-sufficient and not require any 3rd party JavaScript libraries. So using<br \/>\n  these DOM selectors I did the following.<\/p>\n<pre>\n  <code>\n    var can = document.querySelector('canvas'); \n    \n    \/\/ Traverse each header to work out the keys required\n    var thList = document.querySelector('#data').querySelectorAll('th');\n    var dataKey = new Array();\n    \/\/ Start count at 1 to avoid our blank cell\n    for (i = 1; i < thList.length; i++) {\n      dataKey[i] = thList[i].innerHTML;\n    }\n  <\/code>\n<\/pre>\n<p>The first line assigns our canvas element to the variable<br \/>\n  <strong>can<\/strong>. Next up we use the headers to define the number of lines<br \/>\n  we need on our graph and what they represent. Notice that the count starts at<br \/>\n  1 to ignore the first <strong>th<\/strong> cell which is empty. We assign these<br \/>\n  variables to an array (<strong>dataKey<\/strong>).<\/p>\n<pre>\n  <code>\n\/\/ Use the td content to derive the data array\nvar tdList = document.querySelector('#data').querySelectorAll('td');\nvar dataArray = {};\nvar dataCount = 0;\nvar currentKey = '';\n\n\/\/ Variables for our X and Y labels\nvar smallestFigure = 0;\nvar largestFigure = 0;\nvar xLabels = [];\nvar xlableCnt = 0;\n\n\/\/ Traverse the td to derive our data array\nfor (i = 0; i < tdList.length; i++) {\n  \/\/ Set smallest\/largest if value is a number\n  if (!isNaN(tdList[i].innerHTML)) {\n    var testNumber = parseFloat(tdList[i].innerHTML);\n    if (!smallestFigure &#038;&#038; !largestFigure) {\n      smallestFigure = testNumber;\n      largestFigure = testNumber;\n    } else {\n      if (testNumber < smallestFigure) {\n        smallestFigure = testNumber;\n      }\n      if (testNumber > largestFigure) {\n        largestFigure = testNumber;\n      }\n    }\n  }\n\n  \/\/ Col 1 is the key for this row\n  if (dataCount == 0) {\n    currentKey = tdList[i].innerHTML;\n    dataArray[currentKey] = {};\n    xLabels[xlableCnt] = tdList[i].innerHTML;\n    xlableCnt++;\n  } else {\n    dataArray[currentKey][dataKey[dataCount]] = tdList[i].innerHTML;\n  }\n\n  \/\/ Count starts at zero\n  if (dataCount == (dataKey.length - 1)) {\n    dataCount = 0;\n  } else {\n    dataCount++;\n  }\n}\n<\/code>\n<\/pre>\n<p>OK, this may look slightly overwhelming. Just take some deep breaths and I'll<br \/>\n  talk you through it. What this code is trying to do is to use the content of<br \/>\n  the table to form a data array. Firstly the <strong>tdList<\/strong> becomes a<br \/>\n  holder for all of the content within the <strong>td<\/strong> tags. Next, we<br \/>\n  define all of the variables we are going to require to get our array. We loop<br \/>\n  through all of the <strong>td<\/strong> DOM elements. For each<br \/>\n  <strong>td<\/strong> element, we check whether the content is a valid number.<br \/>\n  If it is we check the value against previous values to determine the largest<br \/>\n  and smallest numbers. This will be used to determine our Y axis range. Our X<br \/>\n  axis labels use the first (vertical) column in our table. Each label will also<br \/>\n  be used as our array key to determine the values plotted on the canvas.<\/p>\n<pre>\n<code>\n  \/\/ Each value will be worth a units worth of pixel\n  var unit = can.height \/ largestFigure;\n\n  \/\/ Lets now get our side label\n  document.querySelector('#label-1').innerHTML = largestFigure;\n  document.querySelector('#label-2').innerHTML = (largestFigure \/ 4) * 3;\n  document.querySelector('#label-3').innerHTML = (largestFigure \/ 4) * 2;\n  document.querySelector('#label-4').innerHTML = (largestFigure \/ 4) * 1;\n\n  \/\/ Our bottom label\n  var xWidth = can.width \/ xLabels.length;\n\n  \/\/ Lets fill our x labels\n  for (i = 0; i < xLabels.length; i++) {\n    xSpan = document.createElement('span');\n    xSpan.innerHTML = xLabels[i];\n    xSpan.className = 'spanBlock';\n    xSpan.style.width = xWidth + 'px';\n    document.querySelector('#ul-x-axis').appendChild(xSpan);\n  }\n<\/code>\n<\/pre>\n<p>Next up we use our assigned variables to determine the X and Y axis labels. We<br \/>\n  find our unit (what each value is in pixels) by taking the length of the<br \/>\n  canvas and dividing it by the largest figure we have. The Y axis is split into<br \/>\n  4 so is not very dynamic, I'm sure you can do better. Also, you may have noted<br \/>\n  I've picked some nice round numbers in this example, barely the case in<br \/>\n  reality! Our X-axis uses the <strong>xLabels<\/strong> variable we assigned to<br \/>\n  early. We create a new span DOM element and set the inner HTML to match the<br \/>\n  value of the label. Now for the fun part...<\/p>\n<pre>\n<code>\n  \/\/ Check whether we can use the canvas \n  if (can.getContext){ \n    \/\/ Get our object\n    var ctx = can.getContext('2d');\n    \n    \/\/ Black out back ground \n    ctx.fillStyle = '#000'; \n    ctx.fillRect(0,0, ctx.canvas.width, ctx.canvas.height); \n    ctx.fillStyle = '#fff'; \n    ctx.strokeStyle = '#fff'; \n    \n    \/\/ default drawing style \n    ctx.lineWidth = 5; \n    ctx.lineCap = 'round'; \n    ctx.save(); \n    \n    \/\/ Define array of fill colours first element is \n    \/\/ blank so that our keys can be used \n    strokeArr =new Array('','#fff','#0000FF');\n    <\/code>\n<\/pre>\n<p>The first line checks that we can actually use the<br \/>\n  <strong>getContext<\/strong> method of the object. We set up our ctx object<br \/>\n  which allows us to draw on our canvas tag. We create our black rectangle which<br \/>\n  covers the canvas, this is the base for our chart. The<br \/>\n  <strong>lineWidth<\/strong> and <strong>lineCap<\/strong> set up how our line<br \/>\n  graph will look on the canvas. Our <strong>strokeArr<\/strong> sets the colours<br \/>\n  for each of our lines. This isn't at all dynamic. You may ask<br \/>\n  <em>'why is the first element blank'<\/em> and<br \/>\n  <em>'what if someone adds another column'<\/em>. To which my response will be<br \/>\n  <em>'Shut the hell up!'<\/em>. Anyway, we are now ready to start drawing our<br \/>\n  lines.<\/p>\n<pre>\n<code>\n  for (i=0;i&lt;dataKey.length;i++) { \n    if (dataKey[i] != undefined) { \n      \/\/ Set Starting point \n      ctx.strokeStyle = strokeArr[i]; \n      ctx.beginPath(); \n      ctx.moveTo(0,500); \n      \n      \/\/ Set defualt points \n      xPoint = 0; \n      yPoint = 500; \n      \n      \/\/ Item in object\n      xCount = 0; \n      \n      \/\/ Loop through items \n      for (o in dataArray) { \n        \/\/ X point moves along with the count \n        xPoint  = xCount * xWidth; \n        \n        \/\/ Use the unit calculated earlier to calc the y point \n        if (dataArray[o][dataKey[i]]) {\n          yPoint = 500 - (dataArray[o][dataKey[i]] * unit); \n        } else { \n          yPoint = 500;\n        } \n        \n        \/\/ Start new line or join existing \n        if (xCount == 0) { \n          ctx.moveTo(xPoint,yPoint); \n        } else { \n          ctx.lineTo(xPoint, yPoint);\n        } \n        \n        xCount++; \n      } \n      \n    \/\/ Add stroke\n    ctx.stroke(); \n    \n    \/\/ Add this row to key \n    keyTr = document.createElement('tr');\n    keyTd = document.createElement('td'); \n    keyTd.style.backgroundColor = strokeArr[i]; \n    keyTr.appendChild(keyTd); \n    keyTd2 = document.createElement('td'); \n    keyTd2.innerHTML = dataKey[i];\n    keyTr.appendChild(keyTd2);\n    document.querySelector('#keytbody').appendChild(keyTr);\n  } \n}\n<\/code>\n<\/pre>\n<p>We are now ready to use our data array which we generated earlier. We loop<br \/>\n  through each item (line on the chart) which contains an object of points on<br \/>\n  the map. The <strong>strokeArr<\/strong> value is the colour of the line. The<br \/>\n  <strong>beginPath<\/strong> method is the equivalent of taking your pen off of<br \/>\n  the canvas. The <strong>moveTo<\/strong> method puts the pen back on the canvas<br \/>\n  at the correct point, in this case where the X and Y axis meet. We start<br \/>\n  looping the points on the canvas we need to draw.<\/p>\n<p>The <strong>xPoint<\/strong> (where on the X axis we need to plot our point) is<br \/>\n  calculated by using the <strong>xCount<\/strong> (number of iterations through<br \/>\n  our X-axis) times the <strong>xWidth<\/strong> (number of pixels between each X<br \/>\n  label). The Y axis is a little more complicated as the lowest Y point isn't 0<br \/>\n  it's 500 (because the canvas start at the top left not the bottom left). We<br \/>\n  use our data value and times it by the unit which gives us the number of<br \/>\n  pixels up we need to be. We have our X and Y points so we now just need to set<br \/>\n  them. We check whether this is a new line or an existing line and plot the<br \/>\n  point accordingly. The <strong>ctx.stroke()<\/strong> fills the line for us.<br \/>\n  Now we have our line we need to create a record in our key table. The first<br \/>\n  <strong>td<\/strong> is filled with the same colour of the line while the<br \/>\n  second <strong>td<\/strong> holds the label for that line. We append the new<br \/>\n  table row to the key table.<\/p>\n<p>And that's that. I was very impressed with the ease with which we can do this<br \/>\n  funky stuff and look forward to more of the same!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I recently attended an HTML5 workshop held by Mr Remy Sharp. I was the only honest schmo in the room to admit to having already purchased the HTML5 book on offer. I was waiting for the free T-shirt that never transpired \ud83d\ude41 The workshop itself was really interesting and I left with the usual &#8216;I&#8217;m [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":602,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,6,11],"tags":[83,90,140,218,357,391],"class_list":["post-589","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-html5","category-javascript","category-scripts","tag-canvas","tag-charting","tag-dynamic","tag-html5-2","tag-remy","tag-sharp"],"_links":{"self":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/589","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/comments?post=589"}],"version-history":[{"count":15,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/589\/revisions"}],"predecessor-version":[{"id":1446,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/posts\/589\/revisions\/1446"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/"}],"wp:attachment":[{"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/media?parent=589"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/categories?post=589"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nickbennett.co.uk\/index.php\/wp-json\/wp\/v2\/tags?post=589"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}