January 2012

Volume 27 Number 01

Category - Title

By Brandon Satrom | January 2012

In the early days online, when the Web was little more than a collection of static text and links, there was growing interest in supporting other types of content. In 1993, Marc Andreessen, creator of the Mosaic browser, which would evolve into Netscape Navigator, proposed the IMG tag as a standard for embedding images inline with the text on a page. Soon after, the IMG tag became the de facto standard for adding graphical resources to Web pages—a standard that’s still in use today. You could even argue that, as we’ve moved from the Web of documents to the Web of applications, the IMG tag is more important than ever.

Media, in general, is certainly more important than ever, and though the need for media on the Web has evolved over the past 18 years, the image has remained static. Web authors have increasingly sought to use dynamic media such as audio, video, and interactive animations in their sites and applications and, until recently, the primary solution was a plug-in like Flash or Silverlight.

Now, with HTML5, media elements in the browser get a nice kick in the pants. You’ve probably heard of the new Audio and Video tags, both of which allow these types of content to function as first-class citizens in the browser, no plug-ins required. Next month’s article will cover both of these elements and their APIs in depth. You’ve probably also heard of the canvas element, a drawing surface with a rich set of JavaScript APIs that give you the power to create and manipulate images and animations on the fly. What IMG did for static graphical content, canvas has the potential to do for dynamic and scriptable content.

As exciting as the canvas element is, though, it suffers from a bit of a perception problem. Because of its power, canvas is usually demonstrated via complex animations or games, and while these do convey what’s possible, they can also lead you to believe that working with canvas is complicated and difficult, something that should be attempted only for complex cases, like animation or games.

In this month’s article, I’d like to take a step back from the glitz and complexity of canvas and show you some simple, basic uses of it, all with the goal of positioning the canvas as a powerful option for data visualization in your Web applications. With that in mind, I’ll focus on how you can get started with canvas, and how to draw simple lines, shapes and text. Then I’ll talk about how you can work with gradients in your shapes, as well as how to add external images to a canvas. Finally, and as I’ve done throughout this series, I’ll wrap up with a brief discussion on polyfilling canvas support for older browsers.

Introducing the HTML5 Canvas

According to the W3C HTML5 specification (https://www.w3.org/TR/html-markup/canvas.html), the canvas element “provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics or other visual images on the fly.” Canvas is actually defined across two W3C specifications. The first is as a part of the HTML5 core specification, where the element itself is defined in detail. This specification covers how to use the canvas element, how to obtain its drawing context, APIs for exporting canvas content and security considerations for browser vendors. The second is the HTML Canvas 2D Context (w3.org/TR/2dcontext), which I’ll get to in a moment.

Getting started with canvas is as simple as adding a <canvas> element to HTML5 markup, like so:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>My Canvas Demo </title>               
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <canvas id="chart" width="600" height="450"></canvas>       
  </body>
</html>

Though I now have a canvas eleme­­nt in the DOM, placing this markup on the page does nothing, as the canvas element has no content until you add it. That’s where the drawing context comes in. To show you where my blank canvas is located, I can use CSS to style it, so I’ll add a dotted blue line around the blank element.

canvas {
    border-width: 5px;
    border-style: dashed;
    border-color: rgba(20, 126, 239, 0.50)
}

The result, when my page is opened in Internet Explorer 9+, Chrome, Firefox, Opera or Safari, is depicted in Figure 1.

A Blank, Styled Canvas Element
Figure 1 A Blank, Styled Canvas Element

When using canvas, you’ll do most of your work in JavaScript, where the exposed APIs of a canvas drawing context can be leveraged to manipulate each pixel of the surface. To obtain the canvas drawing context, you need to get your canvas element from the DOM and then call the getContext method of that element.

var _canvas = document.getElementById('chart');
var _ctx = _canvas.getContext("2d");

GetContext returns an object with an API that you can use to draw on the canvas in question. The first argument to that method (in this case, “2d”) specifies the drawing API that we want to use for the canvas. “2d” refers to the HTML Canvas 2D Context I mentioned earlier. As you might guess, 2D means that this is a two-dimensional drawing context. As of this writing, the 2D Context is the only widely supported drawing context, and it’s what we’ll use for this article. There’s ongoing work and experimentation around a 3D drawing context, so canvas should provide even more power for our applications in the future.

Drawing Lines, Shapes, and Text

Now that we have a canvas element on our page and we’ve obtained its drawing context in JavaScript, we can begin to add content. Because I want to focus on data visualization, I’m going to use the canvas to draw a bar chart to represent the current month’s sales data for a fictional sporting goods store. This exercise will require drawing lines for the axes; shapes and fills for the bars; and text for the labels on each axis and bar.

Let’s start with the lines for the x- and y-axes. Drawing lines (or paths) with the canvas context is a two-step process. First, you “trace” the lines on the surface using a series of lineTo(x, y) and moveTo(x, y) calls. Each method takes x- and y-coordinates on the canvas object (starting from the top-left corner) to use when performing the operation (as opposed to coordinates on the screen itself). The moveTo method will move to the coordinates you specify, and lineTo will trace a line from the current coordinates to the coordinates you specify. For example, the following code will trace our y-axis on the surface:

// Draw y axis.
_ctx.moveTo(110, 5);
_ctx.lineTo(110, 375);

If you add this code to your script and run it in the browser, you’ll notice that nothing happens. Because this first step is merely a tracing step, nothing is drawn on the screen. Tracing merely instructs the browser to take note of a path operation that will be flushed to the screen at some point in the future. When I’m ready to draw paths to the screen, I optionally set the strokeStyle property of my context, and then call the stroke method, which will fill in the invisible lines. The result is depicted in Figure 2.

 

// Define Style and stroke lines.
_ctx.strokeStyle = "#000";
_ctx.stroke();

A Single Line on the Canvas
Figure 2 A Single Line on the Canvas

Because defining lines (lineTo, moveTo) and drawing lines (stroke) are decoupled, you can actually batch a series of lineTo and moveTo operations and then output those to the screen all at once. I’ll do this for both the x- and y-axes and the operations that draw arrows as the end of each axis. The complete function for drawing the axes is shown in Figure 3 and the result in Figure 4.

Figure 3 The drawAxes Function

function drawAxes(baseX, baseY, chartWidth) {
   var leftY, rightX;
   leftY = 5;
   rightX = baseX + chartWidth;
   // Draw y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX, baseY);
   // Draw arrow for y axis.
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX + 5, leftY + 5);
   _ctx.moveTo(baseX, leftY);
   _ctx.lineTo(baseX - 5, leftY + 5);
   // Draw x axis.
   _ctx.moveTo(baseX, baseY);
   _ctx.lineTo(rightX, baseY);
   // Draw arrow for x axis.
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY + 5);
   _ctx.moveTo(rightX, baseY);
   _ctx.lineTo(rightX - 5, baseY - 5);
   // Define style and stroke lines.
   _ctx.strokeStyle = "#000";
   _ctx.stroke();
}

Completed X- and Y-Axes
Figure 4 Completed X- and Y-Axes

We have our axes, but we should probably label them to make them more useful. The 2D canvas context specifies APIs for adding text to canvas elements, so you don’t need to fiddle with messy hacks like floating text over the canvas element. That said, canvas text doesn’t provide a box model, nor does it accept CSS styles defined for page-wide text, and so forth. The API does provide a font attribute that works the same as a CSS font rule—as well as textAlign and textBaseline properties to give you some control over position relative to the provided coordinates—but other than that, drawing text on the canvas is a matter of picking an exact point on the canvas for the text you supply.

The x-axis represents products in our fictional sporting goods store, so we should label that axis accordingly:

var height, widthOffset;
height = _ctx.canvas.height;
widthOffset = _ctx.canvas.width/2;
_ctx.font = "bold 18px sans-serif";
_ctx.fillText("Product", widthOffset, height - 20);

In this code snippet, I’m setting the optional font property and providing a string to draw on the surface, along with the x- and y-coordinates to use as the start position of the string. In this example, I’ll draw the word “Product” in the middle of my canvas, 20 pixels up from the bottom, which leaves room for the labels for each product on my bar chart. I’ll do something similar for the y-axis label, which contains the sales data for each product. The result is depicted in Figure 5.

Canvas with Text
Figure 5 Canvas with Text

Now that we have a framework for our chart, we can add the bars. Let’s create some dummy sales data for the bar chart, which I’ll define as a JavaScript array of object literals.

var salesData = [{
   category: "Basketballs",
   sales: 150
}, {
   category: "Baseballs",
   sales: 125
}, {
   category: "Footballs",
   sales: 300
}];

With this data in hand, we can use fillRect and fillStyle to draw our bars on the chart.

fillRect(x, y, width, height) will draw a rectangle on the canvas at the x- and y-coordinates, with the width and height you specify. It’s important to note that fillRect draws shapes starting from the top-left radiating outward, unless you specify negative width and height values, in which case the fill will radiate in the opposite direction. For drawing tasks like charting, that means we’ll be drawing the bars from the top down, as opposed to the bottom up.

To draw the bars, we can loop through the array of sales data and call fillRect with the appropriate coordinates:

var i, length, category, sales;
var barWidth = 80;
var xPos = baseX + 30;
var baseY = 375;       
for (i = 0, length = salesData.length; i < length; i++) {
   category = salesData[i].category;
   sales = salesData[i].sales;
   _ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);
   xPos += 125;
}

In this code, the width of each bar is standard, while the height is taken from the sales property for each product in the array. The result of this code is seen in Figure 6.

Rectangles as Bar Chart Data
Figure 6 Rectangles as Bar Chart Data

Now, we have a chart that’s technically accurate, but those solid black bars leave something to be desired. Let’s spruce them up with some color, and then add a gradient effect.

Working with Colors and Gradients

When the fillRect method of a drawing context is called, the context will use the current fillStyle property to style the rectangle as its being drawn. The default style is a solid black, which is why our chart looks as it does in Figure 6. fillStyle accepts named, hexadecimal and RGB colors, so let’s add some functionality to style each bar before it is drawn:

// Colors can be named hex or RGB.
colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];       
...
_ctx.fillStyle = colors[i % length];
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

First, we create an array of colors. Then, as we loop through each product, we’ll use one of these colors as the fill style for that element. The result is depicted in Figure 7.

Using fillStyle to Style Shapes
Figure 7 Using fillStyle to Style Shapes

This looks better, but fillStyle is very flexible and lets you use linear and radial gradients instead of just solid colors. The 2D drawing context specifies two gradient functions, createLinerGradient and createRadialGradient, both of which can enhance the style of your shapes through smooth color transitions.

For this example, I’m going to define a createGradient function that will accept the x- and y-coordinates for the gradient, a width and the primary color to use:

function createGradient(x, y, width, color) {
   var gradient;
   gradient = _ctx.createLinearGradient(x, y, x+width, y);
   gradient.addColorStop(0, color);
   gradient.addColorStop(1, "#efe3e3");
   return gradient;
}

After calling createLinearGradient with my start and end coor­dinates, I’ll add two color stops to the gradient object returned by the drawing context. The addColorStop method will add color transitions along the gradient; it can be called any number of times with first parameter values between 0 and 1. Once I’ve set up my gradient, I’ll return it from the function.

The gradient object can then be set as the fillStyle property on my context, in place of the hex and RGB strings I specified in the previous example. I’ll use those same colors as my starting point, and then fade them into a light gray.

colors = ["orange", "#0092bf", "rgba(240, 101, 41, 0.90)"];
_ctx.fillStyle = createGradient(xPos, baseY - sales-1, barWidth, colors[i % length]);
_ctx.fillRect(xPos, baseY - sales-1, barWidth, sales);

The result of the gradient option can be seen in Figure 8.

Using Gradients in a Canvas
Figure 8 Using Gradients in a Canvas

Working with Images

At this point, we have a pretty good-looking chart, which we’ve been able to render in the browser using a few dozen lines of JavaScript. I could stop here, but there’s still one basic canvas API related to working with images I want to cover. Not only does canvas let you replace static images with script-based and interactive content, but you can also use static images to enhance your canvas visualizations.

For this demo, I’d like to use images as the bars on the bar chart. And not just any images, but pictures of the items themselves. With that in goal in mind, my Web site has a folder that contains JPG images for each product—in this case, basketballs.jpg, baseballs.jpg and footballs.jpg. All I need to do is position and size each image appropriately.

The 2D drawing context defines a drawImage method with three overloads, accepting three, five or nine parameters. The first parameter is always the DOM element image to draw. The simplest version of drawImage also accepts x- and y-coordinates on the canvas and draws the image as is in that location. You can also provide width and height values as the last two parameters, which will scale the image to that size prior to drawing it on the surface. Finally, the most complex use of drawImage allows you to crop an image down to a defined rectangle, scale it to a given set of dimensions and, finally, draw it on the canvas at the specified coordinates.

Because the source images I have are large-scale images used elsewhere on my site, I’m going to take the latter approach. In this example, rather than calling fillRect for each item as I loop through the salesData array, I’ll create an Image DOM element, set its source to one of my product images, and render a cropped version of that image onto my chart, as Figure 9 shows.

Figure 9 Drawing images on a Canvas

// Set outside of my loop.
xPos = 110 + 30;     
// Create an image DOM element.
img = new Image();
img.onload = (function(height, base, currentImage, currentCategory) {
  return function() {
    var yPos, barWidth, xPos;
    barWidth = 80;
      yPos = base - height - 1;
    _ctx.drawImage(currentImage, 30, 30, barWidth, height, xPos, yPos,
      barWidth, height);
      xPos += 125;           
  }
})(salesData[i].sales, baseY, img, salesData[i].category);
img.src = "images/" + salesData[i].category + ".jpg";

Because I’m creating these images dynamically, as opposed to adding them manually to my markup at design time, I shouldn’t assume that I can set the image source, then immediately draw that image to my canvas. To ensure that I draw each image only when it’s fully loaded, I’ll add my drawing logic to the onload event for the image, then wrap that code in a self-invoking function, which creates a closure with variables pointing to the correct product category,  sales and positioning variables. You can see the result in Figure 10.

Using Images on a Canvas
Figure 10 Using Images on a Canvas

Using a Canvas Polyfill

As you may know, versions of Internet Explorer prior to 9, as well as older versions of other browsers, do not support the canvas element. You can see this for yourself by opening the demo project in Internet Explorer and hitting F12 to open the developer tools. From the F12 tools, you can change the Browser Mode to Internet Explorer 8 or Internet Explorer 7 and refresh the page. What you’re likely to see is a JavaScript exception with the message “Object doesn’t support property of method getContext.” The 2D drawing context isn’t available, nor is the canvas element itself. It’s also important to know that, even in Internet Explorer 9, canvas isn’t available unless you specify a DOCTYPE. As I mentioned in the first article of this series (msdn.microsoft.com/magazine/hh335062), it’s always a good idea to use <!DOCTYPE html> at the top of all your HTML pages to ensure that the latest features in the browser are available.

The simplest course of action you can take for users whose browsers don’t support canvas is to use a fallback element such as image or text. For instance, to display a fallback image to users, you can use markup that looks like this:

<canvas id=”chart”>
  <img id=”chartIMG” src=”images/fallback.png”/>
</canvas>

Any content you place inside of the <canvas> tag will be rendered only if the user’s browser doesn’t support canvas. That means that you can place images or text inside of your canvas as a simple, zero-checks fallback for your users.

If you want to take fallback support further, the good news is that a variety of polyfilling solutions exist for canvas, so you can feel comfortable using it with older browsers as long as you carefully vet potential solutions and stay aware of the limitations of a given polyfill. As I’ve stated in other articles in this series, your starting point for finding a polyfill for any HTML5 technology should be the HTML5 Cross Browser Polyfills page in the Modernizr wiki on GitHub (bit.ly/nZW85d). As of this writing, there are several canvas polyfills available, including two that fall back to Flash and Silverlight.

 In the downloadable demo project for this article, I use explorercanvas (code.google.com/p/explorercanvas), which uses Internet Explorer-supported Vector Markup Language (VML) to create close approximations of canvas functionality, and canvas-text (code.google.com/p/canvas-text), which adds additional support for rendering text in older browsers.

As illustrated in previous articles, you can use Modernizr to feature-detect support for canvas (and canvastext) in a browser by calling Modernizr.canvas and then use Modernizr.load to asynchronously load explorercanvas when needed. For more information, see modernizr.com.

If you don’t want to use Modenrizr, there’s another way to conditionally add explorercanvas for older versions of IE: conditional comments:

<!--[if lt IE 9]>
  <script src="js/excanvas.js"></script>
  <script src="js/canvas.text.js"></script>
<![endif]-->

When Internet Explorer 8 or older versions encounter a comment formatted as such, they will execute the block as an if statement and include the explorercanvas and canvas-text script files. Other browsers, including Internet Explorer 10, will treat the entire block as a comment and ignore it altogether.

When evaluating a potential polyfill for your application, be sure to look into how much of the 2D drawing context a given polyfill supports. Few of them provide full support for every use, though nearly all can handle the basic cases we looked at in this article.

Though I couldn’t cover everything here, there’s a lot more you can do with canvas, from responding to click (and other) events and changing canvas data, to animating the drawing surface, rendering and manipulating images pixel-by-pixel, saving state, and exporting the entire surface as its own image. In fact, there are entire books on canvas out there. You don’t have to be a game developer to experience the power of canvas, and I hope I convinced you of that as I walked through the basics in this article. I encourage you to read the specifications for yourself, and jump in to this exciting new graphics technology with both feet.

If you’re looking for more information on canvas support in Internet Explorer 9, check out the IE9 Developer Guide online (msdn.microsoft.com/ie/ff468705). Also, be sure to check out the Canvas Pad demos available at the IE Test Drive site (bit.ly/9v2zv5). For a list of a few other cross-browser polyfills for canvas, check out the complete polyfilling list at (bit.ly/eBMoLW).

Finally, all of the demos for this article—which are available online—were built using WebMatrix, a free, lightweight Web development tool from Microsoft. You can try WebMatrix out for yourself at aka.ms/webm.


Brandon Satrom works as a developer evangelist for Microsoft outside of Austin. You can follow him on Twitter at twitter.com/BrandonSatrom.

Thanks to the following technical experts for reviewing this article: Jatinder Mann and Clark Sell