I mentioned it yesterday. It has grown a good bit. The Config menu choice now pops up a window where you get to select which rows of data from the CSV file you want to display--and then it autoscales the plot, based on the new data.
I'm learning a lot, and thanks to those who patiently answer questions.
It has been years since I enjoyed myself this much at work.
One area that I am still struggling with is that when I double click on an entry in the ListBox that contains the list of data sets to plot, I want it to select that entry, and be the equivalent of hitting the OK button. The problem is that there's something magic that I need to do from the ListBox event handler that tells the Form that it is time to return DialogReturn.OK--but I can't tell what.
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 ListcolorChoices = 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 ListnumbersArray = 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 ListlegendStrings = new List ();
// And this is where the column header information goes (the years going
// across, for the first sample.
private ColHdr colHdr = new ColHdr();
// This contains a list of all the rows in the CSV file to plot. We'll default this to
// every entry when we first load a CSV file. The user can use the Configure command to
// be more selective.
ListlistToShow = new List ();
private float RecalculateMaxValue()
{
float maxValue = 0.0F;
for (int i = 0; i < numbersArray.Count; i++)
{
NumbersToPlot numbers = numbersArray[i];
if (listToShow.Contains(i))
for (int j = 1; j < numbers.events.Count; j++)
maxValue = Math.Max(maxValue, numbers.max);
}
return(maxValue);
}
// 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.
int dataRowNbr = 0;
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]);
listToShow.Add(dataRowNbr++);
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)
{
SizeF sizef;
// This is where we specify which lines to plot.
Form dlg = new Form();
dlg.Size = new System.Drawing.Size(500, 400);
sizef = dlg.Size;
// Create a panel
FlowLayoutPanel panel = new FlowLayoutPanel();
panel.Size = new System.Drawing.Size(dlg.Size.Width, dlg.Size.Height);
dlg.Controls.Add(panel);
// Add a ListBox
ListBox box = new ListBox();
box.SelectionMode = SelectionMode.MultiExtended;
box.DoubleClick += new EventHandler(this.listBoxDoubleClick);
box.Size = new System.Drawing.Size(panel.Size.Width - 50, panel.Size.Height - 100);
for (int i = 0; i < legendStrings.Count; i++)
{
box.Items.Add(legendStrings[i]);
box.SetSelected(i, true);
}
panel.Controls.Add(box);
// Add a button to the panel.
Button bOk = new System.Windows.Forms.Button();
bOk.Text = "OK";
bOk.DialogResult = DialogResult.OK;
panel.Controls.Add(bOk);
Button bCancel = new System.Windows.Forms.Button();
bCancel.Text = "Cancel";
bCancel.DialogResult = DialogResult.Cancel;
this.AcceptButton = bOk;
panel.Controls.Add(bCancel);
if (dlg.ShowDialog(this) == DialogResult.OK)
{
System.Console.WriteLine("OK");
// Grab the selection list from the list box.
ListBox.SelectedIndexCollection selectedItems = box.SelectedIndices;
// Now go through and convert this into a list of ints.
listToShow.Clear();
for (int i = 0; i < selectedItems.Count; i++)
listToShow.Add(selectedItems[i]);
// Recalculate the maximum value that we have to plot.
numbersArrayMax = RecalculateMaxValue();
}
}
private void listBoxDoubleClick(Object s, EventArgs e)
{
}
public SizeF CalcLeftMargin(Graphics g, Font legendFont, ListlegendStrings)
{
// 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, ListlegendStrings,
float maxLegendsHeight, ListlistToShow)
{
// Now we know how big it is, we can draw the legend strings down the left side.
Brush myBrush;
for (int i = 0, legendLineIndex = 0; i < numbersArray.Count; i++)
{
if (listToShow.Contains(i))
{
myBrush = new SolidBrush(colorChoices[legendLineIndex % 10]);
g.DrawString(legendStrings[i], legendsFont, myBrush, 0,
maxLegendsHeight * legendLineIndex);
legendLineIndex++;
}
}
}
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, listToShow);
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, legendLineIndex = 0; i < numbersArray.Count; i++)
{
if (listToShow.Contains(i))
{
Pen pen = new Pen(colorChoices[legendLineIndex % 10], 2);
PlotData(g, numbersArray[i], pen, maxLegendDimensions.Width,
xScaling, yScaling, bottomMargin);
legendLineIndex++;
}
}
}
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)
{
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
Application.Exit();
}
}
}
public class ColHdr
{
public ListcolHdrStrings;
}
public class NumbersToPlot
{
public string category;
public Listevents;
public float min, max;
public int maxNumbers;
public NumbersToPlot()
{
}
public NumbersToPlot(string Category)
{
category = Category;
}
};
No comments:
Post a Comment