Click here to Skip to main content
14,426,997 members

GraphDisplay: a Bezier based control for graphing functions and curves

Rate this:
4.98 (49 votes)
Please Sign up or sign in to vote.
4.98 (49 votes)
26 Mar 2010CPOL
A WPF control for graphing functions, parametric curves, and polar curves.

Image 1


This article presents a control for the graphing of functions and parametric curves. The control will take a mathematically defined function and make a smooth representation of its graph using Bezier segments. Originally, this was part of some work on creating geometrical representations of curves using Bezier segments. As it evolved, I felt the need to verify that the geometries represented were the geometries I believed them to be. Grids, rules, ticks, and labels were all added for this purpose. In the end, a graphing control was produced that should prove useful in its own right.


This article uses calculus and some simple linear algebra and a bit of trigonometry. The curves are graphed directly from the information on the function or curve and its derivatives. As I was unable to find derivations or formulas of that nature, I was required to recreate them from scratch. If any kind reader knows of somewhere where this derivation is done, please let me know and I will joyfully update this article.

How to Use


As this control graphs functions and curves, it would be best to start by defining exactly what definition of function or curve is being used. Put loosely, a function is a pair of methods, one that represents a value and one that represents a slope or derivative. The base class from which all functions derive is:

public abstract class FunctionBase
    public abstract double Value(double x);
    public abstract double Derivative(double x);
    //... Other stuff

Graphing Functions

To graph a function, we call the AddFunction method of GraphDisplay.

public void AddFunction(FunctionBase f, double start, 
            double end, int segments,   GraphStyle style)

For example:

GraphStyle curveStyle = new GraphStyle()
            {Stroke = new SolidColorBrush(Colors.Red ) ,Thickness = 2};
Exp ex = new Exp() ;
gDisplay.AddFunction(exp, 1, 2, 5, curveStyle);

would use GraphDisplay gDisplay to graph the exponential function y= ex from x= 1 to x= 2 in the color red with a stroke of width 2 using five Bezier segments. However, we are not quite done. The control needs to know how to relate the values in the abstract mathematical space where the function resides.

gDisplay.YTop = 8 ;
gDisplay.YBottom 2;
gDisplay.XLeft =0;
gDisplay.XRight =3;

This gives a small bit of whitespace around the graphed exponential. We are not restricted in the number of functions that we can add to a GraphDisplay, but all of the functions use the same bounds.

Creating Functions through Inheritance

Functions can be created by directly inheriting from FunctionBase. For example, let's say we want the function corresponding to f(x) = sin(x).

public class Sin : Mathematics.BaseClasses.FunctionBase
    public override double Value(double x)
        return  Math.Sin( x );

    public override double Derivative(double x)
        return Math.Cos(x);

Please note that this is a minimalist version of the Mathemaics.Functions.Sin class which corresponds to the family of functions of the form f(x) = ASin(nx + d).

Creating Functions Directly

We can also create functions through the Function class. The function class is as follows:

public sealed  class Function:FunctionBase
    private Func<double,> mF;
    private Func<double,> mDF;

    public Function( Func<double,double> f, Func<double,double> df)
      mF = f;
      mDF = df;

    public override double Value(double x)
        return mF(x);

    public override double Derivative(double x)
        return mDF(x);

We just pass in delegates for the function and its derivative. For example, we could again create f(x) = sin(x) via:

Function f = new Function(x=> Math.Sin(x), x=> Math.Cos(x));

Overriding is probably preferable for creating functions while the Function class works dynamically at runtime.

Combining Functions

Functions can also be combined in various ways. It is important to remember that instances of classes that derive from FunctionBase are Calculus like functions, so the chain rule needs to be obeyed. Fortunately, there are several static methods in FunctionBase for combining functions. To compose y= 2x and y= sin(x) and get y=sin(2x), we could use the following code:

Function twoX = new Function (x=>2*x,x=>2);
Sin sin = new Sin();
Function composition = Function.Compose(sin ,twoX);

All of the complexity is hidden behind the scenes.

public static Function Compose(FunctionBase outerFunction, FunctionBase innerFunction)
    //Change of variables for more readable code
    FunctionBase f = outerFunction;
    FunctionBase g = innerFunction;
    return new Function(
        x => f.Value(g.Value(x)),  //Composition f(g(x))
        x => f.Derivative(g.Value(x)) * g.Derivative(x) 
        //Chain Rule  f'(g(x))*g'(x)

There are also static methods for the sum, difference, product, and quotient of functions. These functions can both be called directly or used via operators. With the same sin and twoX, f(x) = sin(x) + 2x can be done via:

Function plus = Function.Sum(sin, twoX); 
Function plus =  sin + twoX ;

which calls:

public static Function Sum(FunctionBase f, FunctionBase g)
    return new Function(x => f.Value(x) + g.Value(x),
        x => f.Derivative(x) + g.Derivative(x)

f(x) = sin(x) - 2x can be made via:

Function minus = Function.Difference(sin, twoX); 
Function minus =  sin - twoX ;

which calls:

public static Function Difference(FunctionBase f, FunctionBase g)
    return new Function(x => f.Value(x) - g.Value(x),
        x => f.Derivative(x) - g.Derivative(x)

f(x) = 2xsin(x) can be made via:

Function times = Function.Product( twoX ,sin); 
Function  times =  twoX * sin;

which calls:

public static Function Product(FunctionBase f, FunctionBase g)
    return new Function(x => f.Value(x) * g.Value(x),
       x => f.Derivative(x) * g.Value(x) + 
           g.Derivative(x) * f.Value(x) //Chain Rule f'(x)g(x) + f(x)g'(x)

f(x) = sin(x)/2x can be made via:

Function divides = Function.Product( sin , twoX); 
Function  divides = sin /twoX;

which calls:

public static Function Quotient(FunctionBase numerator, FunctionBase denominator)
    //Change of variables for more readable code
    FunctionBase f = numerator;
    FunctionBase g = denominator;
    return new Function(x => f.Value(x) / g.Value(x),
        //Chain rule (f'(x)g(x) - f(x)g'(x))/g(x)^2
       x => (f.Derivative(x) * g.Value(x) - f.Value(x) * 
              g.Derivative(x)) / Math.Pow(g.Value(x), 2)

I have belabored these transformations because they are important. The main reason for creating FunctionBase is to have an entity that will naturally obey the chain rule.

Function Sample

Image 2

As a sample for the graphing of functions, we have the DampedSinusoid.

public class DampedSinusoid : FunctionBase
    public double A { get; private set; }
    public double Gamma { get; private set; }
    public double Omega { get; private set; }
    public double Phi { get; private set; }
    public DampedSinusoid(double a, double gamma, double omega, double phi)
        A = a; Gamma = gamma; Omega = omega; Phi = phi;

    public override double Value(double x)
        return A * Math.Pow(Math.E, -Gamma * x) * Math.Cos(Omega * x + Phi);

    public override double Derivative(double x)
        return A * Math.Pow(Math.E, -Gamma * x) * Math.Cos(Omega * x + Phi) * (-Gamma)
               - A * Math.Pow(Math.E, -Gamma * x) * Math.Sin(Omega * x + Phi) * Omega;


The sample shows two damped sinusoids and their sum. I might note that all of the values used in this class are set in the constructor and thereafter unalterable. I would strongly recommend following this policy.


A curve is very similar to a function except that there are four methods instead of two.

public abstract  class CurveBase
   public abstract Double X(double t);
   public abstract Double Y(double t);
   public abstract Double Dx(double t);
   public abstract Double Dy(double t);
   ... other stuff

The is also a CyclicCurveBase. This should be used in cases where we have a closed curve. In general, we should not expect an end user to know the period of a function.

public abstract class CyclicCurveBase:CurveBase 
  public abstract double CycleStart {get;}
  public abstract double CycleEnd{ get;}

Curves can be added via the AddCurve and AddCyclicCurve methods.

public void AddCurve(CurveBase c, double start, double end, 
                     int segments, GraphStyle style){...}
public void AddCyclicCurve(CyclicCurveBase c, int segments, GraphStyle style){...}

Curve Class Hierarchy

At this point, looking at the class hierarchy for the descendents of CurveBase should be helpful.

Image 3

In order to create a new curve to graph, we should either inherit from one of the abstract base classes (blue) or compose using the compositional classes (purple). This is one of those times when being able to employ multiple inheritance would be nice. For example, I would like to be able to inherit the cyclic properties from some cyclic base class and the polar properties from a polar base. The way things are implemented involves some regrettable duplication. The classes were designed for expansion without significant changes to the graphing control. For example, if bipolar coordinates were added to the mix, no changes would need to be made to the graphing control. We could reasonably add other types of curves such as bipolar, elliptic, or hyperbolic in the future. In that case, we should create four classes for each coordinate system, just as we have PolarCurveBase, PolarCurve, CyclicPolarCurveBase, and CyclicPolarCurve.

Polar Curves and their Constructor Order

The PolarCurve class can be used to construct a curve if we have a function r= f(Θ)

public  sealed  class PolarCurve:PolarCurveBase 
    private Func<double, double> mR;
    private Func<double, double> mDr;

    public PolarCurve(Function r)
        mR = r.Value;
        mDr = r.Derivative;

    public override double R(double theta)
        return mR(theta);

    public override double Dr(double theta)
        return mDr(theta);

The thing to note is that the graphing control does not use r or Θ coordinates. This is handled by the base class.

public abstract class PolarCurveBase:CurveBase 
      public abstract double R(double theta);
      public abstract double Dr(double theta);
      private Curve mC;
     static Cos sCos = new Cos();
     static Sin sSin = new Sin();

     protected PolarCurveBase() {
         Function r = new Function(R,Dr);
         mC = new Curve( r * sCos ,  r * sSin );

     public override double X(double t)
         return mC.X(t);

     public override double Y(double t)
         return mC.Y(t);

     public override double Dx(double t)
         return mC.Dx(t);

     public override double Dy(double t)
         return mC.Dy(t);

The interesting thing is the order in which the two constructors are called. PolarCurveBase's constructor is called before PolarCurve's.

This means that the curve transforming the function is created from overridden methods encapsulating the function before the function itself is introduced. The sample for polar curves is the Rose curve.

Image 4

The Mathematics

Bezier Curves

Bezier curves are extremely useful for the drawing of smooth curves, so a little motivation might be of value. Let's assume we have a curve that we are interested in creating a representation of. It starts at Point a = (ax, ay) and goes to point b = (bx, by). We could represent the curve between them by a pair of functions. Bx(t) where Bx(0)=ax and Bx(1)=bx along with By(t) where By(0)=ay and By(1)=by.

Linear Bezier Curves

The simplest functions we could use for Bx and By (B is for Bezier) would be straight lines of the form f(x) = Ax + B, subject to the initial constraints. (A different equivalent formulation is usually used, but that adds complexity we don't need yet.) The important thing to notice is that we need 4 numbers to uniquely determine this line, two for Bx(t) and two for By(t). Fortunately, these are available in the x and y coordinates of the endpoints. With a little reorganization and notational change, this could be transformed to the customary definition of a Linear Bezier Curve.

B(t) = P0 + t(P1 - P0) where P0 is the starting point and P1 is the end point.

A line is not that good of an approximation to a curve, but it's worth keeping in mind that using it, we can use a lot of Bezier segments next to one another and that in the process of being displayed on the screen, our Bezier curves are converted back into line segments. The Geometry base class even has a GetFlattenedPathGeometry method to do just that. WPF supports this in spirit with the LineSegment class, if not in name.

Quadratic Bezier Curves

We can make a better approximation to our curve using a Quadratic (of the form f(x) = Ax2 + Bx + C) instead of a linear function. The thing to notice is that now we will need 6 numbers to define our segment. The obvious candidate for these numbers is the slope at the initial and final points. Put in vector form, we have:

B(t) = P0 (1-t)<sup>2</sup> + P1 2(1-t)t + P2t<sup>2</sup>

which corresponds to:

B<sub>x</sub>(t) = P0<sub>x</sub>(1-t)<sup>2</sup> + P1<sub>x</sub> 2(1-t)t + P2<sub>x</sub>t<sup>2</sup>
B<sub>y</sub>(t) = P0<sub>y</sub>(1-t)<sup>2</sup> + P1<sub>y</sub> 2(1-t)t + P2<sub>y</sub>t<sup>2</sup>

You might ask why we need to use this format instead of the simpler Ax2 + Bx + C. First, it is obvious that we can do this. Just multiply everything out and collect the terms to get A, B, and C. The benefit of this comes when we evaluate the conditions at the endpoints. If we start at Point a = (ax, ay) and go to point b = (bx, by), then we can immediately get P0x = ax and P0y = ay by evaluating at t=0. We get P2x = bx and P2y = by by evaluating at t=0. We then have the simpler problem of finding P1x and P1y. In WPF, Quadratic Bezier segments are supported by QuadraticBezierSegment. The constructor for QuadraticBezierSegment has a space for a P1 and a P2 but not a P0. At first, this might make it seem as if it only takes 4 rather than 6 numbers to define a Quadratic Bezier segment. This has much more to do with memory management than mathematics. If you have two consecutive segments in a path, P2 for the first acts as P0 for the second. Microsoft both saves memory and removes the chance for us to enter a wrong value by doing this. It does not change the dynamics of finding the values of P1. If we want to think in the Microsoft way, we must lose the two conditions at the initial point along with the requirement to find P0.

Cubic Bezier Curves

These are the type of segments that we are most likely to use. Notably, it is represented by a class named BezierSegment rather than CubicBezierSegment. These are also the segments you use in Adobe Illustrator with the pen tool. In vector form, it looks like:

B(t) = P0 (1-t)<sup>3</sup> + P1 3(1-t)<sup>2</sup> t + P2 3(1-t)t<sup>2</sup> + P3 t<sup>3</sup>

which corresponds to:

B<sub>x</sub>(t) = P0<sub>x</sub> (1-t)<sup>3</sup> + P1<sub>x</sub> 3(1-t)<sup>2</sup> t + P2<sub>x</sub> 3(1-t)t<sup>2</sup> + P3<sub>x</sub> t<sup>3</sup>
B<sub>y</sub>(t) = P0<sub>y</sub> (1-t)<sup>3</sup> + P1<sub>y</sub> 3(1-t)<sup>2</sup> t + P2<sub>y</sub> 3(1-t)t<sup>2</sup> + P3<sub>y</sub> t<sup>3</sup>

One of the nice things about the vector form is that it makes certain common transformations of Bezier segments easy. For instance, if we want to move the Bezier curve by 5 in the positive x direction, all we would need to do is add 5 to the x components of P0, P1, P2, and P3. In fact, any translation, reflection, or other transformation that can be accomplished by matrix multiplication has this property. Also, it means that when drawing pictures of Bezier segments, we lose no generality by rotating them, so they are easier to draw.

There is a very important relationship between the Ps and the derivatives at t =0 and t=1. It is just straightforward algebra, but unpleasant enough that I would advise using Mathematica or something similar rather than pencil and paper.

B'<sub>y</sub>(0) /B'<sub>x</sub>(0) = (P0<sub>y</sub> - P1<sub>y</sub>) /(P0<sub>x</sub> - P1<sub>x</sub>)
B'<sub>y</sub>(1) /B'<sub>x</sub>(1) = (P2<sub>y</sub> - P3<sub>y</sub>) /(P2<sub>x</sub> - P3<sub>x</sub>)

This means that the tangent line to the Bezier curve at Point P0 intersects P1, and that the tangent line at P3 intersects P2. So, if the only conditions that we have are the endpoints and the slopes at the ends, there is a large collection of possible P1s. Similar reasoning applies for P2.

Image 5

Furthermore, let's say we wanted to choose a P1 such that it has a specified slope at P0. We could pick any point that is both on the tangent line and in the direction of P3, and it would have the correct slope at P0. For example, we could have the following possibilities:

Image 6

In the one called leaning, the curve extends further to the right than P3, P1 would have needed to be significantly further out for it to be more noticeable. As we can see, we can get significantly different Bezier segments that have the same endpoints and slopes at their endpoints.

By the same reasoning as with Quadratic curves, we need 8 numbers to define the segment, four of which are provided by the initial and final points.


Now that the preliminaries are out of the way, it's time to graph functions. I am assuming that f(x) and its derivative f'(x) also exist for the range that is being graphed. This type of function was chosen as the graphing sweet spot. Giving up the first derivative makes things significantly harder, and derivatives higher than the first add a lot less than we would like.

B<sub>x</sub>(t) = P0<sub>x</sub> (1-t)<sup>3</sup> + P1<sub>x</sub> 3(1-t)<sup>2</sup> t + P2<sub>x</sub> 3(1-t)t<sup>2</sup> + P3<sub>x</sub> t<sup>3</sup>
B<sub>y</sub>(t) = P0<sub>y</sub> (1-t)<sup>3</sup> + P1<sub>y</sub> 3(1-t)<sup>2</sup> t + P2<sub>y</sub> 3(1-t)t<sup>2</sup> + P3<sub>y</sub> t<sup>3</sup>
a = P0<sub>x</sub>
f(a) = P0<sub>y</sub>
b = P3<sub>x</sub>
f(b) = P3<sub>y</sub>
f'(a) = (P0<sub>y</sub> - P1<sub>y</sub>) /(P0<sub>x</sub> - P1<sub>x</sub>)
f'(b) = (P2<sub>y</sub> - P3<sub>y</sub>) /(P2<sub>x</sub> - P3<sub>x</sub>)

After a bit of simplification, we get:

P1<sub>y</sub> -f'(a) P1<sub>x</sub> = f(a) - af'(a)
<sub>y</sub> -f'(b) P2<sub>x</sub> = f(b) - bf'(b)

It looks a bit better, but we still have two equations and four unknowns, and as we have already seen, this leaves a lot of possible segments. So we need more constraints. We have two, although they are not expressed in the form of equalities. First, we are graphing a function. By definition, that means that for every x, there must be exactly one f(x). That makes Bezier segments that loop and lean unacceptable representations of the function. Also, we expect that as a and b get closer together, the Bezier segment will match its portion of the curve more closely, just as it would if we were approximating the curve with line segments.

To go further, we need more conditions that are in the form of equations. Let's consider the following two:

P1<sub>x</sub> =( 2 a + b)/3
P2<sub>x</sub> =( a + 2 b)/3 

We will later see that these are excellent choices for conditions, but it is worth noting that they are not the only choices we could have made, and this choice puts the curve in the normal category. It also matches with the illustrator pen tool rule of thumb, that control handles should be about 1/3 of the way between nodes. With these conditions, we get:

P1<sub>y</sub> =( 3 f(a) - a f'(a) + b f'(a))/3
P2<sub>y</sub> =( 3 f(b) - a f'(b) + b f'(b))/3 

This is exactly the sort of result we would hope for. P1y and P2y are expressed as linear combinations of the conditions at the endpoints. There is no denominator that could possibly go to zero. One of the major reasons that Quadratic Bezier segments are not used is that there is a denominator that can go to zero, which causes artifacts in the graph. So, in summary, we have the following result:

B<sub>x</sub>(t) = P0<sub>x</sub> (1-t)<sup>3</sup> + P1<sub>x</sub> 3(1-t)<sup>2</sup> t + P2<sub>x</sub> 3(1-t)t<sup>2</sup> + P3<sub>x</sub> t<sup>3</sup>
B<sub>y</sub>(t) = P0<sub>y</sub> (1-t)<sup>3</sup> + P1<sub>y</sub> 3(1-t)<sup>2</sup> t + P2<sub>y</sub> 3(1-t)t<sup>2</sup> + P3<sub>y</sub> t<sup>3</sup>
P0<sub>x</sub> = a , P0<sub>y</sub> = f(a)
P1<sub>x</sub> = ( 2 a + b)/3 , P1<sub>y</sub> =( 3 f(a) - a f'(a) + b f'(a))/3
P2<sub>x</sub> =( a + 2 b)/3 , P2<sub>y</sub> =( 3 f(b) - a f'(b) + b f'(b))/3
P3<sub>x</sub> = b , P3<sub>y</sub> = f(b)

Parametric Curves

Image 7

For a parametric curve, we have a curve defined by two functions: x = x(t) and y=y(t). By long standing tradition, t is used for the parameter (t is a good letter for time), It should be remembered that this t is not the same t as in Bx(t) and By(t). For a curve going from t = a to t= b, and functions x(t) and y(t), we have:

B<sub>x</sub>(t) = P0<sub>x</sub> (1-t)<sup>3</sup> + P1<sub>x</sub> 3(1-t)<sup>2</sup> t + P2<sub>x</sub> 3(1-t)t<sup>2</sup> + P3<sub>x</sub> t<sup>3</sup>
B<sub>y</sub>(t) = P0<sub>y</sub> (1-t)<sup>3</sup> + P1<sub>y</sub> 3(1-t)<sup>2</sup> t + P2<sub>y</sub> 3(1-t)t<sup>2</sup> + P3<sub>y</sub> t<sup>3</sup>
x(a) = P0<sub>x</sub>
y(a) = P0<sub>y</sub>
x(b) = P3<sub>x</sub>
y(b) = P3<sub>y</sub>
y'(a)/x'(a) = (P0<sub>y</sub> - P1<sub>y</sub>) /(P0<sub>x</sub> - P1<sub>x</sub>)
y'(b)/x'(b) = (P0<sub>y</sub> - P1<sub>y</sub>) /(P0<sub>x</sub> - P1<sub>x</sub>)

Again, we need more conditions. Loops and points can occur in parametric curves such as the Lissajous curve, but should not be added as artifacts of our algorithm. One of the implications of this is that P1 cannot be entirely determined from the behavior at P0. To see this, consider bringing b closer and closer to a. Eventually, we would get a pointed looped or leaning curve for the Bezier segment.

We could consider the same idea we used while graphing functions. Since the argument for P1 is completely analogous to the one for P2, the discussion will focus solely on P1. The difference is that instead of moving horizontally 1/3 of the x distance, we could put P1 on the line that intersects the segment connecting P0 and P3. In doing this, we define a triangle and so are able to find P1.

Image 8

First, we know the angle α. The slopes of the lines between P0 and P3 is known since P0 and P3 are known. The slope of the line between P0 and P1 is just y'(a)/x'(a), which again is known. Via a well known formula tan(α) = (mb - ms)/ (1+ mb ms), where mb is the bigger slope and ms is the smaller slope. Due to the law of Sines, we know that the length of the segment from P0 to P1 is 1/3 the distance between P0 and P3 divided by the sine of pi/2 - α.

Image 9

Once we have the length between P0 and P1, call it h, we can get P1.

P1<sub>x</sub> =P0<sub>x</sub> + h cos(μ) where μ is the angle made by the tangent line at P0 with the X axis
P1<sub>y</sub> =P0<sub>y</sub> + h sin(μ)

Since we have dx and dy separately, we can simplify this a bit as cos(μ) = dx/(dx2 + dyx2)1/2 and sin(μ) = dy/(dx2 + dyx2)1/2. This gives us a way to find P1. It is not nearly as good a solution as was found for the functional case, but it provides a procedure that can be used to plot curves.

How it Works


Up until now, coordinates have been taken for granted. Functions and curves have been defined in an abstract space of numbers, but they must be displayed on the screen in terms of screen coordinates. This involves changes in translation, scale, and orientation. On the screen, we need to graph a TransformedFunction rather than the original FunctionBase.

public  class TransformedFunction
   private FunctionBase mF;
   private FunctionBase mXTrans;
   private FunctionBase mYTrans;

   public TransformedFunction(FunctionBase f,  
          FunctionBase xTrans,  FunctionBase yTrans)
       mF = f; mXTrans = xTrans; mYTrans = yTrans;

   public double Input(double x)
       return mXTrans.Value(x);

   public double Value(double x)
       return mYTrans.Value(mF.Value(x));

   public double Derivative(double x)
       return (mYTrans.Derivative(mYTrans.Value(x)) / 
               mXTrans.Derivative(x)) * mF.Derivative(x);//Chain Rule

The only things to note are that all three items must be transformed, the input (x), the output (y), and the derivative (dy/dx). Also, we should remember that the chain rule is not optional. The interesting bits are the two FunctionBases that transform the x and y coordinates.

mXTransform = new Function(x => (DisplayCanvas.ActualWidth / 
   (XRight - XLeft)) * (x - XLeft), x => DisplayCanvas.ActualWidth / (XRight - XLeft));
mYTransform = new Function(y => (DisplayCanvas.ActualHeight / (YBottom - YTop)) * 
    (y - YTop), y => DisplayCanvas.ActualHeight / (YBottom - YTop));

This takes care of all three aspects including the fact that in our functional space, increasing y goes up, while on the canvas, it goes down. This is not the only function we could use. A slightly different expression would give a logarithmic scaling. There is very little difference in this respect between curves and functions, so I will omit the discussion.


When graphing a function, we just add it to the GraphDisplay:

public void AddFunction(FunctionBase f, double start, double end, 
                        int segments, GraphStyle style)
    Path p = new Path();
    TransformedFunction tf = 
       new TransformedFunction(f, mXTransform, mYTransform);
    p.Data = BezierGeometry(tf, start, end, segments);
    p.Fill = style.Fill;
    p.Stroke = style.Stroke;
    p.StrokeThickness = style.Thickness;

This utilizes the BezierGeometry method to produce the geometry.

private PathGeometry BezierGeometry(TransformedFunction tf, 
        double start, double end, int segments)
    PathFigure pF = new PathFigure();
    pF.StartPoint = new Point(tf.Input(start),tf.Value(start));
    pF.IsClosed = false;
    Func<double, double> f = tf.Value;       //for readability
    Func<double, double> df = tf.Derivative; //for readability
    for (int i = 0; i < segments; i++)
        double aOrig = i * (end - start) / segments + start;
        double a = tf.Input(aOrig);
        double bOrig = (i + 1) * (end - start) / segments + start;
        double b = tf.Input(bOrig);
        Point P1 = new Point((2 * a + b) / 3, 
           (3 * f(aOrig) - a * df(aOrig) + b * df(aOrig)) / 3);
        Point P2 = new Point((2 * b + a) / 3, 
           (3 * f(bOrig) + a * df(bOrig) - b * df(bOrig)) / 3);
        Point P3 = new Point(b, f(bOrig));
        BezierSegment bs = new BezierSegment(P1, P2, P3, true);

    PathGeometry pGeo = new PathGeometry();

    return pGeo;


The code for adding a curve or cyclic curve is virtually identical.

public void AddCurve(CurveBase c, double start, 
            double end, int segments, GraphStyle style)
    Path p = new Path();
    TransformedCurve tf = new TransformedCurve(c, mXTransform, mYTransform);
    p.Data = BezierGeometry(tf, start, end, segments);
    p.Fill = style.Fill;
    p.Stroke = style.Stroke;
    p.StrokeThickness = style.Thickness;

The only difference lies in the BezierGeometry method. The algorithm described above is used for generating the Bezier segments. However, the algorithm for generating the curve's geometry is not as stable as in the functional case. Rapid changes of direction are not handled well. For example, Lissajous figures can have spikes in them. These are handled less than optimally.

Image 10

For this reason, there are two distinct algorithms being used in calculating the Bezier segments: BezierBuilder and AjustingBezierBuilder.

private PathGeometry BezierGeometry(TransformedCurve tc, 
                     double start, double end, int segments)
    PathFigure pF = new PathFigure();
    pF.StartPoint = new Point(tc.X(start), tc.Y(start));
    pF.IsClosed = false;
    Stack<double> boundaries = new Stack<double>();
    for (int i = segments; i >= 0; i--)
        boundaries.Push(i * (end - start) / segments + start);

    for (int i = 0; i < segments; i++)
        double a = boundaries.Pop();
        double b =  boundaries.Peek();
        AjustingBezierBuilder abb = new AjustingBezierBuilder(tc, a, b);
        abb.AddTo( pF);

    PathGeometry pGeo = new PathGeometry();

    return pGeo;

The BezierGeometry method first creates a Stack of boundaries to define the start and end points for calculating the Bezier segments. This replaces the loop used in the functional case. This step of creating the stack may appear odd, but it was chosen to be able to harmonize with several different optimization strategies that in the end did not make it to this edition of the graphing control. Under normal circumstances, the results of AjustingBezierBuilder will be the same as for when BezierBuilder will be used for the segment. Normal circumstances means that the distance between the starting and ending points is less than the distance from the points to their nearby control points. This is just following the procedure described in the mathematical area.

  • The distance between the endpoints is calculated.
  • Check if endpoints are close enough to approximate with a straight line.
  • The slope of the lines to the control points is determined from the derivatives of the curve at the endpoints.
  • The slope of the line connecting the endpoints of the segment is determined.
  • The angles between the slope at the endpoints and the endpoint connecting lines is determined from the slopes.
  • The length of the segments connecting the endpoints to the control points is determined via the law of Sines.
  • The control points are determined by moving along the line connecting the endpoints to the control points, the distance to the control points.
  • BezierSegment is created from the control points and the endpoints.
public BezierBuilder(CurveBase c, double a, double  b)
  A = a;
  B = b;
  Point InitialPoint = new Point(c.X(a), c.Y(a));
  FinalPoint = new Point(c.X(b), c.Y(b));
  //The distance between the endpoints is calculated.
  double distance =  Geometry.Distance(InitialPoint, FinalPoint)  ;

  //Check if endpoints are close enough to approximate with a straight line.
  if (distance < tolerance) //optimization 
      mBs = new LineSegment(FinalPoint, true);
      IsStable = true;
      //The slope of the lines to the control points is determined
      //from the derivatives of the curve at the enpoints.
      double curveSlopeA = c.Slope(a);       ///dy(a) / dx(a);
      double curveSlopeB = c.Slope(b);       ///dy(b) / dx(b);

      //The slope of the line connecting
      // the endpoints of the segment is detemined.
      double slope = Geometry.Slope(InitialPoint, FinalPoint);

      //The Angles between the slope at the endpoints 
      //and the endpoint connecting lines is determined from the slopes.
      double angleA = Geometry.AngleBetweenLines(slope, curveSlopeA);
      double angleB = Geometry.AngleBetweenLines(slope, curveSlopeB);

      //The length of the segments connecting the Endpoints 
      //to the control points is determined via the law of sines
      Double distanceIn = distance / 3; //our extra condition
      Double lengthAlongLineA = distanceIn / 
             (Math.Sin(Math.PI / 2 - angleA)); //Law of Sines
      Double lengthAlongLineB = distanceIn / 
             (Math.Sin(Math.PI / 2 - angleB)); //Law of Sines

      //The Control points are determined by moving along the line 
      //conecting the enpoints to the control points 
      //the distance to the control points.
      Point controlPointA = Geometry.AtDistanceFromPointOnLine(InitialPoint, 
                            c.Dx(a), c.Dy(a), lengthAlongLineA);
      Point controlPointB = Geometry.AtDistanceFromPointOnLine(FinalPoint, 
                            -c.Dx(b), -c.Dy(b), lengthAlongLineB);

      //The  BezierSegment is created from the control points and the endpoints.
      mBs = new BezierSegment(controlPointA, controlPointB, FinalPoint, true);
      //Mark if Bezier segment is Stable
      IsStable = distance > lengthAlongLineA && 
                 distance > lengthAlongLineB;
      PointDistance = Geometry.Distance(InitialPoint, FinalPoint);

This works reasonably well. However, it handled spikes less well than could be hoped for. To handle this situation, AdjustingBezierBuilder is used.

public AjustingBezierBuilder(CurveBase c, double a, double b)
   BezierBuilder bb = new BezierBuilder(c,a,b);
   if ( bb.IsStable) // No Need to Adjust
       mSegments = new List<pathsegment>();
     LinkedList<bezierbuilder> builders = new LinkedList<bezierbuilder>();
     LinkedListNode<bezierbuilder>  current=  
       builders.AddFirst(new BezierBuilder(c, a, (a + b) / 2));
     builders.AddAfter(current, new BezierBuilder(c,  (a + b) / 2,b ));

     while (current != null  )
         if (current.Value.IsStable) //No Problems
             current = current.Next;
         else // There is a problem with the current Bezier builder 
             //Slices BezierBuilder into two samller Bezier builders
             builders.AddAfter(current, new BezierBuilder(c, 
               (current.Value.A + current.Value.B) / 2, current.Value.B));
             LinkedListNode<bezierbuilder> newcurrent = 
                new BezierBuilder(c, current.Value.A, 
                (current.Value.A + current.Value.B) / 2)); 
             builders.Remove(current );
             current = newcurrent;
     mSegments = (from bld in builders select bld.Segment).ToList();

When BezierBuilder is normal, AdjustingBezierBuilder defaults to the BezierBuilder's segment. Otherwise, AdjustingBezierBuilder splits the segment that the Bezier segment is made from into two. Each of those segments defines a new BezierBuilder which is checked. If that too is unstable, then it is split again. This process continues until all of the BezierBuilder objects are marked as stable. We know that this process will stop since, once the distance between the endpoints goes below the static value of the Tolerance in the BezierBuilder, the process will stop. It should be noted that each distinct BezierBuilder is only created and tested once. If a BezierBuilder is not found to be stable, it is replaced by two new ones, or equivalently, two new ones are added after the original and then the original is removed. A linked-list was employed to make this more efficient.


I hope that this graphing control serves you well. This is something that would have been significantly harder to do just a few years ago. It is also just a preliminary to a new set of functional geometries that I hope to have ready soon.


This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


About the Author

Software Developer (Senior)
United States United States
Written software for what seems like forever. I'm currenly infatuated with WPF. Hopefully my affections are returned.

Comments and Discussions

QuestionSecond derivative Pin
Baccalore4-Oct-13 4:46
MemberBaccalore4-Oct-13 4:46 
GeneralMy vote of 5 Pin
abat8-Jul-12 4:44
Memberabat8-Jul-12 4:44 
QuestionNovel curve generation project 1 Pin
Member 873609322-Mar-12 12:20
MemberMember 873609322-Mar-12 12:20 

I have a need to generate a set of curves which are completely novel. I am looking for both, the mathematical solution and programing/graphic solution to produce the visual end product. Any programing/graphic solution/language will be fine and 1000% better than what I have now but ultimately I would like to utilize it in CAD/CADD/CAE. (Did I say that right? I know nothing of programing nor its lingo so I probably sound like a dunce. I will have to get help with that from someone locally. :^D )

The following is only one specific example/scenario for simplified discussion purposes. Other scenarios are generated by altering the parameters but are not considered here.



ROTATE/SPIN: to cause to turn on an axis, shaft, or center, like a gear. This is the movement considered/used here.

REVOLUTION: turning of an object which can be either axial or orbital. Only axial revolution/movement is considered here.

ORBIT: path described by one a body in its revolution about another, like Earth around the sun.(Movement NOT used here)

TRANSLATION: movement that changes the position of an object, as opposed to rotation. (Movement NOT used here)

My definition of FIXED IN SPACE: without TRANSLATIONAL or ORBITING movement. Only ROTATION/REVOLUTION movement of object on its axis occurs, like a gear.

For those unfamiliar with cycloids, please see animated examples here:[^] and here[^] This is similar to the solution I seek (two circles carrying out some motion0 EXCEPT there is no translational movement of the small inner circle(it only rotates/spins in place) AND the large outer circle also only rotates/spins in place on its own axis.

Please take a look at these sun gear pictures. It is very similar to one of my solutions which after many revolutions would produce multiple teeth. But, because my problem calls for slippage at the point of contact (no teeth engaging), the resulting curve trace will be novel.[^][^]

My scenario consists of 2 circular curves (circles). The smaller one is located inside the larger circle and in contact at one point. For convenience, let this point be at the "12o'clock" position. Set the size of the smaller circle at 75% of the larger circle and set the rotational velocity of BOTH element's/circle's axis/shafts at 1 rpm WITHOUT any orbiting/translational movement. They will simply spin/rotate in place on their respective axis/shafts which are FIXED IN SPACE (like a vane pump whose casing also rotates on its own axis, or like the ring gear and one planet gear in a sun/planetary gear set but WITHOUT the teeth meshing/engaging [please consider/think off/look at only the ring and the one top planet gear sized to 75% of the ring gear!!!!!!]) and will have different/differential circumferencial velocities. This difference in circumference velocities along with the centers/axis/shafts not being coincident (not the same point[2 different axis/shafts!!!]), their original points of contact (which started at 12o'clock in this specific scenario) will have a translation with respect to each other whose trace in one rotation is a curve that probably resembles an ellipse/football/rugby ball or even a tear drop pointing inward towards the center like in a ring gear[^] whereas the points of a hypocycloid curve point from the center of the circle outward and touch the outer circle somewhere along its circumference with the tips. An example of hypocycloid can be seen here[^] Other scenarios with multiple rotations will produce multiple inward pointing ellipses/teeth/rays like a stylized sun with rays pointing inward, not outward. One such example of inward pointing rays would be the inverse/opposite(?) of[^] )
The parent page is[^]

Another way to analyze this is geometrically.
Let point A lie on the large circle and point B on the small circle located such that they are touching at time zero at the 12o'clock position. Connect A & B with a straight segment which terminates at the axis of the large circle and call this point C. After rotating both circles 90 degrees clockwise, in order for the line to connect A-C it has to pass through B which is clearly above A and C. The line has to either sharply bend at B or gradually curve to achieve this. Since this is a continuous motion and not stepped progression, the line segment A-B had to go through a gradual bending as it passed through B - a curve. After 180 degrees of rotation, points A, B, and C will be in line again. This is the trace of half of the curve. It resembles an arc of a circle or exponential curve. The other 180 degrees of rotation should produce a mirror trace which now I think might look like an ELLIPSE with pointed ends, like an American football or rugby ball or perhaps a tear drop. If it turns out that the first 180 degrees of rotation is not a smooth curve but rather two straight segments joined at a kink, then the complete curve might look like a rhombus[^] or a kite[^]

Another way of saying all of the above is like this. It is a trace of a point on the circumference of the smaller inner rotating circle with respect to the moving point on the circumference of the larger rotating circle without any translation of the circles.

Various differential speeds and various sizes of circles will create different curves(solutions). Other scenarios, such as epicycloidal arrangement, or other circular curves (ellipses) will produce yet other solutions.

As far as I can tell, no such curves have ever been produced and I have not been able to create the curves effectively. I've approximated the curve in my example but it appears I was wrong. I seek a precise solution and hopefully a graphic or animated representation. There is a lot of talent here and it seems that the CodeProject is a perfect place to find resolution to my problem. Your input would be of great assistance. A solution in any programing language will suffice but ultimately I would like to utilize it in CAD/CADD/CAE. If this is not the correct forum for the mathematical solution, any input pointing in the correct direction would also be greatly appreciated.

Thank in advance and I look forward to hearing from you.

Best regards
Milan Kroupa

I may have posted my question inappropriately as I've never participated in forum such as this and am having difficulty understanding and navigating in it. Thank you for your help and patience.

*** Roll eyes | Roll eyes | :rolleyes:
QuestionSmall Problem Pin
Doug Wyrembek20-Aug-11 7:17
MemberDoug Wyrembek20-Aug-11 7:17 
GeneralMy vote of 5 Pin
toantvo15-Aug-11 7:04
Membertoantvo15-Aug-11 7:04 
GeneralMy vote of 5 Pin
Filip D'haene23-Jun-11 7:47
MemberFilip D'haene23-Jun-11 7:47 
QuestionGreat piece of work beautifully presented Pin
Declan Brennan21-Jun-11 23:26
MemberDeclan Brennan21-Jun-11 23:26 
GeneralMy Vote of 5 Pin
Ali BaderEddin6-Apr-10 15:46
MemberAli BaderEddin6-Apr-10 15:46 
GeneralExcellent. Pin
Luc Pattyn27-Mar-10 6:07
sitebuilderLuc Pattyn27-Mar-10 6:07 
GeneralRe: Excellent. Pin
KenJohnson27-Mar-10 16:47
MemberKenJohnson27-Mar-10 16:47 
GeneralThat's cool Pin
Philip.F27-Mar-10 2:38
MemberPhilip.F27-Mar-10 2:38 
GeneralRe: That's cool Pin
KenJohnson27-Mar-10 16:48
MemberKenJohnson27-Mar-10 16:48 
GeneralExcellent work! Pin
Tefik Becirovic27-Mar-10 1:14
MemberTefik Becirovic27-Mar-10 1:14 
GeneralRe: Excellent work! Pin
KenJohnson27-Mar-10 16:56
MemberKenJohnson27-Mar-10 16:56 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Posted 11 Feb 2010

Tagged as


76 bookmarked