
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);
}
}
} } Copyright
2003 Robert W. Powell. All
rights reserved. |