// Main method - compute dummy stub. public Data ComputeDummy(Data Data) { /// Get your needed input data here // Example - get rig state and blovk height DataValues rigStates = util.FindDataColumn(Data, "Rig_State"); DataValues blockHeights = util.FindDataColumn(Data, "Block_Height"); // Define you data output channels here // Create an output data column to store results back into Data DataValues Output = new DataValues(); Output.Name = "Your output data column name"; Output.Units = "Your output data units"; // Create data array storage for output Output.DataColumn = new List <DataValue>(); // Put you calculation logic here // Example do dummy calculation - remember to cast values to the proper type (string, double, int, ....) double dummy = 0; foreach (DataValue dv in blockHeights.DataColumn) { DateTime t = Convert.ToDateTime(dv.Timestamp); double val = Convert.ToDouble(dv.Value); dummy = dummy + val; /// Store result back into Data Output.DataColumn.Add(new DataValue(t, dummy.ToString())); } // or for (int i = 0; i < blockHeights.DataColumn.Count; i++) { DateTime t = Convert.ToDateTime(blockHeights.DataColumn[i].Timestamp); double val = Convert.ToDouble(blockHeights.DataColumn[i].Value); string rigState = RigStateDummy[Convert.ToInt32(rigStates.DataColumn[i].Value)]; if (rigState == "Drilling") { dummy = dummy + val; } else { dummy = dummy - val; } /// Put you output back into Data Output.DataColumn.Add(new DataValue(t, dummy.ToString())); } // Return the updated Data to the master routines Data.DataList.Add(Output); return(Data); }
// Main method - compute ROP from changes into block height in time, using a rig state (rotary drilling, slide drilling, or // oscillate slide drilling) to determine if the bit depth needs to be calculated. Currently this uses a temporary single state // (enumeration = 2, which should be drilling) public Data ComputeBitDepth(Data Data) { // Find the Rig State data column DataValues blockHeight = utility.FindDataColumn(Data, "Block_Height"); DataValues rigStates = utility.FindDataColumn(Data, "Rig_State"); // Create output DataValues bitDepth = new DataValues(); bitDepth.Name = "Bit_Depth"; bitDepth.Units = blockHeight.Units; bitDepth.Raw = false; double oldBitDepth = 0; double oldBlockHeight = 0; // Create data array storage for output bitDepth.DataColumn = new List <DataValue>(); /// do the algorithm for (int i = 0; i < blockHeight.DataColumn.Count; i++) { // Get calculation values DateTime t = Convert.ToDateTime(blockHeight.DataColumn[i].Timestamp); double val = Convert.ToDouble(blockHeight.DataColumn[i].Value); string rigState = RigStateBitDepth[Convert.ToInt32(rigStates.DataColumn[i].Value)]; double bd = 0; if (i == 0) { // Trap first time through algorithm - need at least two values to compute ROP bd = 0; oldBitDepth = 0; oldBlockHeight = val; } else if (val == -999.25) { // Trap schlumberger null value bd = val; } else { // Compute ROP if (rigState == "Drilling" || rigState == "Reaming" || rigState == "TripIn" || rigState == "TripOut") { bd = oldBitDepth + (oldBlockHeight - val); oldBitDepth = bd; } else { bd = oldBitDepth; } oldBlockHeight = val; } /// Put you output back into Data bitDepth.DataColumn.Add(new DataValue(t, bd.ToString())); } Data.DataList.Add(bitDepth); return(Data); }
// Main method - compute ROP from changes into block height in time, using a rig state (rotary drilling, slide drilling, or // oscillate slide drilling) to determine if the bit depth needs to be calculated. Currently this uses a temporary single state // (enumeration = 2, which should be drilling) public Data ComputeBitDepth(Data Data) { // Find the Rig State data column DataValues blockHeight = utility.FindDataColumn( Data, "Block_Height"); DataValues rigStates = utility.FindDataColumn(Data, "Rig_State"); // Create output DataValues bitDepth = new DataValues(); bitDepth.Name = "Bit_Depth"; bitDepth.Units = blockHeight.Units; bitDepth.Raw = false; double oldBitDepth = 0; double oldBlockHeight = 0; // Create data array storage for output bitDepth.DataColumn = new List<DataValue>(); /// do the algorithm for (int i = 0; i < blockHeight.DataColumn.Count; i++) { // Get calculation values DateTime t = Convert.ToDateTime(blockHeight.DataColumn[i].Timestamp); double val = Convert.ToDouble(blockHeight.DataColumn[i].Value); string rigState = RigStateBitDepth[Convert.ToInt32(rigStates.DataColumn[i].Value)]; double bd = 0; if (i == 0) { // Trap first time through algorithm - need at least two values to compute ROP bd = 0; oldBitDepth = 0; oldBlockHeight = val; } else if (val == -999.25) { // Trap schlumberger null value bd = val; } else { // Compute ROP if (rigState == "Drilling" || rigState == "Reaming" || rigState == "TripIn" || rigState == "TripOut") { bd = oldBitDepth + (oldBlockHeight - val); oldBitDepth = bd; } else { bd = oldBitDepth; } oldBlockHeight = val; } /// Put you output back into Data bitDepth.DataColumn.Add(new DataValue(t, bd.ToString())); } Data.DataList.Add(bitDepth); return (Data); }
// Main method - compute dummy stub. public Data ComputeDummy(Data Data) { /// Get your needed input data here // Example - get rig state and blovk height DataValues rigStates = util.FindDataColumn(Data, "Rig_State"); DataValues blockHeights = util.FindDataColumn(Data, "Block_Height"); // Define you data output channels here // Create an output data column to store results back into Data DataValues Output = new DataValues(); Output.Name = "Your output data column name"; Output.Units = "Your output data units"; // Create data array storage for output Output.DataColumn = new List<DataValue>(); // Put you calculation logic here // Example do dummy calculation - remember to cast values to the proper type (string, double, int, ....) double dummy = 0; foreach (DataValue dv in blockHeights.DataColumn) { DateTime t = Convert.ToDateTime(dv.Timestamp); double val = Convert.ToDouble(dv.Value); dummy = dummy + val; /// Store result back into Data Output.DataColumn.Add(new DataValue(t, dummy.ToString())); } // or for (int i = 0; i < blockHeights.DataColumn.Count; i++) { DateTime t = Convert.ToDateTime(blockHeights.DataColumn[i].Timestamp); double val = Convert.ToDouble(blockHeights.DataColumn[i].Value); string rigState = RigStateDummy[Convert.ToInt32(rigStates.DataColumn[i].Value)]; if (rigState == "Drilling") { dummy = dummy + val; } else { dummy = dummy - val; } /// Put you output back into Data Output.DataColumn.Add(new DataValue(t, dummy.ToString())); } // Return the updated Data to the master routines Data.DataList.Add(Output); return (Data); }
public void SetPlot(DataValues dv) { /// Ah, the fun begins - lets play with the Microsoft chart options -- Added in case of thread collisons if (plotChart.InvokeRequired) { SetPlotCallback d = new SetPlotCallback(SetPlot); this.Invoke(d, new object[] { dv }); } else { MakePlot(dv); } }
// These methods interact with Data to return information about data traces or columns // These are effectively the memory management for Data // This method returns an data column (header and values) based on the input column name public DataValues FindDataColumn(Data Data, string name) { DataValues Column = null; // For each data column find a match of the of column name and requested column name // if there is a match, break out of the search and return the data column foreach (DataValues dv in Data.DataList) { if (dv.Name.ToLower() == name.ToLower()) { Column = dv; break; } } return(Column); }
private void PlotButtonClick(object sender, MouseEventArgs e) { // This is the callback for a pushed button. Using MouseButtonClick to indentify left click (add trace to main plot) and right click // (spawn stand-alone plot of trace). MouseEventArgs me = (MouseEventArgs)e; Button btn = (Button)sender; // Left click - add to main plot if (me.Button == MouseButtons.Left) { if (btn.Name == btn.Text) { btn.Text = btn.Text + "."; DataValues dv = util.FindDataColumn(Data, btn.Name); int Index = util.FindDataColumnIndex(Data, btn.Name); btn.BackColor = Color.FromName(PlotColor[Index % 10]); SetPlot(dv); } else { btn.Text = btn.Name; btn.BackColor = SystemColors.Control; foreach (Series s in plotChart.Series) { if (s.Name == btn.Name) { plotChart.Series.Remove(s); break; } } } } else { /// Right click - spawn plot SpawnPlot(btn); } }
public void MakePlot(DataValues dv) { /// Build main graph /// Create series (data) to be input as data to plot and add series to the plot Series series = new Series(dv.Name); plotChart.Series.Add(series); /// Set line properies - type, color, width /// Use color table mod 10 to repeat color table plotChart.Series[dv.Name].ChartType = SeriesChartType.FastLine; plotChart.Series[dv.Name].BorderWidth = 2; int index = util.FindDataColumnIndex(Data, dv.Name); plotChart.Series[dv.Name].Color = Color.FromName(PlotColor[index % 10]); /// Convert DateTime stamp to seconds /// /// Get timestamp of first data sample and use as Time zero - the value to be subtracted from each timestamp DateTime origin = dv.DataColumn[0].Timestamp; double od = Convert.ToDouble(origin.Ticks / 10000000); /// For each value - change the string value to a double and timestamp to seconds for (int i = 0; i < dv.DataColumn.Count; i++) { double val = Convert.ToDouble(Convert.ToDateTime(dv.DataColumn[i].Timestamp).Ticks / 10000000) - od; double rc = Convert.ToDouble(dv.DataColumn[i].Value); /// Add data to plot series - skip if -999.25 Schlumberger null if (rc != -999.25) { plotChart.Series[dv.Name].Points.AddXY(val, rc); } } }
// Main method - compute bit depth from changes into block height in time, using a rig state (rotary drilling, slide drilling, or // oscillate slide drilling) to determine if the ROP needs to be calculated. Currently this uses a temporary single state // (enumeration = 2, which should be drilling) public Data ComputeRop(Data Data) { // Find the Rig State data column DataValues rigStates = util.FindDataColumn(Data, "Rig_State"); // If we have rig state proceed if (rigStates != null) { // Find the block height column, if it exists proceed DataValues blockHeights = util.FindDataColumn(Data, "Block_Height"); if (blockHeights != null) { // Computation algorithm // If the rig state is "2" then compute ROP from the current and previous block heights and timestamps // We keep the previous block height and timestamp in startXXX variables and the current sample in endXXX // Create output space double outRop = 0; // load the first values of block height and its' timestamp into the startXXX varaible for initialization double startBlockHeight = Convert.ToDouble(blockHeights.DataColumn[0].Value); DateTime startTime = blockHeights.DataColumn[0].Timestamp; // // Create an output data column to store results back into Data DataValues DataValues = new DataValues(); DataValues.Name = "Computed_ROP Approach 1"; DataValues.Units = "ft/hr"; DataValues.DataColumn = new List <DataValue>(); DataValues.DataColumn.Add(new DataValue(startTime, outRop.ToString())); // Main computation Loop // For each data array index, extract the values of rig state, block height, and the timestamp // If the state is Drilling, then use current and previous value of block height and timestamp to compute ROP for (int i = 1; i < rigStates.DataColumn.Count; i++) { // Extract the values of rig state, block height, and the timestamp string rigState = string.Empty; try { rigState = RigStateROP[Convert.ToInt32(rigStates.DataColumn[i].Value)]; } catch { rigState = string.Empty; } double endBlockHeight = Convert.ToDouble(blockHeights.DataColumn[i].Value); DateTime endTime = blockHeights.DataColumn[i].Timestamp; // If we are "drilling, compute ROP, else ROP = 0 if (rigState == "Drilling") { /// Trap schlumberger null if (endBlockHeight == -999.25) { outRop = 0; } else { // Compute the difference in time in seconds, and convert the number to a double int diff = (endTime - startTime).Seconds; double div = Convert.ToDouble(Convert.ToDouble(diff)); // Error trap for two samples with the same timestamp, compute ROP if (div != 0) { outRop = 3600 * (endBlockHeight - startBlockHeight) / div; } } // Trap to catch bad rig state values //if (outRop < 0) //{ // outRop = 0; //} } else { // Wrong rig state for ROP calc outRop = 0; } //Final trap throw out ROP < 0 if (outRop < 0) { outRop = 0; } // Take the calculated ROP and append the value to the Data column values DataValues.DataColumn.Add(new DataValue(endTime, outRop.ToString())); startTime = endTime; startBlockHeight = endBlockHeight; } // Add the newly created ROP column to Data DataValues.Raw = false; if (util.FindIfDataColumnExists(Data, DataValues.Name)) { DataValues.Name = util.GenerateVersonedName(Data, DataValues.Name); } Data.DataList.Add(DataValues); } else { /// no block height found } } else { /// no rig state found } return(Data); }
private void SpawnPlot(Button btn) { // make same plot as the master plot, but in a new window. Must add all the settings that are hidden in the Visual GUI Form spawn = new Form(); Panel p = new Panel(); Chart chart = new Chart(); ChartArea ca = new ChartArea(); Legend legend = new Legend(); Title title = new Title(); /// Set Form values spawn.Text = "SPE DSA-TS DQA Surface Derived Data Calculations - Spawned Plot"; spawn.Height = 600; spawn.Width = 800; /// Set Panel values p.Dock = DockStyle.Fill; /// Set Chart Area values ca.Name = "chartArea"; ca.AxisX.Title = "Elapsed Time in Seconds"; ca.AxisX.Minimum = 0; // Legion values legend.Name = "Data Trace"; legend.LegendStyle = LegendStyle.Column; legend.TableStyle = LegendTableStyle.Tall; // Title values title.Text = "Rig Data Display in Seconds"; // Link chart elements into the main chart chart.ChartAreas.Add(ca); chart.Legends.Add(legend); chart.Titles.Add(title); /// Set Chart values chart.Dock = DockStyle.Fill; System.Windows.Forms.DataVisualization.Charting.Cursor cursorX = null; System.Windows.Forms.DataVisualization.Charting.Cursor cursorY = null; cursorX = chart.ChartAreas["chartArea"].CursorX; cursorX.Interval = 1; cursorY = chart.ChartAreas["chartArea"].CursorY; cursorX.LineWidth = 2; cursorY.LineWidth = 2; cursorX.LineDashStyle = ChartDashStyle.DashDot; cursorY.LineDashStyle = ChartDashStyle.DashDot; cursorX.LineColor = Color.Red; cursorY.LineColor = Color.Red; cursorX.SelectionColor = Color.Yellow; cursorY.SelectionColor = Color.Yellow; // Enable end user interactivity chart.ChartAreas["chartArea"].CursorX.IsUserEnabled = true; chart.ChartAreas["chartArea"].CursorX.IsUserSelectionEnabled = true; chart.ChartAreas["chartArea"].CursorY.IsUserEnabled = true; chart.ChartAreas["chartArea"].CursorY.IsUserSelectionEnabled = true; /// Get data to display DataValues dv = util.FindDataColumn(Data, btn.Name); int index = util.FindDataColumnIndex(Data, dv.Name); /// Create Series Series series = new Series(dv.Name); chart.Series.Add(series); /// Set line properies - type, color, width /// Use color table mod 10 to repeat color table chart.Series[dv.Name].ChartType = SeriesChartType.FastLine; chart.Series[dv.Name].BorderWidth = 2; chart.Series[dv.Name].Color = Color.FromName(PlotColor[index % 10]); /// Convert DateTime stamp to seconds /// /// Get timestamp of first data sample and use as Time zero - the value to be subtracted from each timestamp DateTime origin = dv.DataColumn[0].Timestamp; double od = Convert.ToDouble(origin.Ticks / 10000000); /// For each value - change the string value to a double and timestamp to seconds for (int i = 0; i < dv.DataColumn.Count; i++) { double val = Convert.ToDouble(Convert.ToDateTime(dv.DataColumn[i].Timestamp).Ticks / 10000000) - od; double rc = Convert.ToDouble(dv.DataColumn[i].Value); /// Add data to plot series - skip if -999.25 Schlumberger null if (rc != -999.25) { chart.Series[dv.Name].Points.AddXY(val, rc); } } p.Controls.Add(chart); spawn.Controls.Add(p); chart.Show(); spawn.Show(); }
// This region contains the code for reading, writing and manipulating the Data #region CSV file IO private void ReadInputCSV(string filename) { // Opens the input CSV data file and stores it in a temporary string, then parses the string into // data columns, by first reading the file header, then loads the data into Data the application // memory space. Data contains each trace, which is data vector, array, or column. Each // sample in a data column consists of a data value, timestamp pair. Column name and units are // stored in the data column header. // Read the filename from the DataInputFile textbox, open the file and read it into a string string rawData = File.ReadAllText(filename); // Remove carriage return issues and break the raw data string into lines rawData = rawData.Replace('\n', '\r'); string[] lines = rawData.Split(new char[] { '\r' }, StringSplitOptions.RemoveEmptyEntries); // Determine the number of rows and columns in the inoput data int rows = lines.Length; int columns = lines[0].Split(',').Length; // Transfer the string based values into Data, the master app database // Create Data root and root stub for the data columns (arrays) if (columns > 0) { Data = new Data(); List <DataValues> DataValues = new List <DataValues>(); Data.DataList = DataValues; } // Read Column Names and allocate space for each column of the data array // Assumes that there are two header lines - the first for names , the second for units string[] line = lines[0].Split(','); string[] unitLine = lines[1].Split(','); // Read the first two lines, then for each column read the data column name and units, // then create and link each data column in Data for (int i = 0; i < columns; i++) { DataValues dv = new DataValues(); dv.Name = line[i].ToString(); dv.Units = unitLine[i].ToString(); dv.DataColumn = new List <DataValue>(); Data.DataList.Add(dv); } // Now that the columns names are stored in Data, find the DateTime column // We need to find this column as each data value has a timestamp associated with it. // // A more effiecent way of doing this is to find a data samples index in the Data array, // then lookup the index in the timestamp array to find the associated timestamp for the sample. // However, we intend to have some form of graphic display, so having a value, timestamp pair // will be more efficient for plots and charts int tsIndex = util.FindDataColumnIndex(Data, "DateTime"); // Now read the remaining lines into the data columns // Start at the third line in the data file for (int i = 2; i < rows; i++) { line = lines[i].Split(','); // For each data row read the input columns and create a data value for (int j = 0; j < columns; j++) { DataValue dv = new DataValue(); dv.Timestamp = Convert.ToDateTime(line[tsIndex].ToString()); dv.Value = line[j].ToString(); Data.DataList[j].DataColumn.Add(dv); } } }
// Main method - compute bit depth from changes into block height in time, using a rig state (rotary drilling, slide drilling, or // oscillate slide drilling) to determine if the ROP needs to be calculated. Currently this uses a temporary single state // (enumeration = 2, which should be drilling) public Data ComputeRop(Data Data) { // Find the Rig State data column DataValues rigStates = util.FindDataColumn(Data, "Rig_State"); // If we have rig state proceed if (rigStates != null) { // Find the block height column, if it exists proceed DataValues blockHeights = util.FindDataColumn(Data, "Block_Height"); if (blockHeights != null) { // Computation algorithm // If the rig state is "2" then compute ROP from the current and previous block heights and timestamps // We keep the previous block height and timestamp in startXXX variables and the current sample in endXXX // Create output space double outRop = 0; // load the first values of block height and its' timestamp into the startXXX varaible for initialization double startBlockHeight = Convert.ToDouble(blockHeights.DataColumn[0].Value); DateTime startTime = blockHeights.DataColumn[0].Timestamp; // // Create an output data column to store results back into Data DataValues DataValues = new DataValues(); DataValues.Name = "Computed_ROP Approach 1"; DataValues.Units = "ft/hr"; DataValues.DataColumn = new List<DataValue>(); DataValues.DataColumn.Add(new DataValue(startTime, outRop.ToString())); // Main computation Loop // For each data array index, extract the values of rig state, block height, and the timestamp // If the state is Drilling, then use current and previous value of block height and timestamp to compute ROP for (int i = 1; i < rigStates.DataColumn.Count; i++) { // Extract the values of rig state, block height, and the timestamp string rigState = string.Empty; try { rigState = RigStateROP[Convert.ToInt32( rigStates.DataColumn[i].Value)]; } catch { rigState = string.Empty; } double endBlockHeight = Convert.ToDouble(blockHeights.DataColumn[i].Value); DateTime endTime = blockHeights.DataColumn[i].Timestamp; // If we are "drilling, compute ROP, else ROP = 0 if (rigState == "Drilling") { /// Trap schlumberger null if (endBlockHeight == -999.25) { outRop = 0; } else { // Compute the difference in time in seconds, and convert the number to a double int diff = (endTime - startTime).Seconds; double div = Convert.ToDouble(Convert.ToDouble(diff)); // Error trap for two samples with the same timestamp, compute ROP if (div != 0) { outRop = 3600 * (endBlockHeight - startBlockHeight) / div; } } // Trap to catch bad rig state values //if (outRop < 0) //{ // outRop = 0; //} } else { // Wrong rig state for ROP calc outRop = 0; } //Final trap throw out ROP < 0 if (outRop < 0) { outRop = 0; } // Take the calculated ROP and append the value to the Data column values DataValues.DataColumn.Add(new DataValue(endTime, outRop.ToString())); startTime = endTime; startBlockHeight = endBlockHeight; } // Add the newly created ROP column to Data DataValues.Raw = false; if (util.FindIfDataColumnExists(Data, DataValues.Name)) { DataValues.Name = util.GenerateVersonedName(Data, DataValues.Name); } Data.DataList.Add(DataValues); } else { /// no block height found } } else { /// no rig state found } return (Data); }
public void MakePlot(DataValues dv) { /// Build main graph /// Create series (data) to be input as data to plot and add series to the plot Series series = new Series(dv.Name); plotChart.Series.Add(series); /// Set line properies - type, color, width /// Use color table mod 10 to repeat color table plotChart.Series[dv.Name].ChartType = SeriesChartType.FastLine; plotChart.Series[dv.Name].BorderWidth = 2; int index = util.FindDataColumnIndex(Data, dv.Name); plotChart.Series[dv.Name].Color = Color.FromName(PlotColor[index%10]); /// Convert DateTime stamp to seconds /// /// Get timestamp of first data sample and use as Time zero - the value to be subtracted from each timestamp DateTime origin = dv.DataColumn[0].Timestamp; double od = Convert.ToDouble(origin.Ticks/10000000); /// For each value - change the string value to a double and timestamp to seconds for (int i = 0; i < dv.DataColumn.Count; i++) { double val = Convert.ToDouble(Convert.ToDateTime(dv.DataColumn[i].Timestamp).Ticks/10000000) - od; double rc = Convert.ToDouble(dv.DataColumn[i].Value); /// Add data to plot series - skip if -999.25 Schlumberger null if (rc != -999.25) { plotChart.Series[dv.Name].Points.AddXY(val, rc); } } }
public void SetPlot(DataValues dv ) { /// Ah, the fun begins - lets play with the Microsoft chart options -- Added in case of thread collisons if (plotChart.InvokeRequired) { SetPlotCallback d = new SetPlotCallback(SetPlot); this.Invoke(d, new object[] { dv }); } else { MakePlot(dv); } }
// This region contains the code for reading, writing and manipulating the Data #region CSV file IO private void ReadInputCSV(string filename) { // Opens the input CSV data file and stores it in a temporary string, then parses the string into // data columns, by first reading the file header, then loads the data into Data the application // memory space. Data contains each trace, which is data vector, array, or column. Each // sample in a data column consists of a data value, timestamp pair. Column name and units are // stored in the data column header. // Read the filename from the DataInputFile textbox, open the file and read it into a string string rawData = File.ReadAllText(filename); // Remove carriage return issues and break the raw data string into lines rawData = rawData.Replace('\n', '\r'); string[] lines = rawData.Split(new char[] { '\r' }, StringSplitOptions.RemoveEmptyEntries); // Determine the number of rows and columns in the inoput data int rows = lines.Length; int columns = lines[0].Split(',').Length; // Transfer the string based values into Data, the master app database // Create Data root and root stub for the data columns (arrays) if (columns > 0) { Data = new Data(); List<DataValues> DataValues = new List<DataValues>(); Data.DataList = DataValues; } // Read Column Names and allocate space for each column of the data array // Assumes that there are two header lines - the first for names , the second for units string[] line = lines[0].Split(','); string[] unitLine = lines[1].Split(','); // Read the first two lines, then for each column read the data column name and units, // then create and link each data column in Data for (int i = 0; i < columns; i++) { DataValues dv = new DataValues(); dv.Name = line[i].ToString(); dv.Units = unitLine[i].ToString(); dv.DataColumn = new List<DataValue>(); Data.DataList.Add(dv); } // Now that the columns names are stored in Data, find the DateTime column // We need to find this column as each data value has a timestamp associated with it. // // A more effiecent way of doing this is to find a data samples index in the Data array, // then lookup the index in the timestamp array to find the associated timestamp for the sample. // However, we intend to have some form of graphic display, so having a value, timestamp pair // will be more efficient for plots and charts int tsIndex = util.FindDataColumnIndex(Data, "DateTime"); // Now read the remaining lines into the data columns // Start at the third line in the data file for (int i = 2; i < rows; i++) { line = lines[i].Split(','); // For each data row read the input columns and create a data value for (int j = 0; j < columns; j++) { DataValue dv = new DataValue(); dv.Timestamp = Convert.ToDateTime(line[tsIndex].ToString()); dv.Value = line[j].ToString(); Data.DataList[j].DataColumn.Add(dv); } } }