This article describes a graphing control that can be dropped into any Windows application to create scientific and technical graphs in a huge range of styles. The control plots any number of mathematical expressions in rectangular as well as polar mode. By "expression," I mean any combination of algebraic, trigonometric, exponential, logarithmic, hyperbolic or custom user-defined functions. There are various graphing controls at CodeProject (and outside), but almost all of them require us to provide a list of values (points). This control is unique in the way that it expects the user to provide an expression, e.g. 6*sin(5*x)*cos(8*x).

The most important issue for every custom control is how extensible and customizable it is, and how easy it is to use in other Windows applications. Some salient features of this control are:

- Multiple expressions with different colors
- Rectangular as well as polar mode
- High extensibility due to the
`IEvaluatable`

interface - Capability of zooming and scrolling the graph
- Ability to save the current graph as an image
- Capability to reverse axes

The public interface of the control is very handy. Various methods and properties are provided to provide a high degree of customization. Let us have a quick look at some selected methods and properties.

`void AddExpression(IEvaluatable expression, Color color, bool visible)` | This function adds an expression to the graph. We will be looking at `IEvaluatable` later. `color` is the color of expression to be plotted while `visible` controls the visibility of the expression. |

`void SetRangeX(double StartX, double EndX)` | This sets the range for the X-axis. For example, use SetRangeX(-5,15) to construct a graph having x-axis starting from -5 and ending at 15 |

`void SetRangeY(double StartY, double EndY)` | This sets the range for the Y-axis. For example, use SetRangeY(15,25) to construct a graph having y-axis starting from 15 and ending at 25 |

`void ZoomIn()` | Zooms-in the graph. Similarly, we have functions `ZoomInX() ` (for zooming X-axis only), `ZoomInY ` (for zooming Y-axis only) and the corresponding functions for zoom-out (`ZoomOut` , `ZoomOutX` and `ZoomOutY` ). Note that these functions automatically adjust their increasing/decreasing ratio, i.e., if we try to zoom-in/out while viewing a large scale graph, the zooming ratio is also large; and if we zoom a graph displayed for a short range, the zooming ratio is small. |

`void MoveLeft(int division)` | Scrolls the graph to left the number of divisions specified. In a similar fashion, we have functions like `MoveRight()` , `MoveUp()` and `MoveDown()` . |

`Bitmap GetGraphBitmap()` | Returns a bitmap object for the current graph. |

`void CopyToClipboard()` | Copies the current graph to clipboard. |

`double[] GetValues(double point)` | Evaluates all the expressions at a given point and returns the result as an array. |

**ScaleX** | `double` | Base scale for the X-axis, e.g. if `ScaleX` is 10 then the graph will be created from -10 to 10. If we supply a -ve value, the axis is reversed, i.e. supplying `ScaleX` =-10 draws graph from 10 to -10. |

**ForwardX** | `double` | Controls navigation within the X-axis. Note that the graph will be drawn from `-ScaleX+ForwardX` to `ScaleX+ForwardX` , so if we have specified `ScaleX` =20 and `ForwardX` =0 then the graph will draw X-axis from -20 to +20. Similarly, if we have set `ScaleX` =20 with `ForwardX` =-10 then the graph will have X-axis from -30 to +10. |

**DivisionX** | `int` | No of grid divisions for the X-axis. |

**PrintStepX** | `int` | Step (increment) for printing the X-axis labels. Its range is from 0 to `DivisionX` . If set to 0, the graph does not display any labels; if set to `DivisionX` , the graph displays labels with each grid division. |

**GraphMode** | `Enum` | Switches between rectangular and polar modes. |

**PolarSensitivity** | `double` | Adjusts the sensitivity for polar graphs. The higher the value, the more accurate the polar graph. |

**DisplayText** | `bool` | Sets whether to display expression text inside the graph. |

**Grids** | `bool` | Turns on/off the grids. |

**PenWidth** | `int` | Adjusts the pen width for drawing graphs. |

Almost all these methods and properties are demonstrated in the application and the user interface of the demo project simply calls the respective methods of the control.

Before getting into implementation details of the control, we will have a look at its usage. The range for a graph can be controlled using properties (`ScaleX`

, `ForwardX`

, etc.) as well as functions (`SetRange()`

, `ZoomIn()`

, `Move()`

, etc). Constructing a graph using methods is easy while that using properties is recommended for advanced users that want to have greater control.

Let's construct a simple graph using methods. After we have created all the necessary references and placed the control (named "`expPlotter`

") on a Windows form, here's some code to experiment:

expPlotter.SetRangeX(-6, 14);
expPlotter.SetRangeY(-5, 5);
expPlotter.DivisionsX = 5;
expPlotter.DivisionsY = 5;
expPlotter.PenWidth = 2;
expPlotter.AddExpression((IEvaluatable)new Expression("-exp(x/2-3)"),
Color.Green, true);
expPlotter.AddExpression((IEvaluatable)new Expression("2*sin(x/2)*cos(3*x)"),
Color.Blue, true);
expPlotter.AddExpression((IEvaluatable)new Expression("abs(x/2)"),
Color.Brown, true);
expPlotter.Refresh();

The above code will produce the following output:

Let's reconstruct the same graph using properties, the more flexible approach.

expPlotter.ScaleX = 10;
expPlotter.ForwardX = 4;
expPlotter.ScaleY = 5;
expPlotter.ForwardY = 0;
expPlotter.DivisionsX = 5;
expPlotter.DivisionsY = 5;
expPlotter.PenWidth = 2;
expPlotter.AddExpression((IEvaluatable)new Expression("-exp(x/2-3)"),
Color.Green, true);
expPlotter.AddExpression((IEvaluatable)new Expression("2*sin(x/2)*cos(3*x)"),
Color.Blue, true);
expPlotter.AddExpression((IEvaluatable)new Expression("abs(x/2)"),
Color.Brown, true);
expPlotter.Refresh();

The above code will produce the same output as the previous one. Similarly, other methods and properties can be used for further customization. Now we will have a look at the implementation of the control.

**Plotting in rectangular mode:** The control internally stores all expressions in a `List`

of `IEvaluatable`

interface. We iterate through all these expressions (`List<Evaluatable>`

) and start a loop from `-ScaleX+ForwardX`

to `ScaleX+ForwardX`

. For each value of the loop variable, we find the value of expression using `IEvaluatable.Evaluate(loop variable)`

and plot it. For continuity in our graph, we join the previously evaluated value to the newly evaluated value.

**Plotting in polar mode:** Nothing special is done for handling polar modes since we can transform polar coordinates to rectangular coordinates by using the famous formulae: `x=r*cos(theta)`

and `y=r*sin(theta)`

. We evaluate the expressions from `-PI`

to `+PI`

and plot the equivalent rectangular coordinates.

Here's the code for `PlotGraph()`

method:

void PlotGraph(Graphics g)
{
DisplayScale(g);
if (this.bDisplayText)
DisplayExpressionsText(g);
double X, Y;
double dPointX, dPointY;
double dLeastStepX, dLeastStepY;
double dMin, dMax, dStep;
int i;
float X1 = 0, Y1 = 0, X2 = 0, Y2 = 0;
bool bContinuity = false;
dLeastStepX = dScaleX / iLengthScale;
dLeastStepY = dScaleY / iLengthScale;
if (graphMode == GraphMode.Polar)
{
dMin = -Math.PI;
dMax = Math.PI;
dStep = dScaleX / iPolarSensitivity;
}
else
{
dStep = dLeastStepX;
dMin = -dScaleX + dForwardX;
dMax = dScaleX + dForwardX;
}
for (i = 0; i < this.expressions.Count; i++)
{
if (expVisible[i] == true && expressions[i].IsValid == true)
{
bContinuity = false;
for (X = dMin; X != dMax; X += dStep)
{
if (dScaleX < 0 && X < dMax)
break;
if (dScaleX > 0 && X > dMax)
break;
try
{
Y = expressions[i].Evaluate(X);
if (double.IsNaN(Y))
{
bContinuity = false;
continue;
}
if (graphMode == GraphMode.Polar)
{
dPointX = Y * Math.Cos(X) / dLeastStepX;
dPointY = Y * Math.Sin(X) / dLeastStepY;
}
else
{
dPointX = X / dLeastStepX;
dPointY = Y / dLeastStepY;
}
if ((iOriginY - dPointY + dForwardY /
dLeastStepY) < iOriginY - iLengthScale
|| (iOriginY - dPointY + dForwardY /
dLeastStepY) > iOriginY + iLengthScale
|| (iOriginX + dPointX - dForwardX /
dLeastStepX) < iOriginX - iLengthScale
|| (iOriginX + dPointX - dForwardX /
dLeastStepX) > iOriginX + iLengthScale)
{
bContinuity = false;
continue;
}
X2 = (float)(iOriginX + dPointX - dForwardX /
dLeastStepX);
Y2 = (float)(iOriginY - dPointY + dForwardY /
dLeastStepY);
if (bContinuity == false)
{
X1 = X2;
Y1 = Y2;
bContinuity = true;
}
g.DrawLine(new Pen(expColors[i], iPenWidth),
new PointF(X1, Y1), new PointF(X2, Y2));
X1 = X2;
Y1 = Y2;
}
catch
{
bContinuity = false;
continue;
}
}
}
}
}

The expression plotter control expects the expressions to implement `IEvaluatable`

. This way, we can increase the extensibility of our control since we can write any class with custom evaluation behavior. We just need to provide a definition for the following:

`string ExpressionText`

Get/Set the text of expression

`bool IsValid`

Should return `true`

if the expression can be evaluated without any exception

`double Evaluate(double dvalueX)`

This function should evaluate the expression at `dvalueX`

and return the result as `double`

. If the result cannot be calculated (e.g. log for a -ve number) then it should return `double.NaN`

.

The control contains a sample implementation of `IEvaluatable`

, the `Expression`

class. Let me briefly describe this implementation. The following pseudo-code can be used to evaluate a simple expression:

int runningTotal = 0;
Operator lastOperator = "+";
While ( Expression is not scanned )
{
if Expression.Encountered( operand )
runningTotal = runningTotal <lastOperator> operand;
else if Expression.Encountered( operator )
lastOperator = operator;
}

Let's see how the above code works on a sample expression `2*5+6-9`

:

However, the problem with this code is that it "always" evaluates left to right, "ignoring" operator precedences. Thus, `4+3*5`

is evaluated as `35`

instead of `19`

because first `4+3=7`

is evaluated and then multiplied by `5`

to get `7*5=35`

. I solved this problem by inserting parenthesis at appropriate positions in the expression and evaluating parenthesis first, i.e., I converted `4+3*5`

to `4+(3*5)`

. `InsertPrecedenceBrackets()`

is the function that does this stuff while the main function: `EvaluateInternal()`

, calls itself recursively whenever it encounters a parenthesis. The `DoAngleOperation()`

contains the definition of functions that the class supports.

Here's the code for `EvaluateInternal()`

:

public double EvaluateInternal(double dvalueX,
int startIndex, out int endIndex)
{
double dAnswer = 0, dOperand = 0;
char chCurrentChar, chOperator = '+';
string strAngleOperator;
for (int i = startIndex + 1; i < textInternal.Length; i++)
{
startIndex = i;
chCurrentChar = textInternal[startIndex];
if (char.IsDigit(chCurrentChar))
{
while (char.IsDigit(textInternal[i]) || textInternal[i] == '.')
i++;
dOperand =
Convert.ToDouble(textInternal.Substring(startIndex,
i - startIndex));
i--;
}
else if (IsOperator(chCurrentChar))
{
dAnswer = DoOperation(dAnswer, dOperand, chOperator);
chOperator = chCurrentChar;
}
else if (char.ToLower(chCurrentChar) == charX)
{
dOperand = dvalueX;
}
else if (chCurrentChar == '(')
{
dOperand = EvaluateInternal(dvalueX, i, out endIndex);
i = endIndex;
}
else if (chCurrentChar == ')')
{
dAnswer = DoOperation(dAnswer, dOperand, chOperator);
endIndex = i;
return dAnswer;
}
else
{
while (char.IsLetter(textInternal[i]))
i++;
if (textInternal[i] == '(')
{
strAngleOperator = textInternal.Substring(startIndex,
i - startIndex).ToLower();
dOperand = EvaluateInternal(dvalueX, i, out endIndex);
i = endIndex;
dOperand = DoAngleOperation(dOperand, strAngleOperator);
}
else
{
dOperand = this.constants[textInternal.Substring(startIndex,
i - startIndex).ToLower()];
i--;
}
}
if (double.IsNaN(dAnswer) || double.IsNaN(dOperand))
{
endIndex = i;
return double.NaN;
}
}
endIndex = textInternal.Length;
return 0;
}

Also, here are few lines from `DoAngleOperation()`

:

static double DoAngleOperation(double dOperand, string strOperator)
{
strOperator = strOperator.ToLower();
switch (strOperator)
{
case "abs":
return Math.Abs(dOperand);
case "sin":
return Math.Sin(dOperand);
case "arctan":
return Math.Atan(dOperand);
case "arcsinh":
return Math.Log(dOperand + Math.Sqrt(dOperand * dOperand + 1));
case "arccosh":
return Math.Log(dOperand + Math.Sqrt(dOperand * dOperand - 1));
case "MyCustomFunction":
return MyFunctionsClass.MyCustomFunction(dOperand));
:
:
}
}

The current implementation of the `Expression`

class contains definitions for `abs`

(absolute), `sin`

(trigonometric sine), `cos`

(trigonometric cosine), `tan`

(trigonometric tangent), `sec`

(trigonometric secant), `cosec`

(trigonometric cosecant), `cot`

(trigonometric cotangent), `arcsin`

(trigonometric sine inverse), `arccos`

(trigonometric cosine inverse), `arctan`

(trigonometric tangent inverse), `exp`

(exponent), `ln`

(natural logarithm), `log`

(logarithm in base 10), `antilog`

(antilog in base 10), `sqrt`

(square root), `sinh`

(hyperbolic sine), `cosh`

(hyperbolic cosine), `tanh`

(hyperbolic tangent), `arcsinh`

(hyperbolic sine inverse), `arccosh`

(hyperbolic cosine inverse) and `arctanh`

(hyperbolic tangent inverse). As mentioned previously, this list can be extended by adding custom user-defined functions.

Please note that the control does not redraw itself unless asked to do so. So, we manually need to call `expPlotter.Refresh()`

to reflect our changes on the graph. This is done because redrawing the control is a time-consuming task; it involves re-evaluation of all the expressions and re-plotting of all the points. I first thought of an AutoRefresh property for controlling this, but later dropped this idea because most of the time we will be performing more than one operation before refreshing the graph. Please tell me your thoughts on this.

The application fairly demonstrates the usage of the control. Since the control provides a very handy interface, we just need to call the respective functions of the control to make a cool application. A quick look at the event handlers of the program can give us a nice idea of how to use the control. Resize the graph window to observe how the control adjusts itself for every size. Enter expressions and notice how the application provides assistance (e.g. text coloring, inserting `*`

and parenthesis at appropriate places, etc) while entering expressions. I hope you will like this control. Happy graphing..

- Version 1.0: Initial version
- Version 1.1: The control can now have non-rectangular size (snapshot below)