13,150,622 members (42,436 online)
alternative version

#### Stats

64.4K views
58 bookmarked
Posted 2 May 2006

# Autoscaling Graph Control

, 10 May 2006
 Rate this:
Learn how to create a graph that can automatically scale itself!

## Introduction

I write a lot of programs that take data in from outside sources, like lab equipments and other junk. I've always wanted a simple graph control that will display the data I'm reading, so that I can tell, while the code is running, whether or not something has gone wrong. One part of what makes this tough is making the data fit into the graph window. So, I figured out how to use the `Matrix` class to do the auto-scaling for me!

## Background

This article (c-sharpcorner.com) is a pretty good introduction on using Matrix transforms with GDI+.

## Using Matrices to Transform Graphics Objects

So, the basic idea is that we can use a `Matrix` to change the origin and scale of our graph window. To actually perform the transform, you use some code like this:

```// in my Paint even handler...

// transform the graphics context to get
// the origin near the bottom left of the window
Matrix trans = new Matrix(1, 0, 0, -1, 30, this.Height - 30);
trans.Scale(scale_x, scale_y);
e.Graphics.Transform = trans;```

In this code, I'm actually using the `Matrix` object to do three things.

First, I want the origin of the graph to be 30 pixels to the right, and 30 pixels up from the bottom-left corner of the window. This is what the last two arguments of the `Matrix` constructor do. The first one of these, `30`, is the x-axis translation factor. So this argument means, move the origin of the graph 30 pixels to the right. `this.Height - 30` is similar, and it means move the origin down 30 pixels less that the height of the graph window.

The second thing I want to do is change how the coordinates are treated by the graph. GDI, and every other programming language's coordinate system, starts in the upper-right hand corner, and increasing the y-axis coordinates move downwards on the screen. This is the opposite of what humans expect a graph to do. We like the graph to move upwards when the y value increases. So, that's what the first four numbers of the `Matrix` constructor do. Actually, it's the fourth number in the sequence that does this. If you look at the c-sharpcorner.com article mentioned above, you can figure out why you need that `-1` in the matrix. Essentially, it means, y-axis coordinates are the opposite of what the computer normally thinks they are.

The final thing I'm doing with this matrix is scaling the graph. I do this with the `trans.Scale(scale_x, scale_y);` line. Fortunately, this line is a little less complicated than the `Matrix` constructor. All you really need to do is feed it some scale factors, and you're all set.

Finally, you just need to set the current `Graphics` `Transform` member to the `Matrix` object you've just created. I'll explain the scale factors in the next section. Now, your coordinate system will be set up just like it was when you learned how to graph numbers in grade school...

If you're into math, you can look at the end of the article to see exactly what's going on with this `Matrix` mess.

## Determining Auto-scale Factors

So, how do we get the scale factors mentioned in the section above? It's actually pretty simple. In this implementation, I've used an `ArrayList` of `PointF`s to store the data I want on the graph. So, I've created the following function to find the scale factors:

```public void Autoscale()
{
foreach (PointF p in data)
{
if (p.X > xmax)
xmax = p.X;
if (p.Y > ymax)
ymax = p.Y;
}
scale_x = (float)((this.Width - 30f) / xmax);
scale_y = (float)((this.Height - 30f) / ymax);
}```

All this function does is, look through each value in the data and find which one of them is the largest in each dimension. Then, to create the scale factor, it divides the width (in pixels) of the graph window by this number. That's it! (Note that I subtract 30 from the `Width` and `Height` because I moved my origin 30 pixels in.)

## An Alternate Method

If you really don't like creating the `Matrix` object, there's another, possibly simpler way to do this. If you look at my code, you can see I do some translation and rotation to draw my "Y-Axis" string. The code looks like this:

```e.Graphics.TranslateTransform(15, 130);
e.Graphics.RotateTransform(-90);
e.Graphics.DrawString(y_text,
new Font(FontFamily.GenericMonospace, 8f),
new SolidBrush(Color.LawnGreen), new PointF(0f, 0f));```

This code sets a `TranslateTransform` to move the origin to (15, 130), and then rotates the coordinate system by -90 degrees. This way, I can use `Graphics.DrawString` to draw a vertical string next to my y-axis line. I usually use this method when I'm only going to make one or two method calls that need an altered coordinate system. Like in this case. I feel that using the actual `Matrix` object is better when doing multiple draw calls. I'm not really sure why though. I guess I just like it better.

## What is all this Matrix Garbage?

OK! I'll try to explain exactly what's going on with the `Matrix`. If you know even a little matrix algebra, this shouldn't be too hard. If you don't really care, it's not essential to know this, but some people might be curious.

So, what is actually being created if we write a line like the following?

`Matrix trans = new Matrix(1, 0, 0, -1, 0, -10);`

Well, I'll tell you. You're actually getting two matrices with one constructor! That's pretty great! Here is what's actually created:

Of course, `x` and `y` are the input points, and `x'` and `y'` are the output. The left-most matrix is similar to the identity matrix, except for the -1. So, if you multiply this by the input points, you can see that you're just multiplying `y` by -1. If you think about the input as points that GDI deals with, you can see that instead of moving down the screen with increasing `y` values, we start moving up, or vice versa. That's pretty nice! The next matrix in the equation is the translation matrix ([0 -10]'). If you think about performing this operation, you can see that it ends up moving every `y` value 10 units down. So, if you use this exact `Matrix` in your code, you'll be moving your graph 10 pixels down, and inverting the direction points, move in the y-axis. That's exactly what we want to happen for this graph!

Just to prove this, I wrote a quick MATLAB script, and plotted the results:

## Points of Interest

One interesting and frustrating thing I found when writing this code is that the pixel width of a `Pen` is changed when using scaling transforms. If you read my code, you'll see in the paint handler that I set the width of each pen to `1 / scale_x`. Otherwise, your graph line will get really fat if you have a large scale factor. It's really not a perfect solution, but it works OK.

There's not really much documentation on the `Matrix` class in the MSDN docs, so it's sort of hard to figure out exactly what kind of a matrix you need to do what you want. This seemed like a pretty common transform people would want to do, so I thought I'd share it. Now, how about some comments?

## Share

 United States
Whoa! I'm thebeekeeper. I'm from Milwaukee, WI but I'm living in Boston, MA right now. I work for a very large organization doing digital signal processing, but sometimes I write programs for computers!

Go look at my web-site! (thebeekeeper.net)

## You may also be interested in...

 Pro

 First Prev Next
 very clear and easy to understand ~ blue.pan@163.com7-Oct-07 20:57 blue.pan@163.com 7-Oct-07 20:57
 E-mail from Martin on Dec 12 thebeekeeper12-Dec-06 7:41 thebeekeeper 12-Dec-06 7:41
 Pen width OrlandoCurioso11-May-06 3:56 OrlandoCurioso 11-May-06 3:56
 Re: Pen width thebeekeeper11-May-06 7:57 thebeekeeper 11-May-06 7:57
 Hi Trance Junkie9-May-06 1:18 Trance Junkie 9-May-06 1:18
 Re: Hi thebeekeeper10-May-06 3:51 thebeekeeper 10-May-06 3:51
 Last Visit: 31-Dec-99 18:00     Last Update: 25-Sep-17 19:47 Refresh 1