# Background

This example shows how you can use transformations to draw a graph in WPF.

In two-dimensional graphics, transformations let you translate, scale, rotate, and skew the objects you draw. My previous post, Draw a graph in WPF and C#, draws a graph in device coordinates measured in pixels on a `Canvas` control. This example shows how you can use transformations to let you draw in a world coordinate system that’s more convenient for the data. For example, you might like the graph’s X coordinates to represent years between 2000 and 2020, and the Y coordinates to represent sales figures between $10,000 and $100,000.

WPF provides a `Transform` class and several sub-classes (`ScaleTransform`, `TranslateTransform`, `RotateTransform`, and `SkewTransform`) to represent transformations applied to controls. You can set a control’s `LayoutTransform` or `RenderTransform` property to a transform class to apply the transformation to the control during layout or rendering respectively. For example, that lets you rotate or skew a label relatively easily.

If you want to apply multiple transforms to a control, you can combine them in a `TransformGroup` and then set the `LayoutTransform` or `RenderTransform` property to the group.

Unfortunately that technique doesn’t let you easily convert back and forth between the world and device coordinates. As I mentioned in my previous post, you often need to perform those conversions to draw a graph.

Fortunately there’s another way you can represent transformations. Instead of using the transform classes, you can use the `Matrix` class. A `Matrix` object represents a 3×3 matrix that can store information about translation, scaling, rotation, and skew transformations.

A nice feature of matrices is that you can combine them to represent multiple transformations. For example, you can use a single `Matrix` to represent a translation, followed by a rotation, followed by a scaling, followed by another translation.

Another nice feature of matrices is that you cna invert them to get a transformation representing the opposite of the original transformation. Finally you can use a `Matrix` to transform a point from one coordinate system to another.

# Preparing the Transformation Matrix

This example creates two a `Matrix` object that converts from world coordinates to device coordinates. It then uses that `Matrix` to convert data points so they are drawn in their correct positions on the `Canvas` control.

When the program starts, its `Window_Loaded` event handler starts by executing the following code.

const double wxmin = -10; const double wxmax = 110; const double wymin = -1; const double wymax = 11; const double xstep = 10; const double ystep = 1; const double xtic = 5; const double ytic = 0.5; const double dmargin = 10; double dxmin = dmargin; double dxmax = canGraph.Width - dmargin; double dymin = dmargin; double dymax = canGraph.Height - dmargin; // Prepare the transformation matrices. PrepareTransformations( wxmin, wxmax, wymin, wymax, dxmin, dxmax, dymax, dymin);

This code creates some constants to define the world and device coordinate bounds. The world coordinates are `wxmin <= x <= wxmax`, `wymin <= y <= wymax`. It uses the margin in device coordinates `dmargin` to define the device coordinates `dxmin <= x <= dxmax`, `dymin <= y <= dymax`.

The code also defines increment amounts to use in later `for` loops (`xstep` and `ystep`), and the lengths of tic marks in world coordinates (`xtic` and `ytic`).

After making those definitions, the code calls the `PrepareTransformations` method to set up the transformation matrix. Note that I pulled a fast one here by switching the order of the `dymin` and `dymax` parameters passed into the method. More on that a bit later.

The following code shows the `PrepareTransformations` method.

// Prepare values for perform transformations. private Matrix WtoDMatrix, DtoWMatrix; private void PrepareTransformations( double wxmin, double wxmax, double wymin, double wymax, double dxmin, double dxmax, double dymin, double dymax) { // Make WtoD. WtoDMatrix = Matrix.Identity; WtoDMatrix.Translate(-wxmin, -wymin); double xscale = (dxmax - dxmin) / (wxmax - wxmin); double yscale = (dymax - dymin) / (wymax - wymin); WtoDMatrix.Scale(xscale, yscale); WtoDMatrix.Translate(dxmin, dymin); // Make DtoW. DtoWMatrix = WtoDMatrix; DtoWMatrix.Invert(); }

This code first defines two `Matrix` objects, `WtoDMatrix` and `DtoWMatrix`. They will represent the world-to-device and device-to-world transformations.

The `PrepareTransformations` method first initializes `WtoDMatrix` to the identity matrix. The identity matrix represents a transformation that leaves a point unchanged.

The code then calls the `Matrix`'s `Translate` method to add a translation transformation to it. In this example, it translates by distance -wxmin in the X direction and -wymin in the Y direction. The result is that it translates the world coordinate area so its corner `(xwmin, xymin)` has moved to the origin.

Next the code gets the amount by which it needs to scale the world coordinates to make them match the device coordinates. For example, to make the world coordinates match the width of the device coordinates, `xscale` divides by the width of the world coordinate area and multiples by the width of the device coordinate area.

After calculating the scale factors, the method calls the `Matrix`'s `Scale` method to add a scaling transformation to the translation. At this point, the `Matrix` represents translating to the origin followed by scaling.

The code then adds another translation to the `Matrix` to move the point at the origin to the device coordinate point `(dxmin, dymin)`.

The combined transformation `Matrix` represents moving the world coordinate area to the origin, scaling it, and then moving it into position over the device coordinate area.

The method finishes by making an inverse transformation. First it copies `WtoDMatrix`. It then calls the copy's `Invert` method. The result is the `DtoWMatrix` represents the inverse transformation from device to world coordinates. (The example doesn't actually use this transformation, but it'll come in handy in a later example.)

One extra nice feature about this transformation is that it inverts Y coordinates if desired. Normally controls such as `Canvas` place `(0, 0)` in the upper left corner and make coordinates increase down and to the right. In contrast, when you're drawing a graph, you normally want `(0, 0)` to be in the lower left corner.

The program handles this transformation by switching the order of the `dymin` and `dymax` coordinates passed into the `PrepareTransformations` method. That makes the `yscale` value negative, which flips the Y coordinates upside down to match the device coordinate orientation. It also makes the transformation's final translation move the world coordinate origin `(wxmin, wymin)` to the lower left corner of the device coordinates as needed.

# Using the Transformation

Setting up the transformation matrix is a bit of work (it's actually not very long, just a bit confusing), but after the matrix is ready, using it is easy. The following `WtoD` method applies the matrix to a `Point`.

// Transform a point from world to device coordinates. private Point WtoD(Point point) { return WtoDMatrix.Transform(point); }

This code simply calls the matrix's `Transform` method, passing it the `Point` to transform. Getting to this point took some effort, but using the matrix couldn't be any easier.

Whenever the program needs to draw something, it first calls the `WtoD` method to translate the coordinates it's using from world to device coordinates. For example, the following code shows how the program draws the baseline for the X axis. The calls to `WtoD` are shown in blue.

// Make the X axis. GeometryGroup xaxis_geom = new GeometryGroup(); Point p0 = new Point(wxmin, 0); Point p1 = new Point(wxmax, 0); xaxis_geom.Children.Add(new LineGeometry(WtoD(p0), WtoD(p1)));

This code defines two points with world coordinates `(wxmin, 0)` and `(wxmax, 0)`. It calls `WtoD` for them, passes the result into the `LineGeomtry` class's constructor, and adds the resulting object to the X axis's `GeometryGroup` object. (The previous version of the program did the same thing except it worked in device coordinates so it didn't need to transform the points with `WtoD`.)

The following code shows how the program creates the points for a data set.

PointCollection points = new PointCollection(); for (double x = 0; x <= 100; x += 10) { last_y += rand.Next(-10, 10) / 10.0; if (last_y < 0) last_y = 0; if (last_y > 10) last_y = 10; Point p = new Point(x, last_y); points.Add(WtoD(p)); }

That's about all there is to it. Download the example and see the previous post for additional details.

Pingback: Draw a graph with even tic marks in WPF and C# -

Thank you so much!!

When I run it in the demo project, it works. When I integrate it into my project, the graph axes draw nicely with the correct spacings. However, the graph line is completely off, and the WtoD function spits out the exact same numbers as it was fed with, which I find a little bit strange. Any idea what’s wrong here? The WtoD function should change the values under all circumstances, right?

Sorry, I found the error! By accident, I had moved the preparation of my coordinates to somewhere before the PrepareTransformations call. That explains why the coordinates were not changed at all. Simple error.

Wow, I found this intelligent algorithm, so useful. Thank for your considerable contribution.

Hi Rod

While searching for a solution about how to draw graphs with wpf I found your series of very useful tutorials. However, I have a question about how to map the negative ‘world’ values of x and y (for example, let’s consider data point -1000, -1500)?

Thanks again

If you use a transformation to map from world to device coordinates, then it should work just fine with negative values. The only down side is that the coordinate axes x=0 and y=0 may not fit on the graph, for example, if you map -2000 <= x <= -1000, -3000 <= y <= -2000 onto the drawing area.

Cartesian coordinates (and thus usually world coordinates) in the Y axis are always from bottom to top, while device coordinates (the Canvas class) are always from top to bottom.

So shouldn’t we apply another WtoDMatrix.Scale(1, -1) at the end, in order to flip-mirror the Y axis?

A small correction to the above message:

Not at the end, but right after the first Scale.

You have the right idea, but it’s already taken care of in this code:

Notice that the blue values

dymaxanddyminhave reversed order. That does basically the same thing that you describe. (It’s more than a little sneaky, so perhaps I should have made a bigger deal out of it to make it more obvious.)Pingback: Graph equations entered by the user in WPF and C# - C# HelperC# Helper