Showing posts with label Math. Show all posts
Showing posts with label Math. Show all posts

Monday, February 6, 2012

Trigonometry for Web Developers, Part Three

Recap of Part Two
In part two of this series, we learned about the use of the sine and cosine functions and how they can be used to transform a set of polar coordinates (angle and distance) into cartesian (x,y) coordinates.   We  then used them along with an HTML canvas object and the associated translate() and rotate() methods to draw tick marks and labels on our canvas object.    In this final installment, we'll add a needle to our meter and learn a bit about how to efficiently animate it to display rapidly changing values.

Drawing the Needle
Our analog meter example wouldn't be complete without a needle to indicate the current value.    Just like on a real meter, the needle on our example pivots on one end around a point centered along the lower edge of the canvas while the other end points at the value to be displayed.    Conceptually, this is just like drawing another tick line as we did in the previous example: we will compute the angle and then use it, along with a predetermined length, to find the polar coordinates for one end of the line.   We will then draw the line using those coordinates along with the center bottom of the canvas.

The Javascript code required to draw the needle is straightforward.
// Determine the angle of the needle
needleAngle = startAngle + a_value * ((endAngle - startAngle) / 10.0);

// Draw the needle
ctx.clearRect(0, 0, cvs.width, cvs.height);
ctx.beginPath();
ctx.moveTo( centerX, bottomY );
ctx.lineTo( centerX + Math.cos(needleAngle) * (radius * 0.80), 
            bottomY + Math.sin(needleAngle) * (radius * 0.80));
ctx.stroke();
The angle of the needle is first determined by finding the polar distance between the starting and ending angles.   That distance is then divided by ten (the range of our scale), then multiplied by the value.    This result would be all that was needed if the zero value of the needle was aligned with 0 degrees on the graphics context, but in order to match it to the meter's face, the starting angle must be added.
Similar to drawing the tick marks, the drawing cursor is moved to one end of the needle's position and line is then drawn from there to a coordinate determined through the use of sine and cosine functions.

Adding a Layer
While it is certainly possible to draw the needle on the same HTML canvas as the tick marks, labels, etc. it is not very efficient to do so.   Consider that the needle may need to move frequently, thus needing to be redrawn, but the face of the meter is constant.   It would be very inefficient to re-draw the entire meter every time the value to be displayed changes, so we'll use two canvas objects: one for the labels and other items which do not change, and another for the needle.   We will then place them "on top" of each other, with the canvas for the needle in front.   This allows the animation to be much more efficient:  we can draw the face of the meter only once, and draw only the needle when the value is changed. 
<div style="width:500px; height:250px;">
  <canvas id="meter" width="500" height="250" 
          style="position:absolute; left:0; top:0; z-index:0"> 
  </canvas>
  <canvas id="needle" width="500" height="250" 
          style="position:absolute; left:0; top:0; z-index:1"> 
  </canvas>
</div>
The HTML required to set up the layering is shown above.     Notice that each canvas object is given an absolute position within it's containing DIV object.   This is what causes the canvases to appear to be "stacked".   Also, note the z-index value in the style attribute; this is what determines which canvas is "in front".    HTML objects with increasing z-order values will appear "closer" to the user than those with smaller values.  

The full HTML source which demonstrates drawing the needle into a separate canvas can be found here (FF, Chrome, and IE9).


Going Further
As you have seen, combining trigonometry, the HTML canvas object, and some Javascript can yield some neat results.   While it is beyond the scope of this series of articles, the HTML canvas object has some powerful drawing features.   Using the trigonometry concepts presented so far along with not-a-lot-more canvas object methods, you can produce impressive results with relatively little effort - click on the clock for the full-size, animated version (FF, Chrome, and IE9 only) :

This example uses the same techniques described previously, and adds some visual enhancements through the use of gradient fills, shadowing, and line style features of the HTML canvas object.    Like our sample meter, two separate layers are used: one for the clock face, numbers, etc. and a second canvas that only contains the clock hands.     No images were used for this example, so even without minification or other optimization techniques, the file size comes in at around 8KiB which provides for excellent performance even on slow links.

Wrapping It All Up
There is much more that could be done with this relatively simple example, but we have now covered the basics needed to handle angular data and use trigonometry to transform it to the x,y coordinate system used by the HTML canvas object.    If nothing else, remember this :
  • Math functions think in radians.   Convert degrees to radians by multiplying by π/180.0 and convert radians to degrees by dividing by π/180.0.   
  • cosine(angle) returns an X-coordinate, normalized to values between -1.0 and +1.0.   Multiply this value by the radius and add/subtract as needed to move the center of the circle away from X-coordinate 0.
  • sine(angle) returns the Y-coordinate, normalized to values between -1.0 and +1.0.   Multiply this value by the radius and add/subtract as needed to move the center of the circle away from Y-coordinate 0.
A little trigonometry added to an HTML canvas is an easy way to build an experience for your site that is fresh and different from the majority of rectangular, boxy sites which exist today.     What can you build with a little trig?


Tuesday, January 31, 2012

Trigonometry for Web Developers, Part Two

Recap of Part One
In part one of this series, we learned about degrees vs. radians, the orientation of the polar coordinate system, and how to use HTML and Javascript to draw an arc.    Continuing to build the pieces needed to display our analog meter example, this article introduces two new trig functions and demonstrates how to use them.   We'll also look at some additional drawing capabilities of the HTML canvas object as we use Javascript to draw tick marks and labels on the face of our meter.

Tick Marks
Our meter will have a small line, or "tick mark", associated with each whole value i.e. 0, 1, 2, and so on.   In order to put these lines on the outside edge of the arc we drew in the first part of this series, we'll need to determine the angle for each line and use that along with the arc's radius to determine the two end points of the line.   Once we have these points, we can easily draw them using the HTML canvas object:

The first task is to deterermine how far apart to place each tick mark.  Since our meter will display values between and including zero and 10, we will need 11 tick marks.    While it might be possible to determine an answer using only (x,y) coordinate pairs, it's actually quite simple to do using polar coordinates.    Recall that the meter extends from an angle of 210 degrees to an angle of 330 degrees.   The difference between these two values is known as the angular distance, and by dividing this value by the number of tick marks we can determine the angular distance between each tick mark.

For each tick mark, we will use a pair of trigonometric functions to determine the end points of the line.   The sine function will be used to determine the y-axis value, and the cosine function will be used to determine the x-axis value for each end of the line.    Either function by itself cannot give the complete answer - it only knows about angles and, in this case, knows nothing about the radius we need.     Additionally, these functions assume that the base (vertex) of the angle is located at  0,0 ... which is seldom the case.    To correct for these issues, we will use the following functions :
y = sine(angle) * radius + offset_y
x = cosine(angle) * radius + offset_x
The sine and cosine functions always return a value between -1.0 and +1.0, so to translate that value into something meaningful we must do two things:   multiply it by the radius of the arc, and add the offset to the center of the circle.   For any given angle, the formulas above will locate a point exactly on our arc - this is one end of the tick mark.   We still need to find the other end of the tick mark, however.   Fortunately, this is quite trivial - we simply adjust the radius slightly by subtracting the size of the tick mark.   Assume that we use "length" to signify the length of the tick mark, the slightly altered formulas then become :
y = sine(angle) * (radius-length) + offset_y
x = cosine(angle) * (radius-length) + offset_x

All of the above logic can be expressed in a relatively small amount of code:
// Tick marks
var numTicks    = 11,
    tickLength  = 10,
    step        = (endAngle - startAngle) / numTicks,
    angle       = startAngle;
ctx.beginPath();
for(var iX = 0; iX <= numTicks; ++iX) {
  ctx.moveTo( centerX + Math.cos(angle) * radius, 
              bottomY + Math.sin(angle) * radius);
  ctx.lineTo( centerX + Math.cos(angle) * (radius - tickLength), 
              bottomY + Math.sin(angle) * (radius - tickLength));
  angle += step;
}
ctx.stroke();
After specifying the number and size of tick marks to be drawn, the angular distance between each tick mark is calculated as we described earlier.    A loop is then used to draw each tick mark by using the moveTo() method to position the drawing cursor along with the lineTo() method to draw a line from the cursor position to the new position.

The complete HTML file for this example can be obtained from here.

Labels
Wouldn't it be nice if each of the tick marks had a label to go with it?   From a user experience perspective, a label facilitates faster comprehension of the data being displayed than would a tick mark alone.   Here's what we want to end up with :
The mechanism for drawing these labels is similar to those used to draw the tick mark.   We'll again make use of the sine and cosine functions to determine the location of each label, and we'll also introduce a couple of HTML canvas methods which greatly simplify drawing the labels.

As with the tick marks, the first step is to determine the angular distance between each label and is done the same way: by dividing the difference between the ending and starting angles by the number of items we wish to display.     Also, as with drawing the tick marks, the polar coordinates (the angle and the radius) and converted to an x,y coordinate pair through the use of cosine and sine functions.     Compare the code for drawing these labels with that for drawing the tick marks - you'll find many similarities:

//-------------------------------------------------
// Value labels
var   textMetrics,
      textHeight = 14,
      label;
angle = startAngle;
step  = (endAngle - startAngle) / numTicks;
for(var iX = 0; iX <= numTicks; ++iX) {
  label = '' + iX;      // Javascript way to force conversion to string type
  textMetrics = ctx.measureText( label );
  ctx.save();
  ctx.translate(centerX + Math.cos(angle) * radius,
                bottomY + Math.sin(angle) * radius);
  ctx.rotate( angle + Math.PI/2);
  ctx.fillText(label, -textMetrics.width/2, textHeight*2);
  ctx.restore();
  angle += step;
}

You'll also notice a few differences.    Since each label is potentially a different size (due to differing widths of the characters within the label), and since each label is rotated slightly differently, this code relies on some HTML canvas object methods to account for these concerns.   The first method, measureText(), is used to obtain the width of the label in pixels (oddly, this method does not return height as yet).      The save() and restore() methods are used to preserve the graphics context state that we alter with the translate() and rotate() methods.

The interesting things happen with the call to the translate() method.   This method is used to move the origin (point at coordinates 0,0) to a new location.   Using the formulas presented earlier, the code uses the translate() method to establish the new origin at the point where the label should be drawn.

Since we also would like the text to be at the same angle as the tick mark, we'll make use of the canvas object's rotate() method.   This method rotates the entire drawing context by the specified amount.   In this case, we'll rotate by the angle plus an additional 90 degrees, or π/2 radians.     Why the additional 90 degrees of rotation?   The additional 90 degrees of rotation is needed to account for the zero angle being on the right side of the polar coordinate circle .

Without adding the additional 90 degrees of rotation, the labels would appear to be drawn as though laying on their side.

The complete HTML file for this example can be found here, and additional background on the HTML canvas methods can be found here.

Up Next: Drawing the Needle
We're a lot closer to having a functional meter now but still have a couple of things left to do next time.   For now, we've covered several key topics:

  • Determining angular distances between a starting and ending angle
  • Converting from polar coordinates to x,y form
  • Use of HTML canvas translate() and rotate() methods

Next time, we'll add the needle to the meter and animate its movements - stay tuned!

Continue onto Part Three...

Tuesday, January 24, 2012

Trigonometry for Web Developers, Part One

Introduction
Recently my team was tasked with developing a custom UI control for an application being built with the excellent Dojo framework.    The concept makes use of a rotatable dial, which caused some head scratching as folks hadn't used trig (trigonometry) in years.    Realizing that I could better explain the concepts by providing some "real world" examples, I put together some samples using only HTML and Javascript.  

This article is the first in a series of three, with each successive article building on the previous.    Let's get started!

Degrees vs. Radians
Most people think of angles in terms of degrees, with 360 degrees comprising a full circle.   Trig functions available in most common programming languages such as Javascript, C/C++, python, etc. all think in different units, however - these units are known as radians.    Fortunately, converting between the two is relatively easy using these formulas :
radians = (π / 180.0) * degrees
degrees = radians / (180.0 / π)
Don't let radians scare you - it's just another way of expressing the same thing and you'll like find yourself starting to "think" in radians before long. Until that time, let's create some helper functions to make things easy :
function toRadians(a_degrees)
{
  return (Math.PI / 180.0) * a_degrees;
}

function toDegrees(a_radians)
{
  return a_radians / (180.0 / Math.PI);
}
Note the use of "Math.PI" - this is a constant provided by the Javascript "Math" object that is far more precise than the typical "3.14" representation.

The diagram below demonstrates the relationship of degrees to radians, using the mathematical convention of expressing radians in terms of π.    Be sure also to notice that the circle starts from the right-most point, not the top.   This means that a line extending from the center at an angle of zero degrees/zero radians will extend to the right, not to the top, edge of the circle.


Let's Draw Something
For the purposes of demonstrating how to marry trig functions with graphics, we'll construct a simple analog-style meter in Javascript. Our meter will have a scale from 0 through 10 with tick marks for each value.     A needle will point to a value on the scale and will appear to pivot about a fixed point. We'll use Javascript to draw into an HTML canvas object, but the concepts will be the same regardless of the underlying language.

The first step is to create an HTML file to contain the page along with our Javascript.   Example 1 contains a basic HTML document along with the Javascript function DrawMeter().   When you open this file in your browser, you should see a partial circle across the top left area of your browser window, similar to the image on the right.      Go ahead and right-click to get to the "View Source" option in your browser - you'll find the helper functions for converting between degrees and radians along with the DrawMeter() function.


//---------------------------------------------------------------
function DrawMeter()
{
  var       cvs         = document.getElementById('meter'),     // Canvas object 
            centerX     = cvs.width / 2,                        // Horizontal mid-point of canvas
            bottomY     = cvs.height,                           // Bottom of canvas
            radius      = Math.min(centerX, bottomY) - 5,       // Radius of dial that fits into canvas, plus a small margin
            ctx         = cvs.getContext('2d'),                 // Graphics context for drawing
            startAngle  = toRadians(210),                       // Starting angle for scale
            endAngle    = toRadians(330);                       // Ending angle for scale
          
  // Outer edge
  ctx.beginPath();
  ctx.strokeStyle = '#777';
  ctx.arc(centerX, bottomY, radius, startAngle, endAngle, false);
  ctx.stroke();
}

This function does several interesting things.   After getting the HTML canvas element, the horizontal center and vertical bottom is found.   These values are then used to determine the radius for the meter.  The arc is then drawn representing the top of the dial, going clockwise from 210 degrees to 330 degrees, using the canvas drawing functions.

Up Next: Drawing the Tick Marks and Labels
We still have a ways to go before we having a functioning meter, but we've covered some important concepts already:
  • Degrees vs. Radians and how to convert between the two
  • Orientation of the polar coordinate system
  • Drawing an arc on an HTML canvas.

Next time, we'll draw the tick marks and labels on the meter by making use of some common trig functions to convert an angle into usable x,y coordinates.

Continue onto Part Two...