This is a more graphic version of the example Calculate the value of a monthly investment in C#. Instead of adding values to a `ListView` control, this example saves points in three lists of points: `Balance`, `Contributions`, and `Interest`. It then graphs those values.

The following `PerformCalculations` method shows how the program calculates and saves those values.

// The data points. private List<PointF> Contributions = new List<PointF>(); private List<PointF> Interest = new List<PointF>(); private List<PointF> Balance = new List<PointF>(); // Perform the calculations. private void PerformCalculations() { // Get the parameters. decimal monthly_contribution = decimal.Parse( txtMonthlyContribution.Text, NumberStyles.Any); int num_months = int.Parse(txtNumMonths.Text); decimal interest_rate = decimal.Parse( txtInterestRate.Text.Replace("%", "")) / 100; interest_rate /= 12; // Start at 0. Contributions = new List<PointF>(); Interest = new List<PointF>(); Balance = new List<PointF>(); decimal contributions = 0; decimal interest = 0; decimal balance = 0; // Calculate. for (int i = 0; i <= num_months; i++) { // Save the contributions, interest, and balance. Contributions.Add(new PointF(i, (float)contributions)); Interest.Add(new PointF(i, (float)interest)); Balance.Add(new PointF(i, (float)balance)); // Calculate the values for next month. contributions += monthly_contribution; decimal new_interest = balance * interest_rate; interest += new_interest; balance += monthly_contribution + new_interest; } // Add points to close the polygons. Contributions.Add(new PointF(num_months, 0)); Interest.Add(new PointF(num_months, 0)); Balance.Add(new PointF(num_months, 0)); // Redraw. picGraph.Refresh(); }

Most of this method is straightforward. It simply adds new data points representing the account’s current contributions so far, balance, and interest. It then adds the monthly contribution to the total contributions so far, calculates the month’s interest, and updates the balance for the next month.

The end of the method adds one more point to each list. The point has the same X coordinate as the final data point (the X coordinate represents the month) and Y coordinate 0 (representing $0). This makes the series of points return to the X axis so the program can fill them as a polygon as shown in the picture.

The following code shows how the program draws the graph.

// The drawing transformation. private Matrix Transform; // Draw the graph. private void picGraph_Paint(object sender, PaintEventArgs e) { e.Graphics.Clear(picGraph.BackColor); if (Balance.Count < 2) return; e.Graphics.SmoothingMode = SmoothingMode.AntiAlias; // Scale to make the data fit. float xmin = -1; float xmax = Contributions.Count + 1; float ymax = Balance.Max(pt => pt.Y); float ymin = -ymax * 0.05f; RectangleF rect = new RectangleF(xmin, ymin, xmax - xmin, ymax - ymin); PointF[] pts = { new PointF(0, picGraph.ClientSize.Height), new PointF( picGraph.ClientSize.Width, picGraph.ClientSize.Height), new PointF(0, 0), }; Transform = new Matrix(rect, pts); e.Graphics.Transform = Transform; // Draw the curves. e.Graphics.FillPolygon(Brushes.LightGreen, Balance.ToArray()); e.Graphics.FillPolygon(Brushes.LightBlue, Contributions.ToArray()); e.Graphics.FillPolygon(Brushes.Pink, Interest.ToArray()); e.Graphics.DrawPolygon(Pens.Green, Balance.ToArray()); e.Graphics.DrawPolygon(Pens.Blue, Contributions.ToArray()); e.Graphics.DrawPolygon(Pens.Red, Interest.ToArray()); }

The `Paint` event handler creates a transformation to map the data onto the `picGraph PictureBox`. It saves the transformation in the `Transform` variable for later use.

The method then fills the three curves and then outlines them.

The final interesting piece to the program is the following code, which makes the program display data values in a tooltip when you move the mouse over the graph.

// Display the nearest data point's values. private const float max_dx = 5; private void picGraph_MouseMove(object sender, MouseEventArgs e) { if (Balance.Count < 3) return; // Find the data point closest to the mouse. string tip = ""; if (tip == "") tip = GetDataTooltip(Balance, e.Location, "Balance"); if (tip == "") tip = GetDataTooltip(Contributions, e.Location, "Contributions"); if (tip == "") tip = GetDataTooltip(Interest, e.Location, "Interest"); // Display the new tool tip. if (tip != tipAmount.GetToolTip(picGraph)) { tipAmount.SetToolTip(picGraph, tip); Console.WriteLine("[" + tip + "]"); } }

The `MouseMove` event handler calls the `GetDataTooltip` method three times, once for each of the data sets. If the new tooltip text returned by that method is different from the one that the `picGraph PictureBox` is currently displaying, then the program sets the control’s tooltip to the new value. (If you set the new tooltip without checking to see if it has changed, then the tooltip flickers. Remove the `if` test to see this.)

The following code shows the `GetDataTooltip` method.

// Find a tooltip for the given data points. private string GetDataTooltip(List<PointF> point_list, Point location, string type_name) { const float max_dist = 6; // Convert the points to screen coordinates. PointF[] points = point_list.ToArray(); Transform.TransformPoints(points); // See if any of the points is close to the location, // skipping the last point that was used to close the polygon. for (int i = 0; i < point_list.Count - 1; i++) { // See if this point is close enough to the mouse. float dist = Math.Abs(points[i].X - location.X) + Math.Abs(points[i].Y - location.Y); if (dist < max_dist) { return "Month: " + point_list[i].X.ToString() + "\n" + type_name + ": " + point_list[i].Y.ToString("C"); } } return ""; }

This method converts a list of data points into an array. It then uses the drawing transformation to transform them as if it were drawing them. This puts the points and the location of the mouse in the same screen coordinate system.

Next the method loops through the points. If it finds a point that is close enough to the mouse’s location, the method composes an appropriate tooltip message and returns it.

You can see in the picture that the interest curve is getting steeper. If you extend the test out to 240 months or so, the interest curve grows steep enough to exceed the contributions.

After I had written this example, I thought of another way to visualize the same data. My next post will explain that method.

Pingback: Make a stacked graph showing compound interest in C# - C# HelperC# Helper