Skip to content
rpusch edited this page Oct 22, 2016 · 11 revisions

SDCurve.js is a library built to make subdivision curves easy to create, interact with, and use in visualizations.

Setup and Creative a Curve (new SDCurve())

Download sdcurve.js from the Github repository and include it in your HTML or project file: https://github.com/rpusch/sdcurve.js/

An SDCurve must be initialized with an array of control points that will define the curve. Each point is a map with x and y fields.

var pts = [{x:0,y:0}, {x:200,y:200}];

To create a SDCurve, call the new SDCurve constructor and provide a map as the argument. At minimum, the points field must be filled.

var myCurve = new SDCurve({
                points: pts
              });

You can optionally specify more information about the curve by providing more fields in this map. The following fields are accepted. If these fields are not provided, they will be given a default value.

  • type (string): The curve scheme used, which determines the final shape of the curve. Accepted values are "bspline", "dyn-levin", and "catmull-rom". Dyn-Levin and Catmull-Rom are both schemes that interpolate the control points, while B-spline curves create a smooth blend of the control points. (default: "bspline")

  • resolution (integer): The number of subdivision steps. Not very many subdivision steps are needed to produce a visually pleasing curve. For curves with large numbers of control points, you may want to set this number lower. Setting the resolution to 0 will give a final "curve" that is the same as the control points. (default: 5)

  • open (boolean): If true, the curve will interpolate the endpoints. If false, the curve will be a closed loop. (default: true)

  • degree (integer): If "bspline" is the curve type, this determines the degree of the B-spline curve. Has no effect for any other curve type. Higher degrees produce a curve that is a stronger blend of the control points. (default: 2, i.e., quadratic)

  • catmullTension (float): If "catmull-rom" is the curve type, this determines the tension parameter for the Catmull-Rom creation process. Set the value to 0 for a uniform spline, 0.5 for a centripetal spline, or 1 for a chordal spline. Changes the shape of the curve, especially if control points are close together. Has no effect for any other curve type. (default: 0.5)

To change these properties on an existing curve, or to query the value of these properties, the following functions exist: resolution(), type(), open(), degree(), catmullTension(). These functions accept an argument which will change the value of the curve's property and recompute the curve as needed, and will also return the current value.

Drawing a Curve (curve())

To access the curve points, call the curve() function on an existing curve object. curve() returns an array of maps, which contain the curve points themselves (under the point field for each array object) as well as information on how each curve point was calculated (under the weights field). Most of the time, you will only need to use the point field.

To draw the curve using D3, include the d3.js library and then simply use D3's built-in line() function in this way:

var lineFunction = d3.svg.line()
	         .x(function(d) { return d.point.x; })
	         .y(function(d) { return d.point.y; });

Then, the curve can be drawn to any SVG element by specifying lineFunction(myCurve.curve()) to the d attribute of a path.

Here are some example curves showing the various curve types.

Sample Curves

Left: Two B-Spline curves (blue: degree 2, green: degree 5). Right: Two interpolating curves (red: Catmull-Rom, light blue: Dyn-Levin).

Getting Points Along the Curve

To get a point some distance along the curve, call the pointAt(u) function, where 0 <= u <= 1 (u=0 is the start of the curve and u=1 is the end of the curve). The distance is relative to the arclength of the curve.

To get the closest point on the curve to any point in space, call the getClosestPoint(pt) function. The parameter is a map with x and y fields, just like when creating a curve. This function is most useful, for example, to snap the location of a mouse click directly on the curve.

Both the pointAt and getClosestPoint functions return an object with the following fields:

  • pointOnCurve: The actual point, represented by a map of x and y values, that lies on the curve.
  • u: The distance along the curve, between 0 and 1, that pointOnCurve lies.
  • index: The index of the point in the "curve()" array that is closest to pointOnCurve (but still before it).
  • weights: How pointOnCurve was calculated from the array of control points provided. Returns a map of indices into the points() array mapped to the weight coefficient. All coefficients will sum to 1.

For most applications, you will only be interested in the pointOnCurve attribute.

Interacting with the Curve

To reset the control points to a new set of points, call the points(ptArray) function and pass in a new array of control points. The old points will be overwritten and the new curve will be created.

To adjust only some of the control points, call the adjustPoints(map) function. The parameter is a map between control point indices and new positions. Only the parts of the curve that are affected by the changed control points will be updated, which improves performance. Here is an example of adjustPoints:

// move the control point at index 1 and index 3 to new positions
var newPts = {1: {x:30, y:40}, 3: {x:100, 200}};
myCurve.adjustPoints(newPts);

Both the above methods require understanding of the underlying control points to change the curve. In SDCurve.js, it is possible to move the curve without any knowledge of the underlying control point structure (for example, to move the curve from a user's mouse click to a new position).

To do this, call the moveCurve(pointToMove, delta, width) function:

  • pointToMove: The object returned from either the pointAt or getClosestPoint function, outlined above. This will let you move any point along the curve (measured by arclength), or the closest point on the curve from any point in space, such as a mouse click.
  • delta: The movement vector. Note that this is not the desired point's final position, but rather a vector for how much it should move.
  • width (optional): How many of the underlying control points will be allowed to move. This is the "sharpness" or "smoothness" factor, and depends on the curve scheme used. Typical values will be between 1 and 3. (default: 1)

Here are some examples of how to use moveCurve.

// move the halfway point of the curve down 20 pixels
var pt = myCurve.pointAt(0.5);
myCurve.moveCurve(pt, {x:0,y:20});

// drag the curve around based on the user's mouse click
var clickPt, mouseDelta; // assume these variables are saved from the mouse events
var pt = myCurve.getClosestPoint(clickPt);
myCurve.moveCurve(pt, mouseDelta);

moveCurve also returns a point map that describes which points were moved and to where. You can use this map to change other curves, via adjustPoints, that share the same control point structure as the curve that was moved.

Tips for using SDCurve.js

  • We recommend you use the B-spline and Catmull-Rom curve types for smooth or interpolating curves.
  • Moving a Catmull-Rom curve requires a bit more recalculation than moving either the B-spline or Dyn-Levin control types, but the curve should still be responsive on most browsers and platforms. If the Dyn-Levin scheme produces curves pleasing to your visualization, and performance is sluggish with Catmull-Rom, try using the Dyn-Levin scheme instead.
  • If you have many control points that are close together, and you are frequently moving the curve, you may try to reduce the resolution of your curve from the default 5 to keep performance crisp.
  • SDCurve can be used to resample a series of points (for example, dense points from drawing a curve via mouse input into a series of usable control points). Create a temporary curve object with resolution 0, and then use the pointAt function to sample equally spaced points between 0 and 1.

An example of this is below:

var denseMouseInput; // an array of many points from a user drawing a stroke on the screen
var numSamples = 10; // change denseMouseInput to be a "similar" curve using only this many points
var tempCurve = new SDCurve({ points: denseMouseInput, resolution: 0});
var newPoints = [];
for(var i=0; i<numSamples; i++)
    newPoints.push(tempCurve.pointAt( i / (numSamples-1) ).pointOnCurve);

var myCurve = new SDCurve({points: newPoints}); // the new curve with only 10 control points