Creating sparkline graphs with HTML & canvas
A project I’m working on requires displaying small data driven sparkline charts. The data is a basic set of data showing activity over time. The final result needs to look something like:
As you can see its very simple, and my requirements don’t require any features like hover states, or click events. When I started thinking about ways to generate these charts. I assumed there would be a wonderful library to do it, and I’d just use that. Turns out there is a great jquery-sparklines plugin. The only problem was it does way more than I need. Normally this wouldn’t be a problem but the minified code is 43KB. This would represent a pretty sizable chunk of code in my project. Of that 43KB, I would only be using a tiny fraction of it. I decided to re-invent the wheel and come up with a more compact solution that did exactly what I needed.
Initial prototype
The first version I built used some simple jQuery and CSS to generate the graph. I hacked up some CSS to style the charts, which looked like:
- .sparks {
- padding: 1px 1px 0 1px;
- width: 43px;
- height: 23px;
- background :#f1f1f1;
- list-style: none;
- }
- .spark-bar {
- list-style: none;
- position: relative;
- float: left;
- width: 3px;
- height: 100%;
- }
- .spark-bar span {
- background-color: #6633ff;
- position: absolute;
- width: 2px;
- left: 0;
- bottom: 0;
- }
The chart would be a list with elements inside each list item with the calculated height. The initial code using jQuery looked like:
- // Cap is the max value we show on the graph.
- var cap = 10;
- var data = [
- 2, 3, 5, 6, 2, 8, 9, 0, 6, 2, 25, 30, 1, 2
- ];
- var container = $('#container');
- function sparkline(container, data, cap) {
- var ul = $('<ul class="sparks"></ul>');
- for (var i = 0; i < data.length; i++) {
- var barheight = Math.min(data[i] / cap, 1) * 100;
- var li = $('<li class="spark-bar"></li>');
- var bar = $('<span></span>');
- bar.css({
- height: barheight + '%',
- });
- li.append(bar);
- ul.append(li);
- }
- container.append(ul);
- }
- sparkline(container, data, cap);
This worked perfectly and generated the desired results. After I was done I did some profiling to figure out how generating the 100+ of these graphs I needed would perform. The results were a bit higher than I would have liked. Generating 100 graphs took ~280ms on my laptop. While not slow, it could be faster. Using the chrome profiler, I found that most of the execution time was spent in jQuery. I wanted to find out how much faster the charts would be without jQuery. For my next prototype, I only used the native DOM methods and this only took ~45ms to generate 100 graphs.
- // Cap is the max value we show on the graph.
- var cap = 10;
- var data = [
- 2, 3, 5, 6, 2, 8, 9, 0, 6, 2, 25, 30, 1, 2
- ];
- function doFastHtml(container, data, cap) {
- var ul = document.createElement('ul');
- ul.className = 'sparks';
- for (var i = 0; i < data.length; i++) {
- var barheight = Math.min(data[i] / cap, 1) * 100;
- var li = document.createElement('li');
- li.className = 'spark-bar';
- var bar = document.createElement('span');
- bar.style.height = barheight + '%';
- li.appendChild(bar);
- ul.appendChild(li);
- }
- container.appendChild(ul);
- }
- var container = document.getElementById('container');
- doFastHtml(container, data, cap);
This was a big improvement over the previous iteration and didn’t require many more lines of code. A valuable lesson I re-learned was that just because you use a library like jQuery doesn’t mean you should use it for everything. Sometimes the same results can be created much more efficiently using more primitive APIs.
Canvas for the win
As you probably noticed, the graphs I want are so simple they could just be images or <canvas>
elements. I figured I should give canvas a try to see if it was more performant, and didn’t use measurably more code. My quickly hacked out code looked like:
- var background = '#f1f1f1';
- var color = '#6633ff';
- var width = 43;
- var height = 23;
- function doCanvas(container, data, cap) {
- var canvas = document.createElement('canvas');
- canvas.width = width;
- canvas.height = height;
- container.append(canvas);
- var ctx = canvas.getContext('2d');
- ctx.fillStyle = background;
- ctx.fillRect(0, 0, canvas.width, canvas.height);
- var i;
- var x = 0;
- var y = 0;
- var barwidth = 2;
- var barspace = 1;
- ctx.fillStyle = color;
- for (i = 0; i < data.length; i++) {
- var barheight = Math.floor(Math.min(data[i] / cap, 1) * (height - 1));
- x += barspace;
- ctx.fillRect(x, height, barwidth, - barheight);
- x += barwidth;
- }
- }
- var container = document.getElementById('container');
- doCanvas(container, data, cap);
This solution requires a bit more code than the previous two prototypes, but it also runs impressively fast. On my laptop, it took ~25ms to generate 100 graphs, which is a greater than 100x improvement over the original prototype. To double check I created a jsperf test for this which can be used to verify the results, and see how much of a difference each approach makes on your browser.
While canvas isn’t available on all browsers, its not hard to detect the canvas API and fallback to HTML as needed. I think the performance benefit is very much worth the few extra bytes in code weight to support both versions. It was great to learn that canvas is blazingly fast for use cases like this. I also learned that while jQuery is a fantastic tool, its not always the most efficient solution to a problem. I was also happy that I could get the solution I needed including graceful fallback for ~42KB less than the original library I was contemplating.
I am a newbie in the business and this post on creating line graphs on html and canvas is one skill that I badly need to develop. I used to do this during college and I need a refresher. Great post!
Brian B. on 3/26/13
Let me share now that I am trying to follow your instructions and all of them are working. I am excited to finish this. Thanks! – Brian, eye doctor Long Island
Brian B. on 3/26/13
The jQuery plugin provides lots of benefits. One of them is it generates sparklines directly in the browser. This is possible by using data supplied either inline in the HTML, or via javascript. It is even compatible with Firefox 2+, Safari 3+, Opera 9, Google Chrome and Internet Explorer 6, 7, 8, 9 & 10 as well as iOS and Android.
Mike on 4/26/13
I am not an IT person and I do not speak your language but this post is very helpful as I tried the instructions and I was successful in trying. I need to make friends with HTML & canvas since there are a lot of reaction over here.
Mark on 4/26/13
I agree that canvas is not available on all browsers. The The
George on 5/4/13
I agree that canvas is not available on all browsers. The canvas element is not supported in some older browsers. But the good thing is it is supported in Firefox 1.5 and later, Opera 9 and later, newer versions of Safari, Chrome, and Internet Explorer 9.
George on 5/4/13
I don not speak IT language nor understand it but I need to learn on my own to cope up with my new career, internet marketing. It could be hard at the start, but once you learn, you’ll have more fun while at work.
Brett on 5/5/13
The site is very much helpful for newbie like me. I’m a freshman IT student and I’m very hopeful that I’ll become with creating graphs, HTML and canvas. In fact, I just tried, instruction above, and though I commit mistakes, I’m learning a lot.
Mark on 5/5/13
While I was reading the solution, I have realized that it will require a bit more code than the previous two prototypes. But just like what you have said, it will run impressively fast. Thanks for sharing this info.
Tony on 5/6/13