home *** CD-ROM | disk | FTP | other *** search
/ Chip 2001 October / Chip_2001-10_cd1.bin / zkuste / delphi / kompon / d123456 / CHEMPLOT.ZIP / TPlot / Datalist.pas < prev    next >
Pascal/Delphi Source File  |  2001-07-24  |  136KB  |  4,069 lines

  1. unit Datalist;
  2.  
  3. {$I Plot.inc}
  4.  
  5. {-----------------------------------------------------------------------------
  6. The contents of this file are subject to the Q Public License
  7. ("QPL"); you may not use this file except in compliance
  8. with the QPL. You may obtain a copy of the QPL from 
  9. the file QPL.html in this distribution, derived from:
  10.  
  11. http://www.trolltech.com/products/download/freelicense/license.html
  12.  
  13. The QPL prohibits development of proprietary software. 
  14. There is a Professional Version of this software available for this. 
  15. Contact sales@chemware.hypermart.net for more information.
  16.  
  17. Software distributed under the QPL is distributed on an "AS IS" basis,
  18. WITHOUT WARRANTY OF ANY KIND, either expressed or implied. See the QPL for
  19. the specific language governing rights and limitations under the QPL.
  20.  
  21. The Original Code is: PlotList.pas, released 12 September 2000.
  22.  
  23. The Initial Developer of the Original Code is Mat Ballard.
  24. Portions created by Mat Ballard are Copyright (C) 1999 Mat Ballard.
  25. Portions created by Microsoft are Copyright (C) 1998, 1999 Microsoft Corp.
  26. All Rights Reserved.
  27.  
  28. Contributor(s): Mat Ballard                 e-mail: mat.ballard@chemware.hypermart.net.
  29.  
  30. Last Modified: 04/18/2001
  31. Current Version: 2.00
  32.  
  33. You may retrieve the latest version of this file from:
  34.  
  35.         http://Chemware.hypermart.net/
  36.  
  37. This work was created with the Project JEDI VCL guidelines:
  38.  
  39.         http://www.delphi-jedi.org/Jedi:VCLVCL
  40.  
  41. in mind.
  42.  
  43. Purpose:
  44. This unit contains the TSeriesList sub-component - that manages the data for
  45. ALL Series for TPlot.
  46.  
  47. Known Issues:
  48. -----------------------------------------------------------------------------}
  49.  
  50. interface
  51.  
  52. uses
  53.   Classes, SysUtils,
  54. {$IFDEF NO_MATH}NoMath,{$ELSE}Math,{$ENDIF}
  55. {$IFDEF WINDOWS}
  56.   Controls, Dialogs, Graphics, Windows,
  57. {$ENDIF}
  58. {$IFDEF WIN32}
  59.   Controls, Dialogs, Graphics, Windows,
  60. {$ENDIF}
  61. {$IFDEF LINUX}
  62.   Types,
  63.   QControls, QDialogs, QGraphics,
  64. {$ENDIF}
  65.  
  66. {$IFDEF FUNCTIONS}
  67.   Parser10, Functons,
  68. {$ENDIF}
  69.  
  70.   Axis, Data, Parser, Plotdefs, Misc, Titles;
  71.  
  72. type
  73. {TSeriesList is a TList of Series that frees it's items.
  74.  Use only descendents of TObject:}
  75.   TSeriesList = class(TList)
  76.   private
  77. {The AxisList is created and managed in the Plot unit and TCustomPlot component.
  78.  The specific axes are:
  79.    0 .. X Axis
  80.    1 .. Primary Y Axis
  81.    2 .. Secondary Y Axis
  82.    3 .. Tertiary Y Axis
  83.    4 .. etc.}
  84.     FAxisList: TList;
  85.     FDataChanged: Boolean;
  86.     FIgnoreChanges: Boolean;
  87.     FXAxis: TAxis;
  88.     FYAxis: TAxis;
  89.  
  90.  
  91.     FOnStyleChange: TNotifyEvent;
  92.     FOnDataChange: TNotifyEvent;
  93.  
  94.     LastSavedPoint: Integer;
  95.     PlotBorder: TRect;
  96.     NoPieRows: Integer;
  97.     
  98.     function GetXmin: Single;
  99.     function GetXmax: Single;
  100.     function GetYmin: Single;
  101.     function GetYmax: Single;
  102.     function GetZmin: Single;
  103.     function GetZmax: Single;
  104.     function GetYErrorMin: Single;
  105.     function GetYErrorMax: Single;
  106.  
  107.     function GetMaxNoPts: Integer;
  108.     function GetMinNoPts: Integer;
  109.     function GetTotalNoPts: Integer;
  110.   protected
  111.     procedure StyleChange(Sender: TObject);
  112.     procedure DataChange(Sender: TObject);
  113.     procedure DoStyleChange; virtual;
  114.     procedure DoDataChange; virtual;
  115.  
  116.   public
  117.     property DataChanged: Boolean read FDataChanged write FDataChanged stored FALSE;
  118. {Has the data any series changed ?}
  119.     property IgnoreChanges: Boolean read FIgnoreChanges write FIgnoreChanges;
  120. {Shall we ignore Change and DataChange events ?}    
  121.     property TotalNoPts: Integer read GetTotalNoPts stored FALSE;
  122. {The total number of points in all series.}
  123.     property MaxNoPts: Integer read GetMaxNoPts stored FALSE;
  124. {The number of points in the largest series.}
  125.     property MinNoPts: Integer read GetMinNoPts stored FALSE;
  126. {The number of points in the smallest series.}
  127.     property Xmin: Single read GetXmin stored FALSE;
  128. {The minimum X value of ALL Series.}
  129.     property Xmax: Single read GetXmax stored FALSE;
  130. {The maximum X value of ALL Series.}
  131.  
  132.     property Ymin: Single read GetYmin;
  133. {The minimum Y value of ALL Series connected to the PRIMARY Y Axis.}
  134.     property Ymax: Single read GetYmax;
  135. {The maximum Y value of ALL Series connected to the PRIMARY Y Axis.}
  136.     property Zmin: Single read GetZmin;
  137. {The minimum Z value of ALL Series.}
  138.     property Zmax: Single read GetZmax;
  139. {The maximum Z value of ALL Series.}
  140.     property YErrorMin: Single read GetYErrorMin;
  141. {The minimum Y value of ALL Series plus their error.}
  142.     property YErrorMax: Single read GetYErrorMax;
  143. {The maximum Y value of ALL Series plus their error.}
  144.  
  145.     property OnStyleChange: TNotifyEvent read FOnStyleChange write FOnStyleChange;
  146. {This notifies the owner (usually TPlot) of a change in style of this series.}
  147.     property OnDataChange: TNotifyEvent read FOnDataChange write FOnDataChange;
  148. {This notifies the owner (usually TPlot) of a change in the data of this series.}
  149. {}
  150. {NOTE: D1 does not allow published properties for TList descendants.}
  151.  
  152.     Constructor Create(AxisListPtr: TList); virtual;
  153. {Saves the Axes List and initializes LastSavedPoint.}    
  154.     destructor Destroy; override;
  155.  
  156.     function Add(XSeriesIndex: Integer): Integer; 
  157. {This adds a new, empty series to the list.}
  158. {}
  159. {Like its ancestor function, and its relatives AddInternal and AddExternal,
  160.  Add returns the index of the new item, where the first item in the list has
  161.  an index of 0.}
  162.     function AddExternal(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Integer;
  163. {This adds a new, empty series to the list, and sets its data to point to the
  164.  external XPointer, YPointer data.}
  165. {}
  166. {Like its ancestor function, and its relatives AddInternal and Add,
  167.  AddExternal returns the index of the new item, where the first item in the list has
  168.  an index of 0.}
  169.     function AddInternal(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Integer;
  170. {This adds a new, empty series to the list, and copies the data from the
  171.  XPointer, YPointer data.}
  172. {}
  173. {Like its ancestor function, and its relatives Add and AddExternal,
  174.  AddInternal returns the index of the new item, where the first item in the list has
  175.  an index of 0.}
  176.  
  177.     procedure ClearSeries;
  178. {This deletes all Series from the list, freeing the series.}
  179. {}
  180. {Note that the Clear does not exist in the D1-3 version, because TList.Clear
  181.  is a static method and cannot be overridden. This is why we created ClearSeries.}
  182.  
  183.     function CloneSeries(TheSeries: Integer): Integer;
  184. {This adds a new, empty series to the list, copies the data and properties from
  185.  TheSeries into the new clone, and changes the color and Y Displacement.}
  186. {}
  187. {CloneSeries returns TRUE if successful.}
  188.     procedure DeleteSeries(Index: Integer; Ask: Boolean);
  189. {This deletes TheSeries from the list.}
  190.  
  191.     function ParseData(TheData: TStringList; TheHelpFile: String): Boolean;
  192. {This parses imported or pasted data and adds it to the SeriesList.}
  193.     function ConvertTextData(ColCount, SeriesCount, FirstLine: Integer;
  194.       Delimiter: String; TheData: TStringList; SeriesInfo: pSeriesInfoArray): Boolean;
  195. {This takes a parsed stringlist converts it to numerical data.}
  196.     function ConvertXYZData(FirstLine: Integer;
  197.           Delimiter: String; InfoGridRows: TStrings; TheData: TStringList): Boolean;
  198. {This takes an UNparsed stringlist with XYZ values and converts it to numerical data.}
  199.     function ConvertBinaryData(ColCount, SeriesCount: Integer;
  200.       TheStream: TMemoryStream; SeriesInfo: pSeriesInfoArray): Boolean;
  201. {This takes a parsed stringlist converts it to numerical data.}
  202.  
  203. {$IFDEF FUNCTIONS}
  204.     function FunctionSeries: Integer;
  205. {This creates a new series which is a function of the existing series.}
  206. {$ENDIF}
  207.  
  208.     procedure DataAsHTMLTable(var TheData: TStringList);
  209. {This returns all the data in HTML format as a StringList.}
  210.  
  211.     procedure GetStream(AsText: Boolean; Delimiter: Char; var TheStream: TMemoryStream);
  212. {This returns all the data as a MemoryStream.}
  213.     procedure GetSubHeaderStream(Delimiter: Char; TheStream: TMemoryStream);
  214. {This returns all the SubHeader data as a MemoryStream.}
  215.     procedure GetBinaryStream(Start, Finish: Integer;
  216.       TheStream: TMemoryStream);
  217.     procedure GetTextStream(Delimiter: Char; Start, Finish: Integer;
  218.       TheStream: TMemoryStream);
  219.     procedure AppendStream(AsText: Boolean; Delimiter: Char; TheStream: TMemoryStreamEx);
  220. {This returns the data collected since LastSavedPoint as a MemoryStream.}
  221.     function LoadFromStream(AStream: TMemoryStream; var AsText: Boolean): Boolean;
  222. {Opens data, parses it, fires the OnHeader event, and runs ConvertTextData,
  223.  or decides to run it through ParseData instead}
  224.     procedure Draw(ACanvas: TCanvas; XYFastAt: Integer);
  225. {This draws all the series on the given canvas.}
  226.     procedure DrawError(ACanvas: TCanvas);
  227. {Extended Drawing procedure for series with errorbars.}
  228.     procedure DrawBubble(ACanvas: TCanvas; BubbleSize: Integer);
  229. {Extended Drawing procedure for Bubble plots.}
  230.     procedure DrawMultiple(ACanvas: TCanvas;
  231.       Multiplicity: Byte;
  232.       MultiplePen: TPen;
  233.       MultiJoin1, MultiJoin2: Integer);
  234. {Extended Drawing procedure for linking multiple series.}
  235.     procedure DrawColumns(ACanvas: TCanvas; ColumnGap: TPercent);
  236. {This draws all the series on the given canvas in columns.}
  237.     procedure DrawStack(ACanvas: TCanvas; ColumnGap: TPercent);
  238. {This draws all the series on the given canvas in stacked (summed) columns.}
  239.     procedure DrawNormStack(ACanvas: TCanvas; ColumnGap: TPercent);
  240. {This draws all the series on the given canvas in normalized columns.}
  241.     procedure DrawPie(ACanvas: TCanvas; Border: TBorder; NoRows: Integer);
  242. {This draws all the series on the given canvas as Pie graphs.}
  243.     procedure DrawPolar(ACanvas: TCanvas; PolarRange: Single);
  244. {This draws all the series on the given canvas as a Polar graph.}
  245.     procedure Draw3DWire(ACanvas: TCanvas; ZAxis: TAngleAxis; ZLink: Boolean);
  246. {This draws all the series on the given canvas as a 3D WireFrame.}
  247.     procedure Draw3DColumn(ACanvas: TCanvas; ZAxis: TAngleAxis; ColumnGap: TPercent);
  248. {This draws all the series on the given canvas as a 3D Columns.}
  249.     procedure Draw3DContour(ACanvas: TCanvas; ZAxis: TAngleAxis; ContourDetail: TContourDetail; ContourWires: Boolean);
  250. {This draws all the series on the given canvas.}
  251.     procedure DrawContour(ACanvas: TCanvas; ContourDetail: TContourDetail);
  252. {This draws all the series on the given canvas as a solid colour contour map.}
  253.     procedure DrawLineContour(ACanvas: TCanvas;
  254.       ContourStart, ContourInterval: Single;
  255.       ContourDetail: TContourDetail);
  256. {This draws all the series on the given canvas as a line contour map.}
  257.     procedure DrawColorScale(ACanvas: TCanvas; TheMin, Span: Single; TheContourDetail: TContourDetail);
  258.     procedure SortTriangleVertices(var Pt1, Pt2, Pt3: T3DZPoint; var p1, p2, p3: p3DZPoint);
  259.     procedure SortTriangleVerticesOnY(var Pt1, Pt2, Pt3: T3DRealPoint; var p1, p2, p3: p3DRealPoint);
  260.  
  261.     procedure DrawHistory(ACanvas: TCanvas; HistoryX: Single);
  262. {This draws all the series on the given canvas, in a History mode.}
  263.     procedure DrawHistoryMultiple(ACanvas: TCanvas; Multiplicity: Byte);
  264. {Extended Drawing procedure for linking multiple series in History mode.}
  265.     function GetNearestPoint(
  266.       ThePlotType: TPlotType;
  267.       ColumnGap,
  268.       iX, iY: Integer;
  269.       var TheSeries: Integer;
  270.       var MinDistance: Single;
  271.       var pSeries: TSeries): Integer;
  272. {This returns the Index of the nearest point, and sets TheSeries it belongs to}
  273.   {published}
  274.     function GetSeriesOfZ(ZValue: Single): TSeries;
  275.   end;
  276.  
  277. implementation
  278.  
  279. uses
  280.   Plot;
  281.  
  282. const
  283.   PIE_SIZE = 0.8;
  284.  
  285. {TSeriesList Constructor and Destructor:---------------------------------------}
  286. {------------------------------------------------------------------------------
  287.   Constructor: TSeriesList.Create
  288.   Description: standard Constructor
  289.        Author: Mat Ballard
  290.  Date created: 04/25/2000
  291. Date modified: 04/25/2000 by Mat Ballard
  292.       Purpose: saves the Axes List and initializes LastSavedPoint
  293.  Known Issues:
  294.  ------------------------------------------------------------------------------}
  295. Constructor TSeriesList.Create(AxisListPtr: TList);
  296. begin
  297.   inherited Create;
  298.   FAxisList := AxisListPtr;
  299.   FXAxis := TAxis(FAxisList[0]);
  300.   FYAxis := TAxis(FAxisList[1]);
  301.   LastSavedPoint := 0;
  302. end;
  303.  
  304. {------------------------------------------------------------------------------
  305.    Destructor: TSeriesList.Destroy
  306.   Description: standard Destructor
  307.        Author: Mat Ballard
  308.  Date created: 04/25/2000
  309. Date modified: 04/25/2000 by Mat Ballard
  310.       Purpose: Frees the Axes and events
  311.  Known Issues:
  312.  ------------------------------------------------------------------------------}
  313. destructor TSeriesList.Destroy;
  314. begin
  315.   FOnStyleChange := nil;
  316.   FOnDataChange := nil;
  317. {NOTE: in D1-D3, Clear is static and cannot be overridden, so we had to
  318.  add a ClearSeries for them:}
  319.   ClearSeries;
  320.   inherited Destroy;
  321. end;
  322.  
  323. {TSeriesList Set procedures ---------------------------------------------------}
  324.  
  325. {TSeriesList Get functions ----------------------------------------------------}
  326.  
  327. {------------------------------------------------------------------------------
  328.      Function: TSeriesList.GetXmin
  329.   Description: standard property Get function
  330.        Author: Mat Ballard
  331.  Date created: 04/25/2000
  332. Date modified: 04/25/2000 by Mat Ballard
  333.       Purpose: gets the value of the XMin Property over ALL Series
  334.  Known Issues:
  335.  ------------------------------------------------------------------------------}
  336. function TSeriesList.GetXmin: Single;
  337. var
  338.   i: Integer;
  339.   Value: Single;
  340. begin
  341.   Value := 1.e38;
  342.   for i := 0 to Count-1 do
  343.   begin
  344.     if (Value > TSeries(Items[i]).XMin) then
  345.       Value := TSeries(Items[i]).XMin;
  346.   end; {loop over series}
  347.   GetXmin := Value;
  348. end;
  349.  
  350. {------------------------------------------------------------------------------
  351.      Function: TSeriesList.GetXMax
  352.   Description: standard property Get function
  353.        Author: Mat Ballard
  354.  Date created: 04/25/2000
  355. Date modified: 04/25/2000 by Mat Ballard
  356.       Purpose: gets the value of the XMax Property over ALL Series
  357.  Known Issues:
  358.  ------------------------------------------------------------------------------}
  359. function TSeriesList.GetXmax: Single;
  360. var
  361.   i: Integer;
  362.   Value: Single;
  363. begin
  364.   Value := -1.e38;
  365.   for i := 0 to Count-1 do
  366.   begin
  367.     if (Value < TSeries(Items[i]).XMax) then
  368.       Value := TSeries(Items[i]).XMax;
  369.   end; {loop over series}
  370.   GetXmax := Value;
  371. end;
  372.  
  373. {------------------------------------------------------------------------------
  374.      Function: TSeriesList.GetYMin
  375.   Description: standard property Get function
  376.        Author: Mat Ballard
  377.  Date created: 04/25/2000
  378. Date modified: 04/25/2000 by Mat Ballard
  379.       Purpose: gets the value of the YMin Property over ALL Series
  380.  Known Issues:
  381.  ------------------------------------------------------------------------------}
  382. function TSeriesList.GetYMin: Single;
  383. var
  384.   i: Integer;
  385.   Value: Single;
  386. begin
  387.   Value := 1.e38;
  388.   for i := 0 to Count-1 do
  389.   begin
  390.     if (Value > TSeries(Items[i]).YMin) then
  391.       Value := TSeries(Items[i]).YMin;
  392.   end; {loop over series}
  393.   GetYMin := Value;
  394. end;
  395.  
  396. {------------------------------------------------------------------------------
  397.      Function: TSeriesList.GetYMax
  398.   Description: standard property Get function
  399.        Author: Mat Ballard
  400.  Date created: 04/25/2000
  401. Date modified: 04/25/2000 by Mat Ballard
  402.       Purpose: gets the value of the YMax Property over ALL Series
  403.  Known Issues:
  404.  ------------------------------------------------------------------------------}
  405. function TSeriesList.GetYMax: Single;
  406. var
  407.   i: Integer;
  408.   Value: Single;
  409. begin
  410.   Value := -1.e38;
  411.   for i := 0 to Count-1 do
  412.   begin
  413.     if (Value < TSeries(Items[i]).YMax) then
  414.       Value := TSeries(Items[i]).YMax;
  415.   end; {loop over series}
  416.   GetYMax := Value;
  417. end;
  418.  
  419. {------------------------------------------------------------------------------
  420.      Function: TSeriesList.GetZMin
  421.   Description: standard property Get function
  422.        Author: Mat Ballard
  423.  Date created: 04/25/2000
  424. Date modified: 04/25/2000 by Mat Ballard
  425.       Purpose: gets the value of the ZMin Property over ALL Series
  426.  Known Issues:
  427.  ------------------------------------------------------------------------------}
  428. function TSeriesList.GetZMin: Single;
  429. var
  430.   i: Integer;
  431.   Value: Single;
  432. begin
  433.   Value := 1.e38;
  434.   for i := 0 to Count-1 do
  435.   begin
  436.     if (Value > TSeries(Items[i]).ZData) then
  437.       Value := TSeries(Items[i]).ZData;
  438.   end; {loop over series}
  439.   GetZMin := Value;
  440. end;
  441.  
  442. {------------------------------------------------------------------------------
  443.      Function: TSeriesList.GetZMax
  444.   Description: standard property Get function
  445.        Author: Mat Ballard
  446.  Date created: 04/25/2000
  447. Date modified: 04/25/2000 by Mat Ballard
  448.       Purpose: gets the value of the ZMax Property over ALL Series
  449.  Known Issues:
  450.  ------------------------------------------------------------------------------}
  451. function TSeriesList.GetZMax: Single;
  452. var
  453.   i: Integer;
  454.   Value: Single;
  455. begin
  456.   Value := -1.e38;
  457.   for i := 0 to Count-1 do
  458.   begin
  459.     if (Value < TSeries(Items[i]).ZData) then
  460.       Value := TSeries(Items[i]).ZData;
  461.   end; {loop over series}
  462.   GetZMax := Value;
  463. end;
  464.  
  465. {------------------------------------------------------------------------------
  466.      Function: TSeriesList.GetYErrorMin
  467.   Description: standard property Get function
  468.        Author: Mat Ballard
  469.  Date created: 04/25/2000
  470. Date modified: 04/25/2000 by Mat Ballard
  471.       Purpose: gets the value of the YErrorMin Property over ALL Series
  472.  Known Issues:
  473.  ------------------------------------------------------------------------------}
  474. function TSeriesList.GetYErrorMin: Single;
  475. var
  476.   i,
  477.   iSeries: Integer;
  478.   NewValue,
  479.   Value: Single;
  480.   pSeries,
  481.   pErrorSeries: TSeries;
  482. begin
  483.   Value := 1.e38;
  484.   iSeries := 0;
  485.   while (iSeries <= Self.Count-2) do
  486.   begin
  487.     pSeries := TSeries(Items[iSeries]);
  488.     pErrorSeries := TSeries(Items[iSeries+1]);
  489.     for i := 0 to Min(pSeries.NoPts, pErrorSeries.NoPts)-1 do
  490.     begin
  491.       NewValue := pSeries.YData^[i] - pErrorSeries.YData^[i];
  492.       if (Value > NewValue) then
  493.         Value := NewValue;
  494.     end;
  495.     Inc(iSeries, 2)
  496.   end; {loop over series}
  497.   GetYErrorMin := Value;
  498. end;
  499.  
  500. {------------------------------------------------------------------------------
  501.      Function: TSeriesList.GetYErrorMax
  502.   Description: standard property Get function
  503.        Author: Mat Ballard
  504.  Date created: 04/25/2000
  505. Date modified: 04/25/2000 by Mat Ballard
  506.       Purpose: gets the value of the YErrorMax Property over ALL Series
  507.  Known Issues:
  508.  ------------------------------------------------------------------------------}
  509. function TSeriesList.GetYErrorMax: Single;
  510. var
  511.   i,
  512.   iSeries: Integer;
  513.   NewValue,
  514.   Value: Single;
  515.   pSeries,
  516.   pErrorSeries: TSeries;
  517. begin
  518.   Value := 1.e-38;
  519.   iSeries := 0;
  520.   while (iSeries <= Self.Count-2) do
  521.   begin
  522.     pSeries := TSeries(Items[iSeries]);
  523.     pErrorSeries := TSeries(Items[iSeries+1]);
  524.     for i := 0 to Min(pSeries.NoPts, pErrorSeries.NoPts)-1 do
  525.     begin
  526.       NewValue := pSeries.YData^[i] + pErrorSeries.YData^[i];
  527.       if (Value < NewValue) then
  528.         Value := NewValue;
  529.     end;
  530.     Inc(iSeries, 2)
  531.   end; {loop over series}
  532.   GetYErrorMax := Value;
  533. end;
  534.  
  535. {TSeriesList Memory and list management ---------------------------------------}
  536. {------------------------------------------------------------------------------
  537.      Function: TSeriesList.Add
  538.   Description: Adds a new Series
  539.        Author: Mat Ballard
  540.  Date created: 04/25/2000
  541. Date modified: 04/25/2000 by Mat Ballard
  542.       Purpose: creates, initializes and adds a new series
  543.  Return Value: the Index of the new Series
  544.  Known Issues:
  545.  ------------------------------------------------------------------------------}
  546. function TSeriesList.Add(XSeriesIndex: Integer): Integer;
  547. var
  548.   Item,
  549.   XSeries: TSeries;
  550. begin
  551.   if (XSeriesIndex > Count-1) then raise
  552.     EComponentError.Create('TSeriesList.Add: ' + sAdd1);
  553.  
  554.   if (XSeriesIndex >= 0) then
  555.     XSeries := TSeries(Items[XSeriesIndex])
  556.    else
  557.     XSeries := nil;
  558.  
  559.   Item := TSeries.Create(Count, FAxisList, XSeries);
  560.   Item.OnStyleChange := Self.StyleChange;
  561.   Item.OnDataChange := Self.DataChange;
  562.  
  563.   Add := inherited Add(Item);
  564.  
  565.   DoDataChange;
  566. end;
  567.  
  568. {------------------------------------------------------------------------------
  569.      Function: TSeriesList.AddExternal
  570.   Description: Adds a new Series that is externally maintained
  571.        Author: Mat Ballard
  572.  Date created: 04/25/2000
  573. Date modified: 01/25/2001 by Mat Ballard
  574.       Purpose: creates, initializes and adds a new series
  575.  Return Value: the Index of the new Series
  576.  Known Issues:
  577.  ------------------------------------------------------------------------------}
  578. function TSeriesList.AddExternal(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Integer;
  579. var
  580.   Item: TSeries;
  581. begin
  582. {set to failure in case Item.AddData fails:}
  583.   AddExternal := -1;
  584.  
  585.   Item := TSeries.Create(Count, FAxisList, nil);
  586.   Item.OnDataChange := Self.DataChange;
  587.  
  588.   if (Item.PointToData(XPointer, YPointer, NumberOfPoints)) then
  589.   begin
  590. {Success:}
  591.     AddExternal := inherited Add(Item);
  592.   end
  593.   else
  594.   begin
  595. {Failure:}
  596.     Item.Free;
  597.   end;
  598. end;
  599.  
  600. {------------------------------------------------------------------------------
  601.      Function: TSeriesList.AddInternal
  602.   Description: Adds a new Series from external data (copies)
  603.        Author: Mat Ballard
  604.  Date created: 04/25/2000
  605. Date modified: 01/25/2001 by Mat Ballard
  606.       Purpose: creates, initializes and adds a new series
  607.  Return Value: the Index of the new Series
  608.  Known Issues:
  609.  ------------------------------------------------------------------------------}
  610. function TSeriesList.AddInternal(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Integer;
  611. var
  612.   Item: TSeries;
  613. begin
  614. {set to failure in case Item.AddData fails:}
  615.   AddInternal := -1;
  616.  
  617.   Item := TSeries.Create(Count, FAxisList, nil);
  618.   Item.OnDataChange := Self.DataChange;
  619.   if (Item.AddData(XPointer, YPointer, NumberOfPoints)) then
  620.   begin
  621. {Success:}
  622.     AddInternal := inherited Add(Item);
  623.   end
  624.   else
  625.   begin
  626. {Failure:}
  627.     Item.Free;
  628.   end;
  629. end;
  630.  
  631. {------------------------------------------------------------------------------
  632.     Procedure: TSeriesList.ClearSeries
  633.   Description: frees and deletes all the Series
  634.        Author: Mat Ballard
  635.  Date created: 04/25/2000
  636. Date modified: 04/25/2000 by Mat Ballard
  637.       Purpose: Series management
  638.  Known Issues:
  639.  ------------------------------------------------------------------------------}
  640. procedure TSeriesList.ClearSeries;
  641. var
  642.   i: Integer;
  643.   pSeries: TSeries;
  644. begin
  645.   if (Count = 0) then exit;
  646.   
  647.   for i := Count-1 downto 0 do
  648.   begin
  649.     pSeries := TSeries(Items[i]);
  650.     Delete(i);
  651.     pSeries.Free;
  652.   end;
  653.   DoDataChange;
  654. end;
  655.  
  656. {------------------------------------------------------------------------------
  657.     Procedure: TSeriesList.DeleteSeries
  658.   Description: frees and deletes one particular Series
  659.        Author: Mat Ballard
  660.  Date created: 04/25/2000
  661. Date modified: 04/25/2000 by Mat Ballard
  662.       Purpose: Series management
  663.  Known Issues:
  664.  ------------------------------------------------------------------------------}
  665. procedure TSeriesList.DeleteSeries(Index: Integer; Ask: Boolean);
  666. var
  667.   pSeries: TSeries;
  668. begin
  669.   pSeries := TSeries(Items[Index]);
  670.   if (Ask) then
  671.     Ask := not (MessageDlg(
  672. {$IFDEF LINUX}
  673.     sDelete + ' ' + sSeries,
  674. {$ENDIF}
  675.     sReallyDelete + ' ' + pSeries.Name + ' ?',
  676.     mtWarning, [mbYes,mbNo], 0) = mrYes);
  677.  
  678.   if (not Ask) then
  679.   begin
  680.     Delete(Index);
  681.     pSeries.Free;
  682.     DoDataChange;
  683.   end;
  684. end;
  685.  
  686. {$IFDEF FUNCTIONS}
  687. {------------------------------------------------------------------------------
  688.     Procedure: TSeriesList.FunctionSeries
  689.   Description: Creates a new series which is a function of existing series
  690.        Author: Mat Ballard
  691.  Date created: 04/03/2001
  692. Date modified: 04/03/2001 by Mat Ballard
  693.       Purpose: data manipulation
  694.  Known Issues:
  695.  ------------------------------------------------------------------------------}
  696. function TSeriesList.FunctionSeries: Integer;
  697. {This creates a new series which is a function of the existing series.}
  698. var
  699.   i, j,
  700.   TheResult: Integer;
  701.   FunctionsForm: TFunctionsForm;
  702.   TheParser: TParser;
  703.   TheExpression: String;
  704.   TheData: array [0..99] of PParserFloat;
  705. begin
  706.   TheResult := -1;
  707.   if (Self.Count > 0) then
  708.   begin
  709.     FunctionsForm := TFunctionsForm.Create(nil);
  710.     for i := 0 to Self.Count-2 do
  711.       FunctionsForm.FunctionMemo.Lines.Add(Format(sSeries + '%d', [i]) + ' + ');
  712.     FunctionsForm.FunctionMemo.Lines.Add(Format(sSeries + '%d', [Self.Count-1]));
  713.     FunctionsForm.SeriesLabel.Caption := Format(sSeries + '%d :=', [Self.Count]);
  714.     FunctionsForm.SeriesCount := Self.Count;
  715.  
  716.     if (mrOK = FunctionsForm.ShowModal) then
  717.     begin
  718.       TheResult := Self.Add(0);
  719.       TheParser := TParser.Create(nil);
  720.       TheExpression := '';
  721. {read the equation:}
  722.       for i := 0 to FunctionsForm.FunctionMemo.Lines.Count-1 do
  723.         TheExpression := TheExpression + FunctionsForm.FunctionMemo.Lines[i];
  724.       TheExpression := Misc.CleanString(TheExpression, ' ');
  725.  
  726. {try to run the Parser:}
  727.       try
  728. {Set up the variables:}
  729.         for j := 0 to Self.Count-2 do
  730.           TheData[j] := TheParser.SetVariable(Format(UpperCase(sSeries) + '%d', [j]), 0);
  731. {Set up equation:}
  732.         TheParser.Expression := TheExpression;
  733. {Loop over all points in Series[0]:}
  734.         for i := 0 to TSeries(Self.Items[0]).NoPts-1 do
  735.         begin
  736. {add the Series variables to the Parser, or reset their values:}
  737.           for j := 0 to Self.Count-2 do
  738.             TheData[j]^ := TSeries(Self.Items[j]).YData^[i];
  739.           TSeries(Self.Items[TheResult]).AddPoint(-1, TheParser.Value, FALSE, TRUE);
  740.         end;
  741.       except
  742.         Self.DeleteSeries(TheResult, FALSE);
  743.         TheResult := -1;
  744.       end;
  745.       TheParser.Free;
  746.     end;
  747.     FunctionsForm.Free;
  748.   end;
  749.   FunctionSeries := TheResult;
  750. end;
  751.  
  752. {$ENDIF}
  753.  
  754. {TSeriesList the movie ! ------------------------------------------------------}
  755. {------------------------------------------------------------------------------
  756.     Procedure: TSeriesList.Draw
  757.   Description: standard Drawing procedure
  758.        Author: Mat Ballard
  759.  Date created: 04/25/2000
  760. Date modified: 04/25/2000 by Mat Ballard
  761.       Purpose: draws all the Series on a given canvas
  762.  Known Issues: where do we put the drawing and the "GetNearestPoint" methods ?
  763.  
  764.     ptXY         needs FXAxis, FYAxis - done in Data
  765.     ptError      needs FXAxis, FYAxis, AND Index - done in Datalist
  766.     ptMultiple   needs FXAxis, FYAxis - done in Data
  767.     ptColumn     needs ColumnGap, Index, FXAxis, FYAxis - done in Datalist
  768.     ptStack      needs ALL Series, ColumnGap, Index, FXAxis, FYAxis - done in Datalist
  769.     ptNormStack  needs ALL Series, ColumnGap, Index, FXAxis, FYAxis - done in Datalist
  770.     ptPie        needs bounding rectangle - done in Data
  771.     ptPolar      unknown as yet
  772.     pt3D         needs ALL Series, FXAxis, FYAxis, AND FZAxis - done in Serlist
  773.  
  774.     There seems to be no simple solution to this: some methods have to be, or
  775.     are best in this unit; others, according to the principles:
  776.         a. bury as deep as possible;
  777.         b. each series _SHOULD_ know how to draw itself;
  778.     would seem better to be in Data.
  779.  ------------------------------------------------------------------------------}
  780. procedure TSeriesList.Draw(ACanvas: TCanvas; XYFastAt: Integer);
  781. var
  782.   i: Integer;
  783. begin
  784. {$IFDEF DELPHI3_UP}
  785.   Assert(ACanvas <> nil, 'TSeriesList.Draw: ' + sACanvasIsNil);
  786. {$ENDIF}
  787.  
  788.   for i := 0 to Count-1 do
  789.   begin
  790.     if (TSeries(Items[i]).ShadeLimits) then
  791.       TSeries(Items[i]).DrawShades(ACanvas, XYFastAt);
  792.   end; {loop over series}
  793.  
  794.   for i := 0 to Count-1 do
  795.   begin
  796.     TSeries(Items[i]).Draw(ACanvas, XYFastAt);
  797.   end; {loop over series}
  798. end;
  799.  
  800. {------------------------------------------------------------------------------
  801.     Procedure: TSeriesList.DrawColumns
  802.   Description: standard Drawing procedure for Columns
  803.        Author: Mat Ballard
  804.  Date created: 09/25/2000
  805. Date modified: 09/25/2000 by Mat Ballard
  806.       Purpose: draws all the Series on a given canvas in Columns
  807.  Known Issues:
  808.  ------------------------------------------------------------------------------}
  809. procedure TSeriesList.DrawColumns(ACanvas: TCanvas; ColumnGap: TPercent);
  810. var
  811.   i,
  812.   j,
  813.   iX,
  814.   iXp1, {iX plus 1: the next X ordinate;}
  815.   iXStart,
  816.   iXEnd,
  817.   iY: Integer;
  818.   dX: Single;
  819.   pSeries,
  820.   pSeries0: TSeries;
  821. begin
  822. {$IFDEF DELPHI3_UP}
  823.   Assert(ACanvas <> nil, 'TSeriesList.DrawColumns: ' + sACanvasIsNil);
  824. {$ENDIF}
  825.  
  826.   if (Count = 0) then
  827.     exit;
  828.  
  829. {all columns are plotted against the X Axis and Data of the first series,
  830.  otherwise chaos can result.}
  831.   pSeries0 := TSeries(Items[0]);
  832.  
  833. {loop over every series:}
  834.   for i := 0 to Count-1 do
  835.   begin
  836.     pSeries := TSeries(Items[i]);
  837.     ACanvas.Pen.Assign(pSeries.Pen);
  838.     ACanvas.Brush.Assign(pSeries.Brush);
  839.     if ((pSeries.NoPts > 0) and
  840.         (pSeries.Visible)) then
  841.     begin
  842.       iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[0]);
  843.   {loop over every point in each series:}
  844.       for j := 0 to pSeries.NoPts-2 do
  845.       begin
  846.         iX := iXp1;
  847.         iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[j+1]);
  848.         dX := (iXp1-iX) * ((100 - ColumnGap) / (100 * Count));
  849.         iXStart := iX + Round(i*dX);
  850.         iXEnd := iXStart + Round(dX);
  851.         iY := pSeries.YAxis.FofY(pSeries.YData^[j]);
  852.         ACanvas.Rectangle(iXStart, iY, iXEnd, pSeries.YAxis.Bottom);
  853.       end; {for}
  854.     end; {NoPts > 0}
  855.   end; {loop over series}
  856. end;
  857.  
  858. {------------------------------------------------------------------------------
  859.     Procedure: TSeriesList.DrawStack
  860.   Description: standard Drawing procedure for Stacked Columns
  861.        Author: Mat Ballard
  862.  Date created: 09/25/2000
  863. Date modified: 09/25/2000 by Mat Ballard
  864.       Purpose: draws all the Series on a given canvas in Stacked Columns
  865.  Known Issues:
  866.  ------------------------------------------------------------------------------}
  867. procedure TSeriesList.DrawStack(ACanvas: TCanvas; ColumnGap: TPercent);
  868. var
  869.   i,
  870.   j,
  871.   iX,
  872.   iXp1, {iX plus 1: the next X ordinate;}
  873.   iXEnd,
  874.   iY,
  875.   iYNeg,
  876.   iYTop,
  877.   iYNegBottom: Integer;
  878.   NegSum, Sum: Single;
  879.   pSeries0: TSeries;
  880. begin
  881. {$IFDEF DELPHI3_UP}
  882.   Assert(ACanvas <> nil, 'TSeriesList.DrawColumns: ' + sACanvasIsNil);
  883. {$ENDIF}
  884.  
  885.   if (Count = 0) then
  886.     exit;
  887.  
  888. {all columns are plotted against the X and Y Axis and X Data of the first series,
  889.  otherwise chaos can result.}
  890.   pSeries0 := TSeries(Items[0]);
  891.   if (pSeries0.NoPts = 0) then exit;
  892.  
  893.   iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[0]);
  894. {loop over every point:}
  895.   for j := 0 to pSeries0.NoPts-2 do
  896.   begin
  897.     iX := iXp1;
  898.     iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[j+1]);
  899.     iXEnd := iXp1 - Round((ColumnGap) * (iXp1-iX) / 100);
  900.     iYTop := pSeries0.XAxis.MidY;
  901.     iYNegBottom := iYTop;
  902.     Sum := 0;
  903.     NegSum := 0;
  904.   {loop over every series:}
  905.     for i := 0 to Self.Count-1 do
  906.     begin
  907.       if ((TSeries(Items[i]).NoPts > j) and
  908.           (TSeries(Items[i]).Visible)) then
  909.       begin
  910.         ACanvas.Pen.Assign(TSeries(Items[i]).Pen);
  911.         ACanvas.Brush.Assign(TSeries(Items[i]).Brush);
  912.         if (TSeries(Items[i]).YData^[j] >= 0) then
  913.         begin
  914.           Sum := Sum + TSeries(Items[i]).YData^[j];
  915.           iY := iYTop;
  916.           iYTop := pSeries0.YAxis.FofY(Sum);
  917.           ACanvas.Rectangle(iX, iY, iXEnd, iYTop);
  918.         end
  919.         else
  920.         begin
  921.           NegSum := NegSum + TSeries(Items[i]).YData^[j];
  922.           iYNeg := iYNegBottom;
  923.           iYNegBottom := pSeries0.YAxis.FofY(NegSum);
  924.           ACanvas.Rectangle(iX, iYNeg, iXEnd, iYNegBottom);
  925.         end;
  926.       end; {for}
  927.     end; {NoPts > 0}
  928.   end; {loop over series}
  929. end;
  930.  
  931. {------------------------------------------------------------------------------
  932.     Procedure: TSeriesList.DrawNormStack
  933.   Description: standard Drawing procedure for Normalized Stacked Columns
  934.        Author: Mat Ballard
  935.  Date created: 09/25/2000
  936. Date modified: 09/25/2000 by Mat Ballard
  937.       Purpose: draws all the Series on a given canvas in Normalized (percentage) Stacked Columns
  938.  Known Issues:
  939.  ------------------------------------------------------------------------------}
  940. procedure TSeriesList.DrawNormStack(ACanvas: TCanvas; ColumnGap: TPercent);
  941. var
  942.   i,
  943.   j,
  944.   iX,
  945.   iXp1, {iX plus 1: the next X ordinate;}
  946.   iXEnd,
  947.   iY,
  948.   iYNeg,
  949.   iYTop,
  950.   iYNegBottom: Integer;
  951.   Sum, NegSum,
  952.   Total, NegTotal: Single;
  953.   pSeries0: TSeries;
  954. begin
  955. {$IFDEF DELPHI3_UP}
  956.   Assert(ACanvas <> nil, 'TSeriesList.DrawColumns: ' + sACanvasIsNil);
  957. {$ENDIF}
  958.  
  959.   if (Count = 0) then
  960.     exit;
  961.  
  962. {all columns are plotted against the X and Y Axis and X Data of the first series,
  963.  otherwise chaos can result.}
  964.   pSeries0 := TSeries(Items[0]);
  965.   if (pSeries0.NoPts = 0) then exit;
  966.  
  967.   iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[0]);
  968. {loop over every point:}
  969.   for j := 0 to pSeries0.NoPts-2 do
  970.   begin
  971.     iX := iXp1;
  972.     iXp1 := pSeries0.XAxis.FofX(pSeries0.XData^[j+1]);
  973.     iXEnd := iXp1 - Round((ColumnGap) * (iXp1-iX) / 100);
  974.     iYTop := pSeries0.XAxis.MidY;
  975.     iYNegBottom := iYTop;
  976.     Sum := 0;
  977.     NegSum := 0;
  978.     Total := 0;
  979.     NegTotal := 0;
  980. {count every series:}
  981.     for i := 0 to Count-1 do
  982.     begin
  983.       if (TSeries(Items[i]).NoPts > j) then
  984.       begin
  985.         if (TSeries(Items[i]).YData^[j] >= 0) then
  986.           Total := Total + TSeries(Items[i]).YData^[j]
  987.          else
  988.           NegTotal := NegTotal + TSeries(Items[i]).YData^[j];
  989.       end; {NoPts > j}
  990.     end; {count every series}
  991. {prepare for conversion of data to percent:}
  992.     Total := Total / 100;
  993.     NegTotal := - NegTotal / 100;
  994.  
  995. {loop over every series:}
  996.     for i := 0 to Count-1 do
  997.     begin
  998.       if (TSeries(Items[i]).NoPts > j) then
  999.       begin
  1000.         ACanvas.Pen.Assign(TSeries(Items[i]).Pen);
  1001.         ACanvas.Brush.Assign(TSeries(Items[i]).Brush);
  1002.         if (TSeries(Items[i]).YData^[j] >= 0) then
  1003.         begin
  1004.           Sum := Sum + (TSeries(Items[i]).YData^[j] / Total);
  1005.           iY := iYTop;
  1006.           iYTop := pSeries0.YAxis.FofY(Sum);
  1007.           if (TSeries(Items[i]).Visible) then
  1008.             ACanvas.Rectangle(iX, iY, iXEnd, iYTop);
  1009.         end
  1010.         else
  1011.         begin
  1012.           NegSum := NegSum + (TSeries(Items[i]).YData^[j] / NegTotal);
  1013.           iYNeg := iYNegBottom;
  1014.           iYNegBottom := pSeries0.YAxis.FofY(NegSum);
  1015.           if (TSeries(Items[i]).Visible) then
  1016.             ACanvas.Rectangle(iX, iYNeg, iXEnd, iYNegBottom);
  1017.         end;
  1018.       end; {for}
  1019.     end; {NoPts > 0}
  1020.   end; {loop over series}
  1021. end;
  1022.  
  1023. {------------------------------------------------------------------------------
  1024.     Procedure: TSeriesList.DrawPie
  1025.   Description: standard Drawing procedure for Normalized Stacked Columns
  1026.        Author: Mat Ballard
  1027.  Date created: 12/20/2000
  1028. Date modified: 12/20/2000 by Mat Ballard
  1029.       Purpose: This draws all the series on the given canvas as Pie graphs.
  1030.  Known Issues:
  1031.  ------------------------------------------------------------------------------}
  1032. procedure TSeriesList.DrawPie(ACanvas: TCanvas; Border: TBorder; NoRows: Integer);
  1033. var
  1034.   CellWidth,
  1035.   CellHeight,
  1036.   iSeries,
  1037.   iCol,
  1038.   jRow,
  1039.   NoPieCols,
  1040.   PieLeft,
  1041.   PieTop,
  1042.   PieWidth,
  1043.   PieHeight: Integer;
  1044. begin
  1045. {$IFDEF DELPHI3_UP}
  1046.   Assert(ACanvas <> nil, 'TSeriesList.DrawPie: ' + sACanvasIsNil);
  1047. {$ENDIF}
  1048.  
  1049.   if ((Count = 0) or (NoRows = 0)) then
  1050.     exit;
  1051.  
  1052. {remember the number of rows:}
  1053.   NoPieRows := NoRows;
  1054.   NoPieCols := Trunc(0.99 + Self.Count / NoPieRows);
  1055. {remember the border:}
  1056.   PlotBorder.Left := Border.Left;
  1057.   PlotBorder.Top := Border.Top;
  1058.   PlotBorder.Right := Border.Right;
  1059.   PlotBorder.Bottom := Border.Bottom;
  1060.  
  1061. {each Pie sits in a cell:}
  1062.   CellWidth := (PlotBorder.Right - PlotBorder.Left) div NoPieCols;
  1063.   CellHeight := (PlotBorder.Bottom - PlotBorder.Top) div NoPieRows;
  1064. {... but does not occupy the entire cell:}
  1065.   PieWidth := Round(PIE_SIZE * CellWidth);
  1066.   PieHeight := Round(PIE_SIZE * CellHeight);
  1067.   if (PieHeight > PieWidth) then
  1068.     PieHeight := PieWidth;
  1069.  
  1070.   iSeries := 0;
  1071.   for iCol := 0 to NoPieCols-1 do
  1072.   begin
  1073.     for jRow := 0 to NoPieRows-1 do
  1074.     begin
  1075.       if (iSeries >= Count) then break;
  1076. {indent the (left, top) a bit:}
  1077.       PieLeft := PlotBorder.Left + iCol * CellWidth +
  1078.         (CellWidth-PieWidth) div 2;
  1079.       PieTop := PlotBorder.Top + jRow * CellHeight +
  1080.         (CellHeight-PieHeight) div 2;
  1081. {draw it:}
  1082.       TSeries(Self.Items[iSeries]).DrawPie(
  1083.         ACanvas,
  1084.         PieLeft,
  1085.         PieTop,
  1086.         PieWidth,
  1087.         PieHeight);
  1088.       Inc(iSeries);
  1089.     end;
  1090.   end;
  1091. end;
  1092.  
  1093. {------------------------------------------------------------------------------
  1094.     Procedure: TSeriesList.DrawPolar
  1095.   Description: standard Drawing procedure for Normalized Stacked Columns
  1096.        Author: Mat Ballard
  1097.  Date created: 12/20/2000
  1098. Date modified: 12/20/2000 by Mat Ballard
  1099.       Purpose: This draws all the series on the given canvas as a Polar graph.
  1100.  Known Issues:
  1101.  ------------------------------------------------------------------------------}
  1102. procedure TSeriesList.DrawPolar(ACanvas: TCanvas; PolarRange: Single);
  1103. var
  1104.   i: Integer;
  1105. begin
  1106. {$IFDEF DELPHI3_UP}
  1107.   Assert(ACanvas <> nil, 'TSeriesList.Draw: ' + sACanvasIsNil);
  1108. {$ENDIF}
  1109.  
  1110.   if (PolarRange = 0) then
  1111.     PolarRange := TWO_PI;
  1112.   for i := 0 to Self.Count-1 do
  1113.   begin
  1114.     TSeries(Items[i]).DrawPolar(ACanvas, PolarRange);
  1115.   end; {loop over series}
  1116. end;
  1117.  
  1118. {------------------------------------------------------------------------------
  1119.     Procedure: TSeriesList.Draw3DWire
  1120.   Description: standard Drawing procedure for 3D graphs
  1121.        Author: Mat Ballard
  1122.  Date created: 01/24/2001
  1123. Date modified: 01/24/2001 by Mat Ballard
  1124.       Purpose: This draws all the series as a 3D graph
  1125.  Known Issues:
  1126.  ------------------------------------------------------------------------------}
  1127. procedure TSeriesList.Draw3DWire(ACanvas: TCanvas; ZAxis: TAngleAxis; ZLink: Boolean);
  1128. var
  1129.   iSeries,
  1130.   j: Integer;
  1131.   Pt00, Pt01, Pt10, Pt11: TPoint;
  1132. {These correspond to Point[Series #, Point #]}
  1133.   ZShift0,
  1134.   ZShift1: TPoint;
  1135.   pSeries0,
  1136.   pSeries1: TSeries;
  1137. begin
  1138. {$IFDEF DELPHI3_UP}
  1139.   Assert(ACanvas <> nil, 'TSeriesList.Draw3DWire: ' + sACanvasIsNil);
  1140.   Assert(ZAxis <> nil, 'TSeriesList.Draw3DWire: ' + sDraw3D);
  1141. {$ENDIF}
  1142.  
  1143. {Draw each individual series:}
  1144.   for iSeries := 0 to Self.Count-1 do
  1145.   begin
  1146.     ACanvas.Pen.Assign(TSeries(Items[iSeries]).Pen);
  1147.     pSeries0 := TSeries(Items[iSeries]);
  1148.     Pt10.x := Low(Integer);
  1149.     if (pSeries0.NoPts > 0) then
  1150.     begin
  1151.       ZShift0 := ZAxis.dFofZ(pSeries0.ZData);
  1152.       Pt00.x := pSeries0.XAxis.FofX(pSeries0.XData^[0])+ ZShift0.x;
  1153.       Pt00.y := pSeries0.YAxis.FofY(pSeries0.YData^[0]) + ZShift0.y;
  1154.       if ((ZLink) and (iSeries < Count-1)) then
  1155.       begin
  1156.         pSeries1 := TSeries(Items[iSeries+1]);
  1157.         if (pSeries1.NoPts > 0) then
  1158.         begin
  1159.           ZShift1 := ZAxis.dFofZ(pSeries1.ZData);
  1160.           Pt10.x := pSeries1.XAxis.FofX(pSeries1.XData^[0])+ ZShift1.x;
  1161.           Pt10.y := pSeries1.YAxis.FofY(pSeries1.YData^[0]) + ZShift1.y;
  1162.         end;
  1163.       end;
  1164.  
  1165.       for j := 1 to pSeries0.NoPts-1 do
  1166.       begin
  1167.         Pt01.x := pSeries0.XAxis.FofX(pSeries0.XData^[j]) + ZShift0.x;
  1168.         Pt01.y := pSeries0.YAxis.FofY(pSeries0.YData^[j]) + ZShift0.y;
  1169.         ACanvas.MoveTo(Pt00.x, Pt00.y);
  1170.         ACanvas.LineTo(Pt01.x, Pt01.y);
  1171.         if ((ZLink) and (iSeries < Count-1)) then
  1172.         begin
  1173. {Oh yes it was: Delphi isn't smart enough to see that if we got here,
  1174.  then it WAS initialized. Same applies to Pt10.x, Pt10.y.}        
  1175.           if (pSeries1.NoPts > 0) then
  1176.           begin
  1177.             Pt11.x := pSeries1.XAxis.FofX(pSeries1.XData^[j]) + ZShift1.x;
  1178.             Pt11.y := pSeries1.YAxis.FofY(pSeries1.YData^[j]) + ZShift1.y;
  1179.             ACanvas.MoveTo(Pt10.x, Pt10.y);
  1180.             ACanvas.LineTo(Pt00.x, Pt00.y);
  1181. {no triangles please, we're British:            
  1182.             ACanvas.LineTo(Pt11.x, Pt11.y);}
  1183.             Pt10.x := Pt11.x;
  1184.             Pt10.y := Pt11.y;
  1185.           end;
  1186.         end; {not the final series}
  1187.         Pt00.x := Pt01.x;
  1188.         Pt00.y := Pt01.y;
  1189.       end; {loop over points}
  1190.       if (Pt10.x <> Low(Integer)) then
  1191.       begin
  1192.         ACanvas.MoveTo(Pt00.x, Pt00.y);
  1193.         ACanvas.LineTo(Pt10.x, Pt10.y);
  1194.       end;
  1195.     end; {NoPts}
  1196.   end; {over every series}
  1197. end;
  1198.  
  1199. {------------------------------------------------------------------------------
  1200.     Procedure: TSeriesList.Draw3DColumn
  1201.   Description: standard Drawing procedure for 3D graphs
  1202.        Author: Mat Ballard
  1203.  Date created: 06/01/2001
  1204. Date modified: 06/01/2001 by Mat Ballard
  1205.       Purpose: This draws all the series on the given canvas as a 3D Columns.
  1206.  Known Issues:
  1207.  ------------------------------------------------------------------------------}
  1208. procedure TSeriesList.Draw3DColumn(ACanvas: TCanvas; ZAxis: TAngleAxis; ColumnGap: TPercent);
  1209. var
  1210.   iSeries,
  1211.   j: Integer;
  1212.   iX, iXp1, FofX, Height, dX: Integer;
  1213.   FofZ, FofZp1, dz: TPoint;
  1214.   pSeries: TSeries;
  1215.  
  1216. {   NB: FofZ and dz are vectors in TPoint format.
  1217.                 y
  1218.                 ^
  1219.                 |
  1220.                 |
  1221.                 |               FofX
  1222.                 |                |
  1223.                 |----------------/------------> x
  1224.                /                /
  1225.               /        4_______/5 ___
  1226.              /         /.    . /|  ^
  1227.             /         / .   . / |
  1228.            /         /_______/  |  Height
  1229.           /          7  . .  6  |
  1230.          /           |  ..   |  |
  1231.   FofZ _/            |  0....|..1 _v___
  1232.        /             | .     | /     dz
  1233.       z              3_______2/_____/
  1234.                      |       |
  1235.                      |<  dX >|            }
  1236.   procedure DrawCol(FofX, Height, dX: Integer; FofZ, dz: TPoint);
  1237.   var
  1238.     Vertex: array[0..7] of TPoint;
  1239.   begin
  1240. {calculate the positions of the six required vertices in the above diagram:}
  1241.     Vertex[0].x := FofX + FofZ.x;
  1242.     Vertex[4].x := Vertex[0].x;
  1243.     Vertex[3].x := Vertex[4].x + dz.x;
  1244.     Vertex[7].x := Vertex[3].x;
  1245.     Vertex[2].x := Vertex[3].x + dX;
  1246.     Vertex[6].x := Vertex[2].x;
  1247.     Vertex[1].x := Vertex[4].x + dX;
  1248.     Vertex[5].x := Vertex[1].x;
  1249.  
  1250.     Vertex[0].y := FYAxis.Bottom + FofZ.y;
  1251.     Vertex[1].y := Vertex[0].y;
  1252.     Vertex[2].y := Vertex[1].y + dz.y;
  1253.     Vertex[3].y := Vertex[2].y;
  1254.     Vertex[4].y := Vertex[0].y + Height;
  1255.     Vertex[5].y := Vertex[4].y;
  1256.     Vertex[6].y := Vertex[2].y + Height;
  1257.     Vertex[7].y := Vertex[6].y;
  1258. {The rectangular front face:}
  1259.     ACanvas.Rectangle(Vertex[7].x, Vertex[7].y, Vertex[2].x, Vertex[2].y);
  1260. {Make it a little darker:}
  1261.     ACanvas.Brush.Color := GetDarkerColor(ACanvas.Brush.Color, 80);
  1262. {Draw the top parallelogram:}
  1263.     ACanvas.Polygon([Vertex[6], Vertex[7], Vertex[4], Vertex[5]]);
  1264. {Make it a little darker again:}
  1265.     ACanvas.Brush.Color := GetDarkerColor(ACanvas.Brush.Color, 70);
  1266. {Draw the right or left parallelogram:}
  1267.     if (dz.x > 0) then
  1268.       ACanvas.Polygon([Vertex[0], Vertex[3], Vertex[7], Vertex[4]]) {left}
  1269.      else
  1270.       ACanvas.Polygon([Vertex[1], Vertex[2], Vertex[6], Vertex[5]]); {right}
  1271.   end;
  1272.  
  1273. begin
  1274. {$IFDEF DELPHI3_UP}
  1275.   Assert(ACanvas <> nil, 'TSeriesList.Draw3DColumn: ' + sACanvasIsNil);
  1276.   Assert(ZAxis <> nil, 'TSeriesList.Draw3DColumn: ' + sDraw3D);
  1277. {$ENDIF}
  1278.  
  1279. {Draw each individual series:}
  1280. {NB: the ZData values better be in ascending order !}
  1281. {set dz in case there is only one series !}
  1282.   dz := ZAxis.dFofZ(1);
  1283.   for iSeries := 0 to Self.Count-1 do
  1284.   begin
  1285.     pSeries := TSeries(Items[iSeries]);
  1286.     if ((pSeries.NoPts > 0) and
  1287.         (pSeries.Visible)) then
  1288.     begin
  1289.       ACanvas.Pen.Assign(pSeries.Pen);
  1290.       ACanvas.Brush.Assign(pSeries.Brush);
  1291. {the Z co-ordinates are the same for every point in the series:}
  1292.       FofZ := ZAxis.dFofZ(pSeries.ZData);
  1293.       if (iSeries < Self.Count-1) then
  1294.       begin
  1295.         FofZp1 := ZAxis.dFofZ(TSeries(Items[iSeries+1]).ZData);
  1296.         dz.x := (FofZp1.x - FofZ.x) * (100 - ColumnGap) div 100;
  1297.         dz.y := (FofZp1.y - FofZ.y) * (100 - ColumnGap) div 100;
  1298.       end;
  1299. {the starting X Point is:}
  1300.       iXp1 := pSeries.XAxis.FofX(pSeries.XData^[0]);
  1301. {loop over every point in each series:}
  1302.       for j := 0 to pSeries.NoPts-2 do
  1303.       begin
  1304.         ACanvas.Brush.Color := pSeries.Brush.Color;
  1305.         iX := iXp1;
  1306.         iXp1 := pSeries.XAxis.FofX(pSeries.XData^[j+1]);
  1307.         dX := (iXp1-iX) * (100 - ColumnGap) div 100;
  1308.         FofX := pSeries.XAxis.FofX(pSeries.XData^[j]);
  1309.         Height := pSeries.YAxis.FofY(pSeries.YData^[j]) - pSeries.YAxis.Bottom;// FXAxis.MidY;
  1310.         DrawCol(FofX, Height, dX, FofZ, dz);
  1311.       end; {for j}
  1312.     end; {NoPts > 0 & Visible}
  1313.   end; {for iSeries}
  1314. end;
  1315.  
  1316. {------------------------------------------------------------------------------
  1317.     Procedure: TSeriesList.Draw3DContour
  1318.   Description: standard Drawing procedure for 3D contour graphs
  1319.        Author: Mat Ballard
  1320.  Date created: 03/06/2001
  1321. Date modified: 03/06/2001 by Mat Ballard
  1322.       Purpose: This draws all the series as a 3D contour graph
  1323.  Known Issues: Minor glitch that results in small "holes" - reason unknown
  1324.       Comment: Note that the paradigm is Y = F(X, Z).
  1325.                That is, X and Z are the independent variables, and Y is the function.
  1326.                Colour is based on Y Values, not Z.
  1327.  ------------------------------------------------------------------------------}
  1328. procedure TSeriesList.Draw3DContour(
  1329.   ACanvas: TCanvas;
  1330.   ZAxis: TAngleAxis;
  1331.   ContourDetail: TContourDetail;
  1332.   ContourWires: Boolean);
  1333. var
  1334.   i, iSeries: Integer;
  1335.   ZFraction,
  1336.   TheMin,
  1337.   Span: Single;
  1338. {The original 3D data points:}
  1339.   Pt00, Pt01, Pt10, Pt11, PtCentre: T3DRealPoint;
  1340. {The transformed 2D screen points:}
  1341.   Q00, Q10, Q11, Q01, QCentre: TPoint;
  1342.   pSeries0,
  1343.   pSeries1: TSeries;
  1344.  
  1345. {Transform the 3D point to the 2D drawing surface:}
  1346.   function Trans3D2(APoint: T3DRealPoint): TPoint;
  1347.   var
  1348.     ZShift: TPoint;
  1349.   begin
  1350.     ZShift := ZAxis.dFofZ(APoint.Z);
  1351.     Result.x := FXAxis.FofX(APoint.X) + ZShift.x;
  1352.     Result.y := FYAxis.FofY(APoint.Y) + ZShift.y;
  1353.   end;
  1354.  
  1355. {This procedure performs a simple, single color rendition of a triangle:}
  1356.   procedure BltTriangle(Q00, Q10, Q11: TPoint; ZValue: Single);
  1357.   begin
  1358.     ACanvas.Pen.Color := Misc.Rainbow(ZValue);
  1359.     ACanvas.Brush.Color := ACanvas.Pen.Color;
  1360.     ACanvas.Polygon([Q00, Q10, Q11]);
  1361.   end;
  1362.  
  1363. {This procedure performs a complex, three-color graded rendition of a triangle:}
  1364.   procedure RenderTriangle(Pt1, Pt2, Pt3: T3DRealPoint);
  1365.   var
  1366.     ii, nY: Integer;
  1367.     p1, p2, p3: p3DRealPoint; //Highest, medium, lowest points in Z direction
  1368. {Sides of the triangle:}
  1369.     P1P2, P1P3, P2P3,
  1370. {Pj is the left side of the triangle, Pk the right:}
  1371.     Pj, Pk, PjM1, PkM1: T3DRealPoint; //PjM1 = "P(j - 1)"
  1372.     dY, dxj, dzj, dxk, dzk: Single;
  1373.   begin
  1374. {sort the bastards first, on _Y_ values:}
  1375.     SortTriangleVerticesOnY(Pt1, Pt2, Pt3, p1, p2, p3);
  1376.  
  1377. {create the line vectors of the sides of the triangle:}
  1378.     P1P2.X := p2^.X - p1^.X;
  1379.     P1P2.Y := p2^.Y - p1^.Y;
  1380.     P1P2.Z := p2^.Z - p1^.Z;
  1381.     P1P3.X := p3^.X - p1^.X;
  1382.     P1P3.Y := p3^.Y - p1^.Y;
  1383.     P1P3.Z := p3^.Z - p1^.Z;
  1384.     P2P3.X := p3^.X - p2^.X;
  1385.     P2P3.Y := p3^.Y - p2^.Y;
  1386.     P2P3.Z := p3^.Z - p2^.Z;
  1387.  
  1388. {Get ready to loop: set the Pj and Pk to p1, the highest point:}
  1389.     Pj.X := p1^.X;
  1390.     Pj.Y := p1^.Y;
  1391.     Pj.Z := p1^.Z;
  1392.     Pk.X := p1^.X;
  1393.     Pk.Y := p1^.Y;
  1394.     Pk.Z := p1^.Z;
  1395.     Q10 := Trans3D2(Pj);
  1396.     Q11 := Q10;
  1397.  
  1398. {Estimate a colour granularity:}
  1399.     dY := Span / COLOUR_GRANULARITY;
  1400.     nY := Abs(Round(P1P3.Y / dY));
  1401.     if (nY < 2) then
  1402.       nY := 2;
  1403.     dY := P1P2.Y / nY;
  1404.     dxj := P1P2.X / nY;
  1405.     dzj := P1P2.Z / nY;
  1406.     dxk := P1P3.X * (dY / P1P3.Y);
  1407.     dzk := P1P3.Z * (dY / P1P3.Y);
  1408.  
  1409.     for ii := 1 to nY do
  1410.     begin
  1411. {Update last points:}
  1412.       PjM1.X := Pj.X;
  1413.       PjM1.Y := Pj.Y;
  1414.       PjM1.Z := Pj.Z;
  1415.       PkM1.X := Pk.X;
  1416.       PkM1.Y := Pk.Y;
  1417.       PkM1.Z := Pk.Z;
  1418.       Q00 := Q10;
  1419.       Q01 := Q11;
  1420. {move so that colour changes by dY:}
  1421.       Pj.Y := Pj.Y + dY;
  1422.       Pj.X := p1^.X + ii * dxj;
  1423.       Pj.Z := p1^.Z + ii * dzj;
  1424.       Pk.Y := Pj.Y;
  1425.       Pk.X := p1^.X + ii * dxk;
  1426.       Pk.Z := p1^.Z + ii * dzk;
  1427.  
  1428. {Draw:}
  1429.       ZFraction := (PjM1.Y - TheMin) / Span;
  1430.       ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1431.       ACanvas.Brush.Color := ACanvas.Pen.Color;
  1432.       Q10 := Trans3D2(Pj);
  1433.       Q11 := Trans3D2(Pk);
  1434.       if ((Q10.x <> Q00.x) or (Q10.y <> Q00.y) or
  1435.           (Q11.x <> Q01.x) or (Q11.y <> Q01.y)) then
  1436.         ACanvas.Polygon([Q00, Q01, Q11, Q10]);
  1437.     end; {for}
  1438.  
  1439. {Estimate a colour granularity:}
  1440.     dY := Span / COLOUR_GRANULARITY;
  1441.     nY := Abs(Round(P2P3.Y / dY));
  1442.     if (nY < 2) then
  1443.       nY := 2;
  1444.     dY := P2P3.Y / nY;
  1445.     dxj := P2P3.X / nY;
  1446.     dzj := P2P3.Z / nY;
  1447.  
  1448.     for ii := 1 to nY do
  1449.     begin
  1450. {Update last points:}
  1451.       PjM1.X := Pj.X;
  1452.       PjM1.Y := Pj.Y;
  1453.       PjM1.Z := Pj.Z;
  1454.       PkM1.X := Pk.X;
  1455.       PkM1.Y := Pk.Y;
  1456.       PkM1.Z := Pk.Z;
  1457.       Q00 := Q10;
  1458.       Q01 := Q11;
  1459. {move so that colour changes by dY:}
  1460.       Pj.Y := Pj.Y + dY;
  1461.       Pj.X := p2^.X + ii * dxj;
  1462.       Pj.Z := p2^.Z + ii * dzj;
  1463. {Calculate position of same Z on long right side:}
  1464.       Pk.Y := Pj.Y;
  1465.       ZFraction := (Pk.Y-p1^.Y)/(p3^.Y-p1^.Y);
  1466.       Pk.X := p1^.X + ZFraction * P1P3.X;
  1467.       Pk.Z := p1^.Z + ZFraction * P1P3.Z;
  1468. {Draw:}
  1469.       ZFraction := (PjM1.Y - TheMin) / Span;
  1470.       ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1471.       ACanvas.Brush.Color := ACanvas.Pen.Color;
  1472.       Q10 := Trans3D2(Pj);
  1473.       Q11 := Trans3D2(Pk);
  1474.       if ((Q10.x <> Q00.x) or (Q10.y <> Q00.y) or
  1475.           (Q11.x <> Q01.x) or (Q11.y <> Q01.y)) then
  1476.         ACanvas.Polygon([Q00, Q01, Q11, Q10]);
  1477.     end; {for}
  1478.   end;
  1479.  
  1480. begin
  1481. {$IFDEF DELPHI3_UP}
  1482.   Assert(ACanvas <> nil, 'TSeriesList.DrawContour: ' + sACanvasIsNil);
  1483. {$ENDIF}
  1484.  
  1485.   TheMin := Self.Ymin;
  1486.   Span := Self.YMax - TheMin;
  1487.   ACanvas.Brush.Style := bsSolid;
  1488.  
  1489. {Draw each individual series:}
  1490.   for iSeries := 0 to Self.Count-1 do
  1491.   begin
  1492.     pSeries0 := TSeries(Items[iSeries]);
  1493.     if (pSeries0.NoPts > 0) then
  1494.     begin
  1495. {Assign the bottom-left point:}
  1496.       Pt00.X := pSeries0.XData^[0];
  1497.       Pt00.Y := pSeries0.YData^[0];
  1498.       Pt00.Z := pSeries0.ZData;
  1499.       if (iSeries < Count-1) then
  1500.       begin
  1501.         pSeries1 := TSeries(Items[iSeries+1]);
  1502.         if (pSeries1.NoPts > 0) then
  1503.         begin
  1504. {Assign the top-left point:}
  1505.           Pt10.X := pSeries1.XData^[0];
  1506.           Pt10.Y := pSeries1.YData^[0];
  1507.           Pt10.Z := pSeries1.ZData;
  1508.         end;
  1509.       end;
  1510.  
  1511.       for i := 1 to pSeries0.NoPts-1 do
  1512.       begin
  1513. {Assign the bottom-right point:}
  1514.         Pt01.X := pSeries0.XData^[i];
  1515.         Pt01.Y := pSeries0.YData^[i];
  1516.         Pt01.Z := pSeries0.ZData;
  1517. {Oh yes it was: Delphi ain't that smart.}
  1518.         if ((iSeries < Count-1) and (pSeries1.NoPts > 0)) then
  1519.         begin
  1520. {Assign the top-right point:}
  1521.           Pt11.X := pSeries1.XData^[i];
  1522.           Pt11.Y := pSeries1.YData^[i];
  1523.           Pt11.Z := pSeries1.ZData;
  1524.  
  1525. {Calculate the centrum:}
  1526.           PtCentre.X := (Pt00.X + Pt01.X + Pt10.X + Pt11.X) / 4;
  1527.           PtCentre.Y := (Pt00.Y + Pt01.Y + Pt10.Y + Pt11.Y) / 4;
  1528. {Oh yes it was: see above}
  1529.           PtCentre.Z := (Pt00.Z + Pt01.Z + Pt10.Z + Pt11.Z) / 4;
  1530.  
  1531. {just how detailed will the plot be ?}
  1532.           case ContourDetail of
  1533.             cdLow:
  1534.               begin
  1535. {No triangles; base colour on the Y Value:}
  1536.                 ZFraction := (PtCentre.Y - TheMin) / Span;
  1537.                 ACanvas.Brush.Color := Misc.Rainbow(ZFraction);
  1538.                 if (ContourWires) then
  1539.                   ACanvas.Pen.Color := clBlack
  1540.                  else
  1541.                   ACanvas.Pen.Color := ACanvas.Brush.Color;
  1542.                 ACanvas.Polygon([
  1543.                   Trans3D2(Pt00),
  1544.                   Trans3D2(Pt10),
  1545.                   Trans3D2(Pt11),
  1546.                   Trans3D2(Pt01)]);
  1547.               end;
  1548.  
  1549.             cdMedium:
  1550.               begin
  1551.                 Q00 := Trans3D2(Pt00);
  1552.                 Q10 := Trans3D2(Pt10);
  1553.                 Q11 := Trans3D2(Pt11);
  1554.                 Q01 := Trans3D2(Pt01);
  1555.                 QCentre := Trans3D2(PtCentre);
  1556. {Left triangle, then Bottom, Top then Right:}
  1557.                 ZFraction := ((Pt00.Y + Pt10.Y + PtCentre.Y)/3-TheMin) / Span;
  1558.                 BltTriangle(Q00, Q10, QCentre, ZFraction);
  1559.                 ZFraction := ((Pt00.Y + Pt01.Y + PtCentre.Y)/3-TheMin) / Span;
  1560.                 BltTriangle(Q00, Q01, QCentre, ZFraction);
  1561.                 ZFraction := ((Pt10.Y + Pt11.Y + PtCentre.Y)/3-TheMin) / Span;
  1562.                 BltTriangle(Q10, Q11, QCentre, ZFraction);
  1563.                 ZFraction := ((Pt01.Y + Pt11.Y + PtCentre.Y)/3-TheMin) / Span;
  1564.                 BltTriangle(Q01, Q11, QCentre, ZFraction);
  1565.                 if (ContourWires) then
  1566.                 begin
  1567.                   ACanvas.Pen.Color := clBlack;
  1568.                   ACanvas.PolyLine([
  1569.                     Trans3D2(Pt00),
  1570.                     Trans3D2(Pt10),
  1571.                     Trans3D2(Pt11),
  1572.                     Trans3D2(Pt01),
  1573.                     Trans3D2(Pt00)]);
  1574.                 end;
  1575.               end;
  1576.  
  1577.             cdHigh:
  1578.               begin
  1579. {Left triangle, then Bottom, Top then Right:}
  1580.                 RenderTriangle(Pt00, Pt10, PtCentre);
  1581.                 RenderTriangle(Pt00, Pt01, PtCentre);
  1582.                 RenderTriangle(Pt10, Pt11, PtCentre);
  1583.                 RenderTriangle(Pt01, Pt11, PtCentre);
  1584.                 if (ContourWires) then
  1585.                 begin
  1586.                   ACanvas.Pen.Color := clBlack;
  1587.                   ACanvas.PolyLine([
  1588.                     Trans3D2(Pt00),
  1589.                     Trans3D2(Pt10),
  1590.                     Trans3D2(Pt11),
  1591.                     Trans3D2(Pt01),
  1592.                     Trans3D2(Pt00)]);
  1593.                 end;
  1594.               end;
  1595.           end; {case}
  1596.  
  1597. {update values:}
  1598.           Pt10.x := Pt11.x;
  1599.           Pt10.y := Pt11.y;
  1600.           Pt10.Z := Pt11.Z;
  1601.         end; {not the final series}
  1602.         Pt00.x := Pt01.x;
  1603.         Pt00.y := Pt01.y;
  1604.         Pt00.Z := Pt01.Z;
  1605.       end; {loop over points}
  1606.     end; {NoPts}
  1607.   end; {over every series}
  1608. end;
  1609.  
  1610. {------------------------------------------------------------------------------
  1611.     Procedure: TSeriesList.DrawContour
  1612.   Description: standard Drawing procedure for 2.5D contour graphs
  1613.        Author: Mat Ballard
  1614.  Date created: 01/24/2001
  1615. Date modified: 07/15/2001 by Mat Ballard
  1616.       Purpose: This draws all the series as a 2.5D coloured contour graph
  1617.  Known Issues: Note that Y data is plotted as the color, and the Z data
  1618.                as screen Y
  1619.  ------------------------------------------------------------------------------}
  1620. procedure TSeriesList.DrawContour(
  1621.   ACanvas: TCanvas; ContourDetail: TContourDetail);
  1622. var
  1623.   i, iSeries: Integer;
  1624.   ZFraction,
  1625.   TheMin,
  1626.   Span: Single;
  1627.   Pt00, Pt01, Pt10, Pt11, PtCentre: T3DZPoint;
  1628.   pSeries0,
  1629.   pSeries1: TSeries;
  1630.  
  1631. {This procedure performs a simple, single color rendition of a triangle:}
  1632.   procedure BltTriangle(Pt1, Pt2, Pt3: T3DZPoint);
  1633.   begin
  1634.     ZFraction := ((Pt1.Z + Pt2.Z + Pt3.Z)/3-TheMin) / Span;
  1635.     ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1636.     ACanvas.Brush.Color := ACanvas.Pen.Color;
  1637.     ACanvas.Polygon([Point(Pt1.x,Pt1.y), Point(Pt2.x,Pt2.y), Point(Pt3.x,Pt3.y)]);
  1638.   end;
  1639.  
  1640. {This procedure performs a complex, three-color graded rendition of a triangle:}
  1641.   procedure RenderTriangle(Pt1, Pt2, Pt3: T3DZPoint);
  1642.   var
  1643.     ii, nZ: Integer;
  1644.     p1, p2, p3: p3DZPoint; //Highest, medium, lowest points in Z direction
  1645. {Sides of the triangle:}
  1646.     P1P2, P1P3, P2P3,
  1647. {Pj is the left side of the triangle, Pk the right:}
  1648.     Pj, Pk, PjM1, PkM1: T3DZPoint; //PjM1 = "P(j - 1)"
  1649.     dZ, dxj, dyj, dxk, dyk: Single;
  1650.   begin
  1651. {sort the bastards first, on Z values:}
  1652.     SortTriangleVertices(Pt1, Pt2, Pt3, p1, p2, p3);
  1653.  
  1654. {create the line vectors of the sides of the triangle:}
  1655.     P1P2.x := p2^.x - p1^.x;
  1656.     P1P2.y := p2^.y - p1^.y;
  1657.     P1P2.Z := p2^.Z - p1^.Z;
  1658.     P1P3.x := p3^.x - p1^.x;
  1659.     P1P3.y := p3^.y - p1^.y;
  1660.     P1P3.Z := p3^.Z - p1^.Z;
  1661.     P2P3.x := p3^.x - p2^.x;
  1662.     P2P3.y := p3^.y - p2^.y;
  1663.     P2P3.Z := p3^.Z - p2^.Z;
  1664.  
  1665. {Get ready to loop: set the Pj and Pk to p1, the highest point:}
  1666.     Pj.x := p1^.x;
  1667.     Pj.y := p1^.y;
  1668.     Pj.Z := p1^.Z;
  1669.     Pk.x := p1^.x;
  1670.     Pk.y := p1^.y;
  1671.     Pk.Z := p1^.Z;
  1672.  
  1673. {Draw the apex:}
  1674.     //ZFraction := (p1^.Z - TheMin) / Span;
  1675. {$IFDEF MSWINDOWS}
  1676.     //ACanvas.Pixels[p1^.x, p1^.y] := Misc.Rainbow(ZFraction);
  1677. {$ENDIF}
  1678. {$IFDEF LINUX}
  1679.     //ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1680.     //ACanvas.DrawPoint(p1^.x, p1^.y);
  1681. {$ENDIF}
  1682.  
  1683. {Estimate a colour granularity:}
  1684.     dZ := Span / COLOUR_GRANULARITY;
  1685.     nZ := Abs(Round(P1P3.Z / dZ));
  1686.     if (nZ < 2) then
  1687.       nZ := 2;
  1688.     dZ := P1P2.Z / nZ;
  1689.     dxj := P1P2.x / nZ;
  1690.     dyj := P1P2.y / nZ;
  1691.     dxk := P1P3.x * (dZ / P1P3.Z);
  1692.     dyk := P1P3.y * (dZ / P1P3.Z);
  1693.  
  1694.     for ii := 1 to nZ do
  1695.     begin
  1696. {Update last points:}
  1697.       PjM1.x := Pj.x;
  1698.       PjM1.y := Pj.y;
  1699.       PjM1.Z := Pj.Z;
  1700.       PkM1.x := Pk.x;
  1701.       PkM1.y := Pk.y;
  1702.       PkM1.Z := Pk.Z;
  1703. {move so that colour changes by dZ:}
  1704.       Pj.Z := Pj.Z + dZ;
  1705.       Pj.x := p1^.x + Round(ii * dxj);
  1706.       Pj.y := p1^.y + Round(ii * dyj);
  1707.       Pk.Z := Pj.Z; //Pk.Z + dZ;
  1708.       Pk.x := p1^.x + Round(ii * dxk);
  1709.       Pk.y := p1^.y + Round(ii * dyk);
  1710. {Draw:}
  1711.       ZFraction := (PjM1.Z - TheMin) / Span;
  1712.       ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1713.       ACanvas.Brush.Color := ACanvas.Pen.Color;
  1714.       if ((Pj.x <> PjM1.x) or (Pj.y <> PjM1.y) or
  1715.           (Pk.x <> PkM1.x) or (Pk.y <> PkM1.y)) then
  1716.         ACanvas.Polygon([
  1717.           Point(PjM1.x,PjM1.y),
  1718.           Point(PkM1.x,PkM1.y),
  1719.           Point(Pk.x,Pk.y),
  1720.           Point(Pj.x,Pj.y)]);
  1721.     end; {for}
  1722.  
  1723. {Estimate a colour granularity:}
  1724.     dZ := Span / COLOUR_GRANULARITY;
  1725.     nZ := Abs(Round(P2P3.Z / dZ));
  1726.     if (nZ < 2) then
  1727.       nZ := 2;
  1728.     dZ := P2P3.Z / nZ;
  1729.     dxj := P2P3.x / nZ;
  1730.     dyj := P2P3.y / nZ;
  1731.  
  1732.     for ii := 1 to nZ do
  1733.     begin
  1734. {Update last points:}
  1735.       PjM1.x := Pj.x;
  1736.       PjM1.y := Pj.y;
  1737.       PjM1.Z := Pj.Z;
  1738.       PkM1.x := Pk.x;
  1739.       PkM1.y := Pk.y;
  1740.       PkM1.Z := Pk.Z;
  1741. {move so that colour changes by dZ:}
  1742.       Pj.Z := Pj.Z + dZ;
  1743.       Pj.x := p2^.x + Round(ii * dxj);
  1744.       Pj.y := p2^.y + Round(ii * dyj);
  1745.       Pk.Z := Pj.Z;
  1746. {Calculate position of same Z on long right side:}
  1747.       ZFraction := (Pk.Z-p1^.Z)/(p3^.Z-p1^.Z);
  1748.       Pk.x := p1^.x + Round(ZFraction * P1P3.x);
  1749.       Pk.y := p1^.y + Round(ZFraction * P1P3.y);
  1750. {Draw:}
  1751.       ZFraction := (PjM1.Z - TheMin) / Span;
  1752.       ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1753.       ACanvas.Brush.Color := ACanvas.Pen.Color;
  1754.       if ((Pj.x <> PjM1.x) or (Pj.y <> PjM1.y) or
  1755.           (Pk.x <> PkM1.x) or (Pk.y <> PkM1.y)) then
  1756.         ACanvas.Polygon([
  1757.           Point(PjM1.x,PjM1.y),
  1758.           Point(PkM1.x,PkM1.y),
  1759.           Point(Pk.x,Pk.y),
  1760.           Point(Pj.x,Pj.y)]);
  1761.     end; {for}
  1762.   end;
  1763.  
  1764. begin
  1765. {$IFDEF DELPHI3_UP}
  1766.   Assert(ACanvas <> nil, 'TSeriesList.DrawContour: ' + sACanvasIsNil);
  1767. {$ENDIF}
  1768.  
  1769.   TheMin := Self.Ymin;
  1770.   Span := Self.YMax - TheMin;
  1771.   ACanvas.Brush.Style := bsSolid;
  1772.  
  1773. {Draw each individual series:}
  1774.   for iSeries := 0 to Self.Count-1 do
  1775.   begin
  1776.     pSeries0 := TSeries(Items[iSeries]);
  1777.     if (pSeries0.NoPts > 0) then
  1778.     begin
  1779. {Assign the bottom-left point:}
  1780.       Pt00.x := pSeries0.XAxis.FofX(pSeries0.XData^[0]);
  1781. {Note: for contours, we plot the Y data as a function of X and Z,
  1782.  so that Y determines the color, and Z determines the screen Y value.
  1783.  The reason for this is that most data will be in the form of scans (series)
  1784.  at repeated times.}
  1785.       Pt00.y := pSeries0.YAxis.FofY(pSeries0.ZData);
  1786.       Pt00.Z := pSeries0.YData^[0];
  1787.       if (iSeries < Count-1) then
  1788.       begin
  1789.         pSeries1 := TSeries(Items[iSeries+1]);
  1790.         if (pSeries1.NoPts > 0) then
  1791.         begin
  1792. {Assign the top-left point:}
  1793.           Pt10.x := pSeries1.XAxis.FofX(pSeries1.XData^[0]);
  1794.           Pt10.y := pSeries1.YAxis.FofY(pSeries1.ZData);
  1795.           Pt10.Z := pSeries1.YData^[0];
  1796.         end;
  1797.       end;
  1798.  
  1799.       for i := 1 to pSeries0.NoPts-1 do
  1800.       begin
  1801. {Assign the bottom-right point:}
  1802.         Pt01.x := pSeries0.XAxis.FofX(pSeries0.XData^[i]);
  1803.         Pt01.y := pSeries0.YAxis.FofY(pSeries0.ZData);
  1804.         Pt01.Z := pSeries0.YData^[i];
  1805. {Oh yes it was: Delphi ain't that smart.}
  1806.         if ((iSeries < Count-1) and (pSeries1.NoPts > 0)) then
  1807.         begin
  1808. {Assign the top-right point:}
  1809.           Pt11.x := pSeries1.XAxis.FofX(pSeries1.XData^[i]);
  1810.           Pt11.y := pSeries1.YAxis.FofY(pSeries1.ZData);
  1811.           Pt11.Z := pSeries1.YData^[i];
  1812.  
  1813. {Calculate the centrum:}
  1814.           PtCentre.x := (Pt00.x + Pt01.x + Pt10.x + Pt11.x) div 4;
  1815.           PtCentre.y := (Pt00.y + Pt01.y + Pt10.y + Pt11.y) div 4;
  1816. {Oh yes it was: see above}
  1817.           PtCentre.Z := (Pt00.Z + Pt01.Z + Pt10.Z + Pt11.Z) / 4;
  1818.  
  1819. {just how detailed will the plot be ?}
  1820.           case ContourDetail of
  1821.             cdLow:
  1822.               begin
  1823. {No triangles:}
  1824.                 ZFraction := (PtCentre.Z - TheMin) / Span;
  1825.                 ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  1826.                 ACanvas.Brush.Color := ACanvas.Pen.Color;
  1827.                 ACanvas.Polygon([
  1828.                   Point(Pt00.x,Pt00.y),
  1829.                   Point(Pt10.x,Pt10.y),
  1830.                   Point(Pt11.x,Pt11.y),
  1831.                   Point(Pt01.x,Pt01.y)]);
  1832.               end;
  1833.  
  1834.             cdMedium:
  1835.               begin
  1836. {Left triangle, then Bottom, Top then Right:}
  1837.                 BltTriangle(Pt00, Pt10, PtCentre);
  1838.                 BltTriangle(Pt00, Pt01, PtCentre);
  1839.                 BltTriangle(Pt10, Pt11, PtCentre);
  1840.                 BltTriangle(Pt01, Pt11, PtCentre);
  1841.               end;
  1842.  
  1843.             cdHigh:
  1844.               begin
  1845. {Left triangle, then Bottom, Top then Right:}
  1846.                 RenderTriangle(Pt00, Pt10, PtCentre);
  1847.                 RenderTriangle(Pt00, Pt01, PtCentre);
  1848.                 RenderTriangle(Pt10, Pt11, PtCentre);
  1849.                 RenderTriangle(Pt01, Pt11, PtCentre);
  1850.               end;
  1851.           end; {case}
  1852.  
  1853. {update values:}
  1854.           Pt10.x := Pt11.x;
  1855.           Pt10.y := Pt11.y;
  1856.           Pt10.Z := Pt11.Z;
  1857.         end; {not the final series}
  1858.         Pt00.x := Pt01.x;
  1859.         Pt00.y := Pt01.y;
  1860.         Pt00.Z := Pt01.Z;
  1861.       end; {loop over points}
  1862.       {ACanvas.MoveTo(Pt00.x, Pt00.y);
  1863.       ACanvas.LineTo(Pt10.x, Pt10.y);}
  1864.     end; {NoPts}
  1865.   end; {over every series}
  1866.  
  1867. {draw the color scale:}
  1868.   DrawColorScale(ACanvas, TheMin, Span, ContourDetail);
  1869. end;
  1870.  
  1871. {------------------------------------------------------------------------------
  1872.     Procedure: TSeriesList.SortTriangleVertices
  1873.   Description: sorts the vertices of a triangle from highest Z value to lowest
  1874.        Author: Mat Ballard
  1875.  Date created: 07/17/2001
  1876. Date modified: 07/17/2001 by Mat Ballard
  1877.       Purpose: 3D rendering
  1878.  Known Issues: Pti have to be var, so that the location is passed.
  1879.  ------------------------------------------------------------------------------}
  1880. procedure TSeriesList.SortTriangleVertices(var Pt1, Pt2, Pt3: T3DZPoint; var p1, p2, p3: p3DZPoint);
  1881. begin
  1882.   if (Pt1.Z >= Pt2.Z) then
  1883.   begin
  1884.     if (Pt2.Z >= Pt3.Z) then
  1885.     begin
  1886.       p1 := @Pt1;
  1887.       p2 := @Pt2;
  1888.       p3 := @Pt3;
  1889.     end
  1890.     else
  1891.     begin {2 < 3:}
  1892.       if (Pt1.Z >= Pt3.Z) then
  1893.       begin
  1894.         p1 := @Pt1;
  1895.         p2 := @Pt3;
  1896.         p3 := @Pt2;
  1897.       end
  1898.       else
  1899.       begin
  1900.         p1 := @Pt3;
  1901.         p2 := @Pt1;
  1902.         p3 := @Pt2;
  1903.       end;
  1904.     end;
  1905.   end
  1906.   else
  1907.   begin {1 < 2}
  1908.     if (Pt2.Z < Pt3.Z) then
  1909.     begin
  1910.       p1 := @Pt3;
  1911.       p2 := @Pt2;
  1912.       p3 := @Pt1;
  1913.     end
  1914.     else
  1915.     begin {1 < 2 >= 3:}
  1916.       if (Pt1.Z >= Pt3.Z) then
  1917.       begin
  1918.         p1 := @Pt2;
  1919.         p2 := @Pt1;
  1920.         p3 := @Pt3;
  1921.       end
  1922.       else
  1923.       begin
  1924.         p1 := @Pt2;
  1925.         p2 := @Pt3;
  1926.         p3 := @Pt1;
  1927.       end;
  1928.     end;
  1929.   end;
  1930. end;
  1931.  
  1932. {------------------------------------------------------------------------------
  1933.     Procedure: TSeriesList.SortTriangleVerticesOnY
  1934.   Description: sorts the vertices of a triangle from highest Y value to lowest
  1935.        Author: Mat Ballard
  1936.  Date created: 07/17/2001
  1937. Date modified: 07/17/2001 by Mat Ballard
  1938.       Purpose: 3D rendering
  1939.  Known Issues: Pti have to be var, so that the location is passed.
  1940.  ------------------------------------------------------------------------------}
  1941. procedure TSeriesList.SortTriangleVerticesOnY(var Pt1, Pt2, Pt3: T3DRealPoint; var p1, p2, p3: p3DRealPoint);
  1942. begin
  1943.   if (Pt1.Y >= Pt2.Y) then
  1944.   begin
  1945.     if (Pt2.Y >= Pt3.Y) then
  1946.     begin
  1947.       p1 := @Pt1;
  1948.       p2 := @Pt2;
  1949.       p3 := @Pt3;
  1950.     end
  1951.     else
  1952.     begin {2 < 3:}
  1953.       if (Pt1.Y >= Pt3.Y) then
  1954.       begin
  1955.         p1 := @Pt1;
  1956.         p2 := @Pt3;
  1957.         p3 := @Pt2;
  1958.       end
  1959.       else
  1960.       begin
  1961.         p1 := @Pt3;
  1962.         p2 := @Pt1;
  1963.         p3 := @Pt2;
  1964.       end;
  1965.     end;
  1966.   end
  1967.   else
  1968.   begin {1 < 2}
  1969.     if (Pt2.Y < Pt3.Y) then
  1970.     begin
  1971.       p1 := @Pt3;
  1972.       p2 := @Pt2;
  1973.       p3 := @Pt1;
  1974.     end
  1975.     else
  1976.     begin {1 < 2 >= 3:}
  1977.       if (Pt1.Y >= Pt3.Y) then
  1978.       begin
  1979.         p1 := @Pt2;
  1980.         p2 := @Pt1;
  1981.         p3 := @Pt3;
  1982.       end
  1983.       else
  1984.       begin
  1985.         p1 := @Pt2;
  1986.         p2 := @Pt3;
  1987.         p3 := @Pt1;
  1988.       end;
  1989.     end;
  1990.   end;
  1991. end;
  1992.  
  1993. {------------------------------------------------------------------------------
  1994.     Procedure: TSeriesList.DrawLineContour
  1995.   Description: standard Drawing procedure for 2.5D contour graphs
  1996.        Author: Mat Ballard
  1997.  Date created: 01/24/2001
  1998. Date modified: 07/15/2001 by Mat Ballard
  1999.       Purpose: This draws all the series as a 2.5D coloured contour graph
  2000.  Known Issues: Note that Y data is plotted as the color, and the Z data
  2001.                as screen Y
  2002.  ------------------------------------------------------------------------------}
  2003. procedure TSeriesList.DrawLineContour(
  2004.   ACanvas: TCanvas;
  2005.   ContourStart, ContourInterval: Single;
  2006.   ContourDetail: TContourDetail);
  2007. var
  2008.   i, iSeries: Integer;
  2009.   ZFraction,
  2010.   TheMin,
  2011.   Span: Single;
  2012.   Pt00, Pt01, Pt10, Pt11, PtCentre: T3DZPoint;
  2013.   pSeries0,
  2014.   pSeries1: TSeries;
  2015.  
  2016. {This procedure performs a complex, three-color graded rendition of a triangle:}
  2017.   procedure RenderTriangle(Pt1, Pt2, Pt3: T3DZPoint);
  2018.   var
  2019.     ii, mZ, nZ: Integer;
  2020.     p1, p2, p3: p3DZPoint; //Highest, medium, lowest points in Z direction
  2021. {Sides of the triangle:}
  2022.     P1P2, P1P3, P2P3,
  2023. {Pj is the left side of the triangle, Pk the right:}
  2024.     Pj, Pk: T3DZPoint; //PjM1 = "P(j - 1)"
  2025.     //dZ, dxj, dyj, dxk, dyk: Single;
  2026.   begin
  2027. {sort the bastards first, on Z values:}
  2028.     SortTriangleVertices(Pt1, Pt2, Pt3, p1, p2, p3);
  2029.  
  2030. {create the line vectors of the sides of the triangle:}
  2031.     P1P2.x := p2^.x - p1^.x;
  2032.     P1P2.y := p2^.y - p1^.y;
  2033.     P1P2.Z := p2^.Z - p1^.Z;
  2034.     P1P3.x := p3^.x - p1^.x;
  2035.     P1P3.y := p3^.y - p1^.y;
  2036.     P1P3.Z := p3^.Z - p1^.Z;
  2037.     P2P3.x := p3^.x - p2^.x;
  2038.     P2P3.y := p3^.y - p2^.y;
  2039.     P2P3.Z := p3^.Z - p2^.Z;
  2040.  
  2041. {Get ready to loop: }
  2042.     nZ := Round((p1^.Z - ContourStart) / ContourInterval);
  2043.     mZ := Round((p3^.Z - ContourStart) / ContourInterval);
  2044.  
  2045.     for ii := mZ to nZ do
  2046.     begin
  2047.       Pk.Z := ContourStart + ii * ContourInterval;
  2048.       if ((p3^.Z <= Pk.Z) and (Pk.Z <= p1^.Z)) then
  2049.       begin
  2050. {Calculate position of same Z on long right side:}
  2051.         ZFraction := (Pk.Z-p1^.Z) / P1P3.Z;
  2052.         Pk.x := p1^.x + Round(ZFraction * P1P3.x);
  2053.         Pk.y := p1^.y + Round(ZFraction * P1P3.y);
  2054. {Calculate position of same Z on left or bottom side:}
  2055.         Pj.Z := Pk.Z;
  2056.         if (Pj.Z >= p2^.Z) then
  2057.         begin {Left, upper half-triangle}
  2058.           ZFraction := (Pj.Z-p1^.Z) / P1P2.Z;
  2059.           Pj.x := p1^.x + Round(ZFraction * P1P2.x);
  2060.           Pj.y := p1^.y + Round(ZFraction * P1P2.y);
  2061.         end
  2062.         else
  2063.         begin {Bottom, lower half-triangle}
  2064.           ZFraction := (Pj.Z-p2^.Z) / P2P3.Z;
  2065.           Pj.x := p2^.x + Round(ZFraction * P2P3.x);
  2066.           Pj.y := p2^.y + Round(ZFraction * P2P3.y);
  2067.         end;
  2068. {Draw:}
  2069.         if (ContourDetail > cdMedium) then
  2070.         begin
  2071.           ZFraction := (Pj.Z - TheMin) / Span;
  2072.           ACanvas.Pen.Color := Misc.Rainbow(ZFraction);
  2073.         end;
  2074.         ACanvas.MoveTo(Pj.x,Pj.y);
  2075.         ACanvas.LineTo(Pk.x,Pk.y);
  2076.       end; {is in Z range}
  2077.     end; {for}
  2078.   end; {procedure RenderTriangle}
  2079.  
  2080. begin
  2081. {$IFDEF DELPHI3_UP}
  2082.   Assert(ACanvas <> nil, 'TSeriesList.DrawLineContour: ' + sACanvasIsNil);
  2083. {$ENDIF}
  2084.  
  2085.   TheMin := Self.YMin;
  2086.   Span := Self.YMax - ContourStart;
  2087.   //ACanvas.Brush.Style := bsSolid;
  2088.   if (ContourDetail = cdLow) then
  2089.     ACanvas.Pen.Color := clBlack;
  2090.   if (ContourDetail = cdMedium) then
  2091.     ACanvas.Pen.Color := TSeries(Items[0]).Pen.Color;
  2092.  
  2093. {Draw each individual series:}
  2094.   for iSeries := 0 to Self.Count-1 do
  2095.   begin
  2096.     pSeries0 := TSeries(Items[iSeries]);
  2097.     if (pSeries0.NoPts > 0) then
  2098.     begin
  2099. {Assign the bottom-left point:}
  2100.       Pt00.x := pSeries0.XAxis.FofX(pSeries0.XData^[0]);
  2101. {Note: for contours, we plot the Y data as a function of X and Z,
  2102.  so that Y determines the color, and Z determines the screen Y value.
  2103.  The reason for this is that most data will be in the form of scans (series)
  2104.  at repeated times.}
  2105.       Pt00.y := pSeries0.YAxis.FofY(pSeries0.ZData);
  2106.       Pt00.Z := pSeries0.YData^[0];
  2107.       if (iSeries < Count-1) then
  2108.       begin
  2109.         pSeries1 := TSeries(Items[iSeries+1]);
  2110.         if (pSeries1.NoPts > 0) then
  2111.         begin
  2112. {Assign the top-left point:}
  2113.           Pt10.x := pSeries1.XAxis.FofX(pSeries1.XData^[0]);
  2114.           Pt10.y := pSeries1.YAxis.FofY(pSeries1.ZData);
  2115.           Pt10.Z := pSeries1.YData^[0];
  2116.         end;
  2117.       end;
  2118.  
  2119.       for i := 1 to pSeries0.NoPts-1 do
  2120.       begin
  2121. {Assign the bottom-right point:}
  2122.         Pt01.x := pSeries0.XAxis.FofX(pSeries0.XData^[i]);
  2123.         Pt01.y := pSeries0.YAxis.FofY(pSeries0.ZData);
  2124.         Pt01.Z := pSeries0.YData^[i];
  2125. {Oh yes it was: Delphi ain't that smart.}
  2126.         if ((iSeries < Count-1) and (pSeries1.NoPts > 0)) then
  2127.         begin
  2128. {Assign the top-right point:}
  2129.           Pt11.x := pSeries1.XAxis.FofX(pSeries1.XData^[i]);
  2130.           Pt11.y := pSeries1.YAxis.FofY(pSeries1.ZData);
  2131.           Pt11.Z := pSeries1.YData^[i];
  2132.  
  2133. {Calculate the centrum:}
  2134.           PtCentre.x := (Pt00.x + Pt01.x + Pt10.x + Pt11.x) div 4;
  2135.           PtCentre.y := (Pt00.y + Pt01.y + Pt10.y + Pt11.y) div 4;
  2136. {Oh yes it was: see above}
  2137.           PtCentre.Z := (Pt00.Z + Pt01.Z + Pt10.Z + Pt11.Z) / 4;
  2138.  
  2139. {just how detailed will the plot be ?}
  2140.           RenderTriangle(Pt00, Pt10, PtCentre);
  2141.           RenderTriangle(Pt00, Pt01, PtCentre);
  2142.           RenderTriangle(Pt10, Pt11, PtCentre);
  2143.           RenderTriangle(Pt01, Pt11, PtCentre);
  2144.  
  2145. {update values:}
  2146.           Pt10.x := Pt11.x;
  2147.           Pt10.y := Pt11.y;
  2148.           Pt10.Z := Pt11.Z;
  2149.         end; {not the final series}
  2150.         Pt00.x := Pt01.x;
  2151.         Pt00.y := Pt01.y;
  2152.         Pt00.Z := Pt01.Z;
  2153.       end; {loop over points}
  2154.     end; {NoPts}
  2155.   end; {over every series}
  2156.  
  2157. {draw the color scale:}
  2158.   if (ContourDetail > cdMedium) then
  2159.   begin
  2160.     ACanvas.Brush.Style := bsSolid;
  2161.     DrawColorScale(ACanvas, TheMin, Span, ContourDetail);
  2162.   end;
  2163. end;
  2164.  
  2165. {------------------------------------------------------------------------------
  2166.     Procedure: TSeriesList.DrawColorScale
  2167.   Description: draws the Color Scale for Contour plots
  2168.        Author: Mat Ballard
  2169.  Date created: 03/06/2001
  2170. Date modified: 03/06/2001 by Mat Ballard
  2171.       Purpose: contour plot details
  2172.  Known Issues:
  2173.  ------------------------------------------------------------------------------}
  2174. procedure TSeriesList.DrawColorScale(ACanvas: TCanvas; TheMin, Span: Single; TheContourDetail: TContourDetail);
  2175. var
  2176.   i,
  2177.   iX, iXp1, iY,
  2178.   FontHeight: Integer;
  2179.   TheText: String;
  2180. begin
  2181.   iX := FXAxis.Right + FXAxis.Width div 50;
  2182.   iXp1 := iX + FXAxis.Width div 50;
  2183.   if (TheContourDetail = cdHigh) then
  2184.   begin
  2185.     for iY := FYAxis.Bottom downto FYAxis.Top do
  2186.     begin
  2187.       ACanvas.Pen.Color := Misc.Rainbow((FYAxis.Bottom - iY) / (FYAxis.Bottom - FYAxis.Top));
  2188.       ACanvas.Brush.Color := ACanvas.Pen.Color;
  2189.       ACanvas.FillRect(Rect(iX, iY, iXp1, iY+1));
  2190.     end;
  2191.   end
  2192.   else
  2193.   begin {low, medium, high:}
  2194.     for iY := FYAxis.Bottom downto FYAxis.Top do
  2195.     begin
  2196.       ACanvas.Pen.Color := Rainbow((FYAxis.Bottom - iY) / (FYAxis.Bottom - FYAxis.Top));
  2197.       ACanvas.MoveTo(iX, iY);
  2198.       ACanvas.LineTo(iXp1, iY);
  2199.     end;
  2200.   end;
  2201.  
  2202. {put some labels on it:}
  2203.   ACanvas.Font.Assign(FYAxis.Labels.Font);
  2204.   ACanvas.Brush.Style := bsClear;
  2205.   FontHeight := ACanvas.TextHeight('9') div 2;
  2206.   for i := 0 to 4 do
  2207.   begin
  2208.     iY := FYAxis.Bottom +
  2209.       i * (FYAxis.Top - FYAxis.Bottom) div 4 - FontHeight;
  2210.     TheText := FYAxis.LabelToStrF(TheMin);
  2211.     ACanvas.TextOut(iXp1+2, iY, TheText);
  2212.     TheMin := TheMin + Span / 4;
  2213.   end;
  2214. end;
  2215.  
  2216. {------------------------------------------------------------------------------
  2217.     Procedure: TSeriesList.DrawHistory
  2218.   Description: standard Drawing procedure
  2219.        Author: Mat Ballard
  2220.  Date created: 04/25/2000
  2221. Date modified: 04/25/2000 by Mat Ballard
  2222.       Purpose: draws all the Series on a given canvas IN History MODE
  2223.  Known Issues:
  2224.  ------------------------------------------------------------------------------}
  2225. procedure TSeriesList.DrawHistory(ACanvas: TCanvas; HistoryX: Single);
  2226. var
  2227.   i: Integer;
  2228. begin
  2229. {$IFDEF DELPHI3_UP}
  2230.   Assert(ACanvas <> nil, 'TSeriesList.DrawHistory: ' + sACanvasIsNil);
  2231. {$ENDIF}
  2232.  
  2233.   for i := 0 to Count-1 do
  2234.   begin
  2235.     TSeries(Items[i]).DrawHistory(ACanvas, HistoryX);
  2236.   end; {loop over series}
  2237. end;
  2238.  
  2239. {------------------------------------------------------------------------------
  2240.     Procedure: TSeriesList.DrawError
  2241.   Description: extended Drawing procedure for linking multiple series
  2242.        Author: Mat Ballard
  2243.  Date created: 01/21/2001
  2244. Date modified: 01/21/2001 by Mat Ballard
  2245.       Purpose: links Series in pairs on a given canvas
  2246.  Known Issues:
  2247.  ------------------------------------------------------------------------------}
  2248. procedure TSeriesList.DrawError(ACanvas: TCanvas);
  2249. var
  2250.   i,
  2251.   iX,
  2252.   iY,
  2253.   iSeries: Integer;
  2254.   pSeries,
  2255.   pErrorSeries: TSeries;
  2256. begin
  2257. {$IFDEF DELPHI3_UP}
  2258.   Assert(ACanvas <> nil, 'TSeriesList.DrawError: ' + sACanvasIsNil);
  2259. {$ENDIF}
  2260.   iSeries := 0;
  2261.   while (iSeries <= Self.Count-2) do
  2262.   begin
  2263.     pSeries := TSeries(Items[iSeries]);
  2264.     pErrorSeries := TSeries(Items[iSeries+1]);
  2265.     if (pSeries.Visible) then
  2266.     begin
  2267.       ACanvas.Pen.Assign(pSeries.Pen);
  2268.  
  2269.       if ((pSeries.Symbol = syNone) or (pSeries.SymbolSize = 0)) then
  2270.       begin
  2271. {no symbols:}
  2272.         for i := 0 to Min(pSeries.NoPts, pErrorSeries.NoPts)-1 do
  2273.         begin
  2274. {draw the vertical line:}
  2275.           ACanvas.MoveTo(
  2276.             pSeries.XAxis.FofX(pSeries.XData^[i]) + pSeries.DeltaX,
  2277.             pSeries.YAxis.FofY(pSeries.YData^[i] - pErrorSeries.YData^[i]) + pSeries.DeltaY);
  2278.           ACanvas.LineTo(
  2279.             pSeries.XAxis.FofX(pSeries.XData^[i]) + pSeries.DeltaX,
  2280.             pSeries.YAxis.FofY(pSeries.YData^[i] + pErrorSeries.YData^[i]) + pSeries.DeltaY);
  2281. {and the horizontal:}
  2282.           if (pErrorSeries.ExternalXSeries) then
  2283.           begin {no X errors:}
  2284.             ACanvas.MoveTo(
  2285.               pSeries.XAxis.FofX(pSeries.XData^[i]) - 5 + pSeries.DeltaX,
  2286.               pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY);
  2287.             ACanvas.LineTo(
  2288.               pSeries.XAxis.FofX(pSeries.XData^[i]) + 5 + pSeries.DeltaX,
  2289.               pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY);
  2290.           end
  2291.           else
  2292.           begin {X errors:}
  2293.             ACanvas.MoveTo(
  2294.               pSeries.XAxis.FofX(pSeries.XData^[i] - pErrorSeries.XData^[i]) + pSeries.DeltaX,
  2295.               pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY);
  2296.             ACanvas.LineTo(
  2297.               pSeries.XAxis.FofX(pSeries.XData^[i] + pErrorSeries.XData^[i]) + pSeries.DeltaX,
  2298.               pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY);
  2299.           end; {X Errors}
  2300.         end; {for}
  2301.       end
  2302.       else
  2303.       begin
  2304. {symbols:}
  2305.         for i := 0 to Min(pSeries.NoPts, pErrorSeries.NoPts)-1 do
  2306.         begin
  2307.           iX := pSeries.XAxis.FofX(pSeries.XData^[i]) + pSeries.DeltaX;
  2308.           iY := pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY;
  2309.           pSeries.DrawSymbol(ACanvas, iX, iY);
  2310.           ACanvas.MoveTo(iX, iY - pSeries.SymbolSize);
  2311.           ACanvas.LineTo(iX, pSeries.YAxis.FofY(pSeries.YData^[i] + pErrorSeries.YData^[i]));
  2312.           ACanvas.MoveTo(iX, iY + pSeries.SymbolSize);
  2313.           ACanvas.LineTo(iX, pSeries.YAxis.FofY(pSeries.YData^[i] - pErrorSeries.YData^[i]));
  2314.           if (not pErrorSeries.ExternalXSeries) then
  2315.           begin
  2316.             ACanvas.MoveTo(iX - pSeries.SymbolSize, iY);
  2317.             ACanvas.LineTo(pSeries.XAxis.FofX(pSeries.XData^[i] - pErrorSeries.XData^[i]), iY);
  2318.             ACanvas.MoveTo(iX + pSeries.SymbolSize, iY);
  2319.             ACanvas.LineTo(pSeries.XAxis.FofX(pSeries.XData^[i] + pErrorSeries.XData^[i]), iY);
  2320.           end;
  2321.         end; {for}
  2322.       end; {if symbols}
  2323.     end; {if visible}
  2324.     Inc(iSeries, 2);
  2325.   end; {while}
  2326. end;
  2327.  
  2328. {------------------------------------------------------------------------------
  2329.     Procedure: TSeriesList.DrawBubble
  2330.   Description: extended Drawing procedure for Bubble plots
  2331.        Author: Mat Ballard
  2332.  Date created: 04/11/2001
  2333. Date modified: 04/11/2001 by Mat Ballard
  2334.       Purpose: links Series in pairs as Bubble plots
  2335.  Known Issues:
  2336.  ------------------------------------------------------------------------------}
  2337. procedure TSeriesList.DrawBubble(ACanvas: TCanvas; BubbleSize: Integer);
  2338. var
  2339.   i,
  2340.   iX,
  2341.   iY,
  2342.   iSeries,
  2343.   YRadius: Integer;
  2344.   BubbleMax: Single;
  2345.   pSeries,
  2346.   pBubbleSeries: TSeries;
  2347. begin
  2348. {$IFDEF DELPHI3_UP}
  2349.   Assert(ACanvas <> nil, 'TSeriesList.DrawBubble: ' + sACanvasIsNil);
  2350. {$ENDIF}
  2351.   iSeries := 0;
  2352.   while (iSeries <= Self.Count-2) do
  2353.   begin
  2354.     pSeries := TSeries(Items[iSeries]);
  2355. {every odd numbered series contains the Bubble height, and optionally the breadth:}
  2356.     pBubbleSeries := TSeries(Items[iSeries+1]);
  2357.     BubbleMax := 100 * pBubbleSeries.YMax / (BubbleSize * (FYAxis.Bottom - FYAxis.Top));
  2358.     if (pSeries.Visible) then
  2359.     begin
  2360.       ACanvas.Pen.Assign(pSeries.Pen);
  2361.       ACanvas.Brush.Assign(pSeries.Brush);
  2362.  
  2363.       for i := 0 to Min(pSeries.NoPts, pBubbleSeries.NoPts)-1 do
  2364.       begin
  2365.         iX := pSeries.XAxis.FofX(pSeries.XData^[i]) + pSeries.DeltaX;
  2366.         iY := pSeries.YAxis.FofY(pSeries.YData^[i]) + pSeries.DeltaY;
  2367.         YRadius := Round(pBubbleSeries.YData^[i] / BubbleMax);
  2368.         if (YRadius >= 0) then
  2369.           ACanvas.Brush.Style := bsSolid
  2370.          else
  2371.           ACanvas.Brush.Style := bsCross;
  2372.         ACanvas.Ellipse(iX - YRadius, iY - YRadius, iX + YRadius, iY + YRadius)
  2373.       end; {for}
  2374.     end; {if visible}
  2375.     Inc(iSeries, 2);
  2376.   end; {while}
  2377. end;
  2378.  
  2379. {------------------------------------------------------------------------------
  2380.     Procedure: TSeriesList.DrawMultiple
  2381.   Description: extended Drawing procedure for linking multiple series
  2382.        Author: Mat Ballard
  2383.  Date created: 09/21/2000
  2384. Date modified: 09/21/2000 by Mat Ballard
  2385.       Purpose: links multiple Series on a given canvas
  2386.  Known Issues:
  2387.  ------------------------------------------------------------------------------}
  2388. procedure TSeriesList.DrawMultiple(
  2389.   ACanvas: TCanvas;
  2390.   Multiplicity: Byte;
  2391.   MultiplePen: TPen;
  2392.   MultiJoin1, MultiJoin2: Integer);
  2393. var
  2394.   i,
  2395.   j,
  2396.   k,
  2397.   iSeries,
  2398.   NoGroups: Integer;
  2399.   pSeries, pSeries2: TSeries;
  2400.   DoMultiJoin: Boolean;
  2401. begin
  2402. {$IFDEF DELPHI3_UP}
  2403.   Assert(ACanvas <> nil, 'TSeriesList.DrawMultiple: ' + sACanvasIsNil);
  2404.   Assert(Multiplicity > 1, 'TSeriesList.DrawMultiple: ' + sDrawMultiple1);
  2405. {$ENDIF}
  2406.  
  2407. {are there two valid multijoined series ?}
  2408.   DoMultiJoin := FALSE;
  2409.   if ((0 <= MultiJoin1) and (MultiJoin1 < Self.Count) and
  2410.       (0 <= MultiJoin2) and (MultiJoin2 < Self.Count) and
  2411.       (MultiJoin1 <> MultiJoin2)) then
  2412.   begin
  2413. {We don't draw MultiJoined series:}
  2414.     TSeries(Items[MultiJoin1]).Symbol := syNone;
  2415.     TSeries(Items[MultiJoin2]).Symbol := syNone;
  2416.     DoMultiJoin := TRUE;
  2417.   end;
  2418.  
  2419. {Draw the normal series lines and symbols:}
  2420.   Self.Draw(ACanvas, High(Integer)); 
  2421. {Prepare for the vertical lines:}
  2422.   ACanvas.Pen.Assign(MultiplePen);
  2423. {And MultiJoin symbols:}  
  2424.   if (DoMultiJoin) then
  2425.   begin
  2426.     ACanvas.Brush.Assign(TSeries(Items[MultiJoin1]).Brush);
  2427.   end;
  2428.  
  2429.   NoGroups := Count div Multiplicity;
  2430.   for i := 0 to MinNoPts-1 do
  2431.   begin
  2432.     iSeries := -1;
  2433.     for j := 1 to NoGroups do
  2434.     begin
  2435.       Inc(iSeries);
  2436.       if (iSeries >= Count) then break;
  2437.       pSeries := TSeries(Items[iSeries]);
  2438.       ACanvas.MoveTo(
  2439.         pSeries.XAxis.FofX(pSeries.XData^[i]),
  2440.         pSeries.YAxis.FofY(pSeries.YData^[i]));
  2441.       for k := 2 to Multiplicity do
  2442.       begin
  2443.         Inc(iSeries);
  2444.         if (iSeries >= Count) then break;
  2445.         pSeries := TSeries(Items[iSeries]);
  2446.         ACanvas.LineTo(
  2447.           pSeries.XAxis.FofX(pSeries.XData^[i]),
  2448.           pSeries.YAxis.FofY(pSeries.YData^[i]));
  2449.       end; {Multiplicity}
  2450.       if (DoMultiJoin) then
  2451.       begin
  2452.         pSeries := TSeries(Items[MultiJoin1]);
  2453.         pSeries2 := TSeries(Items[MultiJoin2]);
  2454.         ACanvas.Rectangle(
  2455.           pSeries.XAxis.FofX(pSeries.XData^[i]) - pSeries.SymbolSize,
  2456.           pSeries.YAxis.FofY(pSeries.YData^[i]),
  2457.           pSeries2.XAxis.FofX(pSeries2.XData^[i]) + pSeries.SymbolSize,
  2458.           pSeries2.YAxis.FofY(pSeries2.YData^[i]));
  2459.       end;
  2460.     end; {NoGroups}
  2461.   end; {MinNoPts}
  2462. end;
  2463.  
  2464. {------------------------------------------------------------------------------
  2465.     Procedure: TSeriesList.DrawHistoryMultiple
  2466.   Description: extended Drawing procedure for linking multiple series in History mode
  2467.        Author: Mat Ballard
  2468.  Date created: 09/21/2000
  2469. Date modified: 09/21/2000 by Mat Ballard
  2470.       Purpose: links multiple Series in History mode on a given canvas
  2471.  Known Issues:
  2472.  ------------------------------------------------------------------------------}
  2473. procedure TSeriesList.DrawHistoryMultiple(ACanvas: TCanvas; Multiplicity: Byte);
  2474. var
  2475.   i,
  2476.   j,
  2477.   k,
  2478.   iSeries,
  2479.   NoGroups: Integer;
  2480.   pSeries: TSeries;
  2481. begin
  2482. {$IFDEF DELPHI3_UP}
  2483.   Assert(ACanvas <> nil, 'TSeriesList.DrawHistoryMultiple: ' + sACanvasIsNil);
  2484.   Assert(Multiplicity > 1, 'TSeriesList.DrawHistoryMultiple: ' + sDrawMultiple1);
  2485. {$ENDIF}
  2486.  
  2487. {we set the pen mode so that a second call to DrawHistory
  2488. erases the curve on screen:}
  2489.   ACanvas.Pen.Mode := pmNotXOR;
  2490.   NoGroups := Count div Multiplicity;
  2491.   for i := MinNoPts-1 downto 0 do
  2492.   begin
  2493.     iSeries := -1;
  2494.     for j := 1 to NoGroups do
  2495.     begin
  2496.       Inc(iSeries);
  2497.       if (iSeries >= Count) then break;
  2498.       pSeries := TSeries(Items[iSeries]);
  2499.       ACanvas.MoveTo(
  2500.         pSeries.XAxis.FofX(pSeries.XData^[i]),
  2501.         pSeries.YAxis.FofY(pSeries.YData^[i]));
  2502.       for k := 2 to Multiplicity do
  2503.       begin
  2504.         Inc(iSeries);
  2505.         if (iSeries >= Count) then break;
  2506.         pSeries := TSeries(Items[iSeries]);
  2507.         ACanvas.LineTo(
  2508.           pSeries.XAxis.FofX(pSeries.XData^[i]),
  2509.           pSeries.YAxis.FofY(pSeries.YData^[i]));
  2510.       end; {Multiplicity}
  2511.     end; {NoGroups}
  2512.   end; {MinNoPts}
  2513. end;
  2514.  
  2515. {TSeriesList editing, both in designer and active modes -----------------------}
  2516. {------------------------------------------------------------------------------
  2517.      Function: TSeriesList.CloneSeries
  2518.   Description: Clones (Adds then Copies) a Series to a new one
  2519.        Author: Mat Ballard
  2520.  Date created: 04/25/2000
  2521. Date modified: 04/25/2000 by Mat Ballard
  2522.       Purpose: creates, initializes, adds then copies a Series
  2523.  Return Value: the Index of the new Series
  2524.  Known Issues:
  2525.  ------------------------------------------------------------------------------}
  2526. function TSeriesList.CloneSeries(
  2527.   TheSeries: Integer): Integer;
  2528. var
  2529.   pClone,
  2530.   pSeries: TSeries;
  2531.   TheClone,
  2532.   TheXSeries: Integer;
  2533. begin
  2534.   if ((TheSeries < 0) or (TheSeries > Count-1)) then raise
  2535.     ERangeError.CreateFmt(sCloneSeries1, [TheSeries, Count]);
  2536.  
  2537. {so lets create the clone: the XDataSeries is either nil, or an existing series}
  2538.   pSeries := TSeries(Items[TheSeries]);
  2539.   TheXSeries := -1;
  2540.   if (pSeries.XDataSeries <> nil) then
  2541.     TheXSeries := IndexOf(pSeries.XDataSeries);
  2542.   TheClone := Add(TheXSeries);
  2543.   pClone := TSeries(Items[TheClone]);
  2544.  
  2545. {set properties:}
  2546.   pClone.YAxisIndex := pSeries.YAxisIndex;
  2547.   pClone.DefSize := pSeries.DefSize;
  2548.   pClone.DeltaX := pSeries.DeltaX;
  2549. {move the cloned series up by 20 pixels:}
  2550.   pClone.DeltaY := pSeries.DeltaY - 30;
  2551.   pClone.Name := sClone + sOf + pSeries.Name;
  2552.   pClone.OnStyleChange:= pSeries.OnStyleChange;
  2553.   pClone.OnDataChange:= pSeries.OnDataChange;
  2554.   pClone.Pen.Style := pSeries.Pen.Style;
  2555.   pClone.Pen.Width := pSeries.Pen.Width;
  2556.   pClone.Symbol:= pSeries.Symbol;
  2557.   pClone.SymbolSize:= pSeries.SymbolSize;
  2558.   pClone.Visible:= pSeries.Visible;
  2559.   {pClone.SeriesType := pSeries.SeriesType;}
  2560.  
  2561.   {case pSeries.SeriesType of
  2562.     stXY: pClone.AddData(pSeries.XData, pSeries.YData, nil, pSeries.NoPts);
  2563.     stXY_Error: pClone.AddData(pSeries.XData, pSeries.YData, pSeries.YErrorData, pSeries.NoPts);
  2564.     stXYZ: pClone.AddData(pSeries.XData, pSeries.YData, pSeries.ZData, pSeries.NoPts);
  2565.   end;}
  2566.  
  2567.   pClone.AddData(pSeries.XData, pSeries.YData, pSeries.NoPts);
  2568.   pClone.ResetBounds;
  2569.   pClone.GetBounds;
  2570.  
  2571. {AddData above fires the OnDataChange event for the series:}
  2572.   {DoDataChange;}
  2573.   CloneSeries := TheClone;
  2574. end;
  2575.  
  2576. {TSeriesList editing, both in designer and active modes -----------------------}
  2577. {------------------------------------------------------------------------------
  2578.     Procedure: TSeriesList.DataAsHTMLTable
  2579.   Description: creates a stringlist of the data as a HTML table
  2580.        Author: Mat Ballard
  2581.  Date created: 04/25/2000
  2582. Date modified: 04/25/2000 by Mat Ballard
  2583.       Purpose: copying data
  2584.  Known Issues:
  2585.  ------------------------------------------------------------------------------}
  2586. procedure TSeriesList.DataAsHTMLTable(var TheData: TStringList);
  2587. {This puts the data into a stringlist for saving or copying}
  2588. var
  2589.   i, j: Integer;
  2590.   NoPtsMax: Integer;
  2591.   pSeries: TSeries;
  2592.   SubHeader: String;
  2593. begin
  2594.   if (TheData = nil) then exit;
  2595.  
  2596. {Determine the maximum number of points in all the series:}
  2597.   NoPtsMax := 0;
  2598.   for i := 0 to Count-1 do
  2599.   begin
  2600.     if (NoPtsMax < TSeries(Items[i]).NoPts) then
  2601.       NoPtsMax := TSeries(Items[i]).NoPts;
  2602.   end;
  2603.  
  2604. {loop over all series:}
  2605.   SubHeader := #9 + '<tr>';
  2606.   for i := 0 to Count-1 do
  2607.   begin
  2608.     pSeries := TSeries(Items[i]);
  2609. {create the sub-headings:}
  2610.     if (not pSeries.ExternalXSeries) then
  2611.     begin
  2612.       SubHeader := SubHeader + '<td>' + pSeries.Name + ' - ' + pSeries.XAxis.Title.Caption + '</td>';
  2613.     end;
  2614.     SubHeader := SubHeader + '<td>' + pSeries.Name + ' - ' + pSeries.YAxis.Title.Caption + '</td>';
  2615. {loop over points in each series:}
  2616.     for j := 0 to pSeries.NoPts-1 do
  2617.     begin
  2618.       if (TheData.Count <= j) then
  2619.       begin
  2620. {start a new row:}
  2621.         TheData.Add(#9+#9+ '<tr>');
  2622.       end;
  2623.       if (not pSeries.ExternalXSeries) then
  2624.       begin
  2625.         TheData[j] := TheData[j] + '<td>' + FloatToStr(pSeries.XData^[j]) + '</td>';
  2626.       end;
  2627.       TheData[j] := TheData[j] + '<td>' + FloatToStr(pSeries.YData^[j]) + '</td>';
  2628.     end; {loop over points}
  2629.     for j := pSeries.NoPts to NoPtsMax-1 do
  2630.     begin
  2631.       if (TheData.Count <= j) then
  2632.       begin
  2633. {start a new row:}
  2634.         TheData.Add(#9+#9+ '<tr>');
  2635.       end;
  2636.       if (not pSeries.ExternalXSeries) then
  2637.       begin
  2638.         TheData[j] := TheData[j] + '<td></td>';
  2639.       end;
  2640.       TheData[j] := TheData[j] + '<td></td>';
  2641.     end;
  2642.   end; {loop over series}
  2643.   SubHeader := SubHeader + '</tr>';
  2644.   TheData.Insert(0, SubHeader);
  2645.   TheData.Insert(0, '<table border=2 cellpadding=2 cellspacing=2>');
  2646.   TheData.Add('</table>');
  2647. end;
  2648.  
  2649. {------------------------------------------------------------------------------
  2650.     Procedure: TSeriesList.AppendStream
  2651.   Description: puts the data collected since LastSavedPoint into a stringlist for saving or copying.
  2652.        Author: Mat Ballard
  2653.  Date created: 04/25/2000
  2654. Date modified: 04/25/2000 by Mat Ballard
  2655.       Purpose: saving data
  2656.  Known Issues:
  2657.  ------------------------------------------------------------------------------}
  2658. procedure TSeriesList.AppendStream(
  2659.   AsText: Boolean;
  2660.   Delimiter: Char;
  2661.   TheStream: TMemoryStreamEx);
  2662. var
  2663.   i: Integer;
  2664.   NoPtsMax: Integer;
  2665. begin
  2666.   if (TheStream = nil) then exit;
  2667.  
  2668. {Determine the maximum number of points in all the series:}
  2669.   NoPtsMax := 0;
  2670.   for i := 0 to Count-1 do
  2671.   begin
  2672.     if (NoPtsMax < TSeries(Items[i]).NoPts) then
  2673.       NoPtsMax := TSeries(Items[i]).NoPts;
  2674.   end;
  2675.  
  2676.   if (AsText) then
  2677.     GetTextStream(Delimiter, LastSavedPoint, NoPtsMax, TheStream)
  2678.    else
  2679.     GetBinaryStream(LastSavedPoint, NoPtsMax, TheStream);
  2680.  
  2681.   LastSavedPoint := NoPtsMax-1;
  2682. end;
  2683.  
  2684. {------------------------------------------------------------------------------
  2685.     Procedure: TSeriesList.GetStream
  2686.   Description: puts the data into a MemoryStream
  2687.        Author: Mat Ballard
  2688.  Date created: 04/25/2000
  2689. Date modified: 03/07/2001 by Mat Ballard
  2690.       Purpose: for saving or copying
  2691.  Known Issues:
  2692.  ------------------------------------------------------------------------------}
  2693. procedure TSeriesList.GetStream(
  2694.   AsText: Boolean;
  2695.   Delimiter: Char;
  2696.   var TheStream: TMemoryStream);
  2697. var
  2698.   i,
  2699.   NoPtsMax: Integer;
  2700.   ALine: String;
  2701.   pLine: array [0..1023] of char;
  2702. begin
  2703.   if (TheStream = nil) then
  2704.     TheStream := TMemoryStream.Create;
  2705.  
  2706.   Self.GetSubHeaderStream(Delimiter, TheStream);
  2707.  
  2708.   if (AsText) then
  2709.     StrPCopy(pLine, 'Binary=0' + CRLF)
  2710.    else
  2711.     StrPCopy(pLine, 'Binary=1' + CRLF);
  2712.   TheStream.Write(pLine, StrLen(pLine));
  2713.  
  2714.   if (Count = 0) then exit;
  2715.  
  2716. {Determine the maximum number of points in all the series:}
  2717.   NoPtsMax := 0;
  2718.   for i := 0 to Count-1 do
  2719.   begin
  2720.     if (NoPtsMax < TSeries(Items[i]).NoPts) then
  2721.       NoPtsMax := TSeries(Items[i]).NoPts;
  2722.   end;
  2723.  
  2724.   if (AsText) then
  2725.   begin
  2726.     ALine := 'ZData:' + Delimiter;
  2727.     if (TSeries(Items[0]).XStringData <> nil) then
  2728.       if (TSeries(Items[0]).XStringData.Count > 0) then
  2729.         ALine := ALine + Delimiter;
  2730.     ALine := ALine + FloatToStr(TSeries(Items[0]).ZData);
  2731.     for i := 1 to Count-1 do
  2732.     begin
  2733.       if (not TSeries(Items[i]).ExternalXSeries) then
  2734.       begin
  2735.         if (TSeries(Items[i]).XStringData <> nil) then
  2736.           if (TSeries(Items[i]).XStringData.Count > 0) then
  2737.             ALine := ALine + Delimiter;
  2738.         ALine := ALine + Delimiter;    
  2739.       end;
  2740.       ALine := ALine + Delimiter +
  2741.         FloatToStr(TSeries(Items[i]).ZData)
  2742.     end;
  2743.     ALine := ALine + CRLF;
  2744. {$IFDEF DELPHI1}
  2745.     StrPCopy(pLine, ALine);
  2746.     TheStream.Write(pLine, StrLen(pLine));
  2747. {$ELSE}
  2748.     TheStream.Write(Pointer(ALine)^, Length(ALine));
  2749. {$ENDIF}
  2750.     GetTextStream(Delimiter, 0, NoPtsMax-1, TheStream)
  2751.   end
  2752.   else
  2753.   begin
  2754.     ALine := 'ZData:';
  2755. {$IFDEF DELPHI1}
  2756.     StrPCopy(pLine, ALine);
  2757.     TheStream.Write(pLine, StrLen(pLine));
  2758. {$ELSE}
  2759.     TheStream.Write(Pointer(ALine)^, Length(ALine));
  2760. {$ENDIF}
  2761.     for i := 0 to Count-1 do
  2762.       TheStream.Write(TSeries(Items[i]).ZData, SizeOf(Single));
  2763.     GetBinaryStream(0, NoPtsMax-1, TheStream);
  2764.   end;
  2765.  
  2766.   FDataChanged := FALSE;
  2767.   LastSavedPoint := NoPtsMax-1;
  2768. end;
  2769.  
  2770.  
  2771. {------------------------------------------------------------------------------
  2772.     Procedure: TSeriesList.GetSubHeaderStream
  2773.   Description: puts the data sub header onto a stream
  2774.        Author: Mat Ballard
  2775.  Date created: 08/06/2000
  2776. Date modified: 08/06/2000 by Mat Ballard
  2777.       Purpose: for saving or copying
  2778.  Known Issues:
  2779.  ------------------------------------------------------------------------------}
  2780. procedure TSeriesList.GetSubHeaderStream(
  2781.   Delimiter: Char;
  2782.   TheStream: TMemoryStream);
  2783. var
  2784.   i: Integer;
  2785.   pSeries: TSeries;
  2786.   SeriesNameLine,
  2787.   AxisNameLine,
  2788.   DataTypeLine,
  2789.   XDataSeriesLine: String;
  2790. {$IFDEF DELPHI1}
  2791.   pLine: array [0..4095] of char;
  2792. {$ENDIF}
  2793. begin
  2794.   if (TheStream = nil) then exit;
  2795.  
  2796. {point at the first series:}
  2797.   pSeries := TSeries(Items[0]);
  2798. {the first series ALWAYS has both an X data:}
  2799.   SeriesNameLine := '';
  2800.   AxisNameLine := pSeries.XAxis.Title.Caption;
  2801.   DataTypeLine := 'X';
  2802.   XDataSeriesLine := '-';
  2803. {maybe XTEXT data:}
  2804.   if ((pSeries.XStringData <> nil) and
  2805.       (pSeries.XStringData.Count > 0)) then
  2806.   begin
  2807.     SeriesNameLine := SeriesNameLine + Delimiter;
  2808.     AxisNameLine := AxisNameLine + Delimiter;
  2809.     DataTypeLine := DataTypeLine + Delimiter + 'XTEXT';
  2810.     XDataSeriesLine := XDataSeriesLine + Delimiter + '-';
  2811.   end;
  2812. {and ALWAYS has Y data:}
  2813.   SeriesNameLine := SeriesNameLine + Delimiter + pSeries.Name;
  2814.   AxisNameLine := AxisNameLine + Delimiter + pSeries.YAxis.Title.Caption;
  2815.   DataTypeLine := DataTypeLine + Delimiter + 'Y';
  2816.   XDataSeriesLine := XDataSeriesLine + Delimiter + '-';
  2817.  
  2818. {loop over all the rest of the series:}
  2819.   for i := 1 to Count-1 do
  2820.   begin
  2821.     pSeries := TSeries(Items[i]);
  2822.  
  2823. {create the sub-headings:}
  2824.     if (not pSeries.ExternalXSeries) then
  2825.     begin
  2826. {The X data belongs to this series:}
  2827.       SeriesNameLine := SeriesNameLine + Delimiter;
  2828.       AxisNameLine := AxisNameLine + Delimiter + pSeries.XAxis.Title.Caption;
  2829.       DataTypeLine := DataTypeLine + Delimiter + 'X';
  2830.       XDataSeriesLine := XDataSeriesLine + Delimiter + '-';
  2831.       if ((pSeries.XStringData <> nil) and
  2832.           (pSeries.XStringData.Count > 0)) then
  2833.       begin
  2834.         SeriesNameLine := SeriesNameLine + Delimiter;
  2835.         AxisNameLine := AxisNameLine + Delimiter;
  2836.         DataTypeLine := DataTypeLine + Delimiter + 'XTEXT';
  2837.         XDataSeriesLine := XDataSeriesLine + Delimiter + '-';
  2838.       end;
  2839.     end;
  2840. {The Y data belongs to this series:}
  2841. {put the Series Name and YAxis name above the Y column:}
  2842.     SeriesNameLine := SeriesNameLine + Delimiter + pSeries.Name;
  2843.     AxisNameLine := AxisNameLine + Delimiter + pSeries.YAxis.Title.Caption;
  2844.     DataTypeLine := DataTypeLine + Delimiter + 'Y';
  2845.     if (pSeries.ExternalXSeries) then
  2846.       XDataSeriesLine := XDataSeriesLine + Delimiter +
  2847.         IntToStr(IndexOf(pSeries.XDataSeries))
  2848.      else
  2849.       XDataSeriesLine := XDataSeriesLine + Delimiter;
  2850.   end; {for i}
  2851.  
  2852.   SeriesNameLine := SeriesNameLine + CRLF;
  2853.   AxisNameLine := AxisNameLine + CRLF;
  2854.   DataTypeLine := DataTypeLine + CRLF;
  2855.   XDataSeriesLine := XDataSeriesLine + CRLF;
  2856.  
  2857. {$IFDEF DELPHI1}
  2858.   StrPCopy(pLine, SeriesNameLine);
  2859.   TheStream.Write(pLine, StrLen(pLine));
  2860.   StrPCopy(pLine, AxisNameLine);
  2861.   TheStream.Write(pLine, StrLen(pLine));
  2862.   StrPCopy(pLine, DataTypeLine);
  2863.   TheStream.Write(pLine, StrLen(pLine));
  2864.   StrPCopy(pLine, XDataSeriesLine);
  2865.   TheStream.Write(pLine, StrLen(pLine));
  2866. {$ELSE}
  2867.   TheStream.Write(Pointer(SeriesNameLine)^, Length(SeriesNameLine));
  2868.   TheStream.Write(Pointer(AxisNameLine)^, Length(AxisNameLine));
  2869.   TheStream.Write(Pointer(DataTypeLine)^, Length(DataTypeLine));
  2870.   TheStream.Write(Pointer(XDataSeriesLine)^, Length(XDataSeriesLine));
  2871. {$ENDIF}
  2872. end;
  2873.  
  2874. {------------------------------------------------------------------------------
  2875.     procedure: TSeriesList.GetBinaryStream
  2876.   Description: gets the data as a binary stream
  2877.        Author: Mat Ballard
  2878.  Date created: 04/25/2000
  2879. Date modified: 04/25/2000 by Mat Ballard
  2880.       Purpose: data management: copying and saving
  2881.  Known Issues:
  2882.  ------------------------------------------------------------------------------}
  2883. procedure TSeriesList.GetBinaryStream(
  2884.   Start, Finish: Integer;
  2885.   TheStream: TMemoryStream);
  2886. var
  2887.   i,
  2888.   j: Integer;
  2889.   pSeries: TSeries;
  2890.   pNullData: array [0..16] of char;
  2891.   pLine: array [0..255] of char;
  2892.  
  2893.   procedure WriteStringData;
  2894.   begin
  2895.     if (pSeries.XStringData <> nil)  then
  2896.       if (pSeries.XStringData.Count > 0)  then
  2897.       begin
  2898.         if (i < pSeries.XStringData.Count)  then
  2899.           StrPCopy(pLine, pSeries.XStringData.Strings[i] + CRLF)
  2900.         else
  2901.           StrPCopy(pLine, CRLF);
  2902.         TheStream.Write(pLine, StrLen(pLine));
  2903.       end;
  2904.   end;
  2905.   
  2906. begin
  2907.   for i := 0 to SizeOf(Single)-1 do
  2908.     pNullData[i] := 'x';
  2909.   pNullData[SizeOf(Single)] := Chr(0);
  2910.  
  2911. {all the data is written in BINARY format:}
  2912.   for i := Start to Finish do
  2913.   begin
  2914. {loop over all series:}
  2915.     for j := 0 to Count-1 do
  2916.     begin
  2917.       pSeries := TSeries(Items[j]);
  2918.       if (i < pSeries.NoPts) then
  2919.       begin
  2920.         if (not pSeries.ExternalXSeries) then
  2921.         begin
  2922.           TheStream.Write(pSeries.XData^[i], SizeOf(Single));
  2923.           WriteStringData;
  2924.         end;
  2925.         TheStream.Write(pSeries.YData^[i], SizeOf(Single));
  2926.       end
  2927.       else
  2928.       begin
  2929.         if (not pSeries.ExternalXSeries) then
  2930.         begin
  2931.           TheStream.Write(pNullData, SizeOf(Single));
  2932.           WriteStringData;
  2933.         end;
  2934.         TheStream.Write(pNullData, SizeOf(Single));
  2935.       end;
  2936.     end; {loop over points}
  2937.   end;
  2938. end;
  2939.  
  2940. {------------------------------------------------------------------------------
  2941.     procedure: TSeriesList.GetTextStream
  2942.   Description: gets the data as a text stream
  2943.        Author: Mat Ballard
  2944.  Date created: 04/25/2000
  2945. Date modified: 04/25/2000 by Mat Ballard
  2946.       Purpose: data management: copying and saving
  2947.  Known Issues:
  2948.  ------------------------------------------------------------------------------}
  2949. procedure TSeriesList.GetTextStream(
  2950.   Delimiter: Char;
  2951.   Start, Finish: Integer;
  2952.   TheStream: TMemoryStream);
  2953. var
  2954.   i,
  2955.   j: Integer;
  2956.   pSeries: TSeries;
  2957.   //DoStringXData: Boolean;
  2958.   TheLine: String;
  2959. {$IFDEF DELPHI1}
  2960.   pLine: array [0..255] of char;
  2961. {$ENDIF}
  2962.  
  2963.   procedure WriteStringData;
  2964.   begin
  2965.     if (pSeries.XStringData <> nil)  then
  2966.       if (pSeries.XStringData.Count > 0)  then
  2967.       begin
  2968.         if (i < pSeries.XStringData.Count)  then
  2969.           TheLine := TheLine + Delimiter + pSeries.XStringData.Strings[i]
  2970.         else
  2971.           TheLine := TheLine + Delimiter;
  2972.       end;
  2973.   end;
  2974.  
  2975. begin
  2976. {all the data is written in text format:}
  2977.   for i := Start to Finish do
  2978.   begin
  2979. {loop over all the remaining series:}
  2980.     for j := 0 to Count-1 do
  2981.     begin
  2982.       pSeries := TSeries(Items[j]);
  2983.       if (i < pSeries.NoPts) then
  2984.       begin
  2985.         if (not pSeries.ExternalXSeries) then
  2986.         begin
  2987.           TheLine := FloatToStr(pSeries.XData^[i]);
  2988.           WriteStringData;
  2989.         end;
  2990.         TheLine := TheLine + Delimiter + FloatToStr(pSeries.YData^[i]);
  2991.       end
  2992.       else
  2993.       begin
  2994.         if (not pSeries.ExternalXSeries) then
  2995.         begin
  2996.           TheLine := TheLine + Delimiter;
  2997.           WriteStringData;
  2998.         end;
  2999.         TheLine := TheLine + Delimiter;
  3000.       end;
  3001.     end; {loop over points}
  3002.     TheLine := TheLine + CRLF;
  3003. {$IFDEF DELPHI1}
  3004.     StrPCopy(pLine, TheLine);
  3005.     TheStream.Write(pLine, StrLen(pLine));
  3006. {$ELSE}
  3007.     TheStream.Write(Pointer(TheLine)^, Length(TheLine));
  3008. {$ENDIF}
  3009.   end;
  3010. end;
  3011.  
  3012. {------------------------------------------------------------------------------
  3013.      Function: TSeriesList.LoadFromStream
  3014.   Description: Opens data on disk
  3015.        Author: Mat Ballard
  3016.  Date created: 04/25/2001
  3017. Date modified: 04/25/2001 by Mat Ballard
  3018.       Purpose: Opens data, parses it, fires the OnHeader event, and runs ConvertTextData,
  3019.                or decides to run it through ParseData instead
  3020.  Known Issues: Called by TPlot.LoadFromStream
  3021.  ------------------------------------------------------------------------------}
  3022. function TSeriesList.LoadFromStream(
  3023.   AStream: TMemoryStream; var AsText: Boolean): Boolean;
  3024. var
  3025.   TheResult: Boolean;
  3026.   ColCount,
  3027.   //FileVersion,
  3028.   i,
  3029.   iColumn,
  3030.   InitialSeriesCount,
  3031.   //LineLength,
  3032.   NoFileSeries: Integer;
  3033.   TheLine,
  3034.   SeriesNameLine,
  3035.   AxisNameLine,
  3036.   DataTypeLine,
  3037.   XDataSeriesLine,
  3038.   TheCell: String;
  3039.   TheStrings: TStringList;
  3040.   SeriesInfo: pSeriesInfoArray;
  3041.   SeriesOfCol: pIntegerArray;
  3042.   //OldIgnoreChanges: Boolean;
  3043.  
  3044.   procedure CleanUp;
  3045.   begin
  3046.     if (SeriesInfo <> nil) then
  3047.       FreeMem(SeriesInfo, ColCount * SizeOf(TSeriesInfo));
  3048.     SeriesInfo := nil;
  3049.     if (SeriesOfCol <> nil) then
  3050.       FreeMem(SeriesOfCol, ColCount * SizeOf(Integer));
  3051.     SeriesOfCol := nil;
  3052.     if (TheStrings <> nil) then
  3053.       TheStrings.Free;
  3054.     TheStrings := nil;  
  3055.   end;
  3056.  
  3057. begin
  3058.   //LoadFromStream := FALSE;
  3059.   ColCount := 1;
  3060.   SeriesInfo := nil;
  3061.   SeriesOfCol := nil;
  3062.   TheStrings := nil;
  3063.  
  3064.   InitialSeriesCount := Self.Count;
  3065.   try
  3066. {get the sub-header data:}
  3067.     SeriesNameLine := ReadLine(AStream);
  3068.     AxisNameLine := ReadLine(AStream);
  3069.     DataTypeLine := ReadLine(AStream);
  3070.     XDataSeriesLine := ReadLine(AStream);
  3071.  
  3072. {find out how many columns there are:}
  3073.     for i := 1 to Length(DataTypeLine) do
  3074.       if (DataTypeLine[i] = ',') then
  3075.         Inc(ColCount);
  3076.  
  3077.     if (ColCount < 2) then raise
  3078.       EFOpenError.CreateFmt(sLoadFromStream1, [ColCount]);
  3079.  
  3080. {allocate memory for the dynamic arrays, which are small:}
  3081.     GetMem(SeriesInfo, ColCount * SizeOf(TSeriesInfo));
  3082.     GetMem(SeriesOfCol, (ColCount+1) * SizeOf(Integer));
  3083. {this allocates more memory than SeriesInfo needs, but so what ?}
  3084.  
  3085. {Determine the number of series:}
  3086.     NoFileSeries := 0;
  3087.     for i := 0 to ColCount-1 do
  3088.     begin
  3089.       SeriesInfo^[i].XCol := 0;
  3090.       SeriesInfo^[i].XTextCol := -1;
  3091.     end;
  3092. {examine the columns one by one:}
  3093.     for iColumn := 1 to ColCount do
  3094.     begin
  3095. {No new column yet belongs to a Series:}
  3096.       SeriesOfCol^[iColumn] := -1;
  3097.       TheCell := GetWord(DataTypeLine, ',');
  3098.       if (TheCell = 'X') then
  3099.       begin
  3100. {we've found a X data column to add.}
  3101.         SeriesOfCol^[iColumn] := NoFileSeries;
  3102.         SeriesInfo^[NoFileSeries].XCol := iColumn;
  3103.         GetWord(XDataSeriesLine, ',');
  3104.         GetWord(SeriesNameLine, ',');
  3105.       end
  3106.       else if (TheCell = 'XTEXT') then
  3107.       begin
  3108. {we've found a X STRING data column to add.}
  3109.         SeriesOfCol^[iColumn] := NoFileSeries;
  3110.         SeriesInfo^[NoFileSeries].XTextCol := iColumn;
  3111.         GetWord(XDataSeriesLine, ',');
  3112.         GetWord(SeriesNameLine, ',');
  3113.       end
  3114.       else if (TheCell = 'Y') then
  3115.       begin
  3116. {we've found a Y data column to add.}
  3117.         SeriesInfo^[NoFileSeries].YCol := iColumn;
  3118. {find the X Column that this Y column uses:}
  3119.         TheCell := GetWord(XDataSeriesLine, ',');
  3120.         if (TheCell = '-') then
  3121.         begin
  3122.           SeriesInfo^[NoFileSeries].XSeriesIndex := -1;
  3123.         end
  3124.         else
  3125.         begin
  3126.           SeriesInfo^[NoFileSeries].XSeriesIndex :=
  3127.             StrToInt(TheCell) + InitialSeriesCount;
  3128.         end;
  3129. {Add a new series:}
  3130.         SeriesInfo^[NoFileSeries].Index :=
  3131.           Self.Add(SeriesInfo^[NoFileSeries].XSeriesIndex);
  3132.         TSeries(Self.Items[SeriesInfo^[NoFileSeries].Index]).Name :=
  3133.           GetWord(SeriesNameLine, ',');
  3134.         Inc(NoFileSeries);
  3135.       end; {found a Y data column}
  3136.     end; {for}
  3137.  
  3138. {Get the type of data:}
  3139.     TheLine := ReadLine(AStream);
  3140.     GetWord(TheLine, '=');
  3141. {'Binary=X': X=0 => Text, X=1 => Binary:}
  3142.     i := StrToInt(TheLine);
  3143.  
  3144. {now we try to convert all the data:}
  3145.     if (i = 0) then
  3146.     begin {text}
  3147.       TheStrings := TStringList.Create;
  3148. {although not documented, TStrings.LoadFromStream starts from
  3149. the CURRENT TStream.Position ! Ain't that nice !}
  3150.       TheStrings.LoadFromStream(AStream);
  3151.       TheResult := Self.ConvertTextData(ColCount, NoFileSeries, 0, ',', TheStrings, SeriesInfo);
  3152.       AsText := TRUE;
  3153.     end
  3154.     else if (i = 1) then
  3155.     begin {binary}
  3156.       TheResult := Self.ConvertBinaryData(ColCount, NoFileSeries, AStream, SeriesInfo);
  3157.       AsText := FALSE;
  3158.     end
  3159.     else
  3160.     begin
  3161.       raise EFOpenError.Create(sLoadFromFile1);
  3162.     end;
  3163.  
  3164. {new data has not changed:}
  3165.     Self.DataChanged := FALSE;
  3166.   except
  3167.     CleanUp;
  3168.     raise;
  3169.   end; {try}
  3170.  
  3171.   for i := InitialSeriesCount to Self.Count-1 do
  3172.     TSeries(Self.Items[i]).GetBounds;
  3173.  
  3174.   LoadFromStream := TheResult;
  3175. end;
  3176.  
  3177. {------------------------------------------------------------------------------
  3178.      Function: TSeriesList.GetNearestPoint
  3179.   Description: gets the nearest series and point to the input point
  3180.        Author: Mat Ballard
  3181.  Date created: 04/25/2000
  3182. Date modified: 04/25/2000 by Mat Ballard
  3183.       Purpose: user selection of data on screen
  3184.  Return Value: the Index of the nearest point
  3185.  Known Issues: 1. this is a long and messy function: while some parts could be done
  3186.                lower done in TSeries, other bits can't. i am still not
  3187.                completely happy with it.
  3188.                2. We "GenerateXXXOutline" here for Columns and Pies, but not for XY types.
  3189.  ------------------------------------------------------------------------------}
  3190. function TSeriesList.GetNearestPoint(
  3191.   ThePlotType: TPlotType;
  3192.   ColumnGap,
  3193.   iX, iY: Integer;
  3194.   var TheSeries: Integer;
  3195.   var MinDistance: Single;
  3196.   var pSeries: TSeries): Integer;
  3197. var
  3198.   CellWidth, CellHeight,
  3199.   iCol, jRow, NoPieCols,
  3200.   iSeries,
  3201.   NearestPoint,
  3202.   PieLeft, PieTop, PieWidth, PieHeight: Integer;
  3203.   Distance, dX, Span, X, Y,
  3204.   YSum, YNegSum, YSumOld, YNegSumOld, YTotal, YNegTotal: Single;
  3205.   pSeriesi: TSeries;
  3206.  
  3207.   function GetColumnIndex: Boolean;
  3208.   begin
  3209.     GetColumnIndex := FALSE;
  3210. {all column plots use Series[0].XData:}
  3211.     pSeriesi := TSeries(Self.Items[0]);
  3212.     X := FXAxis.XofF(iX);
  3213. {bug out if outside span:}
  3214.     if (X < pSeriesi.XData^[0]) then exit;
  3215.     dX := ((100 - ColumnGap) / 100) * (
  3216.       pSeriesi.XData^[pSeriesi.NoPts-1] -
  3217.       pSeriesi.XData^[pSeriesi.NoPts-2]);
  3218.     if (X > pSeriesi.XData^[pSeriesi.NoPts-1] + dX) then exit;
  3219. {find the nearest point:}
  3220.     NearestPoint := pSeriesi.GetNearestPointToFX(iX);
  3221.     if (NearestPoint <> 0) then
  3222.       if (X < pSeriesi.XData^[NearestPoint]) then
  3223.         Dec(NearestPoint);
  3224.     if (NearestPoint = pSeriesi.NoPts-1) then
  3225.       Span := pSeriesi.XData^[NearestPoint] - pSeriesi.XData^[NearestPoint-1]
  3226.     else
  3227.       Span := pSeriesi.XData^[NearestPoint+1] - pSeriesi.XData^[NearestPoint];
  3228.     dX := ((100 - ColumnGap) / 100) * Span;
  3229. {was the click in the gap between bars ?}
  3230.     if (X > pSeriesi.XData^[NearestPoint] + dX) then
  3231.       exit;
  3232.     GetColumnIndex := TRUE;
  3233.   end;
  3234.  
  3235. begin
  3236.   Distance := 1.0e38;
  3237.   MinDistance := 1.0e38;
  3238.   TheSeries := -1;
  3239.   GetNearestPoint := -1;
  3240.   pSeries := nil;
  3241.  
  3242.   if (Self.Count = 0) then exit;
  3243.  
  3244.   iSeries := 0;
  3245.   case ThePlotType of
  3246.     ptXY, ptError, ptMultiple, ptBubble:
  3247.       begin
  3248. {loop over series: note that ptError and ptBubble skips every second series -
  3249.  the error/size ones}
  3250.         while (iSeries < Count) do
  3251.         begin
  3252.           pSeriesi := TSeries(Self.Items[iSeries]);
  3253. {Find the nearest point IN THIS SERIES:}
  3254.           NearestPoint := pSeriesi.GetNearestXYPoint(
  3255.             iX, iY,
  3256.             0, 0,
  3257.             Distance);
  3258. {Mirror, Mirror on the wall,
  3259.  who is the nearest one of all ?}
  3260.           if (Distance < MinDistance) then
  3261.           begin
  3262.             GetNearestPoint := NearestPoint;
  3263.             MinDistance := Distance;
  3264.             TheSeries := iSeries;
  3265.             pSeries := pSeriesi;
  3266.           end;
  3267. {Note: we don't pSeries.GenerateXYOutline here, because that would be running
  3268.  that method every time the screen got clicked on, which is a bit of a drain.
  3269.  However, we do run pSeries.GenerateColumnOutline and pSeries.GeneratePieOutline
  3270.  because they are simple assignments.}
  3271.  
  3272. {ptError: every second series is just the X and Y error:}
  3273.           if ((ThePlotType = ptError) or
  3274.               (ThePlotType = ptBubble)) then
  3275.             Inc(iSeries, 2)
  3276.            else
  3277.             Inc(iSeries);
  3278.         end; {while over series}
  3279.       end;
  3280.  
  3281.     ptColumn:
  3282.       begin
  3283.         if (not GetColumnIndex) then
  3284.           exit;
  3285. {now home in: which series was it:}
  3286.         TheSeries := 0;
  3287.         if (Self.Count > 1) then
  3288.           TheSeries := Trunc(Self.Count * (X - pSeriesi.XData^[NearestPoint]) / dX);
  3289. {we now know which point in  which series:}
  3290.         pSeries := TSeries(Self.Items[TheSeries]);
  3291.         Y := FYAxis.YofF(iY);
  3292.         if (Y > pSeries.YData^[NearestPoint]) then
  3293.           exit;
  3294.         if ((Y < 0) and (Y < pSeries.YData^[NearestPoint])) then
  3295.           exit;
  3296.         GetNearestPoint := NearestPoint;
  3297.         MinDistance := 0;
  3298.         pSeries.GenerateColumnOutline(
  3299.           FXAxis.FofX(pSeries.XData^[NearestPoint] + TheSeries * dX / Self.Count),
  3300.           FYAxis.FofY(0),
  3301.           FXAxis.FofX(pSeries.XData^[NearestPoint] + (TheSeries+1) * dX / Self.Count),
  3302.           FYAxis.FofY(pSeries.YData^[NearestPoint]));
  3303.       end;
  3304.  
  3305.     ptStack, ptNormStack:
  3306.       begin
  3307.         if (not GetColumnIndex) then
  3308.           exit;
  3309.         Y := FYAxis.YofF(iY);
  3310. {now home in: which series was it:}
  3311.         TheSeries := 0;
  3312.         YSum := 0;
  3313.         YNegSum := 0;
  3314.         YTotal := 0;
  3315.         YNegTotal := 0;
  3316.         if (ThePlotType = ptNormStack) then
  3317. {ptStack and ptNormStack are pretty similar expcept for ...}
  3318.         begin
  3319.           if ((Y < 0) or (Y > 100)) then
  3320.             exit;
  3321. {count every series:}
  3322.           for iSeries := 0 to Count-1 do
  3323.           begin
  3324.             if (TSeries(Items[iSeries]).YData^[NearestPoint] >= 0) then
  3325.               YTotal := YTotal + TSeries(Items[iSeries]).YData^[NearestPoint]
  3326.              else
  3327.               YNegTotal := YNegTotal + TSeries(Items[iSeries]).YData^[NearestPoint];
  3328.           end; {count every series}
  3329. {prepare for conversion of data to percent:}
  3330.           YTotal := YTotal / 100;
  3331.           YNegTotal := - YNegTotal / 100;
  3332.         end;
  3333. {loop over every series:}
  3334.         for iSeries := 0 to Count-1 do
  3335.         begin
  3336.           pSeries := TSeries(Items[iSeries]);
  3337.           if (pSeries.YData^[NearestPoint] >= 0) then
  3338.           begin
  3339.             YSumOld := YSum;
  3340.             if (ThePlotType = ptStack) then
  3341.               YSum := YSum + pSeries.YData^[NearestPoint]
  3342.              else {ptNormStack}
  3343.               YSum := YSum + pSeries.YData^[NearestPoint] / YTotal; 
  3344.             if ((YSumOld < Y) and (Y < YSum)) then
  3345.             begin {Bingo !}
  3346.               GetNearestPoint := NearestPoint;
  3347.               MinDistance := 0;
  3348.               pSeries.GenerateColumnOutline(
  3349.                 FXAxis.FofX(pSeries.XData^[NearestPoint]),
  3350.                 FYAxis.FofY(YSumOld),
  3351.                 FXAxis.FofX(pSeries.XData^[NearestPoint] + dX),
  3352.                 FYAxis.FofY(YSum));
  3353.               break;
  3354.             end;
  3355.           end
  3356.           else
  3357.           begin
  3358.             YNegSumOld := YNegSum;
  3359.             if (ThePlotType = ptStack) then
  3360.               YNegSum := YNegSum + pSeries.YData^[NearestPoint]
  3361.              else {ptNormStack}
  3362.               YNegSum := YNegSum + pSeries.YData^[NearestPoint] / YNegTotal;
  3363.             if ((YNegSum < Y) and (Y < YNegSumOld)) then
  3364.             begin {Bingo !}
  3365.               GetNearestPoint := NearestPoint;
  3366.               MinDistance := 0;
  3367.               pSeries.GenerateColumnOutline(
  3368.                 FXAxis.FofX(pSeries.XData^[NearestPoint]),
  3369.                 FYAxis.FofY(YNegSumOld),
  3370.                 FXAxis.FofX(pSeries.XData^[NearestPoint] + dX),
  3371.                 FYAxis.FofY(YNegSum));
  3372.               break;
  3373.             end;
  3374.           end; {YData >= 0}
  3375.         end; {for iSeries}
  3376.       end;
  3377.  
  3378.     ptPie:
  3379.       begin
  3380. {each Pie sits in a cell:}
  3381.         NoPieCols := Trunc(0.99 + Count / NoPieRows);
  3382.         CellWidth := (PlotBorder.Right - PlotBorder.Left) div NoPieCols;
  3383.         CellHeight := (PlotBorder.Bottom - PlotBorder.Top) div NoPieRows;
  3384. {... but does not occupy the entire cell:}
  3385.         PieWidth := Round(PIE_SIZE * CellWidth);
  3386.         PieHeight := Round(PIE_SIZE * CellHeight);
  3387.         if (PieHeight > PieWidth) then
  3388.           PieHeight := PieWidth;
  3389.  
  3390.         iSeries := 0;
  3391.         for iCol := 0 to NoPieCols-1 do
  3392.         begin
  3393.           for jRow := 0 to NoPieRows-1 do
  3394.           begin
  3395.             if (iSeries >= Count) then break;
  3396. {indent the (left, top) a bit:}
  3397.             PieLeft := PlotBorder.Left + iCol * CellWidth +
  3398.               (CellWidth-PieWidth) div 2;
  3399.             PieTop := PlotBorder.Top + jRow * CellHeight +
  3400.               (CellHeight-PieHeight) div 2;
  3401.             pSeries := TSeries(Self.Items[iSeries]);
  3402. {Find the nearest point IN THIS SERIES:}
  3403.             NearestPoint := pSeries.GetNearestPieSlice(
  3404.               iX, iY,
  3405.               PieLeft, PieTop, PieWidth, PieHeight,
  3406.               Distance);
  3407.             if (Distance = 0) then
  3408.             begin
  3409.               GetNearestPoint := NearestPoint;
  3410.               MinDistance := Distance;
  3411.               TheSeries := iSeries;
  3412.               pSeries.GeneratePieOutline(
  3413.                 PieLeft,
  3414.                 PieTop,
  3415.                 PieWidth,
  3416.                 PieHeight,
  3417.                 NearestPoint);
  3418.               break;
  3419.             end;
  3420.             Inc(iSeries);
  3421.           end; {jRow}
  3422.         end; {iCol}
  3423.       end; {ptPie}
  3424.     ptPolar:
  3425.       begin
  3426.       end;
  3427.   end; {case}
  3428. end;
  3429.  
  3430. {------------------------------------------------------------------------------
  3431.      Function: TSeriesList.GetSeriesOfZ
  3432.   Description: gets the series with ZData ZValue
  3433.        Author: Mat Ballard
  3434.  Date created: 04/25/2001
  3435. Date modified: 04/25/2001 by Mat Ballard
  3436.       Purpose: parsing data from strange files
  3437.  Return Value: the guilty series, or nil
  3438.  Known Issues:
  3439.  ------------------------------------------------------------------------------}
  3440. function TSeriesList.GetSeriesOfZ(ZValue: Single): TSeries;
  3441. var
  3442.   i: Integer;
  3443. begin
  3444.   GetSeriesOfZ := nil;
  3445.   for i := 0 to Self.Count-1 do
  3446.   begin
  3447.     if (ZValue = TSeries(Self.Items[i]).ZData) then
  3448.     begin
  3449.       GetSeriesOfZ := TSeries(Self.Items[i]);
  3450.       break;
  3451.     end;    
  3452.   end;       
  3453. end;
  3454.  
  3455. {------------------------------------------------------------------------------
  3456.     Procedure: TSeriesList.DoChange
  3457.   Description: event firing proedure
  3458.        Author: Mat Ballard
  3459.  Date created: 04/25/2000
  3460. Date modified: 04/25/2000 by Mat Ballard
  3461.       Purpose: fires the OnDataChange event
  3462.  Known Issues:
  3463.  ------------------------------------------------------------------------------}
  3464. procedure TSeriesList.DoStyleChange;
  3465. begin
  3466.   if Assigned(FOnStyleChange) then OnStyleChange(Self);
  3467. end;
  3468.  
  3469. {------------------------------------------------------------------------------
  3470.     Procedure: TSeriesList.DoDataChange
  3471.   Description: event firing proedure
  3472.        Author: Mat Ballard
  3473.  Date created: 04/25/2000
  3474. Date modified: 04/25/2000 by Mat Ballard
  3475.       Purpose: fires the OnDataChange event
  3476.  Known Issues:
  3477.  ------------------------------------------------------------------------------}
  3478. procedure TSeriesList.DoDataChange;
  3479. begin
  3480.   FDataChanged := TRUE;
  3481.   if Assigned(FOnDataChange) then OnDataChange(Self);
  3482. end;
  3483.  
  3484. {------------------------------------------------------------------------------
  3485.      Function: TSeriesList.GetTotalNoPts
  3486.   Description: standard property Get function
  3487.        Author: Mat Ballard
  3488.  Date created: 04/25/2000
  3489. Date modified: 04/25/2000 by Mat Ballard
  3490.       Purpose: gets the value of the TotalNoPts Property
  3491.  Return Value: Integer
  3492.  Known Issues:
  3493.  ------------------------------------------------------------------------------}
  3494. function TSeriesList.GetTotalNoPts: Integer;
  3495. var
  3496.   i,
  3497.   Sum: Integer;
  3498. begin
  3499. {loop over all series:}
  3500.   Sum := 0;
  3501.   for i := 0 to Count-1 do
  3502.   begin
  3503.     Sum := Sum + TSeries(Items[i]).NoPts;
  3504.   end;
  3505.   GetTotalNoPts := Sum;
  3506. end;
  3507.  
  3508. {------------------------------------------------------------------------------
  3509.      Function: TSeriesList.GetMaxNoPts
  3510.   Description: standard property Get function
  3511.        Author: Mat Ballard
  3512.  Date created: 09/21/2000
  3513. Date modified: 09/21/2000 by Mat Ballard
  3514.       Purpose: gets the value of the MaxNoPts Property
  3515.  Return Value: Integer
  3516.  Known Issues:
  3517.  ------------------------------------------------------------------------------}
  3518. function TSeriesList.GetMaxNoPts: Integer;
  3519. var
  3520.   i,
  3521.   TheMax: Integer;
  3522. begin
  3523.   TheMax := 0;
  3524.   if (Count > 0) then
  3525.   begin
  3526.     TheMax := TSeries(Items[0]).NoPts;
  3527.     for i := 1 to Count-1 do
  3528.     begin
  3529.       if (TheMax < TSeries(Items[i]).NoPts) then
  3530.         TheMax := TSeries(Items[i]).NoPts;
  3531.     end;
  3532.   end;
  3533.   GetMaxNoPts := TheMax;
  3534. end;
  3535.  
  3536. {------------------------------------------------------------------------------
  3537.      Function: TSeriesList.GetMinNoPts
  3538.   Description: standard property Get function
  3539.        Author: Mat Ballard
  3540.  Date created: 09/21/2000
  3541. Date modified: 09/21/2000 by Mat Ballard
  3542.       Purpose: gets the value of the MinNoPts Property
  3543.  Return Value: Integer
  3544.  Known Issues:
  3545.  ------------------------------------------------------------------------------}
  3546. function TSeriesList.GetMinNoPts: Integer;
  3547. var
  3548.   i,
  3549.   TheMin: Integer;
  3550. begin
  3551.   TheMin := 0;
  3552.   if (Count > 0) then
  3553.   begin
  3554.     TheMin := TSeries(Items[0]).NoPts;
  3555.     for i := 1 to Count-1 do
  3556.     begin
  3557.       if (TheMin < TSeries(Items[i]).NoPts) then
  3558.         TheMin := TSeries(Items[i]).NoPts;
  3559.     end;
  3560.   end;
  3561.   GetMinNoPts := TheMin;
  3562. end;
  3563.  
  3564. {------------------------------------------------------------------------------
  3565.      Function: TSeriesList.ParseData
  3566.   Description: oversees the importation and pasting of data
  3567.        Author: Mat Ballard
  3568.  Date created: 12/1/1999
  3569. Date modified: 04/27/2001 by Mat Ballard
  3570.       Purpose: runs the ParserForm, and adds the new data as new Axis
  3571.  Return Value: TRUE is successful
  3572.  Known Issues: moved from TCustomPlot
  3573.  ------------------------------------------------------------------------------}
  3574. function TSeriesList.ParseData(
  3575.   TheData: TStringList;
  3576.   TheHelpFile: String): Boolean;
  3577. var
  3578.   i,
  3579.   InitialSeriesCount,
  3580.   iColumn,
  3581.   jRow,
  3582.   NoPastedSeries,
  3583.   NoXs, NoYs, NoZs,
  3584.   XSeriesCol: Integer;
  3585.   Delimiter,
  3586.   ZDataLine,
  3587.   ZValue: String;
  3588.   ParserForm: TParserForm;
  3589.   SeriesInfo: pSeriesInfoArray;
  3590.   SeriesOfCol: pIntegerArray;
  3591. begin
  3592.   ParseData := FALSE;
  3593.   InitialSeriesCount := Self.Count;
  3594.  
  3595.   ParserForm := TParserForm.Create(nil);
  3596.   jRow := 0;
  3597.   try
  3598.     for jRow := 0 to TheData.Count-1 do
  3599.     begin
  3600.       ParserForm.DataListBox.Items.Add(TheData.Strings[jRow]);
  3601.     end;
  3602.     jRow := 0;
  3603.   except
  3604. {the file was to big to place into the listbox:}
  3605.     ShowMessageFmt(sParseData1, [jRow-1]);
  3606.   end;
  3607.  
  3608.   ParserForm.HelpFile := TheHelpFile;
  3609.  
  3610.   if (ParserForm.ShowModal = mrOK) then
  3611.   begin
  3612.     Delimiter := ParserForm.Delimiters[ParserForm.TheDelimiter];
  3613.     NoXs := 0;
  3614.     NoYs := 0;
  3615.     NoZs := 0;
  3616.     for iColumn := 1 to ParserForm.InfoGrid.ColCount-1 do
  3617.     begin
  3618.       if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'X') then
  3619.         Inc(NoXs)
  3620.       else if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'Y') then
  3621.         Inc(NoYs)
  3622.       else if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'Z') then
  3623.         Inc(NoZs)
  3624.     end;
  3625.     ZDataLine := ParserForm.InfoGrid.Rows[X_OR_Y_OR_Z].CommaText;
  3626. {The data might be in the form of:
  3627.     x1,y1,z1
  3628.     x2,y2,z2
  3629.     ...
  3630.  or in the form of columns of series (ie: like we save it):}
  3631.     if (NoZs > 0) then
  3632.     begin
  3633.       if ((NoXs = NoYs) and (NoYs = NoZs)) then
  3634.       begin
  3635.         ParseData := ConvertXYZData(ParserForm.TheFirstDataLine,
  3636.           Delimiter, ParserForm.InfoGrid.Rows[X_OR_Y_OR_Z], TheData);
  3637.       end
  3638.       else
  3639.         ShowMessage(sSorryTooComplex);
  3640.     end
  3641.     else
  3642.     begin
  3643. {allocate memory for the dynamic arrays, which are small:}
  3644.       GetMem(SeriesInfo, ParserForm.InfoGrid.ColCount * SizeOf(TSeriesInfo));
  3645.       GetMem(SeriesOfCol, (ParserForm.InfoGrid.ColCount+1) * SizeOf(Integer));
  3646.   {this allocates more memory than SeriesInfo needs, but so what ?}
  3647.       for i := 0 to ParserForm.InfoGrid.ColCount-1 do
  3648.       begin
  3649.         SeriesInfo^[i].XCol := 0;
  3650.         SeriesInfo^[i].XTextCol := -1;
  3651.       end;
  3652.       if (NoXs = 0) then
  3653.         SeriesInfo^[0].XCol := -2;
  3654.   {Grab the line of Z Data, if any:}
  3655.   {This is complex: ConvertTextData expects the Z Data line, if present,
  3656.    to be String[FirstLine] AND to start with 'ZData' - because this is the
  3657.    TPlot file format. However, this may NOT be the case with third party text
  3658.    files: the Z Data could come anywhere, or it could even be manually entered
  3659.    by the user. Because of these problems, we process the Z Data in this
  3660.    routine, then remove any Z references from the Z Data Line.}
  3661.       if (ParserForm.TheZDataLine >= 0) then
  3662.       begin
  3663.         ZDataLine := Uppercase(TheData.Strings[ParserForm.TheZDataLine]);
  3664.         iColumn := Pos('ZDATA', ZDataLine);
  3665.         if (iColumn > 0) then
  3666.         begin
  3667.   {remove ZDATA because we process it in this routine:}
  3668.           TheData.Strings[ParserForm.TheZDataLine] :=
  3669.             Copy(ZDataLine, 1, iColumn-1) +
  3670.             Copy(ZDataLine, iColumn+5, 9999);
  3671.         end;
  3672.       end  
  3673.       else
  3674.       begin
  3675.   {The user may have entered Z Data manually:}    
  3676.         ZDataLine := '';
  3677.         for iColumn := 1 to ParserForm.InfoGrid.ColCount-1 do
  3678.         begin
  3679.           if (Length(ParserForm.InfoGrid.Cells[iColumn, Z_DATA_LINE]) > 0) then
  3680.           begin
  3681.             ZDataLine := ZDataLine +
  3682.               ParserForm.InfoGrid.Cells[iColumn, Z_DATA_LINE] + Delimiter;
  3683.           end;
  3684.         end;
  3685.       end;
  3686.  
  3687.   {Determine the number of series:}
  3688.       NoPastedSeries := 0;
  3689.   {examine the columns one by one:}
  3690.       for iColumn := 1 to ParserForm.InfoGrid.ColCount-1 do
  3691.       begin
  3692.         SeriesOfCol^[iColumn] := -1;
  3693.         if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'X') then
  3694.         begin
  3695.   {This is a column of X data:}
  3696.           SeriesOfCol^[iColumn] := NoPastedSeries;
  3697.           SeriesInfo^[NoPastedSeries].XCol := iColumn;
  3698.         end
  3699.         else if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'XTEXT') then
  3700.         begin
  3701.   {This is a column of X STRING data:}
  3702.           SeriesOfCol^[iColumn] := NoPastedSeries;
  3703.           SeriesInfo^[NoPastedSeries].XTextCol := iColumn;
  3704.         end
  3705.         else if (ParserForm.InfoGrid.Cells[iColumn, X_OR_Y_OR_Z] = 'Y') then
  3706.         begin
  3707.   {we've found a series - this is a column of Y data:}
  3708.           SeriesOfCol^[iColumn] := NoPastedSeries;
  3709.           SeriesInfo^[NoPastedSeries].YCol := iColumn;
  3710.           if (Length(ParserForm.InfoGrid.Cells[iColumn,DEPENDS_ON_X]) > 0) then
  3711.           begin
  3712.             XSeriesCol := StrToInt(ParserForm.InfoGrid.Cells[iColumn,DEPENDS_ON_X]);
  3713.             if (SeriesOfCol^[XSeriesCol] < 0) then
  3714.               raise EComponentError.Create('TCustomPlot.ParseData: ' + sParseData2);
  3715.             if (SeriesOfCol^[XSeriesCol] = NoPastedSeries) then
  3716.             begin
  3717.   {This column of Y Data has its own X Data (column):}
  3718.               SeriesInfo^[NoPastedSeries].XSeriesIndex := -1;
  3719.             end
  3720.             else
  3721.             begin
  3722.   {This column of Y Data uses another Series X Data (column):}
  3723.               SeriesInfo^[NoPastedSeries].XSeriesIndex :=
  3724.                 SeriesOfCol^[XSeriesCol] + InitialSeriesCount;
  3725.             end;
  3726.           end;
  3727. {This Y Column has come before any X columns:}
  3728.           if (iColumn = 1) then
  3729.             SeriesInfo^[NoPastedSeries].XSeriesIndex := -1;
  3730. {There is no X data at all !}
  3731.           if ((NoXs = 0) and (iColumn > 1)) then
  3732.             SeriesInfo^[NoPastedSeries].XSeriesIndex :=
  3733.               InitialSeriesCount;
  3734.               
  3735. {We add the new series:}
  3736.           SeriesInfo^[NoPastedSeries].Index :=
  3737.             Self.Add(SeriesInfo^[NoPastedSeries].XSeriesIndex);
  3738.   {and set its name:}
  3739.           TSeries(Self.Items[SeriesInfo^[NoPastedSeries].Index]).Name :=
  3740.             ParserForm.InfoGrid.Cells[iColumn, SERIES_NAMES];
  3741.   {and its Z value:}
  3742.           if (Length(ZDataLine) > 0) then
  3743.           begin
  3744.             repeat
  3745.               ZValue := GetWord(ZDataLine, Delimiter);
  3746.               if IsReal(ZValue) then
  3747.               begin
  3748.                 try
  3749.                   TSeries(Self.Items[SeriesInfo^[NoPastedSeries].Index]).ZData :=
  3750.                     StrToFloat(ZValue);
  3751.                   ZValue := '';
  3752.                 except
  3753.                   On EConvertError do
  3754.                     ZValue := '?';
  3755.                 end;
  3756.               end
  3757.               else
  3758.                 ZValue := '?';
  3759.             until ((Length(ZValue) = 0) or (Length(ZDataLine) = 0));
  3760.           end
  3761.           else
  3762.           begin
  3763.             TSeries(Self.Items[SeriesInfo^[NoPastedSeries].Index]).ZData :=
  3764.               SeriesInfo^[NoPastedSeries].Index;
  3765.           end;
  3766.  
  3767.           Inc(NoPastedSeries);
  3768.         end
  3769.         else
  3770.         begin
  3771.           SeriesInfo^[NoPastedSeries].XCol := -1;
  3772.         end;
  3773.       end;
  3774.  
  3775.   {now we try to convert all the data:}
  3776.       if (NoPastedSeries > 0) then
  3777.         ParseData := ConvertTextData(ParserForm.InfoGrid.ColCount-1, NoPastedSeries, ParserForm.TheFirstDataLine,
  3778.           Delimiter, TheData, SeriesInfo);
  3779.       FreeMem(SeriesInfo, ParserForm.InfoGrid.ColCount * SizeOf(TSeriesInfo));
  3780.       FreeMem(SeriesOfCol, ParserForm.InfoGrid.ColCount * SizeOf(Integer));
  3781.     end; {No Z's on us !}
  3782.   end; {ShowModal = mrOK}
  3783.   ParserForm.Free;
  3784. end;
  3785.  
  3786. {------------------------------------------------------------------------------
  3787.      Function: TSeriesList.ConvertBinaryData
  3788.   Description: Adds binary data to the new Series
  3789.        Author: Mat Ballard
  3790.  Date created: 12/1/1999
  3791. Date modified: 04/27/2001 by Mat Ballard
  3792.       Purpose: Given a AxisLocationArray, converts the text data to numeric data and adds it to the new Axis
  3793.  Return Value: TRUE is successful
  3794.  Known Issues: This procedure assumes that TheStream.Position points to the start
  3795.                of the binary data.
  3796.                Moved from TCustomPlot.
  3797.  ------------------------------------------------------------------------------}
  3798. function TSeriesList.ConvertBinaryData(
  3799.   ColCount,
  3800.   SeriesCount: Integer;
  3801.   TheStream: TMemoryStream;
  3802.   SeriesInfo: pSeriesInfoArray): Boolean;
  3803. var
  3804.   DataSize,
  3805.   i: Integer; {iColumn}
  3806.   MemPosition: Longint;
  3807.   NullValue,
  3808.   ZValue: Single;
  3809.   ptr: Pointer;
  3810.   pTheChar: PChar;
  3811.   pValues: pSingleArray;
  3812.   pLine: array [0..32] of char;
  3813.   XTextValue: String;
  3814. begin
  3815.   //ConvertBinaryData := FALSE;
  3816.   GetMem(pValues, ColCount * SizeOf(Single));
  3817.   DataSize := SizeOf(Single);
  3818.  
  3819. {calculate our "ignore" value:}
  3820.   ptr := @NullValue;
  3821.   pTheChar := ptr;
  3822.   for i := 1 to DataSize do
  3823.   begin
  3824.     pTheChar^ := 'x';
  3825.     Inc(pTheChar);
  3826.   end;
  3827.  
  3828. {check fir ZData:}
  3829.   MemPosition := TheStream.Position;
  3830.   TheStream.Read(pLine, 6);
  3831.   pLine[6] := Chr(0);
  3832.   if (StrComp(pLine, 'ZData:') = 0) then
  3833.   begin
  3834.     for i := 0 to SeriesCount-1 do
  3835.     begin
  3836.       TheStream.Read(ZValue, DataSize);
  3837.       TSeries(Self.Items[SeriesInfo^[i].Index]).ZData := ZValue;
  3838.     end;
  3839.   end
  3840.   else
  3841.     TheStream.Position := MemPosition;
  3842.  
  3843.   while (TheStream.Position < TheStream.Size) do
  3844.   begin
  3845.     for i := 0 to SeriesCount-1 do
  3846.     begin
  3847.       if (SeriesInfo^[i].XCol > 0) then
  3848.         TheStream.Read(SeriesInfo^[i].XValue, DataSize);
  3849.       if (SeriesInfo^[i].XTextCol > 0) then
  3850.         XTextValue := ReadLine(TheStream);
  3851.       TheStream.Read(SeriesInfo^[i].YValue, DataSize);
  3852.  
  3853.       if (SeriesInfo^[i].XTextCol > 0) then
  3854.         TSeries(Self.Items[SeriesInfo^[i].Index]).AddStringPoint(
  3855.           XTextValue,
  3856.           SeriesInfo^[i].XValue,
  3857.           SeriesInfo^[i].YValue,
  3858.           FALSE, FALSE)
  3859.       else
  3860.         TSeries(Self.Items[SeriesInfo^[i].Index]).AddPoint(
  3861.           SeriesInfo^[i].XValue,
  3862.           SeriesInfo^[i].YValue,
  3863.           FALSE, FALSE);
  3864.     end; {for iColumn}
  3865.   end; {for lines of data}
  3866.  
  3867.   FreeMem(pValues, ColCount * SizeOf(Single));
  3868. {for a subsequent SaveClick:}
  3869.   ConvertBinaryData := TRUE;
  3870. end;
  3871.  
  3872. {------------------------------------------------------------------------------
  3873.      Function: TSeriesList.ConvertTextData
  3874.   Description: Adds text data to the new Series
  3875.        Author: Mat Ballard
  3876.  Date created: 12/1/1999
  3877. Date modified: 04/27/2001 by Mat Ballard
  3878.       Purpose: Given a pSeriesInfoArray, converts the text data to numeric data and adds it to the new Axis
  3879.  Return Value: TRUE is successful
  3880.  Known Issues: moved from TCustomPlot
  3881.  ------------------------------------------------------------------------------}
  3882. function TSeriesList.ConvertTextData(
  3883.   ColCount,
  3884.   SeriesCount,
  3885.   FirstLine: Integer;
  3886.   Delimiter: String;
  3887.   TheData: TStringList;
  3888.   SeriesInfo: pSeriesInfoArray): Boolean;
  3889. var
  3890.   i,
  3891.   jRow: Integer;
  3892.   TheCell,
  3893.   TheLine: String;
  3894.   TheSortedLine: TStringList;
  3895. begin
  3896. {Does this contain Z Data ?}
  3897.   if (Pos('ZData', TheData.Strings[FirstLine]) > 0) then
  3898.   begin
  3899.     TheLine := TheData.Strings[0];
  3900.     for i := 0 to SeriesCount-1 do
  3901.     begin
  3902.       if (SeriesInfo^[i].XCol > 0) then
  3903.         GetWord(TheLine, Delimiter);
  3904.       if (SeriesInfo^[i].XTextCol > 0) then
  3905.         GetWord(TheLine, Delimiter);
  3906.       TheCell := GetWord(TheLine, Delimiter);
  3907.       TSeries(Self.Items[SeriesInfo^[i].Index]).ZData :=
  3908.         StrToFloat(TheCell);
  3909.     end;
  3910.     Inc(FirstLine);
  3911.   end;
  3912.  
  3913.   TheSortedLine := TStringList.Create;
  3914.   for i := 0 to ColCount-1 do
  3915.     TheSortedLine.Add('');
  3916.  
  3917.   for jRow := FirstLine to TheData.Count-1 do
  3918.   begin
  3919.     TheLine := TheData.Strings[jRow];
  3920.  
  3921.     for i := 0 to ColCount-1 do
  3922.     begin
  3923.       TheSortedLine.Strings[i] := GetWord(TheLine, Delimiter);
  3924.     end;
  3925.  
  3926.     for i := 0 to SeriesCount-1 do
  3927.     begin
  3928.       try
  3929.         if (SeriesInfo^[i].XCol = -2) then
  3930.           SeriesInfo^[i].XValue := (jRow - FirstLine)
  3931.         else if (SeriesInfo^[i].XCol > 0) then
  3932.           SeriesInfo^[i].XValue := StrToFloat(TheSortedLine.Strings[SeriesInfo^[i].XCol-1]);
  3933.         if (Length(TheSortedLine.Strings[SeriesInfo^[i].YCol-1]) > 0) then
  3934.         begin
  3935.           SeriesInfo^[i].YValue := StrToFloat(TheSortedLine.Strings[SeriesInfo^[i].YCol-1]);
  3936.           if (SeriesInfo^[i].XTextCol > 0) then
  3937.             TSeries(Self.Items[SeriesInfo^[i].Index]).AddStringPoint(
  3938.               TheSortedLine.Strings[SeriesInfo^[i].XTextCol-1],
  3939.               SeriesInfo^[i].XValue,
  3940.               SeriesInfo^[i].YValue,
  3941.               FALSE, FALSE)
  3942.            else
  3943.             TSeries(Self.Items[SeriesInfo^[i].Index]).AddPoint(
  3944.               SeriesInfo^[i].XValue,
  3945.               SeriesInfo^[i].YValue,
  3946.               FALSE, FALSE);
  3947.         end;      
  3948.       except
  3949.         SeriesInfo^[i].XValue := -999999;
  3950.       end;
  3951.     end; {for i}
  3952.   end; {for lines of data}
  3953. {cleanup:}
  3954.   TheSortedLine.Free;
  3955.  
  3956. {Make the new Series visible:}
  3957.   for i := 0 to SeriesCount-1 do
  3958.   begin
  3959.     if (SeriesInfo^[i].Index >= 0) then
  3960.       TSeries(Self.Items[SeriesInfo^[i].Index]).Visible := TRUE;
  3961.   end;
  3962.  
  3963. {for a subsequent SaveClick:}
  3964.   ConvertTextData := TRUE;
  3965. end;
  3966.  
  3967. {------------------------------------------------------------------------------
  3968.      Function: TSeriesList.ConvertXYZData
  3969.   Description: Processes XYZ text data
  3970.        Author: Mat Ballard
  3971.  Date created: 04/19/2001
  3972. Date modified: 04/27/2001 by Mat Ballard
  3973.       Purpose: data importation
  3974.  Return Value: TRUE is successful
  3975.  Known Issues: moved from TCustomPlot
  3976.  ------------------------------------------------------------------------------}
  3977. function TSeriesList.ConvertXYZData(
  3978.   FirstLine: Integer;
  3979.   Delimiter: String;
  3980.   InfoGridRows: TStrings;
  3981.   TheData: TStringList): Boolean;
  3982. var
  3983.   iCol,
  3984.   jRow,
  3985.   NoXYZs: Integer;
  3986.   TheCell, TheLine: String;
  3987.   X, Y, Z: Single;
  3988.   pSeries: TSeries;
  3989. begin
  3990.   //ConvertXYZData := FALSE;
  3991.   for jRow := FirstLine to TheData.Count-1 do
  3992.   begin
  3993.     TheLine := TheData[jRow];
  3994.     NoXYZs := 0;
  3995.     X := -999999;
  3996.     Y := -999999;
  3997.     Z := -999999;
  3998.     for iCol := 1 to InfoGridRows.Count-1 do
  3999.     begin
  4000.       TheCell := GetWord(TheLine, Delimiter);
  4001.       if (InfoGridRows.Strings[iCol] = 'X') then
  4002.       begin
  4003.         X := StrToFloat(TheCell);
  4004.         Inc(NoXYZs);
  4005.       end
  4006.       else if (InfoGridRows.Strings[iCol] = 'Y') then
  4007.       begin
  4008.         Y := StrToFloat(TheCell);
  4009.         Inc(NoXYZs);
  4010.       end
  4011.       else if (InfoGridRows.Strings[iCol] = 'Z') then
  4012.       begin
  4013.         Z := StrToFloat(TheCell);
  4014.         Inc(NoXYZs);
  4015.       end;
  4016. {test for a triple:}
  4017.       if (NoXYZs = 3) then
  4018.       begin
  4019.         NoXYZs := 0;
  4020.         pSeries := Self.GetSeriesOfZ(Z);
  4021.         if (pSeries = nil) then
  4022.         begin
  4023.           pSeries := TSeries(Self.Items[Self.Add(-1)]);
  4024.           pSeries.ZData := Z;
  4025.         end;
  4026.         pSeries.AddPoint(X, Y, FALSE, TRUE);
  4027.       end;
  4028.     end;
  4029.   end;
  4030.   ConvertXYZData := TRUE;
  4031. end;
  4032.  
  4033. {------------------------------------------------------------------------------
  4034.     Procedure: TSeriesList.StyleChange
  4035.   Description: target of all of the TSeries(Items[i]) OnStyleChange events
  4036.        Author: Mat Ballard
  4037.  Date created: 03/07/2001
  4038. Date modified: 03/07/2001 by Mat Ballard
  4039.       Purpose: responds to changes in style of the member series
  4040.  Known Issues:
  4041.  ------------------------------------------------------------------------------}
  4042. procedure TSeriesList.StyleChange(
  4043.   Sender: TObject);
  4044. begin
  4045.   if (FIgnoreChanges) then exit;
  4046.  
  4047.   DoStyleChange;
  4048. end;
  4049.  
  4050. {------------------------------------------------------------------------------
  4051.     Procedure: TSeriesList.DataChange
  4052.   Description: target of all of the TSeries(Items[i]) OnStyleChange events
  4053.        Author: Mat Ballard
  4054.  Date created: 03/07/2001
  4055. Date modified: 03/07/2001 by Mat Ballard
  4056.       Purpose: responds to changes in data of the member series
  4057.  Known Issues: get up to 3 screen re-draws
  4058.  ------------------------------------------------------------------------------}
  4059. procedure TSeriesList.DataChange(
  4060.   Sender: TObject);
  4061. begin
  4062.   if (IgnoreChanges) then exit;
  4063.  
  4064.   DoDataChange;
  4065. end;
  4066.  
  4067.  
  4068. end.
  4069.