## Display a graphic representation of compound interest in C#

This is a more graphic version of the example Calculate compound interest over time in C#. Instead of adding values to a ListView control, this example saves points in three lists of points: Balance, Contributions, and Interest. The following PerformCalculations method shows how the program calculates and saves those values.

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 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(); }

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.// 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 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 even if the new and old tooltips are the same, then the tooltip flickers. Remove the "if" test to see this.) The following code shows the GetDataTooltip method.// 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 + "]"); } }

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. After I had written this example, I thought of another way to visualize the same data. My next post will explain that 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 ""; }

## Comments