Reader Level:
ARTICLE

MathGraph in C#

Posted by Joshua M Articles | GDI+ & Graphics January 26, 2004
This article and the sample code shows you to draw and print a math graph using GDI+ and C#.
  • 0
  • 0
  • 49095

There I was... in the cruel, dark, world of Algebra 2, when suddenly the most feared question of all was asked. "Draw a graph". I froze in my tracks, (I wasn't moving, but this needs to sound dramatic...) my face turned pale as dozens of questions raced through my mind... "How?", "Why?", "When?".

I was in a serious jam, my homework was to draw graphs by using the formulas given, but how? I'm completely out of graph paper!!! And everyone knows that JOSH + PENCIL = SLOPPY. Whatever shall I do? Well, after greatly pondering this serious predicament, I discovered I have two options:

  1. Open PAINT and draw little lines to print out.
  2. Whoop together a nice, quick and easy C# Console Application to print out a clean graph to do my work on.

Well, since I'm always doing things the easy way, I chose option 2. (I consider it easier than option 1, because it's FUN!). So I sat, scratched my head once or twice, stroked my imaginary beard a couple times, and then began. Coffee at hand, it didn't take too long. (At the cost of one slight headache) It only took 155 lines of code... so let's begin!

First and foremost, we must decided WHAT exactly our app will do! First, it will EXECUTE! Secondly, it will perform the first line of code! Then, it will... ok enough of this.

Print a graph

Easy enough! But now we need to consider very carefully how we should go about doing this. Print in the same entry function? Create a new class? Derive a new class from an existing class?

Well the first rule I always go by, is to make extra sure you have a cup of coffee nearby. Secondly, we need to make this program as OO as possible. We need to take under consideration that someday we might need to use it again! (And I will! This graph business has only begun!) Therefore, this will require us to create a whole new class.

public class MathGraphPrint : PrintDocument
{
}

We're going to derive it from the class PrintDocument in the System.Drawing.Printing namespace. Now we get to the dirty work. First you must consider this, - are you ever going to come back and use it again? If so, you need to make it... - how should I say this? - easy to change and edit!

Instead of doing this:
.DrawLine(Pens.Black, 0, 0, 100, 100);
(first line of graph)

Make it changeable! Supposing next week you need a graph, only slightly longer! You would then have to come back, edit those x-y values, and then recompile! So first of all, we need some public properties in our class to allow us to specify things like: Height, Width, Color, and so forth of our graph.

So here's the beginning of our new class:

public class MathGraphPrint : PrintDocument
{
private
Size g_size;
private int
width;
private string
text;
private bool
shownums;
private
Color color;
private
Color midcolor;
public
Size Size
{
set

{
g_size = value
;
}
get

{
return
g_size;
}
}
public int
Width
{
set

{
width = value
;
} get

{
return
width;
}
}
public bool
ShowNums
{
set

{
shownums = value
;
} get

{
return
shownums;
}
}
public
Color Color
{
set

{
color = value
;
} get

{
return
color;
}
}
public
Color MidColor
{
set

{
midcolor = value
;
} get

{
return
midcolor;
}
}
}

I deem all those properties absolutely necessary. The "Size" property will specify the size (x-y) of our graph, while the "Width" property will specify the width (in pixels) of each box in the graph. The boolean "ShowNums" property, will let us decide whether we want each bar in the graph numbered. The "Color" property specifies the color of each bar except the middle bar of the x bars and the y bars, that is where the property "MidColor" comes in.

That being said, it is now time to construct the constructor! Just in case we use this class sometime later, and forget to specify each of those vital variables, we need to create some default values.

public MathGraphPrint()
{
g_size = new
Size(378, 378);
width = 13;
text = "";
shownums = true
;
color = Color.Black;
midcolor = Color.Red;
this.PrintPage += new PrintPageEventHandler(this
.printme);
}

Simple enough! Something that may look different is the "text" variable. Just added that. All it is, is the header for the graph. (Also added a property "Text")
You will also notice we added an eventhandler to our class! This is where the code begins. We now need to create a new finction called "printme", this will be where all the drawing takes place.

So we then create the function "printme"...

private void printme(object sender, PrintPageEventArgs ppea)
{
///
This is the graphics object that actually gets printed!
Graphics gfx = ppea.Graphics;
///
The starting position for drawing those bar on the graph.
int
_startx = 5;
///
(Same as above.)
int
_starty = 28;
///
^ THIS is the font we use to number each bar!
Font littletext = new
Font("Times New Roman", 8);
///
v THIS draws the header for our graph. v
gfx.DrawString(text, new Font("Times New Roman", 15), Brushes.Black, 0, 0);

Simple enough... now we move on to start drawing the horizontal bars of the graph. We will need a FOR... loop for this...

for(int x = 0; x < (g_size.Width / width); x++)
{
if(x==((g_size.Width / width) / 2))
{
gfx.DrawLine(new
Pen(midcolor),
(x * width) + _startx, _starty,
(x * width + _startx),
g_size.Height + _starty);
}
else

{
gfx.DrawLine(new
Pen(color),
(x * width) + _startx, _starty,
(x * width + _startx),
g_size.Height + _starty);
}
}

That's rather ugly if I may say so myself... but for any experienced programmer, that's just every day stuff!!! (I'm NOT one of those "experienced" programmers). First, it sees how long you want your graph! Then it checks to see the width of each box. With those two values, it determines how many bars it will be capable of drawing. (By dividing the width of the graph by the width of each individual box) Now that it knows how many bars it will be drawing, it then decides how many times it will need to loop.

Next you notice an interesting IF...ELSE statement... no fear, this is only to check whether it is drawing the MIDDLE bar or not, because if it is, we want it to be a different color! Remember?

The two DRAWLINE functions are identical, except when it creates a new pen.
It multiplies the current integer it's on, with the desired width of each box. THEN it addes the "_startx" integer to that. (Just in case we don't want the graph to be drawn RIGHT on the edge of the paper) That's basically it!

Now all we need to do is to number each of these lines. (Which I found to be a tad bit more time consuming (and paper consuming) then I thought.)

Here it is:

gfx.DrawString((x - ((g_size.Width / width)) / 2).ToString(),
littletext,
Brushes.Black,
(x * width + _startx) - 4,
g_size.Height + _starty + 4);

Peachy! Just peachy! That's all we need... or that's all we WOULD need if the length of the string were the same! But it won't be! It will vary from 1 to 3. AT one point it will be "5", (one character) and at another time it will be "-10". (THREE characters)
So we're going to need to do some validating. The position of the number is going to vary on how many characters are in the string. (Because the length will be different!)
Here's the solution:

gfx.DrawString((x - ((g_size.Width / width)) / 2).ToString(),
littletext,
Brushes.Black,
(x * width + _startx) - ((x - ((g_size.Width / width) / 2)).ToString().Length * 4),
g_size.Height + _starty + 4);

We basically do the same thing for drawing the horizontal lines.
We dispose the graphics object we're printing, and then we're done! A nice, clean, (with STRAIGHT lines too!) graph to draw on. Making my math teacher a happy-camper.

Now all I have to do is create a class that will perform all the algebraic formula's for me, and I'll have it made!!! (Ok, maybe not.)

There are a few things that I would do different if I had the time, but for now, it's good enough. For example, instead of deriving our newly created class from the PRINTDOCUMENT class, derive it from the GRAPHICS object, so it can draw to the graphis object and then send it to the PRINTDOCUMENT object. (That way, if you later wanted to display it on the screen, it would be a piece of cake.

Maybe someday I'll do that, and then turn this into a WinForms app, that will let the user draw it out and print it. (Instead of having to specify the width and height in pixels)
But until then, I've got some homework I need to finish... Happy coding!!!

Command used to compile:

/t:exe /out:C:\Josh\cs\graph\Graph.exe C:\Josh\cs\graph\Graph.cs /r:System.dll,System.Drawing.dll

Final results

/// Notice this AIN'T COPYRIGHTED!!!! (Hope me English teacher don't see this)
///
Have fun! And HAPPY CODING!!!!
///
Happy Easter and a MERRY ST. PATRICK'S DAY!!!!!
using
System;
using
System.Drawing;
using
System.Drawing.Printing;
namespace
Josh
{
public class
Graph
{
[STAThread]
static void Main(string
[] args)
{
MathGraphPrint pp = new
MathGraphPrint();
pp.DocumentName = "Math Graph";
Console.WriteLine("Will now print graph.");
Console.WriteLine("Specify width in pixels of graph: ");
int
x = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Specify height in pixels of graph: ");
int
y = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Name of graph: ");
string
text = Console.ReadLine();
Console.WriteLine("Width of squares: ");
int
width = Convert.ToInt32(Console.ReadLine());
Console.WriteLine("Hold on to your hoola-hoops! CAUSE WE'RE PRINTING!!!");
pp.Text = text;
pp.Size = new
Size(x, y);
pp.Width = width;
pp.Print();
Console.ReadLine();
}
}
public class
MathGraphPrint : PrintDocument
{
private
Size g_size;
private int
width;
private string
text;
private bool
shownums;
private
Color color;
private
Color midcolor;
private int
_mod;
public string
Text
{
set

{
text = value
;
} get

{
return
text;
}
}
public
Size Size
{
set

{
g_size = value
;
} get

{
return
g_size;
}
}
public int
Width
{
set

{
width = value
;
} get

{
return
width;
}
}
public bool
ShowNums
{
set

{
shownums = value
;
} get

{
return
shownums;
}
}
public
Color Color
{
set

{
color = value
;
} get

{
return
color;
}
}
public
Color MidColor
{
set

{
midcolor = value
;
} get

{
return
midcolor;
}
}
public int
Mod
{
set

{
_mod = value
;
} get

{
return
_mod;
}
}
public
MathGraphPrint()
{
g_size = new
Size(378, 378);
width = 13;
text = "";
shownums = true
;
color = Color.Black;
midcolor = Color.Red;
_mod = 1;
this.PrintPage += new PrintPageEventHandler(this
.printme);
}
private void printme(object
sender, PrintPageEventArgs ppea)
{
Graphics gfx = ppea.Graphics;
int
_starty = 28;
int
_startx = 5;
Font littletext = new
Font("Times New Roman", 8);
///
Draw text
gfx.DrawString(text, new
Font("Times New Roman", 15), Brushes.Black, 0, 0);
///
Draw horizontal lines.
for(int
x = 0; x < (g_size.Width / width); x++)
{
if
(x==((g_size.Width / width) / 2))
{
gfx.DrawLine(new
Pen(midcolor), (x * width) + _startx, _starty, (x * width + _startx), g_size.Height + _starty);
}
else

{
gfx.DrawLine(new
Pen(color), (x * width) + _startx, _starty, (x * width + _startx), g_size.Height + _starty);
}
if(shownums==true
)
{
gfx.DrawString((x - ((g_size.Width / width)) / 2).ToString(), littletext,
rushes.Black, (x * width + _startx) - ((x - ((g_size.Width / width) / 2)).ToString().Length * 4), g_size.Height + _starty + 4);
}
}
///
Draw vertical lines.
for(int
y = 0; y < (g_size.Height / width); y++)
{
if
(y==((g_size.Height / width) / 2))
{
gfx.DrawLine(new
Pen(midcolor), _startx, (y * width + _starty), g_size.Width + _startx, (y * width + _starty));
}
else

{
gfx.DrawLine(new
Pen(color), _startx, (y * width + _starty), g_size.Width + _startx, (y * width + _starty));
}
if(shownums==true
)
{
gfx.DrawString((((g_size.Height / width) / 2) - y).ToString(), littletext,
rushes.Black, g_size.Width + 4 + _startx, (y * width + _starty) - 6);
}
}
gfx.Dispose();
}
}
}

COMMENT USING

Trending up