Chapter 11

JavaScript Graphics

One of the most impressive features introduced in HTML5 is the capability to render graphics directly in the browser. Not only can you generate static images with JavaScript, but also you can leverage these techniques to create stunning interactive applications such as video games. In fact, these apps aren't limited to only two dimensions. With the integration of the WebGL engine, you can now create 3D applications that run natively in the browser.

In this chapter, you first discover how to draw 2D scenes in canvas, integrating animation as well as mouse events. You then learn how to render similar scenes with scalable vector graphics (SVG), which are much easier to animate and make interactive. Next, you discover the library Raphaël.js, which will streamline your SVG development with intuitive APIs. You're then introduced to Raphaël's charting libraries, and you find out how to customize these charts with your own code.

After you understand the different two-dimensional techniques, you'll be ready to dive in and work with 3D rendering using canvas and WebGL. You find out why to avoid the low-level WebGL API and instead use the library Three.js, which you leverage to build and animate some basic three-dimensional scenes. Finally, you consider another 3D rendering option, CSS3 transforms, which can be an attractive alternative for more basic apps. By the end of this chapter, you'll know enough JavaScript drawing techniques to render just about any scene you want directly in the browser. This chapter is a jumping-off point for developing a rich visual application based in JavaScript.

Canvas Basics

HTML5 canvas allows you to dynamically render just about any shape or scene you can imagine. It all starts by defining a canvas element in the markup:

<canvas id="my-canvas" width="200" height="150"></canvas>

Next, you use JavaScript to hook into this element and render whatever scene you want to draw. Start by setting the context using the element's ID hook:

var canvas = document.getElementById('my-canvas'),

if ( canvas.getContext ) {

  var ctx = canvas.getContext('2d'),

}

The context of the canvas element tells the browser which type of rendering engine you want to use—in this case, basic two-dimensional rendering. But before setting the context, it's important to check for canvas support. Some users have older browsers that don't support canvas, and you can avoid throwing errors in these browsers by first verifying that canvas.getContext is available. Fortunately, canvas support is pretty decent these days, with the exception of IE8 and earlier, as you can see here: http://caniuse.com/#feat=canvas.

Later in the 3D Canvas with WebGL section, you learn how to use a different context to render 3D scenes.

Drawing Basic Shapes

Now, you're ready to dive in and start drawing on the canvas. The canvas API provides a number of different methods you can use to render shapes and lines, along with a variety of methods for styling these objects. For example, this script draws a rectangle on the canvas:

var canvas = document.getElementById('my-canvas'),

if ( canvas.getContext ) {

  var ctx = canvas.getContext('2d'),

  

  ctx.fillStyle = '#d64e34';

  ctx.fillRect(10, 15, 80, 50);

}

Here the fillStyle first defines the color of the rectangle (and the color of any subsequent shapes you draw). Then fillRect() renders a solid rectangle at coordinates (10,15) with a size of 80px by 50px, which means the rectangle is drawn 10 pixels from the top of the canvas element and 15 pixels from the left, as shown in Figure 11-1.

9781118524404-fg1101.tif

Figure 11-1 fillRect draws a rectangle on the canvas.

fillRect is easy shorthand for drawing boxes, but you can also build your own shapes by drawing paths. For example, you can draw a triangle:

ctx.beginPath();

ctx.moveTo(20, 90);

ctx.lineTo(140, 90);

ctx.lineTo(80, 25);

ctx.fill();

Drawing your own shapes is a little more complicated:

1. beginPath() starts a new path.

2. moveTo() defines the start point of the path, in this case 20px from the left and 90px from the top.

3. lineTo() draws lines to coordinates, in this case a first line is drawn from the start point to (140,90). Then another line is drawn to (80,25).

4. Finally, the shape is colored in using fill(), which draws the triangle shown in Figure 11-2.

9781118524404-fg1102.tif

Figure 11-2 This triangle was drawn using paths.

In addition to drawing filled shapes, you can draw lines. For example, draw the same triangle with a stroke instead of a fill:

ctx.beginPath();

ctx.moveTo(20, 90);

ctx.lineTo(140, 90);

ctx.lineTo(80, 25);

ctx.stroke();

However, this code produces a bit of an unexpected result. As shown in Figure 11-3, only two lines of the triangle have been drawn.

9781118524404-fg1103.tif

Figure 11-3 When you apply a stroke to the same triangle, it only draws two of the three arms.

While fill() automatically filled in the rest of the triangle, in this case, only two lines were drawn, so the canvas appropriately renders exactly that. To connect the last two points of the triangle, simply add a closePath():

ctx.beginPath();

ctx.moveTo(20, 90);

ctx.lineTo(140, 90);

ctx.lineTo(80, 25);

ctx.closePath();

ctx.stroke();

As shown in Figure 11-4, the canvas renders the completed triangle.

9781118524404-fg1104.tif

Figure 11-4 closePath() finishes off the last arm of the triangle.

Now you know how to draw a few basic shapes, but this is just the tip of the iceberg when it comes to what is possible in HTML5 canvas. The API also allows for drawing arcs and complex Bézier curves, styling elements with gradients, drop shadows and stroke styles, and much more. The API is pretty easy to use, but I don't want to bore you with a manual of different API options. A number of resources are listed at the end of this chapter if you want to dig deeper.

Animating the Canvas

Animating the canvas isn't much more complicated than drawing on it. The trick is simply to draw and redraw elements that you want to move on the screen. Start with a simple two-frame animation. First, draw a rectangle for the first frame:

ctx.fillRect(10, 10, 50, 50);

Then clear the entire contents of the canvas using clearRect() and draw the next frame of the rectangle:

ctx.clearRect(0, 0, 200, 150);

ctx.fillRect(11, 11, 50, 50);

That's all there is to it. Of course, you shouldn't hard-code every single frame of an animation. Instead, you can script the frames and apply them over time using an interval:

var posX = 0;

var drawInterval = setInterval(function(){

  posX++;

  // stop when it gets to the edge

  if ( posX > 150 ) {

    clearInterval(drawInterval);

    return;

  }

  

  ctx.clearRect(0, 0, 200, 150);

  ctx.fillRect(posX, 10, 50, 50);

}, 1000/60);

Here, you're using setInterval to redraw the square every 60th of a second, which moves it across the screen to the right (stopping when it reaches the bounds).

You might worry that this practice of drawing and redrawing the canvas is really resource-intensive. But, you'd be surprised: Browsers handle canvas rendering well, and these types of animations are typically smooth. And in the next chapter, you learn how to use requestAnimationFrame to make them even smoother.

Canvas Mouse Events

HTML5 canvas is often used to create interactive elements, which means that you need to set up an interface to manipulate these elements. However, adding mouse interaction to the canvas isn't as intuitive as you'd expect. You can't set up click listeners on the elements you draw to the canvas. Instead, you have to set up generalized listeners that determine whether the location of a click event lines up with the position of the elements that are rendered on the canvas. For example, take the rectangle from the earlier example:

ctx.fillRect(10, 15, 80, 50);

Unfortunately, you can't get a DOM reference to this rectangle and use it to apply a click handler because the canvas is just a static rendering of everything that has been drawn to it. Thus you'll have to be a little more creative when setting up mouse handlers with canvas. First, set up a generalized click listener on the canvas element and determine the location of the click event relative to the canvas:

$('#my-canvas').click(function(e) {

  var offsetX = e.pageX – this.offsetLeft;

  var offsetY = e.pageY – this.offsetTop;

});

Next, determine whether this point exists within the boundaries of the rectangle:

$('#my-canvas').click(function(e) {

  var offsetX = e.pageX - this.offsetLeft;

  var offsetY = e.pageY - this.offsetTop;

  if ( (offsetX > 10 && offsetX < 90) && (offsetY > 15 && offsetY < 65) ) {

    alert('Rectangle clicked'),

  }

});

Here, the script compares the coordinates of the click event against the coordinates of the rectangle. For instance, the x points of the rectangle go from 10 (the start point) to 90 (the start point plus the width). While this may seem complicated, calculating the click points for a rectangle is relatively simple. Imagine how much harder this math would be for a complex polygon or curve!

Unless you're really into linear algebra, you're going to want to use a library to handle this sort of behavior. Or read on to the next section, where you learn about SVGs, which are a lot friendlier to mouse interactions.

SVG Basics

Unlike canvas, you can actually define SVG images using only markup and no JavaScript. For example, this markup draws a rectangle:

<svg height="200" xmlns="http://www.w3.org/2000/svg">

  <rect width="150" height="100" fill="blue" />

</svg>

Here, SVG markup first starts with an <svg> wrapper. Then the markup declares a rectangle element, with dimensions and a fill color.

You can add as many shapes as you want to your SVG. For example, add a circle and a triangle:

<svg height="200" xmlns="http://www.w3.org/2000/svg">

  <rect width="150" height="100" fill="blue" />

  <circle r="50" cx="50" cy="50" fill="red" />

  <polygon points="0,0 50,50 100,0" fill="orange" />

</svg>

Here, the circle and polygon (triangle) are stacked on top of the rectangle, as shown in Figure 11-5.

9781118524404-fg1105.tif

Figure 11-5 Three shapes have been drawn in this SVG using simple markup. Shapes in an SVG pile up in the order they are added.

Animating the SVG

You already know that it can be difficult to work with canvas, since everything drawn to the canvas is completely static. Well, you'll be happy to know that SVG is precisely the opposite. You don't have to draw and redraw everything in the SVG whenever you want to move something; you can just grab the node from the DOM and move it with JavaScript:

$('svg circle').attr('cx', 60).attr('cy', 60);

This example uses jQuery to select the circle and move its center point to (60,60). You can use a similar technique for anything else you want to manipulate: the color, dimensions, and so on. You can even set up simple animation—for instance, to increase the size of the circle:

var circle = $('svg circle'),

    radius = 50;

var svgInterval = setInterval(function() {

  radius += .2;

  if ( radius > 70 ) {

    clearInterval(svgInterval);

    return;

  }

  circle.attr('r', radius);

}, 1000/60);

SVG Mouse Events

Likewise, setting up mouse event handlers for SVGs is significantly easier than it is for canvas drawings. Simply grab a DOM reference and attach the listener:

$('svg circle').click(function() {

  alert('Circle clicked!'),

});

There's no need to calculate the clickable area of complex shapes or handle overlapping content; the DOM takes care of all those issues for you. Indeed, if you run the example code, you'll see that the event fires only when you click the exposed area of the circle, and not the shape on top or behind it.

If you have a number of shapes in your SVG, you can set up class and ID hooks to help you grab the appropriate reference.

Scripting SVG

Creating SVGs with markup is great for static images, but not really that versatile or useful for dynamic content. Fortunately, you can create SVGs with JavaScript as well. But again, much like with canvas, the SVG API runs pretty deep and I don't want to write a manual. I've listed a number of resources at the end of this chapter if you want to dig any deeper.

Additionally, while it's a good idea to understand the fundamentals of the SVG API, you don't necessarily have to work with it. There are libraries out there that can handle all the SVG heavy lifting for you and streamline your development process—namely, Raphaël.js.

Raphaël.js

Raphaël.js is an SVG JavaScript library that makes it much easier to generate vectored graphics in the browser. It provides an intuitive API for developing rich SVG applications, and getting started with Raphaël is relatively easy. First download the library from http://raphaeljs.com and then create a wrapper for your drawing:

<div id="my-svg"></div>

Next, you need to create a drawing canvas that Raphaël can use to add SVGs. In Raphaël, that's called the paper, which you can add to your wrapper element:

var paper = Raphael(document.getElementById('my-svg'), 600, 400);

Here, the code references the my-svg wrapper and creates a drawing canvas with a width of 600px and a height of 400px. Now, you can begin adding shapes to the paper:

var rect = paper.rect(0, 0, 600, 400);

rect.attr('fill', '#FFF'),

var circle = paper.circle(300, 200, 120);

circle.attr('fill', '#F00'),

circle.attr('stroke-width', 0);

This code adds two shapes to the drawing (shown in Figure 11-6):

1. The rect() method adds a rectangle to the canvas—in this case, it starts at the top-left corner (0, 0) of the paper and has a width of 600px and a height of 400px. Then a fill color is defined for the rectangle (#FFF).

2. The circle() method adds a circle centered at (300, 200) with a radius of 120px. The script then defines a red fill color and sets the stroke-width to zero to remove the default 1px black stroke.

9781118524404-fg1106.tif

Figure 11-6 This simple Japanese flag was drawn with Raphaël.

Drawing Paths

In addition to using Raphaël's basic built-in shapes, you can draw more complex shapes using paths. Although it is quite powerful, the API for drawing paths is a bit strange because everything is defined in a path string. For example, this code draws a simple triangle:

var triangle = paper.path('M300,50 L100,350 L500,350 L300,50'),

While the string passed into the path() method might seem a bit intimidating, it's not actually all that complicated:

1. It starts with the move operation, M300,50, which moves the start point of the path to (300,50), which is similar to the moveTo() method you learned about earlier in the Canvas Basics section.

2. Next, L100,350 draws a line to (100,350), similar to the lineTo() method in canvas.

3. Then L500,350, and L300,50 draw two more lines to complete the triangle, as shown in Figure 11-7.

9781118524404-fg1107.tif

Figure 11-7 This triangle is drawn using a complex path string.

Next, the path string can be simplified a bit:

var triangle = paper.path('M300,50 L100,350 L500,350 Z'),

Here, the last line command has been replaced with Z, which closes the path, thereby drawing the third line of the triangle.

Path strings in Raphaël are built upon the standardized SVG path strings from the W3C spec. You can read more about path strings here: www.w3.org/TR/SVG/paths.html#PathData.

Drawing Curves

So far, I've shown you how to draw only straight lines, but you can also use path strings to draw a number of curves, from basic arcs to complex Béziers. However, manually generating path strings for complex curves can be challenging. Sure, you can draw any type of curve you want; the only problem is that getting there is difficult. It's generally a better idea to use a visual editor to create the curves you need and then export SVG strings for your JavaScript. For example, Adobe Illustrator offers a SaveDocsAsSVG option, as shown in Figure 11-8. With this tool, you can draw complex shapes in Illustrator, and then save them as an SVG.

9781118524404-fg1108.tif

Figure 11-8 Adobe Illustrator allows you to export your vectored graphics as an SVG.

Once you save the SVG, open it up in a text editor and extract the pieces you need. For example, here's the output from a simple curve:

<?xml version="1.0" encoding="iso-8859-1"?>

<!-- Generator: Adobe Illustrator 16.0.4, SVG Export Plug-In . SVG Version:

6.00 Build 0)  -->

<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"

"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">

<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg"

xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="600px"

height="400px" viewBox="0 0 600 400" style="enable-background:new 0 0 600

400;" xml:space="preserve">

<path style="fill:none;stroke:#000000;stroke-width:2.1155;

stroke-miterlimit:10;" d="M251.742,85.146 C75.453,48.476,100.839,430.671,

309.565,250.152"/>

</svg>

I've highlighted the important piece here: M251.742,85.146 C75.453,48.476,100.839,430.671, 309.565,250.152.

You can then pass this path string into Raphaël's path() API to generate the curve in your SVG. Depending on how you draw the path in Illustrator, you may need to adjust some of the values to get it to line up correctly on your drawing canvas.

This example is pretty easy to port into Raphaël by hand, but if you have a more complicated SVG, you can save yourself even more legwork. Just upload the exported SVG to www.readysetraphael.com, which automatically converts SVGs to Raphaël code.

Alternatively, to find out how to build curves yourself, go to https://developer.mozilla.org/en-US/docs/SVG/Tutorial/Paths, where you can read up on a number of different curve types.

Styling

Raphaël offers a variety of styling options for your SVGs that can be applied with the attr() method. You already saw some of these styles in the examples earlier, where attr() was used to apply fill colors and stroke widths. But those were just the tip of the iceberg in terms of the rich styling capabilities in Raphaël.js. For example, to apply a gradient to the triangle in the previous example, you write

triangle.attr({

  gradient: '90-#444-#EEE'

});

Here, the gradient string defines three different options. First, 90 is the angle of the gradient, in this case, a vertical gradient starting from the bottom (for top down, you use 270). Next, two hex colors are defined for the gradient, which transitions from a dark gray to a lighter gray, as shown in Figure 11-9.

9781118524404-fg1109.tif

Figure 11-9 This gradient uses Raphaël's attr() method.

Raphaël also provides a wide variety of stroke options, as in this example:

triangle.attr({

  'gradient': '90-#444-#EEE',

  'stroke': 'green',

  'stroke-width': 20,

  'stroke-linejoin': 'round'

});

This code first changes the stroke color to green, demonstrating that Raphaël is able to parse color strings (in addition to hexadecimal colors, RGB, RGBa, HSL, and so on). Then the stroke width is increased to 20px, and the joins are set to round, which renders rounded corners like those in Figure 11-10.

9781118524404-fg1110.tif

Figure 11-10 A number of stroke styles have been added to the triangle.

Raphaël even includes some interesting options for dashed and dotted lines. You can use any combination of dashes and dots with the stroke-dasharray option:

var circle = paper.circle(300,200,120);

circle.attr({

  'stroke-width': 15,

  'stroke-dasharray': '-..'

});

As shown in Figure 11-11, this dash array creates a dash-and-two-dots pattern for the stroke.

9781118524404-fg1111.tif

Figure 11-11 This custom stroke leverages the stroke-dasharray option.

These are just some of the many styling options you can use with Raphaël.js. To find more about its comprehensive styling capabilities, visit http://raphaeljs.com/reference.html#Element.attr.

Animation

One the best arguments for using Raphaël is its robust animation support, which allows you to accomplish a wide variety of animations with minimal effort. For example, you can rotate the triangle:

var triangle = paper.path('M300,100 L150,300 L450,300 Z'),

triangle.animate({transform: 'r 360'}, 3000, 'bounce'),

Here, r 360 rotates the triangle 360º over the course of 3000 milliseconds using the bounce easing formula. However, the bounce timing function can be a bit jarring. It's a great option at times, but if you'd like something more conservative try < to ease in, > to ease out, or <> to ease in and out. You can also write your own cubic Bézier function or use one of the other defaults listed at http://raphaeljs.com/reference.html#Raphael.easing_formulas.

Also notice that the animation leverages another option string, this time to control the transformation. You can add more transformations to this string if you want—for instance, to control the scale:

triangle.animate({transform: 'r 360 s 0.2'}, 3000, '<>'),

Here, the triangle rotates 360º and scales down to 20 percent of its original size. You can also add a callback to fire another animation after the first or execute any other JavaScript you want:

triangle.animate({transform: 'r 360 s 0.2'}, 3000, '<>', function() {

  triangle.animate({transform: 'r 0 s 1'}, 3000, '<>'),

});

Here, the triangle animates back to its original size and position after the first transformation finishes.

In addition to transformations, you can animate just about any of the options you set in the attr() method. For example, you can animate a number of features of a circle:

var circle = paper.circle(300,200,120);

circle.attr({

  fill: '#FFF',

  'stroke-width': 20

});

circle.animate({

  fill: '#444',

  'stroke-width': 1,

  r: 60,

  cx: 500,

  cy: 100

}, 2000, '<>'),

Here, the script animates several features of the circle: the fill color, stroke-width, radius (r), and center point (cx and cy).

Finally, you can animate the individual points of a path. Simply pass in a new path to the animation, and Raphaël will automatically set up a tween between the old and new points, animating smoothly between them:

var triangle = paper.path('M300,100 L150,300 L450,300 Z'),

triangle.animate({path: 'M300,300 L150,100 L450,100 Z'}, 2000, '<>'),

If you run this script, you'll see paths of the triangle flip over. But it doesn't have to stop there; you can use path animations to create a very complex animation:

var triangle = paper.path('M300,100 L150,300 L450,300 Z'),

(function animationCycle() {

  triangle.animate({path: 'M300,300 L150,100 L450,100 Z'}, 1200, '<>',

  function() {

    triangle.animate({path: 'M300,300 L600,300 L450,100 Z'}, 1200, '<>',

    function() {

      triangle.animate({path: 'M450,100 L600,300 L300,300 Z'}, 1200,

      '<>', function() {

        triangle.animate({path: 'M300,100 L150,300 L450,300 Z'}, 1200,

        '<>', animationCycle);

      });

    });

  });

})();

Here, the triangle flips over and over in a variety of directions in an endless loop. Animations in Raphaël are really limited only by your imagination.

Mouse Events

Earlier in the SVG Mouse Events section, you saw how easy it is to apply mouse interaction to SVGs, and SVGs built with Raphaël are no exception. But before you can apply mouse interaction, you have to get a DOM reference for the SVG. To do so, tap into the node property of the Raphaël object:

var triangle = paper.path('M300,100 L150,300 L450,300 Z'),

triangle.attr('fill', 'white'),

triangle.node.onclick = function() {

  triangle.attr('fill', 'red'),

};

Here, a click event has been bound to the triangle node, which changes its fill color. If you prefer to avoid using the basic JavaScript event handlers, you can also pass this reference into jQuery or another library:

$(triangle.node).on('mouseover', function() {

  triangle.attr('fill', 'red'),

}).on('mouseout', function() {

  triangle.attr('fill', 'white'),

});

Here, jQuery binds mouseover and mouseout handlers to change the color of the triangle on hover.

When binding mouse events, if your SVG doesn't have a fill color, be careful. Without a fill, the event fires only when the user clicks on the stroke, and won't fire in the interior of the shape. If that's not the behavior you want, you can apply a transparent fill of rgba(0,0,0,0) to get around the issue.

Charting with gRaphaël

In addition to the standard graphics library, the Raphaël project maintains a charting library called gRaphaël (http://g.raphaeljs.com). gRaphaël is built on top of Raphaël and provides a few charting options. Compared to other libraries, these charts are relatively lightweight, especially if you're already using Raphaël for other features. However, that filesize benefit comes at a price—gRaphaël's charts aren't as feature-rich as those in other charting libraries. That said, if you want something simple and elegant, gRaphaël is an excellent option.

Pie Charts

You can create a pie chart in gRaphaël with a single line of code:

paper.piechart(300, 200, 150, [65, 40, 13, 32, 5, 1, 2]);

This script creates a pie chart centered at (300,200) with a radius of 150px and showing the data in the array (as shown in Figure 11-12).

9781118524404-fg1112.tif

Figure 11-12 A basic pie chart in gRaphaël.

To add a legend to the chart, as shown in Figure 11-13, pass the legend as part of an options object in the fifth argument:

paper.piechart(300, 200, 150, [65, 40, 13, 32, 5, 1, 2], {

  legend: ['Donkeys', 'Monkeys', 'Llamas', 'Pandas', 'Giraffes', 'Rhinos',

'Gorillas']

});

9781118524404-fg1113.tif

Figure 11-13 A pie chart with a legend.

You can also tweak the legend array to show values by including ##.## in your legend key, such as ‘##.## - Monkeys' (‘40 Monkeys'), or use %%.%% to convert the value to a percent.

Finally, to convert the different slices in the chart to links, simply pass the links as a href array:

paper.piechart(300, 200, 150, [65, 40, 13, 32, 5, 1, 2], {

  legend: ['Donkeys', 'Monkeys', 'Llamas', 'Pandas', 'Giraffes', 'Rhinos',

'Gorillas'],

  href: ['url-1.html', 'url-2.html', 'url-3.html', 'url-4.html',

'url-5.html', 'url-6.html', 'url-7.html']

});

These are just some of the options available in gRaphaël's pie chart. To learn more about other capabilities, visit http://g.raphaeljs.com/reference.html#Paper.piechart.

Bar Chart

gRaphaël also supports basic bar graphs. For example:

paper.barchart(0, 0, 600, 400, [[63, 86, 26, 15, 36, 62, 18, 78]]);

Here, the first two arguments are the start point (0, 0), and the next two are the width and height, respectively. Finally, an array of values is passed to generate the chart shown in Figure 11-14.

9781118524404-fg1114.tif

Figure 11-14 A basic bar chart in gRaphaël.

Notice that the values array is contained in another array. That's because you can pass multiple sets of values to the chart to compare different values:

var data1 = [63, 86, 26, 15, 36, 62, 18, 78],

    data2 = [12, 47, 75, 84, 7, 41, 29, 4],

    data3 = [39, 91, 78, 4, 80, 54, 43, 49];

paper.barchart(0, 0, 600, 400, [data1, data2, data3]);

Here, the three different arrays are compared across the chart, as shown in Figure 11-15.

9781118524404-fg1115.tif

Figure 11-15 A bar chart with three different value sets.

You can also stack the value sets by setting the stacked option (shown in Figure 11-16):

paper.barchart(0, 0, 600, 400, [data1, data2, data3], {stacked: true});

9781118524404-fg1116.tif

Figure 11-16 A bar chart with stacked values.

Unfortunately, gRaphaël's bar chart does not provide an easy way to add labels, which makes it somewhat limited. A number of hacks for adding labels have been suggested, but they no longer work in the more recent versions of the library. You can, however, add your own labels using Raphaël's text() method. Here's a function that you can leverage to create the labels:

Raphael.fn.labelBarChart = function(opt) {

  var paper = this;

  

  // offset x_start and width for bar chart gutters

  opt.x_start += 10;

  opt.width -= 20;

  

  var labelWidth = opt.width / opt.labels.length;

  

  // offset x_start to center under each column

  opt.x_start += labelWidth / 2;

  

  for ( var i = 0, len = opt.labels.length; i < len; i++ ) {

    paper.text( opt.x_start + ( i * labelWidth ), opt.y_start,

  opt.labels[i] ).attr( opt.textAttr );

  }

};

Don't worry too much about the code in here. It just calculates a position for each column's label and then writes it to the SVG. Here's how to use it:

var chart = paper.barchart(0, 0, 600, 380, [[63, 86, 26, 15, 36, 62, 18,

78]]);

var labels = ['Col 1', 'Col 2', 'Col 3', 'Col 4', 'Col 5', 'Col 6',

  'Col 7', 'Col 8'];

paper.labelBarChart({

  x_start: 0,

  y_start: 390,

  width: 600,

  labels: labels,

  textAttr: {'font-size': 14}

});

In this implementation, an object of settings is passed to labelBarChart, defining the start coordinates for the labels (0,390), as well as the width of the chart. Additionally, it defines the labels for each column, along with optional settings for the text. As shown in Figure 11-17, this labels the x-axis of the bar chart.

9781118524404-fg1117.tif

Figure 11-17 The bar chart with custom labels.

Labeling the y-axis is a bit more challenging, but certainly not impossible. Write it as an exercise, following the example for the x-axis. Alternatively, if you need y-axis labels and don't want to write this bit of code, consider using a different library for your bar chart, such as Raphy Charts: http://softwarebyjosh.com/raphy-charts/.

Line Chart

Fortunately, gRaphaël's line charts are a little more robust than its bar charts. The API is also fairly similar, the main difference being that you need to pass two value arrays for the x and y values:

var xVals = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55],

    yVals = [63, 84, 75, 91, 62, 75, 35, 53, 47, 75, 78, 54];

var chart = paper.linechart(0, 0, 600, 380, xVals, yVals);

Here, the first two arguments are the start coordinates (0,0), followed by the width and height of the chart, and then the two value arrays. It generates a very basic line graph as shown in Figure 11-18.

9781118524404-fg1118.tif

Figure 11-18 gRaphaël's basic line chart.

You can also pass multiple sets of values to add lines to the chart:

var xVals = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55],

    yVals1 = [63, 84, 75, 91, 62, 75, 35, 53, 47, 75, 78, 54],

    yVals2 = [24, 45, 31, 42, 88, 85, 67, 88, 72, 37, 54, 48];

var chart = paper.linechart(0, 0, 600, 380, xVals, [yVals1, yVals2]);

Here, a second set of y values has been added, which uses the same set of x values as shown in Figure 11-19.

9781118524404-fg1119.tif

Figure 11-19 You can add multiple lines to the chart.

Fortunately, unlike bar charts, gRaphaël provides native methods for adding axes to line charts. Axes can be added using the axis option:

var chart = paper.linechart(30, 0, 570, 380, xVals, [yVals1, yVals2], {axis: '0 0 1 1'});

The axis string defines which axes to render in TRBL order (top right bottom left)—in this case, showing the bottom and left axes (see Figure 11-20). Also notice how the x_start and width arguments have been adjusted to allow for 30px of room for the y-axis.

9781118524404-fg1120.tif

Figure 11-20 A line chart with axes.

Finally, you may have noticed that the step values on the axes are a bit strange. Since the x values are all evenly spaced, labeling them accordingly is good practice. You can set up more natural labeling using the axisxstep option:

var chart = paper.linechart(30, 0, 570, 380, xVals, [yVals1, yVals2], {axis:

'0 0 1 1', axisxstep: 11});

Now the x-axis is labeled much more intuitively, as you can see in Figure 11-21.

9781118524404-fg1121.tif

Figure 11-21 The axisxstep value has been adjusted in this chart.

The axisxstep value is a bit confusing since it is one less than the number of steps you want to show. In this case, the value of 11 shows the 12 steps on the axis.

gRaphaël's axis-labeling options leave a lot to be desired. The documentation states that axisxstep and axisystep define the distance between values in axes x and y. However, in actuality, they define the number of steps on each axis, which is considerably less useful. Hopefully, this shortcoming will be resolved soon.

3D Canvas with WebGL

One of the most exciting aspects of writing JavaScript today is the ability to build full-scale 3D applications directly in the browser. These apps are possible thanks to the WebGL engine, which has been integrated as a special context of the HTML5 canvas element. However, writing code with WebGL is challenging because the API is really low level—meaning that it was designed for power rather than ease of use. Therefore you can accomplish just about anything you want with the API, provided you are willing to navigate large blocks of code for even the smallest tasks. In fact, the WebGL API is so low-level that working with it pretty much requires creating a library. Fortunately, a number of other developers have paved the way with some really great WebGL libraries for JavaScript. In this section, you learn how to use one of the best WebGL libraries, Three.js.

Introducing Three.js

Although Three.js streamlines WebGL development, it still uses a few concepts that may be foreign if you haven't worked with 3D programming. In this subsection, I introduce you to all the techniques you need to render a basic 3D scene—such as cameras, geometry, materials, and lighting.

Setting Up the Scene

To get started, download the latest version of Three.js (https://github.com/mrdoob/three.js) and include it on your page along with a container element:

<div id="container" style="background-color: #000; width: 600px;

height: 400px;"></div>

Now, set up the scene with some basic JS:

// get the wrapper

var container = document.getElementById('container'),

    width = container.offsetWidth,

    height = container.offsetHeight;

// create renderer and add it to the container

var renderer = new THREE.WebGLRenderer();

renderer.setSize( width, height );

container.appendChild( renderer.domElement );

// create a new scene

var scene = new THREE.Scene();

This snippet references the container for the scene and creates a WebGL renderer and scene in Three.js. Make sure to call this script sometime after the container is added to the DOM.

Adding a Camera

Next, create an integral part of any 3D scene—the camera. By itself, a 3D scene in WebGL is just a conceptual model of how shapes exist in 3D space. The camera is used to render that information and relay it to the user:

// create a camera, position it and add it to the scene

var camera = new THREE.PerspectiveCamera( 45, width/height, 0.1, 1000 );

camera.position.z = 400;

scene.add( camera );

Here, a new camera is added to the scene.

• The first argument in THREE.PerspectiveCamera is the angle of view for the camera lens. Generally, a good option is 45º, but you can increase it to 80º for a wide-angle lens effect (like the fish-eye lens in skateboarding videos).

• The next argument is the aspect ratio; in this case, it uses the aspect ratio of the container element.

• The last two values are the near and far boundaries that you want to render in the scene. WebGL conserves resources by limiting the scope of the scene that is rendered in the camera. Thus you can improve performance by using intelligent defaults for the scene area to allow the engine to ignore irrelevant distances.

After the camera is created, you then position it in the scene. Much like the shapes that make up a scene, the camera also exists in 3D space. You can think of this position as where you want the cameraman to stand and film the scene that will eventually unfold. In this example, camera.position.z moves the camera 400 units along the z-axis.

Adding a Mesh

In WebGL, any shape you add to the scene is called a mesh. Meshes are made up of two components:

• Geometry, which defines the vertices that shape the object

• Material, which defines how the surface of the material is lit and rendered

To add a simple sphere to the scene, the script needs to create these components:

// add a sphere

var geometry = new THREE.SphereGeometry( 100, 24, 24 ),

    material = new THREE.MeshPhongMaterial({

      color: 0x00FF00

    }),

    sphere = new THREE.Mesh( geometry, material );

    

scene.add( sphere );

This code first creates some sphere geometry. The first argument defines a radius of 100 units, and the second two define the number of segments and rings to use when creating the model. The more segments and rings you use, the smoother the surface of the model will appear when rendered. That's because a sphere in WebGL isn't a true sphere: It's an approximation made up of polygons. To get an idea of how this works, see Figure 11-22.

9781118524404-fg1122.tif

Figure 11-22 A 3D sphere comprised of polygons. The jagged edges are smoothed by the renderer at runtime. This sphere was rendered using the 3D graphics program Blender, but WebGL uses the same concept.

Next, a Phong material is defined with a color of #00FF00. Phong is a type of shader that defines how light affects the object in the renderer. Don't worry too much about shaders just yet. Finally, the geometry and the material are put together to create the mesh, which is added to the scene.

Another common material option in Three.js is MeshLambertMaterial, which is similar to Phong except that Lambert is less reflective and tends to show less shadow.

Adding a Light

Now that the camera and sphere have been added to the scene, it's just about finished. However, if you try rendering it as is, Three.js shows a black screen because you still have to add lighting:

// add a light

var light = new THREE.DirectionalLight( 0xFFFFFF, 1 );

light.position.set( 0, 0.5, 1 );

scene.add( light );

This code adds a directional light to the scene. The first argument specifies the color of light, and the second specifies the intensity. Directional lights are one of a handful of different lighting options in Three.js, including ambient light (which lights the whole scene evenly) and point light (which emanates from an individual point like a candle). Directional lights, on the other hand, light evenly in a given direction, much like the parallel rays of the sun beating down on the earth (okay, practically parallel).

To set the angle of the directional light, you just have to position it. Here, the light is positioned 0.5 units up on the y-axis and 1 unit toward the camera on the z-axis. The light then automatically orients its angle to point into the origin (0,0,0). In this case, the result is a 30º angle down and away from the camera.

Rendering the Scene

Last but not least, you need to render the scene:

renderer.render( scene, camera );

As shown in Figure 11-23, this script creates a simple sphere.

9781118524404-fg1123.tif

Figure 11-23 This sphere has been rendered with Three.js and WebGL.

Just to recap, here's the entire example:

// get the wrapper

var container = document.getElementById('container'),

    width = container.offsetWidth,

    height = container.offsetHeight;

// create renderer and add it to the container

var renderer = new THREE.WebGLRenderer();

renderer.setSize( width, height );

container.appendChild( renderer.domElement );

// create a new scene

var scene = new THREE.Scene();

// create a camera, position it and add it to the scene

var camera = new THREE.PerspectiveCamera( 45, width/height, 0.1, 1000 );

camera.position.z = 400;

scene.add( camera );

// add a sphere

var geometry = new THREE.SphereGeometry( 100, 24, 24 ),

    material = new THREE.MeshPhongMaterial({

      color: 0x00FF00

    }),

    sphere = new THREE.Mesh( geometry, material );

    

scene.add( sphere );

// add a light

var light = new THREE.DirectionalLight( 0xFFFFFF, 1 );

light.position.set( 0, 0.5, 1 );

scene.add( light );

// render it

renderer.render( scene, camera );

As shown in this example, you take four main steps to render this scene in Three.js:

1. Set up the renderer and the scene.

2. Add a camera and position it in 3D space.

3. Build the geometry and material to create a mesh for your shape.

4. Light the scene.

Creating Texture with Images

The previous example used a simple block color when creating the material for the sphere. But Three.js also allows you to use images to create custom textures for the models in your scene. For instance, you can add a realistic Earth texture to the sphere from the previous example. First, go to www.3dstudio-max.com/download.php?id=57 and download the Earth texture, which is shown in Figure 11-24.

9781118524404-fg1124.tif

Figure 11-24 The Earth texture will be mapped to the sphere.

Next, add this texture to the mesh:

// add image map

var map = THREE.ImageUtils.loadTexture( 'images/earth.jpg' );

// add the earth

var geometry = new THREE.SphereGeometry( 100, 24, 24 ),

    material = new THREE.MeshPhongMaterial({

      map: map

    }),

    earth = new THREE.Mesh( geometry, material );

    

scene.add( earth );

Here, THREE.ImageUtils.loadTexture() is used to load the texture into Three.js. That texture is then assigned to the Phong material as an image map. However, if you try to render the scene now, you will most likely have problems because the image takes a little while to load. To get around this issue, make sure to render it after the window loads:

window.onload = function() {

  renderer.render( scene, camera );

};

In Figure 11-25, the image map positions the texture correctly around the sphere to make it look like the planet Earth. This calculation is actually very complex under the hood, so be thankful that Three.js handles it for you and that you aren't writing vanilla WebGL.

9781118524404-fg1125.tif

Figure 11-25 A sphere with a photorealistic image map.

Animating 3D Canvas

You now know how to render static 3D scenes with WebGL. But if you needed only static images, you could just render them in a 3D graphics program and include a basic <image> on the page. The real reason to use WebGL is to create interactive, animated 3D scenes, which is precisely what you learn in this subsection.

Animation in the WebGL canvas works similarly to that in the 2D canvas: You simply draw and redraw the scene for each frame in the animation. For example, the following script rotates the Earth:

window.onload = function() {

  var rotation = 0;

  

  var animationLoop = setInterval(function() {

    rotation += 0.05;

    earth.rotation.y = rotation;

    renderer.render( scene, camera );

  }, 100);

};

This example uses a basic interval to adjust the rotation of the earth mesh every 100 milliseconds. If you run the script in your browser, you'll see the Earth slowly rotating around its axis.

In the next chapter, you discover how to use HTM5's requestAnimationFrame, an alternative to animating with a simple interval. requestAnimationFrame produces smoother animations with better performance.

Adding Mouse Events

Earlier in the Canvas Mouse Events section, you saw that applying event listeners to canvas drawings is tricky since the drawn shapes can't be referenced in the DOM. Unfortunately, WebGL canvas is no exception. You can, however, use a technique to add mouse interactivity to your 3D scenes. The trick is essentially to create a ray from the camera to the mouse position and see if that intersects the object:

var projector = new THREE.Projector();

container.onmousedown = function(e) {

  e.preventDefault();

  var vector = new THREE.Vector3( ( e.pageX - this.offsetLeft ) /

this.offsetWidth * 2 - 1, - ( e.pageY - this.offsetTop ) /

this.offsetHeight *  2 + 1, 0.5 );

  projector.unprojectVector( vector, camera );

  var raycaster = new THREE.Raycaster( camera.position, vector.sub(

camera.position ).normalize() );

  var intersects = raycaster.intersectObject( earth );

  

  if ( intersects.length > 0 ) {

    console.log('Earth'),

  }

};

The script first uses Three.Vector3() to create a vector from the mouse position relative to its container. That vector is then “unprojected” (projected in reverse) toward the camera. Next, a ray is cast from the camera toward the unprojected vector. If that ray intersects the object, the user is clicking that object.

Don't worry about following the code in THREE.Vector3—you can simply reuse this snippet to unproject vectors and calculate intersection.

You can also use this technique with layered objects to ensure that the mouse event applies to only the topmost object. For example, you can add a moon to this scene:

var moonGeometry = new THREE.SphereGeometry( 20, 24, 24 ),

    moonMaterial = new THREE.MeshPhongMaterial({

      color: 0xDDDDDD

    }),

    moon = new THREE.Mesh( moonGeometry, moonMaterial );

moon.position.z = 150;

moon.position.x = 50;

scene.add( moon );

Now, instead of using intersectObject in the click handler, use intersectObjects with an array of objects:

var intersects = raycaster.intersectObjects( [earth, moon] );

if ( intersects.length > 0 ) {

  var topObjectId = intersects[0].object.id;

  

  if ( topObjectId == earth.id ) {

    console.log('Earth'),

  }

  else if ( topObjectId == moon.id ) {

    console.log('Moon'),

  }

}

This script first passes both the earth and moon mesh references into intersectsObjects. Then it determines the id of the topmost object that intersects the click event, which is the first item in the intersects array. Finally, it compares that id to those of the two objects to determine which is the top layer.

Using a 2D Canvas Fallback

Unfortunately, browser support for WebGL canvas is still lacking, with no IE support and very limited mobile coverage at the time of this writing (details here: http://caniuse.com/#feat=webgl). However, one of the nicest features of the Three.js library is its built-in fallback to 2D canvas. Although the fallback doesn't look as nice as the WebGL renderer, it provides a certain amount of coverage for non-supportive browsers. Setting up the 2D renderer works just like it does with the WebGL counterpart:

var renderer = new THREE.CanvasRenderer();

To use the fallback, simply set up detection for WebGL support, using either Modernizr or Three.js' built-in detector:

var renderer = Detector.webgl ? new THREE.WebGLRenderer():

  new THREE.CanvasRenderer();

The 2D canvas renderer certainly isn't as pretty, but it's a decent fallback, as you can see in Figure 11-26.

9781118524404-fg1126.tif

Figure 11-26 Comparison of Three.js' WebGL and Canvas renderers. The WebGL version on the left has better shading and lighting. Additionally, the canvas version shows some artifacts of the polygons used to create the image map.

3D Transforms in CSS

You don't necessarily need WebGL to create interactive 3D graphics on the web. Provided that the functionality you need is simple enough, you may be able to accomplish the same features using 3D transforms in CSS3.

While browser support still isn't perfect for 3D transforms, they are supported in the current versions of all major desktop and mobile browsers (details provided at http://caniuse.com/#feat=transforms3d). That's a good deal better than WebGL canvas, which isn't supported in IE10, iOS Safari, or Android Browser at the time of this writing.

Of course, CSS3's capabilities are a lot more limited than those of WebGL, being confined to basic manipulations of flat objects, such as rotating a card or line of text in 3D space. To see how this works, start with the following markup:

<div class="container">

  <div class="card">CSS3 Transform</div>

</div>

Then style it with 3D transforms (relevant parts in bold):

.container {

  width: 400px;

  height: 250px;

  position: relative;

  -webkit-perspective: 500px;

     -moz-perspective: 500px;

          perspective: 500px;

}

.card {

  position: absolute;

  top: 0;

  right: 0;

  bottom: 0;

  left: 0;

  padding: 50px;

  font-size: 50px;

  background: tomato;

  color: #FFF;

  -webkit-transform: rotateY( 45deg );

     -moz-transform: rotateY( 45deg );

          transform: rotateY( 45deg );

  

}

The first step for 3D transforms is defining the perspective on the parent element. That's similar to setting the angle of view for the camera in WebGL—it alters how the 3D view is rendered. Next, the CSS applies the actual 3D transforms to the .card. These rotate it 45º around the y-axis, producing the slightly angled card shown in Figure 11-27.

9781118524404-fg1127.tif

Figure 11-27 This card has been rotated using CSS3 transforms.

By default, perspective applies to only the direct descendants of an element. To extend it to all descendants, add transform-style: preserve-3d to the .card element (along with the -webkit and -moz extensions).

To animate the transform, you have a couple options. First, you can use a CSS3 transition:

.card {

  position: absolute;

  top: 0;

  right: 0;

  bottom: 0;

  left: 0;

  padding: 50px;

  font-size: 48px;

  background: tomato;

  color: #FFF;

  -webkit-transform: rotateY( 45deg );

     -moz-transform: rotateY( 45deg );

          transform: rotateY( 45deg );

  -webkit-transition: -webkit-transform .5s ease;

     -moz-transition:    -moz-transform .5s ease;

          transition:         transform .5s ease;

}

.card:hover {

  -webkit-transform: rotateY( -45deg );

     -moz-transform: rotateY( -45deg );

          transform: rotateY( -45deg );

}

Here, CSS applies a transition to the element, which will smoothly animate between style changes. Then a :hover pseudo class is added to the card, to rotate it backward to –45º. Alternatively, you can use CSS keyframe animation (the next chapter has more about transitions and keyframe animations).

Another option is to animate the change with JavaScript. However, doing so generally isn't a good idea since the CSS3 animations tend to outperform JavaScript alternatives. That said, you can take advantage of the CSS3's animation performance, while still triggering the actual change using JavaScript. Simply apply a new class to alter the style of the transform, and then let the previously applied transition handle the animation for you.

Summary

In this chapter, you discovered a number of techniques for rendering graphics in the browser. First, you learned about canvas and SVG and how to use both technologies to render basic shapes. You then animated these shapes and applied mouse interactivity.

Next, you jump-started your 2D drawing capabilities with the SVG library Raphaël.js. With Raphaël, you examined how to draw more complex shapes and how to add a variety of styling options and take advantage of the built-in animation support. Then you extended your knowledge of Raphaël on to gRaphaël, a simple charting library.

After solidifying your skills for 2D graphics, you moved on to 3D canvas with WebGL. You learned how to use the Three.js library to render a basic scene using cameras, lighting, geometry and materials. Then you discovered image maps and used them to create a photorealistic texture for the scene. Next ,you learned how to animate the 3D canvas, as well as a technique for adding mouse event handlers to the scene. Finally, you considered an alternative to WebGL: 3D transforms in CSS3. These transforms aren't nearly as powerful as WebGL but can provide a more elegant solution to simple problems.

Now that you are able to draw custom 2D and 3D graphics in the browser, the possibilities are practically endless for what you can achieve on the front end. Use these techniques to create a rich, interactive app that will delight and impress your users.

Additional Resources

Inspiration

Chrome Experiments: www.chromeexperiments.com

21 Ridiculously Impressive HTML5 Canvas Experiments: http://net.tutsplus.com/articles/web-roundups/21-ridiculously-impressive-html5-canvas-experiments

Interactive Experiments Focused on HTML5: http://hakim.se/experiments

Canvas

Canvas Tutorial: https://developer.mozilla.org/en-US/docs/HTML/Canvas/Tutorial

Fulten, Steve, and Jeff Fulton. HTML5 Canvas (O'Reilly Media, Inc., 2011): http://shop.oreilly.com/product/0636920013327.do

SVG

SVG Tutorial: https://developer.mozilla.org/en-US/docs/SVG/Tutorial

SVG Path String Specifications: www.w3.org/TR/SVG/paths.html#PathData

Raphaël.js

Raphaël.js Documentation: http://raphaeljs.com/reference.html

gRaphaël Documentation: http://g.raphaeljs.com/reference.html

SVG with a Little Help from Raphaël: www.alistapart.com/articles/svg-with-a-little-help-from-raphael

Ready Set Raphaël (SVG Converter): www.readysetraphael.com

WebGL

WebGL Fundamentals: www.html5rocks.com/en/tutorials/webgl/webgl_fundamentals

Cantor, Diego, and Brandon Jones. WebGL Beginner's Guide (Packt Publishing, 2012): www.packtpub.com/webgl-javascript-beginners-guide/book

Three.js

Three.js Documentation: http://mrdoob.github.com/three.js/docs

Parisi, Tony. WebGL: Up and Running (O'Reilly Media, Inc., 2012): http://shop.oreilly.com/product/0636920024729.do

Take a Whirlwind Look at Three.js: http://2011.12devsofxmas.co.uk/2012/01/webgl-and-three-js

Three.js Click Event Example: http://mrdoob.github.com/three.js/examples/canvas_interactive_cubes.html

3D Transforms in CSS3

Intro to CSS 3D Transforms: http://desandro.github.com/3dtransforms

20 Stunning Examples of CSS 3D Transforms: www.netmagazine.com/features/20-stunning-examples-css-3d-transforms

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset