The example Download and graph COVID-19 case data in C# shows how to graph different countries’ COVID-19 case data. The graphs are informative. For example, they can show you whether a country’s graph is upward bending or whether it has started to level off.

Unfortunately that program makes some comparisons difficult. For example, the following graph shows the case data for Belgium and New Zealand.

Belgium’s curve is on the top so it looks like it is doing much worse than New Zealand. However, Belgium recorded its first case of COVID-19 on February 4 but New Zealand didn’t find a case until more than three weeks later on February 28. It could be that Belgium has more cases because the virus has been spreading there longer.

This example lets you align graphs so each one’s left edge shows the day where that country recorded a certain number of cases. You can set the number of cases to one or some larger value. You can also set it to zero to get a result similar to the previous example.

# The CountryData Class

The basic idea is that the `CountryData` class should keep track of the first entry in its case data that is larger than a certain number of cases.

The class stores that value in the new variable `FirstDrawnDate`.

public int FirstDrawnDate = -1;

For example, if the number of cases that you want to use for alignment is 10, then `FirstDrawnDate` is the index of the first `Cases` entry that has a value at least 10.

When the class draws a country’s data, the `Draw` method sets `FirstDrawnDate` as shown in the following code.

// Draw this country's data. public void Draw(int align_cases, Graphics gr, Pen pen, Matrix transform) { // Find the first date with align_cases cases. FirstDrawnDate = -1; int num_cases = Cases.Length; for (int i = 0; i < num_cases; i++) if (Cases[i] >= align_cases) { FirstDrawnDate = i; break; } // Don't draw unless we have at least one day of data left. if ((FirstDrawnDate < 0) || (FirstDrawnDate >= num_cases - 1)) return; // Make the points. List<PointF> point_list = new List<PointF>(); for (int i = FirstDrawnDate; i < num_cases; i++) point_list.Add(new PointF(i - FirstDrawnDate, Cases[i])); PointF[] points = point_list.ToArray(); // Draw the curve. gr.DrawLines(pen, points); // Find device coordinates for tooltips. transform.TransformPoints(points); DeviceCoords = points; }

The method first loops through the case data until it finds the first entry with value greater than the parameter `align_cases`.

If there are any entries left to draw, the code loops through the cases from that point onward to draw the graph. This part is similar to the previous code except the program subtracts `FirstDrawnDate` from each of the points’ X coordinates. That moves the graph to the left so the first point is on the left edge of the program’s drawing area.

That’s the only change needed to align the graphs. The class’s `PointIsAt` method must also be modified so the program can correctly figure out what date is under the mouse. The following code shows the new `PointIsAt` method.

public bool PointIsAt(PointF device_point, out int day_num, out int num_cases, out PointF close_point) { if (FirstDrawnDate >= 0) { const double close_dist = 4; PointF closest; for (int i = FirstDrawnDate + 1; i < Cases.Length; i++) { int coord_num = i - FirstDrawnDate; double dist = FindDistanceToSegment(device_point, DeviceCoords[coord_num - 1], DeviceCoords[coord_num], out closest); if (dist <= close_dist) { // See whether it is closer to this this // segment's left or right point. if (DistanceBetweenPoints(DeviceCoords[coord_num - 1], closest) < DistanceBetweenPoints(DeviceCoords[coord_num], closest)) coord_num--; day_num = coord_num + FirstDrawnDate; num_cases = Cases[day_num]; // Use the point on the segment. //close_point = closest; // Use the closer segment end point. close_point = DeviceCoords[coord_num]; return true; } } } day_num = -1; num_cases = -1; close_point = new PointF(-1, -1); return false; }

The method first checks the `FirstDrawnDate` value. If that value is less than zero, then the country’s data does not have any dates with the desired number of cases so the graph is not drawn. In that case, the `PointIsAt` method does not bother to search for a segment close to the point `device_point`. Instead it skips to the bottom, sets its return parameters to indicate that it did not find a close point, and returns.

If the graph does have visible points, the code loops variable `i` over data indices starting at entry number `FirstDrawnDate + 1`. It examines the segment between that point and the previous point to see if the segment is close to the target point `device_point`.

Inside the loop, the program sets variable `coord_num` equal to `i` minus `FirstDrawnDate`. That makes `coord_num` give the entry in the `DeviceCoords` array corresponding to case entry `i`. (Recall that `DeviceCoords` holds the coordinates of the graph’s points on the screen.

If it finds a close point, the program calculates the distance between `device_point` and the segment’s end point to see which one is closer. If the first point is closer, the program subtracts one from `coord_num` so it indicates the segment’s left point (in the `DeviceCoords` array).

To prepare to return, the method sets `day_num = coord_num + FirstDrawnDate` so `day_num` indicates the index of the case corresponding to the close point. It also sets `num_cases` equal to the number of cases on that day and saves the closer segment end point in output parameter `close_point`.

This may all seem a bit confusing. All it’s really doing is offsetting the array indices by `FirstDrawnDate` because the graph has been

# Main Program

The only changes needed to the main program are in the `GraphCountries` method. That method uses the following code snippet to get the number of cases that should be used to align the graphs.

// Get the number of cases where we should align countries. int align_cases = 0; int.TryParse(txtAlignCases.Text, out align_cases);

This code sets `align_cases` to 0 and the tries to parse the value entered in the `txtAlignCases TextBox`. If the value doesn’t parse, the value of `align_cases` remains 0.

Later the method uses the following statement to draw the countries’ graphs.

country.Draw(align_cases, gr, pen, Transform);

The only change here is this statement passes the `align_cases` value into the `Draw` method.

# Conclusion

The following picture shows the graphs for Belgium and New Zealand when they are aligned at the first days when they saw at least one case. (Recall that Belgium’s graph is the taller one.)

In this picture you can see that New Zealand actually has more cases than Belgium had at the same number of day into its epidemic. The shapes of the curves, however, imply that New Zealand may be better off, as long as their curve remains relatively flat.

Download the new example and give it a try. Future COVID-19 posts will make further improvements so we can study the pandemic in other ways.

Pingback: Graph COVID-19 cases per million in C# - C# HelperC# Helper