Softening polylines with DojoX Graphics

By on July 16, 2007 2:46 pm

One of the more exciting projects coming out of the Dojo Toolkit is DojoX Graphics–a cross-browser API for creating vector graphics. Over the next few months, I’ll be writing a series of tutorials showing you how you can use DojoX Graphics to accomplish a number of tasks–starting with showing you how to soften a polyline like this:

Straight polyline…to this…Eventual spline representation…using cubic bezier curve segments.

Let’s say you have a polyline (a line changing direction on a series of points) that looks like this:

Straight polyline
This line shows a series of points with a connecting line, useful in a number of ways (like trends, etc). This particular set of points is taken from the Dojo 0.4 Charting engine test.

Now, this polyline does the job, but it’s pretty stark and angular, and doesn’t really represent any kind of real-world interaction–a simplistic view. Instead, we’d like to connect the points like so:

Eventual spline representation
Notice how much more intuitive this looks: we recognize that nothing is ever as straightforward as the original polyline, and the slight curve we add “humanizes” the data we are presenting–in some ways being a lot prettier than the original.

Believe it or not, with some basic math, making the former look like the latter is actually fairly simple. We’ll be using cubic bezier curves to create the softening effect. For those who don’t know what a bezier curve is, it is an invention by Pierre Bezier, an automotive engineer working for Renault. It uses a set of forumulae to represent any curve in an efficient way by defining that curve using a set of anchor points and control points. Anchor points lie on the line of the curve itself; control points lie off the curve, and control the amount of curve there is (hence the term control points). The problem is that working with control points can be very daunting at first; they aren’t the most intuitive things in the world, and take a little while to get used to.

(For those of you used to using a vector graphics program, such as Adobe Illustrator, you know exactly what I’m talking about as soon as you try to use the Path tools.)

To begin, we need to select a tensioning factor. This is simply a number that we will use to calculate where our control points will end up; the bigger the number, the higher the tension and the tighter the curve. For this tutorial, we’re going to choose use a tensioning factor of 3; it’s strong enough to make sure the curve is weighted towards the next point on the line, but soft enough to not make the curve too sharp.

Let’s assume we have the following points on our polyline (these are simplified from the actual curve in the figures):

[ {50,67}, {98,124}, {171,137}, {244,75}, {268,104} ]

These points are a pretty close approximation to the circles on our polyline segment.

The first thing we’ll need to do is figure out exactly how we’re going to calculate our control points. When we’re done, here’s where the control points will end up:

Spline representation with control points

Here’s the basic concept:

  1. Figure out the X difference (dx) between each pair of points on our line
  2. Set the x of the first control point to be x0 + (dx/tension), and y to be y0
  3. Set the x of the second control point to be x1 – (dx/tension), and y to be y1
  4. Set the end of the path segment to be x1, y1

Here’s another version of our curve with the divisions by tension factor shown:

Showing the divisions of the control points on the x-plane

Each segment is divided by color. Notice that the x coordinates are the most important; our y coordinates will either drag or predict our actual points.

Now let’s do this using DojoX Graphics. The first thing we need to do is create our surface:

var surface=dojox.gfx.createSurface(myNode, 600, 350);

This will be the surface we’ll draw our curve on.

The next thing we’ll need to do is create our path segment string. This is an SVG-based path string, using the same commands that you can use in SVG. For our curve, we’ll be using only 2 commands: “M” (moveto, absolute) and “C” (cubic bezier, absolute). We start by moving to the first point in our data:

var pathdata="M50,67";

Next, we’ll figure out dx (x1-x0):

var dx=(98-50);    // ==48

Next, we’ll divide by our tensioning factor–which is 3 (we chose it above):

var add=dx/tension;   // 48/3 = 16

From there, we can figure out the x coordinate of our first control point:

var control0x=50+add;  // 50+16 = 66

…and we can figure out the x coordinate of our second control point:

var control1x=98-add;  // 98-16 = 82

…so the two control points in the first path segment are:

{66,67}, {82,124}

Finally, we add that to our path string, complete with the end point (from our data set):

pathdata += " C"+control0x+",67 "+control1x+",124 98,124";

We’ll do the same for the next segment (98,124 to 171,137):

pathdata += " C"+control0x +",124 "+control1x+",137 171,137";

…and so on, until we finish all our segments. Finally, we’ll add the path to our surface:

surface.createPath(pathdata).setStroke({width:1, color:"#4090b1" });

…and that’s it. We’ve taken our straight path and softened it, creating a more realistic version of what our points might actually represent.