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:
- Open PAINT and draw little lines to print out.
- 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();
}
}
}