Introduction
2D drawing on Windows Forms and the use of transformation matrices while demonstrating the Trapezoidal Rule for integration.
Background
Inspired by Rod Stephens' blog, I thought it would be interesting to have a program that demonstrates integrating a function using the trapezoidal rule.
Using the Code
This program demonstrates the use of the Trapezoidal Rule for integrating a function. This technique is well documented in any high school calculus book, but essentially the area of a number of trapezoids is summed as an approximation to the area under a curve, and thus the value of the function's integral. Five functions are available and can be selected using "Function" on the menu bar: Constant, Linear, Quadratic, Cubic, and Quartic. Each of the functions and their respective integral are accessed by the generic Func
delegate type. The Func
delegate type is available in the .NET 3.5 System namespace and is simply a handy predefined generic type that uses the last type as the return type. The functions and their integrals are in a region called math
. Here are the declarations of myFunc
, the function to integrate, and myFuncIntegral
, the function's integral:
private Func<float, float> myFunc;
private Func<float, float, float> myFuncIntegral;
For example, if we wanted the function y = x2 / 5, which has an integral of x3 / 15, we could write this:
private float FuncQuadratic(float x)
{
return (float)(Math.Pow(x, 2.0) / 5.0);
}
private float FuncIntegralQuadratic(float x1, float x2)
{
double val1 = Math.Pow(x1, 3.0) / 15.0;
double val2 = Math.Pow(x2, 3.0) / 15.0;
return (float)(val2 - val1);
}
myFunc = FuncQuadratic;
myFuncIntegral = FuncIntegralQuadratic;
float y = myFunc(3.0f);
float area = myFuncIntegral(1.0f, 3.0f);
Drawing the curve (that is, plotting the function on an x-y graph) is performed in a region called drawCurve
, and the axes are drawn in a region called drawAxes
. They are drawn in green and blue, respectively. The lower and upper integration limits are entered in text boxes labeled X1 and X2, and the number of intervals are selected using the numeric up/down box labelled "Intervals". These three values and the selected function are saved when exiting the program, and restored when the program is started. The area under the curve between X1 and X2 is computed using both the trapezoidal rule (an estimate) and using the derivative of the function (an exact value). The areas and per-cent error between the two values are displayed. It is interesting to see how increasing the number of intervals improves the accuracy.
The graph can be zoomed using the zoom control in the toolbar at the top left. It is similar to the zooming in the popular Paint.NET paint program. That is, you can click on the zoom in and zoom out buttons, or select a value from the dropdown. To scroll the image, you can use the scroll bars which limits the scrolled area to the area defined by the axes. I found during development that it was handy to be able to left-click on the graph and drag it to scroll. When you do so, the cursor changes to indicate you are dragging. When you release the left mouse button, the graph is scrolled and the scroll bars are updated. Clicking on a point on (or near) the curve displays the x and y values in the text box and draws a dashed red line from the curve to the axes. To clear it, just left-click on a point not near the curve. There are also check boxes to toggle the drawing of the curve and axes, as well as a button to center the axes. In the image above, we are using the cubic function and integrating it from 1 to 4 using an interval of 11, that is 11 trapezoids. Note in this example that most of the area lies below the x axis and therefore the value of the integral is less than zero.
The trapezoids themselves are drawn in a region called trapezoid
using DrawPolygon
and five points representing the lower left, upper left, upper right, lower right, and lower left vertices of the trapezoid. The area of a trapezoid is simply the sum of left and right sides times the width divided by 2.
approxArea += ((yLeft + yRight) * width) / 2.0;
PointF[] pts = new PointF[5] { new PointF(xLeft, 0.0f), new PointF(xLeft, yLeft),
new PointF(xRight, yRight),
new PointF(xRight, 0.0f), new PointF(xLeft, 0.0f) };
graphics.DrawPolygon(trapPen, pts);
The image below shows how we are using the quadratic function, and have zoomed in to see the two trapezoids drawn in orange. Note that the curve is drawn in green, with the part of the curve that is within the interval drawn in red.
Points of Interest
As it has been some time since I have used OpenGL or DirectX for graphics programming, I wanted to brush up on the use of transformation matrices. The buttons on the toolbar labeled "Flip Horizontal" and "Flip Vertical" produce a mirror image of the curve about the y and x axes, respectively. (This is also similar to Paint.NET's behaviour.) Admittedly, they are of little use in this application, but demonstrate how to transform the curve, while leaving the axes untransformed. The two matrices to perform these transformations are explained in many computer graphics textbooks:
private Matrix yReflectMat = new Matrix(-1.0f, 0.0f, 0.0f, +1.0f, 0.0f, 0.0f);
private Matrix xReflectMat = new Matrix(+1.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
Since it is customary to have y values increase up the vertical axis, but the y pixel coordinate decreases as we move up the screen, keeping track of the algebraic sign of the y function values is essential. I have documented this in the code.
History
- Version 1.0: December 16, 2014