Monday, September 1, 2008

My First C# Application

My First C# Application

I'm not completely done, but I would like to verify that the publish feature of Microsoft Visual Studio C# actually works. You can run the setup program from here. This should install the first significant C# application that I have written, which loads data from CSV files and plots the data. You can download some files of the correct format here, or or here. This is quite similar to the Java applets that you can select here--except that it looks for a local file.

I still haven't figured out how to get it to fully redraw the data without putting the mouse over the File menu in the upper left.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

// Set up a color palette to use for drawing lines.
private List colorChoices = new List()
{Color.Yellow, Color.Aqua, Color.Red, Color.Blue, Color.Beige,
Color.Violet, Color.Coral, Color.CornflowerBlue,
Color.Cornsilk, Color.Crimson, Color.Cyan, Color.DarkBlue};
// This is where the numeric data will go.
private List numbersArray = new List();
// This is the largest count of numbers in any element of numbersArray
int numbersArrayMaxCount = 0;
// This is the largest value that we have to plot across all rows.
float numbersArrayMax = 0.0F;
// The legend information (contained in the first column) goes here.
private List legendStrings = new List();
// And this is where the column header information goes (the years going
// across, for the first sample.
private ColHdr colHdr = new ColHdr();

// Open the data file and populate legend and numbersArray.
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Title = "Data Input File (CSV)";
openFileDialog1.DefaultExt = "*.csv";
openFileDialog1.Filter = "CSV files|*.csv";
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
// This is where we specify the CSV file to open.
Stream myStream = new FileStream(openFileDialog1.FileName, FileMode.Open);
if (myStream != null)
{
string[] strArray;
char[] charArray = new char[] { ',' };

// Dispose of the old data set, if any
if (numbersArray.Count > 0)
{
numbersArray.Clear();
numbersArrayMax = 0.0F;
numbersArrayMaxCount = 0;
legendStrings.Clear();
}

StreamReader sr = new StreamReader(myStream);
string strLine;
// Pull in the first line--the column header line.
strLine = sr.ReadLine();
if (strLine != null)
{
strArray = strLine.Split(charArray);
colHdr.colHdrStrings = new List();
for (int i = 2; i < strArray.Length; i++)
colHdr.colHdrStrings.Add(strArray[i]);
}
// Now pull in the data that we are going to plot.
do
{
strLine = sr.ReadLine();
if (strLine != null)
{
System.Console.WriteLine("reading " + strLine);
strArray = strLine.Split(charArray);
NumbersToPlot numbers = new NumbersToPlot();
// Add the legend (the first column) to this list.
legendStrings.Add(strArray[0]);
numbers.category = strArray[1];
numbers.events = new List();
// We need to keep track of the smallest and largest values we see.
numbers.min = numbers.max = 0;
int lastNumber;
for (int i = 2; i < strArray.Length; i++)
{
numbers.events.Add(float.Parse(strArray[i]));
lastNumber = numbers.events.Count - 1;
numbers.min = Math.Min(numbers.min, numbers.events[lastNumber]);
numbers.max = Math.Max(numbers.max, numbers.events[lastNumber]);
}
numbersArray.Add(numbers);
numbersArrayMax = Math.Max(numbersArrayMax, numbers.max);
numbersArrayMaxCount = Math.Max(numbersArrayMaxCount, numbers.events.Count);
}
}
while (strLine != null);
myStream.Close();
}
}
}

// Configure which lines in the data set to display.
private void configureToolStripMenuItem_Click(object sender, EventArgs e)
{
// This is where we specify which lines to plot.
}

public SizeF CalcLeftMargin(Graphics g, Font legendFont, List legendStrings)
{
// Figure out the width of the legends on the left. These are the
// first column of information in the CSV file.
SizeF maxLegend = new SizeF();
int i;
SizeF legendStrSize;
for (maxLegend.Width = maxLegend.Height = 0, i = 0; i < numbersArray.Count; i++)
{
legendStrSize = g.MeasureString(legendStrings[i], legendFont);
maxLegend.Width = Math.Max(maxLegend.Width, legendStrSize.Width);
maxLegend.Height = Math.Max(maxLegend.Height, legendStrSize.Height);
}
maxLegend.Width *= 1.10F;
return (maxLegend);
}

public float CalcTopMargin(Graphics g, Font colHdrFont, ColHdr colHdrStrings)
{
// Now figure out how much room we need at the bottom for the column headers.
// Complicating this is that we need to adjust font size (perhaps) for too many
// columns.
float maxColHdrWidth = 0.0F;
float maxColHdrHeight = 0.0F;
SizeF colHdrStrSize;
for (int i = 0; i < colHdr.colHdrStrings.Count; i++)
{
colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[i], colHdrFont);
maxColHdrWidth = Math.Max(maxColHdrWidth, colHdrStrSize.Width);
maxColHdrHeight = Math.Max(maxColHdrHeight, colHdrStrSize.Height);
}
return (maxColHdrHeight);
}

public float CalcRightMargin(Graphics g, Font colHdrFont, ColHdr colHdr)
{
// We have to leave a little room on the right side of the last column because
// we are centering the column headers under the center point for the lines.
// So we need to know how much room that will be for the last column header.
SizeF colHdrStrSize;
colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[colHdr.colHdrStrings.Count-1], colHdrFont);
float rightMargin = (colHdrStrSize.Width / 2.0F) * 1.25F;
return (rightMargin);
}

public void PlotLegends(Graphics g, Font legendsFont, List legendStrings,
float maxLegendsHeight)
{
// Now we know how big it is, we can draw the legend strings down the left side.
Brush myBrush;
for (int i = 0; i < numbersArray.Count; i++)
{
myBrush = new SolidBrush(colorChoices[i % 10]);
g.DrawString(legendStrings[i], legendsFont, myBrush, 0, maxLegendsHeight * i);
}
}

public void PlotColHdrs(Graphics g, Font colHdrFont, ColHdr colHdr,
float rightMargin, float leftMargin, float xScaling,
float plotWidth)
{
SizeF colHdrStrSize;

Brush myBrush = new SolidBrush(Color.White);
float lastColHdrEndsAt = 0.0F; // not correct, but the first header must appear
float colHdrStartsAt = 0.0F;
for (int i = 0; i < colHdr.colHdrStrings.Count; i++)
{
// Get the size of the string so that we can move it over to be centered.
colHdrStrSize = g.MeasureString(colHdr.colHdrStrings[i], colHdrFont);
colHdrStartsAt = (i * xScaling) + leftMargin - colHdrStrSize.Width;
// We don't want to step on the last column header.
if (colHdrStartsAt > lastColHdrEndsAt + 5.0F)
{
g.DrawString(colHdr.colHdrStrings[i], colHdrFont, myBrush,
(i * xScaling) + leftMargin - (colHdrStrSize.Width / 2), 0);
lastColHdrEndsAt = (i * xScaling) + leftMargin + (colHdrStrSize.Width / 2);
}
}
}

public void PlotLines(Graphics g, float leftMargin, float topMargin,
float bottomMargin, float rightMargin, float xScaling, ColHdr colHdr,
float numbersArrayMax, int divisions, float yScaling, Font font)
{
Pen pen = new Pen(Color.White, 2);
// Draw the left, right, top and bottom lines.
g.DrawLine(pen, leftMargin, topMargin, leftMargin, bottomMargin);
g.DrawLine(pen, rightMargin, topMargin, rightMargin, bottomMargin);
g.DrawLine(pen, leftMargin, topMargin, rightMargin, topMargin);
g.DrawLine(pen, leftMargin, bottomMargin, rightMargin, bottomMargin);
// Draw the vertical grid lines.
int xCoord = 0;
for (int i = 0; i < colHdr.colHdrStrings.Count; i++)
{
xCoord = (int)((float)i * xScaling + leftMargin);
g.DrawLine(pen, xCoord, topMargin, xCoord, bottomMargin);
}
// The brush for drawing the numbers left of the grid lines.
Brush myBrush = new SolidBrush(Color.White);
// Draw the horizontal grid lines.
float perDivision = yScaling * numbersArrayMax / divisions;
float perDivisionNumber = 0.0F;
for (int i = 0; i < divisions + 1; i++)
{
g.DrawLine(pen, leftMargin, bottomMargin - (i * perDivision),
rightMargin, bottomMargin - (i * perDivision));
perDivisionNumber = i * numbersArrayMax / divisions;
// Figure out how much we have to move the string to the left of the vertical grid
// line, and how much we have to raise it up to center on the horizontal grid line.
SizeF strSize = g.MeasureString(perDivisionNumber.ToString(), font);
g.DrawString(perDivisionNumber.ToString(), font, myBrush,
leftMargin - strSize.Width,
(bottomMargin - (i * perDivision)) - strSize.Height/2);
}
}

public void PlotData(Graphics g, NumbersToPlot numbers,
Pen pen, float leftMargin,
float xScaling, float yScaling,
float panelHeight)
{
for (int j = 1; j < numbers.events.Count; j++)
{
float value = (float)numbers.events[j];
float prevValue = (float)numbers.events[j - 1];
Point prevValueCoord = new Point((int)((j - 1) * xScaling + leftMargin),
(int)(panelHeight - (prevValue * yScaling)));
Point curValueCoord = new Point((int)(j * xScaling + leftMargin),
(int)(panelHeight - (value * yScaling)));
g.DrawLine(pen, prevValueCoord, curValueCoord);
}
}

protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
panel1.Invalidate();
}

public static double RoundUp(double valueToRound)
{
return (Math.Floor(valueToRound + 0.5));
}
private void panel1_Paint_1(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
if (numbersArray.Count > 0)
{
// Clear the panel.
g.Clear(Color.Black);
// Create legend font.
Font legendFont = new System.Drawing.Font("Times Roman", 10.0F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
// Calculate left margin from legend strings.
SizeF maxLegendDimensions = CalcLeftMargin(g, legendFont, legendStrings);
// Create column header font.
Font colHdrFont = new System.Drawing.Font("Times Roman", 10.0F,
System.Drawing.FontStyle.Regular,
System.Drawing.GraphicsUnit.Point, ((byte)(0)));
// Calculate top margin from column headers.
float topMargin = CalcTopMargin(g, colHdrFont, colHdr) + 20.0F;
// Calculate right margin from column headers.
float rightMargin = CalcRightMargin(g, colHdrFont, colHdr);
PlotLegends(g, legendFont, legendStrings, maxLegendDimensions.Height);
g.PageUnit = GraphicsUnit.Pixel;
// Calculate the scaling required.
float yScaling;
SizeF sizef = g.VisibleClipBounds.Size;
rightMargin = sizef.Width - rightMargin;
float leftMargin = maxLegendDimensions.Width;
// Leave some room at the bottom of the display.
float bottomMargin = sizef.Height - 30.0F;
// Before we do the y-scaling, we want to round the maximum y-axis value up to
// a nice even number.
numbersArrayMax = (float)RoundUp(numbersArrayMax);
yScaling = (float)((bottomMargin - topMargin) / numbersArrayMax);
float xScaling = (float)(rightMargin - leftMargin) / (numbersArrayMaxCount - 1);
// Write the column header strings.
PlotColHdrs(g, colHdrFont, colHdr, rightMargin, leftMargin, xScaling,
rightMargin - leftMargin);
// Draw the grid lines.
PlotLines(g, leftMargin, topMargin, bottomMargin, rightMargin, xScaling,
colHdr, numbersArrayMax, 4, yScaling, colHdrFont);
for (int i = 0; i < numbersArray.Count; i++)
{
Pen pen = new Pen(colorChoices[i % 10], 2);
PlotData(g, numbersArray[i], pen, maxLegendDimensions.Width,
xScaling, yScaling, bottomMargin);
}
}
else
g.Clear(Color.Black);
}

private void fileToolStripMenuItem_VisibleChanged(object sender, EventArgs e)
{
panel1.Invalidate();
}

private void fileToolStripMenuItem_Paint(object sender, PaintEventArgs e)
{
panel1.Invalidate();
}

private void fileToolStripMenuItem_DropDownClosed(object sender, EventArgs e)
{
panel1.Invalidate();
}

private void fileToolStripMenuItem_MouseLeave(object sender, EventArgs e)
{
panel1.Invalidate();
}

private void Form1_Load(object sender, EventArgs e)
{

}
}
}

public class ColHdr
{
public List colHdrStrings;
}


public class NumbersToPlot
{
public string category;
public List events;
public float min, max;
public int maxNumbers;

public NumbersToPlot()
{
}

public NumbersToPlot(string Category)
{
category = Category;
}
};

No comments:

Post a Comment