Drawing Text On Reversed Axes.

 

The power of graphics transformations using matrices comes from a double edged sword. You can easily create a matrix that scales, reflects, rotates or otherwise modifies the positions of your output pixels but occasionally, the application of the matrix in one instance creates the desired effect and in another totally messes up your life.

 

One such example is in the drawing of graphs where the matrix can be manipulated to provide a more logical coordinate system which has its origin at the bottom-left of the screen and for which the Y axis extends upwards when the values are positive. In this instance, drawing lines and shapes is a breeze but the power of the matrix totally ruins text because when you come to annotate your wonderful chart, the text is mirrored vertically also.

 

In such a case, you need to separate your drawing operations and use additional transformations that enable you to position and display text in the correct orientation but using the same coordinate system as the other non-textual graphic elements.

 

First, take a look at a typical matrix that will reverse your Y axis and enable you to plot data on a chart in the correct orientation.

 

private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

    {

      //Reverse the axis of the drawing surface

      Matrix mx = new Matrix(1,0,0,-1,0,this.ClientSize.Height);

      e.Graphics.Transform=mx;

 

The matrix shown in the partial listing above moves the origin of the screen from the top-left to the bottom-left of the screen and reverses the Y axis. Now, all line drawing operations will be in the correct sense for the graph plotting exercise.

 

To illustrate this, the next portion of the listing draws X and Y axes and places tick marks on them.

 

      //Draw some stuff

      //axes..

      e.Graphics.DrawLine(Pens.Black,50,30,350,30);

      e.Graphics.DrawLine(Pens.Black,50,30,50,330);

 

      for(int x=0;x<=10;x++)

      {

        e.Graphics.DrawLine(Pens.Black,45,30+(30*x),50,30+(30*x));

        e.Graphics.DrawLine(Pens.Black,50+(30*x),25,50+(30*x),30);

      }

 

The next portion of the code shows how a matrix may be used to transform the existing transform. Here, the origin of the drawing area is moved to the origin of the X and Y axes so that the data plots may be drawn without having to add fudge factors to the data values. Note how the GraphicsState object is used to maintain the settings of the original matrix so that the origin can be reset later.

 

      Pen[] pens=new Pen[]{Pens.Red,Pens.Blue,Pens.Green,Pens.Magenta};

 

      GraphicsState gs = e.Graphics.Save();

     

      //move the axis for the benefit of our drawn axes.

      Matrix mx1=mx.Clone();

      mx1.Translate(50,30);

      e.Graphics.Transform=mx1;

 

      for(int p=0;p<4;p++)

      {

        Random r = new Random((int)DateTime.Now.Millisecond);

        Point last = new Point(0,r.Next(300));

        for(int x=1;x<=10;x++)

        {

          Point curr=new Point(x*30,r.Next(300));

          e.Graphics.DrawLine(pens[p],last,curr);

          last=curr;

        }

      }

 

     

      e.Graphics.Restore(gs);

 

Finally, the text must be drawn to annotate the scale. As I mentioned in the introduction, the text with the current transform would be shown mirrored so we need to add a separate transform for each bit of text to place it correctly and turn it the right way up again.

 

      //Now draw the text...

 

      for(int x=0;x<=10;x++)

      {

        //keep the old graphics setting

        gs=e.Graphics.Save();

        //create a matrix to offset the text to the desired position and flip it the

        //right way up again

        Matrix mx2=new Matrix(1,0,0,-1, // matrix is mirrored

                              // and displaced to the desired position

                              25,30+(x*30)+(Font.Height/2));

        //get a copy of the current matrix

        mx1=mx.Clone();

        //transform it by that of the text matrix

        mx1.Multiply(mx2);

        //change the graphics transform

        e.Graphics.Transform=mx1;

        //emit the text to 0,0. The transform has positioned for us

        e.Graphics.DrawString(x.ToString(), Font, Brushes.Black, 0, 0, StringFormat.GenericTypographic );

        //restore the saved graphics state

        e.Graphics.Restore(gs);

      }

 

The final result of this drawing routine is shown in Figure 1.

 

Figure 1: Screenshot of the Reverse Axis Text application

 

The full listing for the application is included below.

 

 

using System;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Collections;

using System.ComponentModel;

using System.Windows.Forms;

using System.Data;

 

namespace ReverseAxisText

{

  /// <summary>

  /// Summary description for Form1.

  /// </summary>

  public class Form1 : System.Windows.Forms.Form

  {

    /// <summary>

    /// Required designer variable.

    /// </summary>

    private System.ComponentModel.Container components = null;

 

    public Form1()

    {

      //

      // Required for Windows Form Designer support

      //

      InitializeComponent();

 

      //

      // TODO: Add any constructor code after InitializeComponent call

      //

 

      this.SetStyle(ControlStyles.ResizeRedraw,true);

    }

 

    /// <summary>

    /// Clean up any resources being used.

    /// </summary>

    protected override void Dispose( bool disposing )

    {

      if( disposing )

      {

        if (components != null)

        {

          components.Dispose();

        }

      }

      base.Dispose( disposing );

    }

 

    #region Windows Form Designer generated code

    /// <summary>

    /// Required method for Designer support - do not modify

    /// the contents of this method with the code editor.

    /// </summary>

    private void InitializeComponent()

    {

      //

      // Form1

      //

      this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);

      this.ClientSize = new System.Drawing.Size(292, 273);

      this.Name = "Form1";

      this.Text = "Form1";

      this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);

 

    }

    #endregion

 

    /// <summary>

    /// The main entry point for the application.

    /// </summary>

    [STAThread]

    static void Main()

    {

      Application.Run(new Form1());

    }

 

    private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)

    {

      //Reverse the axis of the drawing surface

      Matrix mx = new Matrix(1,0,0,-1,0,this.ClientSize.Height);

      e.Graphics.Transform=mx;

 

      //Draw some stuff

      //axes..

      e.Graphics.DrawLine(Pens.Black,50,30,350,30);

      e.Graphics.DrawLine(Pens.Black,50,30,50,330);

 

      for(int x=0;x<=10;x++)

      {

        e.Graphics.DrawLine(Pens.Black,45,30+(30*x),50,30+(30*x));

        e.Graphics.DrawLine(Pens.Black,50+(30*x),25,50+(30*x),30);

      }

 

      Pen[] pens=new Pen[]{Pens.Red,Pens.Blue,Pens.Green,Pens.Magenta};

 

      GraphicsState gs = e.Graphics.Save();

     

      //move the axis for the benefit of our drawn axes.

      Matrix mx1=mx.Clone();

      mx1.Translate(50,30);

      e.Graphics.Transform=mx1;

 

      for(int p=0;p<4;p++)

      {

        Random r = new Random((int)DateTime.Now.Millisecond);

        Point last = new Point(0,r.Next(300));

        for(int x=1;x<=10;x++)

        {

          Point curr=new Point(x*30,r.Next(300));

          e.Graphics.DrawLine(pens[p],last,curr);

          last=curr;

        }

      }

 

     

      e.Graphics.Restore(gs);

 

      //Now draw the text...

 

      for(int x=0;x<=10;x++)

      {

        //keep the old graphics setting

        gs=e.Graphics.Save();

        //create a matrix to offset the text to the desired position and flip it the

        //right way up again

        Matrix mx2=new Matrix(1,0,0,-1, // matrix is mirrored

                              // and displaced to the desired position

                              25,30+(x*30)+(Font.Height/2));

        //get a copy of the current matrix

        mx1=mx.Clone();

        //transform it by that of the text matrix

        mx1.Multiply(mx2);

        //change the graphics transform

        e.Graphics.Transform=mx1;

        //emit the text to 0,0. The transform has positioned for us

        e.Graphics.DrawString(x.ToString(), Font, Brushes.Black, 0, 0, StringFormat.GenericTypographic );

        //restore the saved graphics state

        e.Graphics.Restore(gs);

      }

    }

  }

}

 

Back to the GDI+ FAQ.

 

Copyright 2003 Robert W. Powell.  All rights reserved.