Graph an equation in C#

[graph]


This example uses the the following MakeGraph method to draw the graph.

// Make the graph.
private void MakeGraph()
{
    // The bounds to draw.
    float xmin = -3;
    float xmax = 3;
    float ymin = -3;
    float ymax = 3;

    // Make the Bitmap.
    int wid = picGraph.ClientSize.Width;
    int hgt = picGraph.ClientSize.Height;
    Bitmap bm = new Bitmap(wid, hgt);
    using (Graphics gr = Graphics.FromImage(bm))
    {
        gr.SmoothingMode = SmoothingMode.AntiAlias;

        // Transform to map the graph bounds to the Bitmap.
        RectangleF rect = new RectangleF(
            xmin, ymin, xmax - xmin, ymax - ymin);
        PointF[] pts = 
        {
            new PointF(0, hgt),
            new PointF(wid, hgt),
            new PointF(0, 0),
        };
        gr.Transform = new Matrix(rect, pts);

        // Draw the graph.
        using (Pen graph_pen = new Pen(Color.Blue, 0))
        {
            // Draw the axes.
            gr.DrawLine(graph_pen, xmin, 0, xmax, 0);
            gr.DrawLine(graph_pen, 0, ymin, 0, ymax);
            for (int x = (int)xmin; x <= xmax; x++)
            {
                gr.DrawLine(graph_pen, x, -0.1f, x, 0.1f);
            }
            for (int y = (int)ymin; y <= ymax; y++)
            {
                gr.DrawLine(graph_pen, -0.1f, y, 0.1f, y);
            }
            graph_pen.Color = Color.Red;

            // See how big 1 pixel is horizontally.
            Matrix inverse = gr.Transform;
            inverse.Invert();
            PointF[] pixel_pts =
            {
                new PointF(0, 0),
                new PointF(1, 0)
            };
            inverse.TransformPoints(pixel_pts);
            float dx = pixel_pts[1].X - pixel_pts[0].X;
            dx /= 2;

            // Loop over x values to generate points.
            List<PointF> points = new List<PointF>();
            for (float x = xmin; x <= xmax; x += dx)
            {
                bool valid_point = false;
                try
                {
                    // Get the next point.
                    float y = F(x);

                    // If the slope is reasonable,
                    // this is a valid point.
                    if (points.Count == 0) valid_point = true;
                    else
                    {
                        float dy = y - points[points.Count - 1].Y;
                        if (Math.Abs(dy / dx) < 1000)
                            valid_point = true;
                    }
                    if (valid_point) points.Add(new PointF(x, y));
                }
                catch
                {
                }

                // If the new point is invalid, draw
                // the points in the latest batch.
                if (!valid_point)
                {
                    if (points.Count > 1)
                        gr.DrawLines(graph_pen, points.ToArray());
                    points.Clear();
                }

            }

            // Draw the last batch of points.
            if (points.Count > 1)
                gr.DrawLines(graph_pen, points.ToArray());
        }
    }

    // Display the result.
    picGraph.Image = bm;
}

This code starts by defining the coordinate bounds -3 ≤ x ≤ 3, -3 ≤ y ≤ 3 where it will draw the graph. The code then makes a Bitmap to fit the program’s picGraph PictureBox and makes a Graphics object to draw on the Bitmap.

Next the code defines a transformation to map the coordinate bounds to the bitmap. This example doesn’t worry about whether that distorts the graph. Alternatively you might want to scale the graph to make the coordinate area as large as possible without distortion.

The code sets the Graphics object’s Transform property to the transformation that performs the mapping. Now the code can draw in normal coordinate space and the transformation automatically converts the drawing into the transformed coordinate system.

Next the program creates a Pen with thickness 0. This is important because the transformation will affect pens that have any other thickness. For example, if the transformation scales to make the result slightly tall and thin, then it will scale pens to make them thicker in the vertical direction than in the horizontal direction. Pens with 0 thickness, however, are always drawn 1 pixel wide and are not transformed.

The code draws the X and Y axes using the thin pen and then changes the pen’s color before drawing the graph.

To make the graph smooth, the program plots values where X increases by 1/2 pixel at each step. You could plot a value for every pixel horizontally but every half pixel produces a better result when the graph is very vertical. (More analytically, you could take the function’s derivative and plot more points when the derivative is large and fewer when it is low. To think of it another way, you could plot similar numbers of points per unit of curve length. However, plotting one point per half pixel seems to produce a fairly good result.)

To plot points for every 1/2 pixel in the X direction, the curve makes an array holding 2 points 1 pixel apart. It uses the inverse of the Graphics object’s transformation to see, if the points represent points on the Bitmap, where they map from in coordinate space. The distance between them tells how far apart points in coordinate space must be to end up 1 pixel apart on the Bitmap. The program simply divides by 2 to get the X coordinate distance for half a pixel.

Finally the program is ready to plot some points. It loops through the points calling the function F to get Y values. If the slope between one point and the previous one is extremely steep, the program assumes that this is a discontinuity. For example, if F(x) = 1/x, then there is a discontinuity at x = 0 because the point slightly to the left is a very large negative value, the point slightly to the right is a very large positive value, and the function is undefined at x = 0.

If the program does not finds a discontinuity, it adds the new point to the points list. If the program does find a discontinuity, it draws the previously saved points and starts a new list.

After it has covered every X value in the desired range, the program draws any points that it has not yet drawn.

The following code shows the function F that this example uses, but you should be able to plug in other equations without too much work.

// The function to graph.
private float F(float x)
{
    return (float)((1 / x + 1 / (x + 1) - 2 * x * x) / 10);
}

If you look at the equation, you can see that it should have discontinuities at x = -1 and x = 0. The picture at the top of the post shows those discontinuities.


Download Example   Follow me on Twitter   RSS feed   Donate




This entry was posted in algorithms, drawing, graphics, mathematics and tagged , , , , , , , , , , . Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *