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

  1. unit Data;
  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: pSeries.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/09/2000
  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 TSeries sub-component - that manages the data for a single series.
  45.  
  46. Known Issues:
  47.       - This would normally be called Series, but TeeChart already uses that unit name.
  48.  
  49. History:
  50.  1.20 15 Jan 2001: change name from pSeries to Data: TChart uses Series, but
  51.  
  52.                    'Data' is what this unit manages.
  53.                    many changes to accomodate new plot types. 
  54.  1.01 21 September 2000: add Brush property for columns
  55. -----------------------------------------------------------------------------}
  56.  
  57. interface
  58.  
  59. uses
  60.   Classes, SysUtils, 
  61. {$IFDEF WINDOWS}
  62.   Wintypes,
  63.   Clipbrd, Controls, Dialogs, Forms, Graphics,
  64. {$ENDIF}
  65. {$IFDEF WIN32}
  66.   Windows,
  67.   Clipbrd, Controls, Dialogs, Forms, Graphics,
  68. {$ENDIF}
  69. {$IFDEF LINUX}
  70.   Types,
  71.   QClipbrd, QControls, QDialogs, QForms, QGraphics,
  72. {$ENDIF}
  73.  
  74. {$IFNDEF NO_MATH}
  75.   Math,
  76. {$ENDIF}
  77.   Axis, Dataedit, Displace, NoMath, PlotDefs, Ptedit, Misc;
  78.  
  79. const
  80.   OUTLINE_DENSITY = 20;
  81. {This is the number of points in a branch of an Outline.}
  82.  
  83. type
  84.   //TOnMinMaxChangeEvent = procedure(Sender: TObject; Value: Single) of object;
  85.  
  86.   THighLow = (hlLow, hlHigh);
  87.   TSetHighLow = set of THighLow;
  88.  
  89.   TDataStatus = (dsNone, dsInternal, dsInternalString, dsExternal);
  90. {These are the data storage states:}
  91. {}
  92. {    dsNone - no data as yet;}
  93. {    dsInternal - there is internal memory;}
  94. {    dsInternalString - there is internal memory, with string X values;}
  95. {    dsExternal - both the X and Y data are stored elsewhere (not in this component).}
  96.  
  97. {$IFDEF DELPHI1}
  98.   EAccessViolation = class(Exception);
  99. {$ENDIF}
  100.   
  101.   TSeries = class(TPersistent)
  102.   private
  103.     FAxisList: TList;
  104.     FBrush: TBrush;
  105.     //FDataChanged: Boolean;
  106.     FDefSize: Word;
  107.     FDeltaX: Integer;
  108.     FDeltaY: Integer;
  109.     FName: String;
  110.     FNoPts: Integer;
  111.     FPen: TPen;
  112.     FHighCapacity: Integer;
  113.     FHighCount: Integer;
  114.     FHighLow: TSetHighLow;
  115.     FHighs: pIntegerArray;
  116.      FLowCount: Integer;
  117.      FLows: pIntegerArray;
  118.     FShadeLimits: Boolean;
  119.     FSymbol: TSymbol;
  120.     FSymbolSize: Integer;
  121.     FVisible: Boolean;
  122.     FXAxis: TAxis;
  123.     FXMin: Single;
  124.     FXMax: Single;
  125.     FXData: pSingleArray;
  126.     FXStringData: TStringList;
  127.     FYAxis: TAxis;
  128.     FYAxisIndex: Byte;
  129.     FYData: pSingleArray;
  130.     FZData: Single;
  131.     Fd2Y_dX2: pSingleArray;
  132.       Size2ndDeriv: Integer;
  133.     FYMin: Single;
  134.     FYMax: Single;
  135. {The bounding rectangle for a Pie, which is stored:}
  136.     //PieLeft, PieTop, Width, Height: Longint;
  137. {The sum  of Y values: used in Pie graphs}
  138.     YSum: Single;
  139.  
  140.     FOnStyleChange: TNotifyEvent;
  141.     FOnDataChange: TNotifyEvent;
  142.     {FOnAddPoint: TNotifyEvent;}
  143. {Currently superceded by direct calls to TAxis.SetMin[Max]FromSeries:}
  144.     {FOnXMinChange: TOnMinMaxChangeEvent;
  145.     FOnXMaxChange: TOnMinMaxChangeEvent;
  146.     FOnYMinChange: TOnMinMaxChangeEvent;
  147.     FOnYMaxChange: TOnMinMaxChangeEvent;}
  148. {may be re-implemented later for other needs.}
  149.  
  150.     FDependentSeries: TList;
  151. {The list of series that use this series' X-Data.}
  152.  
  153.     FExternalXSeries: Boolean;
  154. {Is the X data maintained in a different series ?}
  155.     FXDataSeries: TSeries;
  156. {This is the Data Series in which the External X data, if any, is stored.}
  157.     DataStatus: TDataStatus;
  158. {Was this data generated externally ? If it was, then we do not manage it,
  159.  nor manipulate it.}
  160.     MemSize: LongInt;
  161. {The current number of points allocated in memory.}
  162.     TheOutline: array [0..OUTLINE_DENSITY+1] of TPoint;
  163. {An array of Outline points. These points are in screen co-ordinates (pixels).}
  164. {}
  165. {The Outline is used for clicking and dragging operations.}
  166. {}
  167. {Note that for XY-type series, the Outline is a series of points,
  168.  but for Pie series, the first two are (Top, Left), (Right, Bottom).}
  169.     NoOutlinePts: Integer;
  170. {The number of Outline points.}
  171.     //FOutlineWidth: Integer;
  172. {This is the width of the Outline.}
  173.  
  174.  
  175.     procedure CheckBounds(ThePointNo: Integer; AdjustAxis: Boolean);
  176. {Check the Min and Max properties against this point.}
  177.     function IncMemSize: Boolean;
  178. {Allocate memory for the data.}
  179.  
  180.   protected
  181. {The one and only property getting function:}
  182.     function GetXDataRefCount: Word;
  183.  
  184. {The property-setting routines:}
  185.     procedure SetBrush(Value: TBrush);
  186.     procedure SetDeltaX(Value: Integer);
  187.     procedure SetDeltaY(Value: Integer);
  188.     procedure SetName(Value: String);
  189.     procedure SetPen(Value: TPen);
  190.     procedure SetShadeLimits(Value: Boolean);
  191.     procedure SetSymbol(Value: TSymbol);
  192.     procedure SetSymbolSize(Value: Integer);
  193.     procedure SetVisible(Value: Boolean);
  194.     procedure SetXStringData(Value: TStringList);
  195.     procedure SetYAxisIndex(Value: Byte);
  196.     procedure SetZData(Value: Single);
  197.  
  198.     procedure DoStyleChange;
  199.     procedure DoDataChange;
  200.  
  201.   public
  202.     //property DataChanged: Boolean read FDataChanged write FDataChanged;
  203. {Has the data in this series changed ?}
  204.     property ExternalXSeries: Boolean read FExternalXSeries;
  205. {Is the X data maintained in a different series ?}
  206.     property XDataSeries: TSeries read FXDataSeries;
  207. {If the X data is maintained in a different series, this is the series.}
  208.     property NoPts: Integer read FNoPts;
  209. {The number of points in the series.}
  210.     property HighCount: Integer read FHighCount;
  211. {The number of Highs (Peaks)}
  212.     property Highs: pIntegerArray read FHighs;
  213. {This is a list of the Highs (Peaks) in the plot. See Lows.}
  214.     property LowCount: Integer read FLowCount;
  215. {The number of Lows (Troughs)}
  216.     property Lows: pIntegerArray read FLows;
  217. {This is a list of the Lows (Troughs) in the plot. See Highs.}
  218.     property XDataRefCount: Word read GetXDataRefCount;
  219. {This is the number of series that use this series as an X Data holder.}
  220.  
  221.     property XAxis: TAxis read FXAxis;
  222. {The X Axis to which this series is bound - needed for scaling purposes.}
  223.  
  224.     property YAxis: TAxis read FYAxis;
  225. {The Y Axis to which this series IS bound - can be any of the Y Axes - needed for scaling purposes.}
  226.     property YAxisIndex: Byte read FYAxisIndex write SetYAxisIndex;
  227. {The Y Axis Index to which this series IS bound - can be any of the Y Axes - needed for scaling purposes.
  228.  We define YAxisIndex to run from 1 to FAxisList.Count-1:
  229.     1 => The primary Y Axis,
  230.     2 => The secondary Y Axis,
  231.     etc.}
  232.  
  233.     property XData: pSingleArray read FXData;
  234. {This is the dynamic X data array.
  235.  It can be set by the user, or memory for the data
  236.  can be allocated and managed by this component.}
  237. {}
  238. {The user can access the data points either through the GetPoint / GetXYPoint /
  239.  ReplacePoint methods, or directly by:}
  240. {}
  241. {    ASeries.FXData^[i] := NewValue;}
  242. {}
  243. {Note that the POINTER XData is read-only, but that the array elements are
  244.  read/write.}
  245.  
  246.     property XStringData: TStringlist read FXStringData write SetXStringData;
  247. {This is the X data in string format.}
  248.  
  249.     property YData: pSingleArray read FYData;
  250. {This is the dynamic Y data array.
  251.  It can be set by the user, or memory for the data
  252.  can be allocated and managed by this component.}
  253. {}
  254. {The user can access the data points either through the GetPoint / GetXYPoint /
  255.  ReplacePoint methods, or directly by:}
  256. {}
  257. {    ASeries.FYData^[i] := NewValue;}
  258. {}
  259. {Note that the POINTER YData is read-only, but that the array elements are
  260.  read/write.}
  261.  
  262.     property ZData: Single read FZData write SetZData;
  263. {This is the Z-value for this series.
  264.  It is set by the user.}
  265. {}
  266. {Note that unlike the read-only POINTERS XData and YData,
  267.  ZData is a single read/write value.}
  268.  
  269.     property d2Y_dX2: pSingleArray read Fd2Y_dX2;
  270. {The array of second derivatives - used in cubic splines.}
  271.     property XMin: Single read FXMin;
  272. {The minimum X value, determined by GetBounds.}
  273.     property XMax: Single read FXMax;
  274. {The maximum X value, determined by GetBounds.}
  275.     property YMin: Single read FYMin;
  276. {The minimum Y value, determined by GetBounds.}
  277.     property YMax: Single read FYMax;
  278. {The maximum Y value, determined by GetBounds.}
  279.  
  280.     Constructor Create(
  281.       Index: Integer;
  282.       AxisList: TList;
  283.       XDataSeriesValue: TSeries); virtual;
  284. {Each series needs to know a few things:}
  285. {}
  286. {    1. What axes it can relate to;}
  287. {    2. Does it use another (previous) series X Values ?}
  288.     Destructor Destroy; override;
  289.  
  290. {The following Addxxx methods are now overloaded to add error and 3D functionality.}
  291.     function AddData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean;
  292. {This adds an entire Internal data set of an X array, a Y array,
  293.  and the new number of points: Success returns TRUE.}
  294. {}
  295. {Internal means that TSeries allocates and manages the memory for this data,
  296.  and makes a copy of the data located at XPointer and YPointer into this
  297.  internally managed memory.}
  298. {}
  299. {It can therefore add, remove or edit any points.}
  300.  
  301.     function AddDrawPoint(X, Y: Single; ACanvas: TCanvas): Integer;
  302. {This adds a single point to the xy data (using AddPoint),
  303.  draws the line segment and new point, and returns the number
  304.  of points: -1 indicates failure.}
  305.     function AddPoint(X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer;
  306. {This adds a single point to the xy data and returns the number of points:
  307.  -1 indicates failure. If no memory has been allocated for the data yet, then
  308.  IncMemSize is called automatically.}
  309.     function AddStringPoint(XString: String; X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer;
  310. {This adds a single point, which has a string X value, to the data and returns
  311.  the number of points: -1 indicates failure. If no memory has been allocated
  312.  for the data yet, then IncMemSize is called automatically.}
  313.     function InsertPoint(X, Y: Single): Integer;
  314. {This inserts a single point in the xy data and returns the location of the point:
  315.  -1 indicates failure. The point is inserted at the appropriate X value.}
  316.     function PointToData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean;
  317. {This adds an entire External data set of an X array, a Y array,
  318.  and the new number of points: Success returns TRUE.}
  319. {}
  320. {External means that TSeries does not manage the memory for this data,
  321.  nor can it add, remove or edit any points.}
  322.     procedure ReplacePoint(N: Integer; NewX, NewY: Single);
  323. {This replaces the Nth point's values with X and Y.}
  324.  
  325. {These are the overloaded Addxxx methods to add error and 3D functionality.}
  326.     {function AddData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean; overload;
  327.     function AddDrawPoint(X, Y: Single; ACanvas: TCanvas): Integer; overload;
  328.     function AddPoint(X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer; overload;
  329.     function AddStringPoint(XString: String; X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer; overload;
  330.     function InsertPoint(X, Y: Single): Integer; overload;
  331.     function PointToData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean; overload;
  332.     procedure ReplacePoint(N: Integer; NewX, NewY: Single); overload;}
  333.  
  334.     function AllocateNoPts(Value: LongInt): Boolean;
  335. {Directly allocates memory for a fixed number of points.}
  336. {}
  337. {If AllocateNoPts cannot allocate memory for the requested number of points,
  338.  it allocates what it can and returns FALSE.}
  339.  
  340.     procedure Compress(CompressRatio: Integer);
  341. {This averages every N points in a row and so reduces the size of the
  342.  data set by a factor of N.}
  343.     procedure Contract(TheStart, TheFinish: Integer);
  344. {This throws away all points before TheStart and after TheFinish.}
  345.     procedure CopyToClipBoard;
  346. {Does what it says.}
  347.  
  348.     procedure Displace(TheHelpFile: String);
  349. {Runs the dialog box to set the displacement (DeltaX) of the Series.}
  350.     procedure ApplyDisplacementChange(Sender: TObject);
  351. {This applies changes from the Displacement Dialog.}
  352.  
  353.     function AddDependentSeries(ASeries: TSeries): Boolean;
  354. {This function ADDS a series that depends on this series' X-Data from the list of dependent series.}
  355.     function RemoveDependentSeries(ASeries: TSeries): Boolean;
  356. {This function REMOVES a series that depends on this series' X-Data from the list of dependent series.}
  357.     function AssumeMasterSeries(XPts: Integer; OldMaster: TSeries; AList: TList): Boolean;
  358. {This function makes this series' X-Data the MASTER for the given list of dependent series.}
  359.     function ResetXDataSeries(OldSeries, NewSeries: TSeries): Boolean;
  360.  
  361.     function DelPoint(X, Y: Single; Confirm: Boolean): Integer;
  362. {This deletes a single point, the closest one, from the xy data.}
  363.  
  364.     function DelPointNumber(ThePoint: Integer; Confirm: Boolean): Integer;
  365. {This deletes a single point, the Nth one, from the xy data.}
  366.  
  367.     function DelData: Boolean;
  368. {This deletes an entire data set. It only works on internal data sets.}
  369.  
  370.     procedure DrawHistory(ACanvas: TCanvas; HistoryX: Single);
  371. {This draws the series on the given canvas, in History mode.
  372.  That is, from the latest point backwards a distance HistoryX}
  373.     procedure Draw(ACanvas: TCanvas; XYFastAt: Integer);
  374. {This draws the series in XY fashion on the given canvas.}
  375.     procedure DrawShades(ACanvas: TCanvas; XYFastAt: Integer);
  376. {This shades the series in XY fashion on the given canvas, if the Axis Limits are exceeded.}
  377.     procedure DrawPie(ACanvas: TCanvas; PieLeft, PieTop, PieWidth, PieHeight: Integer);
  378. {This draws the series on the given canvas as a Pie.}
  379.     procedure DrawPolar(ACanvas: TCanvas; PolarRange: Single);
  380. {This draws the series in Polar fashion on the given canvas.}
  381.     procedure DrawSymbol(ACanvas: TCanvas; iX, iY: Integer);
  382. {This draws one of the symbols on the given canvas.}
  383.     procedure Trace(ACanvas: TCanvas);
  384. {This draws the series on the given canvas in an erasable mode.
  385.  The first call draws, the second call erases.}
  386.  
  387.     procedure EditData(TheHelpFile: String);
  388. {This runs the Data Editor dialog box.}
  389.     procedure ApplyDataChange(Sender: TObject);
  390. {This applies changes from the DataEditor Dialog.}
  391.  
  392.     procedure EditPoint(ThePointNumber: Integer; TheHelpFile: String);
  393. {This runs the Point Editor dialog box.}
  394.     procedure ApplyPointChange(Sender: TObject; TheResult: TModalResult);
  395. {This applies changes from the PointEditor Dialog.}
  396.  
  397.     procedure GetBounds;
  398. {Determines the Min and Max properties for the whole series.}
  399. {Data manipulation:}
  400.     procedure ResetBounds;
  401. {Reset the Min and Max properties.}
  402.  
  403.     function GetNearestPointToX(X: Single): Integer;
  404. {This returns the point that has an X value closest to X.}
  405.     function GetNearestPointToFX(FX: Integer): Integer;
  406. {This returns the point that has an F(X) / Screen value closest to FX.}
  407.  
  408.     function GetNearestPieSlice(
  409.       iX, iY,
  410.       PieLeft, PieTop, PieWidth, PieHeight: Integer;
  411.       var MinDistance: Single): Integer;
  412. {This returns the Index of the nearest point, and sets its XValue and YValue.}
  413.  
  414.     function GetNearestXYPoint(
  415.       iX, iY, StartPt, EndPt: Integer;
  416.       var MinDistance: Single): Integer;
  417. {This returns the Index of the nearest point, and sets its XValue and YValue.
  418.  It is guaranteed to find the nearest point.}
  419.  
  420.     function GetNearestXYPointFast(
  421.       iX, iY: Integer;
  422.       var MinDistance: Single): Integer;
  423. {This returns the Index of the nearest point, and sets its XValue and YValue.
  424.  This is much quicker than GetNearestXYPoint, especially for big data sets,
  425.  but MAY NOT return the closest point.}
  426.  
  427.     procedure GetPoint(N: Integer; var X, Y: Single);
  428. {This returns the Nth point's X and Y values.}
  429.  
  430.     function GetXYPoint(N: Integer): TXYPoint;
  431. {This returns the Nth point's X and Y values in a TXYPoint record.}
  432.  
  433.     procedure Smooth(SmoothOrder: Integer);
  434. {This smooths the xy data using a midpoint method.}
  435.  
  436.     procedure Sort;
  437. {This sorts the xy data in ascending X order.}
  438.  
  439.     procedure GeneratePieOutline(
  440.       PieLeft,
  441.       PieTop,
  442.       PieWidth,
  443.       PieHeight,
  444.       TheNearestPoint: Integer);
  445. {This generates an pIE Outline from the data, for the specified point/rectangle.}
  446.     procedure GenerateColumnOutline(X1, Y1, X2, Y2: Integer);
  447. {This generates an Column Outline from the data, for the specified point/rectangle.}
  448.     procedure GenerateXYOutline;
  449. {This generates an XY Outline from the data. An Outline contains
  450.  the screen coordinates for (OUTLINE_DENSITY +1) points.}
  451. {}
  452. {Note that the memory for the Outline is allocated in the constructor and
  453.  freed in the destructor.}
  454.     procedure Outline(ACanvas: TCanvas; ThePlotType: TPlotType; TheOutlineWidth: Integer);
  455. {This draws (or erases) the Outline on the canvas.}
  456.  
  457.     procedure MoveBy(ACanvas: TCanvas; ThePlotType: TPlotType; DX, DY, TheOutlineWidth: Integer);
  458. {This erases the old Outline from the canvas, then redraws it
  459.  at (DX, DY) from its current position.}
  460.  
  461.     procedure MoveTo(
  462.       ACanvas: TCanvas;
  463.       ThePlotType: TPlotType;
  464.       TheOutlineWidth,
  465.       X, Y: Integer);                  {by how much}
  466. {This erases the old Outline from the canvas, then redraws it
  467.  at the new location (X, Y).}
  468.  
  469.     procedure LineBestFit(TheLeft, TheRight: Single;
  470.       var NoLSPts: Integer;
  471.       var SumX, SumY, SumXsq, SumXY, SumYsq: Double;
  472.       var Slope, Intercept, Rsq: Single);
  473. {This performs a linear least-squares fit of TheSeries from points Start to Finish,
  474.  and returns the Slope, Intercept and R-Square value.}
  475. {}
  476. {Normally you would initialize NoPts and the Sumxxx variables to zero.}
  477. {}
  478. {However, if you wish to fit over multiple regions (very useful in determining baselines)
  479.  then simply call this function twice in a row with no re-initialization between calls.}
  480.  
  481.     procedure Differentiate;
  482. {This replaces the series by its differential.}
  483.     procedure Integrate;
  484. {This replaces the series by its integral.}
  485.     function Integral(TheLeft, TheRight: Single): Single;
  486. {This calculates the integral of a series by X co-ordinate.}
  487.     function IntegralByPoint(Start, Finish: Integer): Single;
  488. {This calculates the integral of a series by points.}
  489.  
  490.     procedure DoSpline(Density: Integer; pSplineSeries: TSeries);
  491. {This calculates the cubic spline interpolation of the data (XSpline, YSpline),
  492.  which resides in another Series.}
  493.     procedure SecondDerivative;
  494. {This calculates the second derivate for a cubic spline interpolation by SplineValue.}
  495.     function SplineValue(X: Single): Single;
  496. {This calculates the cubic spline interpolation of the data at a given point X.}
  497.     procedure ClearSpline;
  498.  
  499.     function FindHighsLows(Start, Finish, HeightSensitivity: Integer): Integer;
  500.     procedure MovingAverage(Span: Integer);
  501.     function Average(TheLeft, TheRight: Single): Single;
  502.     procedure Linearize(TheLeft, TheRight: Single);
  503.     procedure Zero(TheLeft, TheRight: Single);
  504.     procedure ClearHighsLows;
  505.     procedure DrawHighs(ACanvas: TCanvas);
  506.     procedure MakeXDataIndependent;
  507.  
  508.   published
  509.     property Brush: TBrush read FBrush write SetBrush;
  510. {The Brush (color, width, etc) with which the series is drawn on the Canvas.}
  511.     property DeltaX: Integer read FDeltaX write SetDeltaX;
  512. {The displacement of the series on the screen from its X origin.}
  513.     property DeltaY: Integer read FDeltaY write SetDeltaY;
  514. {The displacement of the series on the screen from its Y origin.}
  515.     property DefSize: Word read FDefSize write FDefSize;
  516. {The default memory allocation block size. Allocated memory grows in blocks of
  517.  this number of points.}
  518.     property HighLow: TSetHighLow read FHighLow write FHighLow;
  519. {Do we show any Highs ? any Lows ? Both ? or None ?}
  520.     property Name: String read FName write SetName;
  521. {The name of the data set.}
  522.     property Pen: TPen read FPen write SetPen;
  523. {The Pen (color, width, etc) with which the series is drawn on the Canvas.}
  524.     property ShadeLimits: Boolean read FShadeLimits write SetShadeLimits;
  525. {Do we shade this series above and below the Y-axis limits ?}    
  526.     property Symbol: TSymbol read FSymbol write SetSymbol;
  527. {The symbol (square, circle, etc) with which each data point is drawn.}
  528.     property SymbolSize: Integer read FSymbolSize write SetSymbolSize;
  529. {How big is the Symbol (0 means invisible).}
  530.     property Visible: Boolean read FVisible write SetVisible;
  531. {Is this series visible ?}
  532.  
  533.     property OnStyleChange: TNotifyEvent read FOnStyleChange write FOnStyleChange;
  534. {This notifies the owner (usually TSeriesList) of a change in style of this series.}
  535.     property OnDataChange: TNotifyEvent read FOnDataChange write FOnDataChange;
  536. {This notifies the owner (usually TSeriesList) of a change in the data of this series.}
  537.  
  538.     {property OnXMinChange: TOnMinMaxChangeEvent read FOnXMinChange write FOnXMinChange;
  539.     property OnXMaxChange: TOnMinMaxChangeEvent read FOnXMaxChange write FOnXMaxChange;
  540.     property OnYMinChange: TOnMinMaxChangeEvent read FOnYMinChange write FOnYMinChange;
  541.     property OnYMaxChange: TOnMinMaxChangeEvent read FOnYMaxChange write FOnYMaxChange;}
  542.  
  543.     {property OnAddPoint: TNotifyEvent read FOnAddPoint write FOnAddPoint;}
  544.   end;
  545.  
  546.   function Compare(Item1, Item2: Pointer): Integer;
  547.  
  548. implementation
  549.  
  550. uses
  551.   Plot;
  552.  
  553. {TSeries Constructor and Destructor:-------------------------------------------}
  554. {------------------------------------------------------------------------------
  555.   Constructor: TSeries.Create
  556.   Description: standard Constructor
  557.        Author: Mat Ballard
  558.  Date created: 04/25/2000
  559. Date modified: 04/25/2000 by Mat Ballard
  560.       Purpose: creates Pen and initializes many things
  561.  Known Issues:
  562.  ------------------------------------------------------------------------------}
  563. Constructor TSeries.Create(
  564.   Index: Integer;
  565.   AxisList: TList;
  566.   XDataSeriesValue: TSeries);
  567. begin
  568. {First call the ancestor:}
  569.   inherited Create;
  570. {create sub-components:}
  571.   FBrush := TBrush.Create;
  572.   //FBrush.Bitmap := nil;
  573.   FPen := TPen.Create;
  574.   FPen.Width := 1;
  575. {we insert the default values that cannot be "defaulted":}
  576.   DataStatus := dsNone;
  577.   FDefSize := 256;
  578.   FDeltaX := 0;
  579.   FDeltaY := 0;
  580.   FNoPts := 0;
  581.   FYAxisIndex := 1;
  582.   {FVisible := TRUE;}
  583.  
  584. {Set axes:}
  585.   FAxisList := AxisList;
  586.   FXAxis := TAxis(AxisList[0]);
  587.   FYAxis := TAxis(AxisList[1]);
  588.  
  589.   FSymbolSize := 5;
  590.  
  591.   FDependentSeries := TList.Create;
  592.   FXDataSeries := XDataSeriesValue;
  593.   if (FXDataSeries = nil) then
  594.   begin
  595.     FExternalXSeries := FALSE;
  596.     FXData := nil;
  597.   end
  598.   else
  599.   begin
  600.     FExternalXSeries := TRUE;
  601.     FXData := FXDataSeries.XData;
  602.     FXDataSeries.AddDependentSeries(Self);
  603.   end;
  604.   FXStringData := nil;
  605.  
  606. {set names and color:}
  607.   FName := Format(sSeries + ' %d', [Index]);
  608.   FPen.Color := MyColorValues[Index mod 16];
  609. {make the brush color paler by 70%:}
  610.   FBrush.Color := Misc.GetPalerColor(FPen.Color, 70);
  611.  
  612.   FYData := nil;
  613.   MemSize := 0;
  614.  
  615.   Fd2Y_dX2 := nil;
  616.  
  617.   FHighs := nil;
  618.   FLows := nil;
  619.   FHighCount := 0;
  620.   FLowCount := 0;
  621.   FHighCapacity := 0;
  622.   FVisible := TRUE;
  623.  
  624. {allocate memory so as to create X and Y pointers:
  625.   IncMemSize; - not needed: done in AddPoint}
  626. end;
  627.  
  628. {------------------------------------------------------------------------------
  629.    Destructor: TSeries.Destroy
  630.   Description: standard Destructor
  631.        Author: Mat Ballard
  632.  Date created: 04/25/2000
  633. Date modified: 04/25/2000 by Mat Ballard
  634.       Purpose: Frees Pen and events
  635.  Known Issues: would like a better solution to FXDataRefCount
  636.  ------------------------------------------------------------------------------}
  637. Destructor TSeries.Destroy;
  638. begin
  639.   FOnStyleChange := nil;
  640.   FOnDataChange := nil;
  641.   FVisible := FALSE;
  642.   ClearSpline;
  643.   ClearHighsLows;
  644.   FBrush.Free;
  645.   FPen.Free;
  646.  
  647.   if (FXDataSeries <> nil) then
  648.     FXDataSeries.RemoveDependentSeries(Self);
  649.   if (FXStringData <> nil) then
  650.     XStringData := nil;
  651.  
  652.  
  653.   DelData;
  654.  
  655.   FDependentSeries.Free;
  656.  
  657. {then call ancestor:}
  658.   inherited Destroy;
  659. end;
  660.  
  661. {Begin Set and Get Functions and Procedures----------------------------------}
  662. {------------------------------------------------------------------------------
  663.     Procedure: TSeries.SetBrush
  664.   Description: property Setting procedure
  665.        Author: Mat Ballard
  666.  Date created: 09/21/2000
  667. Date modified: 09/21/2000 by Mat Ballard
  668.       Purpose: sets the Brush Property
  669.  Known Issues:
  670.  ------------------------------------------------------------------------------}
  671. procedure TSeries.SetBrush(Value: TBrush);
  672. begin
  673.   FBrush.Assign(Value);
  674.   DoStyleChange;
  675. end;
  676.  
  677. {------------------------------------------------------------------------------
  678.     Procedure: TSeries.SetDeltaX
  679.   Description: property Setting procedure
  680.        Author: Mat Ballard
  681.  Date created: 04/25/2000
  682. Date modified: 04/25/2000 by Mat Ballard
  683.       Purpose: sets the DeltaX displacement Property
  684.  Known Issues:
  685.  ------------------------------------------------------------------------------}
  686. procedure TSeries.SetDeltaX(Value: Integer);
  687. begin
  688.   if (FDeltaX = Value) then exit;
  689.   FDeltaX := Value;
  690.   DoStyleChange;
  691. end;
  692.  
  693. {------------------------------------------------------------------------------
  694.     Procedure: TSeries.SetDeltaY
  695.   Description: property Setting procedure
  696.        Author: Mat Ballard
  697.  Date created: 04/25/2000
  698. Date modified: 04/25/2000 by Mat Ballard
  699.       Purpose: sets the DeltaY displacement Property
  700.  Known Issues:
  701.  ------------------------------------------------------------------------------}
  702. procedure TSeries.SetDeltaY(Value: Integer);
  703. begin
  704.   if (FDeltaY = Value) then exit;
  705.   FDeltaY := Value;
  706.   DoStyleChange;
  707. end;
  708.  
  709. {------------------------------------------------------------------------------
  710.     Procedure: TSeries.SetName
  711.   Description: property Setting procedure
  712.        Author: Mat Ballard
  713.  Date created: 04/25/2000
  714. Date modified: 04/25/2000 by Mat Ballard
  715.       Purpose: sets the Name Property
  716.  Known Issues:
  717.  ------------------------------------------------------------------------------}
  718. procedure TSeries.SetName(Value: String);
  719. begin
  720.   if (FName = Value) then exit;
  721.   FName := Value;
  722.   DoDataChange;
  723. end;
  724.  
  725. {------------------------------------------------------------------------------
  726.     Procedure: TSeries.SetPen
  727.   Description: property Setting procedure
  728.        Author: Mat Ballard
  729.  Date created: 04/25/2000
  730. Date modified: 04/25/2000 by Mat Ballard
  731.       Purpose: sets the Pen Property
  732.  Known Issues:
  733.  ------------------------------------------------------------------------------}
  734. procedure TSeries.SetPen(Value: TPen);
  735. begin
  736.   FPen.Assign(Value);
  737.   DoStyleChange;
  738. end;
  739.  
  740. procedure TSeries.SetShadeLimits(Value: Boolean);
  741. begin
  742.   FShadeLimits := Value;
  743.   DoStyleChange;
  744. end;
  745.  
  746. {------------------------------------------------------------------------------
  747.     Procedure: TSeries.SetXStringData
  748.   Description: property Setting procedure
  749.        Author: Mat Ballard
  750.  Date created: 04/25/2000
  751. Date modified: 04/25/2000 by Mat Ballard
  752.       Purpose: sets the XStringData: the X data as text strings
  753.  Known Issues:
  754.  ------------------------------------------------------------------------------}
  755. procedure TSeries.SetXStringData(Value: TStringList);
  756.  
  757.   procedure NukeStringList;
  758.   begin
  759.     if (FXStringData <> nil) then
  760.     begin
  761.       FXStringData.Free;
  762.       FXStringData := nil;
  763.     end;
  764.     FXAxis.SetLabelSeries(nil);
  765.     DataStatus := dsInternal;
  766.     exit;
  767.   end;
  768.  
  769. begin
  770.   if (Value = nil) then
  771.   begin
  772.     NukeStringList;
  773.     exit;
  774.   end;
  775.  
  776.   if (Value.Count = 0) then
  777.   begin
  778.     NukeStringList;
  779.     exit;
  780.   end;
  781.  
  782.   if (FXStringData = nil) then
  783.     FXStringData := TStringList.Create;
  784.   if (DataStatus = dsInternal) then
  785.     DataStatus := dsInternalString;
  786.   if (Value.Count <> FNoPts) then
  787.     ShowMessage(Format(sSetXStringDataWarning,
  788.       [FNoPts, Value.Count]));
  789.   FXStringData.Clear;
  790.   FXStringData.Assign(Value);
  791.   FXAxis.SetLabelSeries(Self);
  792.   DoDataChange;
  793. end;
  794.  
  795. {------------------------------------------------------------------------------
  796.     Procedure: TSeries.SetSeriesType
  797.   Description: property Setting procedure
  798.        Author: Mat Ballard
  799.  Date created: 12/15/2000
  800. Date modified: 12/15/2000 by Mat Ballard
  801.       Purpose: sets the SeriesType Property
  802.  Known Issues:
  803.  ------------------------------------------------------------------------------}
  804. {procedure TSeries.SetSeriesType(Value: TSeriesType);
  805. begin
  806.   if (FNoPts > 0) then raise
  807.     EComponentError.Create(Self.FName +  ': you MUST Clear a Series before you can change its type !');
  808.   SeriesType := Value;
  809. end;}
  810.  
  811. {------------------------------------------------------------------------------
  812.     Procedure: TSeries.SetSymbol
  813.   Description: property Setting procedure
  814.        Author: Mat Ballard
  815.  Date created: 04/25/2000
  816. Date modified: 04/25/2000 by Mat Ballard
  817.       Purpose: sets the Symbol Property
  818.  Known Issues:
  819.  ------------------------------------------------------------------------------}
  820. procedure TSeries.SetSymbol(Value: TSymbol);
  821. begin
  822.   if (FSymbol = Value) then exit;
  823.   FSymbol := Value;
  824.   DoStyleChange;
  825. end;
  826.  
  827. {------------------------------------------------------------------------------
  828.     Procedure: TSeries.SetSymbolSize
  829.   Description: property Setting procedure
  830.        Author: Mat Ballard
  831.  Date created: 04/25/2000
  832. Date modified: 04/25/2000 by Mat Ballard
  833.       Purpose: sets the SymbolSize Property
  834.  Known Issues:
  835.  ------------------------------------------------------------------------------}
  836. procedure TSeries.SetSymbolSize(Value: Integer);
  837. begin
  838.   if ((FSymbolSize = Value) or (FSymbolSize < 0)) then exit;
  839.   FSymbolSize := Value;
  840.   DoStyleChange;
  841. end;
  842.  
  843. {------------------------------------------------------------------------------
  844.     Procedure: TSeries.SetVisible
  845.   Description: property Setting procedure
  846.        Author: Mat Ballard
  847.  Date created: 04/25/2000
  848. Date modified: 04/25/2000 by Mat Ballard
  849.       Purpose: sets the Visible Property
  850.  Known Issues:
  851.  ------------------------------------------------------------------------------}
  852. procedure TSeries.SetVisible(Value: Boolean);
  853. begin
  854. {Can't become visible if Axes or Data have not been set:}
  855.   if ((FXAxis = nil) or (FYAxis = nil)) then raise
  856.     EInvalidPointer.CreateFmt('TSeries.SetVisible: ' + sSetVisible1 +
  857.       CRLF + '(X: %p; Y: %p)', [FXAxis, FYAxis]);
  858.  
  859.   FVisible := Value;
  860.   DoStyleChange;
  861. end;
  862.  
  863. {------------------------------------------------------------------------------
  864.     Procedure: TSeries.SetYAxisIndex
  865.   Description: property Setting procedure
  866.        Author: Mat Ballard
  867.  Date created: 04/25/2000
  868. Date modified: 04/25/2000 by Mat Ballard
  869.       Purpose: sets the YAxis Property
  870.  Known Issues: We define YAxisIndex to run from 1 to FAxisList.Count-1
  871.  ------------------------------------------------------------------------------}
  872. procedure TSeries.SetYAxisIndex(Value: Byte);
  873. begin
  874.   if ((Value < 1) or
  875.       (Value >= FAxisList.Count)) then raise
  876.     ERangeError.Create('TSeries.SetYAxisIndex: ' + sSetYAxisIndex1);
  877.  
  878.   FYAxisIndex := Value;
  879.   FYAxis := TAxis(FAxisList[Value]);
  880.   FYAxis.Visible := TRUE;
  881. end;
  882.  
  883. procedure TSeries.SetZData(Value: Single);
  884. begin
  885.   if (FZData = Value) then exit;
  886.  
  887.   FZData := Value;
  888.   DoDataChange;
  889. end;
  890.  
  891. {end Set procedures, begin general procedures ---------------------------------}
  892. {------------------------------------------------------------------------------
  893.      Function: TSeries.AllocateNoPts
  894.   Description: allocates memory for data points
  895.        Author: Mat Ballard
  896.  Date created: 04/25/2000
  897. Date modified: 04/25/2000 by Mat Ballard
  898.       Purpose: memory management
  899.  Return Value: TRUE if successful
  900.  Known Issues:
  901.  ------------------------------------------------------------------------------}
  902. function TSeries.AllocateNoPts(Value: LongInt): Boolean;
  903. var
  904.   Msg: String;
  905. begin
  906.   AllocateNoPts := FALSE;
  907.  
  908.   if (Value < 0) then
  909.     exit;
  910.  
  911.   try
  912. {$IFDEF DELPHI1} {Delphi 1 can only allocate 64K chunks locally:}
  913.     if ((Value) * SizeOf(Single) > 65535) then
  914.     begin
  915.       Value := 65535 div SizeOf(Single);
  916.       ShowMessage(Format('Running out of memory - can only allocate %d points', [Value]));
  917.       AllocateNoPts := FALSE;
  918.     end;
  919. {$ENDIF}
  920.  
  921.     if (FExternalXSeries) then
  922.     begin
  923. {we don't allocate memory for X data that is held in a different series:}
  924.       FXData := Self.FXDataSeries.XData
  925. {doesn't hurt, but should not be neccessary.}
  926.     end
  927.     else
  928. {$IFDEF DELPHI1}
  929.     begin
  930.       if (FXData = nil) then
  931.         GetMem(FXData, Value * SizeOf(Single))
  932.        else
  933.         ReAllocMem(FXData, MemSize * SizeOf(Single), Value * SizeOf(Single));
  934.     end;
  935.     if (FYData = nil) then
  936.       GetMem(FYData, Value * SizeOf(Single))
  937.      else
  938.       ReAllocMem(FYData, MemSize * SizeOf(Single), Value * SizeOf(Single));
  939. {$ELSE}
  940.     begin
  941.       ReAllocMem(FXData, Value * SizeOf(Single));
  942.     end;
  943.     ReAllocMem(FYData, Value * SizeOf(Single));
  944. {$ENDIF}
  945.     AllocateNoPts := TRUE;
  946.   except
  947.     Msg := Format(sAllocateNoPts2,
  948.       [FName, Value, Value * SizeOf(Single)]);
  949.     ShowMessage(Msg);
  950.     raise;
  951.   end;
  952.   MemSize := Value;
  953. end;
  954.  
  955. {------------------------------------------------------------------------------
  956.     Procedure: TSeries.DoStyleChange
  957.   Description: Fires the OnStyleChange event
  958.        Author: Mat Ballard
  959.  Date created: 04/25/2000
  960. Date modified: 04/25/2000 by Mat Ballard
  961.       Purpose: event handling
  962.  Known Issues:
  963.  ------------------------------------------------------------------------------}
  964. procedure TSeries.DoStyleChange;
  965. begin
  966.   if (Assigned(FOnStyleChange) and Visible) then OnStyleChange(Self);
  967. end;
  968.  
  969. {------------------------------------------------------------------------------
  970.     Procedure: TSeries.DoDataChange
  971.   Description: Fires the OnDataChange event
  972.        Author: Mat Ballard
  973.  Date created: 04/25/2000
  974. Date modified: 04/25/2000 by Mat Ballard
  975.       Purpose: event handling
  976.  Known Issues:
  977.  ------------------------------------------------------------------------------}
  978. procedure TSeries.DoDataChange;
  979. begin
  980.   if (Assigned(FOnDataChange)) then OnDataChange(Self);
  981. end;
  982.  
  983. {Data manipulation Functions and Procedures----------------------------------}
  984. {------------------------------------------------------------------------------
  985.      Function: TSeries.AddData
  986.   Description: adds data from an externally-managed array
  987.        Author: Mat Ballard
  988.  Date created: 04/25/2000
  989. Date modified: 04/25/2000 by Mat Ballard
  990.       Purpose: data management
  991.  Return Value: TRUE if successful
  992.  Known Issues:
  993.  ------------------------------------------------------------------------------}
  994. function TSeries.AddData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean;
  995. var
  996.   i: Integer;
  997. begin
  998. {clear any existing data:}
  999.   if (FNoPts > 0) then DelData;
  1000.  
  1001.   try
  1002. {Allocate memory:}
  1003.     AllocateNoPts(NumberOfPoints + FDefSize);
  1004.  
  1005. {NB: this causes terminal access violations:
  1006.       System.Move(XPointer, FXData, NumberOfPoints * SizeOf(Single));}
  1007.     if (not FExternalXSeries) then
  1008.     begin
  1009.       for i := 0 to NumberOfPoints-1 do
  1010.         FXData^[i] := XPointer^[i];
  1011.     end;
  1012.     for i := 0 to NumberOfPoints-1 do
  1013.       FYData^[i] := YPointer^[i];
  1014.  
  1015.     DataStatus := dsInternal;
  1016.     FNoPts := NumberOfPoints;
  1017.  
  1018. {find the new min and max:}
  1019.     GetBounds; {which calls ResetBounds}
  1020.  
  1021.     DoDataChange;
  1022.     AddData := TRUE;
  1023.   except
  1024.     AddData := FALSE;
  1025.   end;
  1026. end;
  1027.  
  1028. {------------------------------------------------------------------------------
  1029.      Function: TSeries.MakeXDataIndependent
  1030.   Description: This procedure makes an internal copy of external X Data
  1031.        Author: Mat Ballard
  1032.  Date created: 08/31/2000
  1033. Date modified: 08/31/2000 by Mat Ballard
  1034.       Purpose: series management
  1035.  Known Issues:
  1036.  ------------------------------------------------------------------------------}
  1037. procedure TSeries.MakeXDataIndependent;
  1038. var
  1039.   i: Integer;
  1040.   pOldXData: pSingleArray;
  1041.   Msg: String;
  1042. begin
  1043.   if (not FExternalXSeries) then exit;
  1044.  
  1045.   pOldXData := FXData;
  1046.   try
  1047. {$IFDEF DELPHI1}
  1048.     GetMem(FXData, MemSize * SizeOf(Single));
  1049. {$ELSE}
  1050.     ReAllocMem(FXData, MemSize * SizeOf(Single));
  1051. {$ENDIF}
  1052. {NB: the following generates gross access violations:
  1053.     System.Move(pOldXData, FXData, FNoPts * SizeOf(Single));}
  1054.     for i := 0 to FNoPts-1 do
  1055.       FXData^[i] := pOldXData^[i];
  1056.     FXDataSeries.RemoveDependentSeries(Self);
  1057.     FExternalXSeries := FALSE;
  1058.   except
  1059.     Msg := Format(sAllocateNoPts2,
  1060.       [FName, MemSize, MemSize * SizeOf(Single)]);
  1061.     ShowMessage(Msg);
  1062.     raise;
  1063.   end;
  1064. end;
  1065.  
  1066. {------------------------------------------------------------------------------
  1067.      Function: TSeries.AddDependentSeries
  1068.   Description: This function ADDS a series that depends on this series' X-Data from the list of dependent series.
  1069.        Author: Mat Ballard
  1070.  Date created: 08/25/2000
  1071. Date modified: 08/25/2000 by Mat Ballard
  1072.       Purpose: data management
  1073.  Return Value: TRUE if successful
  1074.  Known Issues:
  1075.  ------------------------------------------------------------------------------}
  1076. function TSeries.AddDependentSeries(ASeries: TSeries): Boolean;
  1077. {var
  1078.   pASeries: Pointer;}
  1079. begin
  1080.   if (FDependentSeries.IndexOf(ASeries) < 0) then
  1081.   begin
  1082.     FDependentSeries.Add(ASeries);
  1083.     AddDependentSeries := TRUE;
  1084.   end
  1085.   else
  1086.     AddDependentSeries := FALSE;
  1087. end;
  1088.  
  1089. {------------------------------------------------------------------------------
  1090.      Function: TSeries.RemoveDependentSeries
  1091.   Description: This function REMOVES a series that depends on this series' X-Data from the list of dependent series.
  1092.        Author: Mat Ballard
  1093.  Date created: 08/25/2000
  1094. Date modified: 08/25/2000 by Mat Ballard
  1095.       Purpose: data management
  1096.  Return Value: TRUE if successful
  1097.  Known Issues:
  1098.  ------------------------------------------------------------------------------}
  1099. function TSeries.RemoveDependentSeries(ASeries: TSeries): Boolean;
  1100. {var
  1101.   pASeries: Pointer;}
  1102. begin
  1103.   if (FDependentSeries.IndexOf(ASeries) >= 0) then
  1104.   begin
  1105.     FDependentSeries.Remove(ASeries);
  1106.     RemoveDependentSeries := TRUE;
  1107.   end
  1108.   else
  1109.     RemoveDependentSeries := FALSE;
  1110. end;
  1111.  
  1112. {------------------------------------------------------------------------------
  1113.      Function: TSeries.GetXDataRefCount
  1114.   Description: This function returns the number of dependent series
  1115.        Author: Mat Ballard
  1116.  Date created: 08/25/2000
  1117. Date modified: 08/25/2000 by Mat Ballard
  1118.       Purpose: data management
  1119.  Return Value: Word
  1120.  Known Issues:
  1121.  ------------------------------------------------------------------------------}
  1122. function TSeries.GetXDataRefCount: Word;
  1123. begin
  1124.   GetXDataRefCount := FDependentSeries.Count;
  1125. end;
  1126.  
  1127. {------------------------------------------------------------------------------
  1128.      Function: TSeries.AssumeMasterSeries
  1129.   Description: This function makes this series' X-Data the MASTER for the given list of dependent series.
  1130.        Author: Mat Ballard
  1131.  Date created: 08/25/2000
  1132. Date modified: 08/25/2000 by Mat Ballard
  1133.       Purpose: data management
  1134.  Return Value: TRUE if successful
  1135.  Known Issues:
  1136.  ------------------------------------------------------------------------------}
  1137. function TSeries.AssumeMasterSeries(
  1138.   XPts: Integer;
  1139.   OldMaster: TSeries;
  1140.   AList: TList): Boolean;
  1141. var
  1142.   i: Integer;
  1143. begin
  1144. {There are many reasons why this might be a bad idea:}
  1145.   if (OldMaster = nil) then raise
  1146.     EComponentError.Create(Self.Name +
  1147.       sAssumeMasterSeries1 + sAssumeMasterSeries2);
  1148.  
  1149.   if (OldMaster <> FXDataSeries) then raise
  1150.     EComponentError.Create(Self.Name +
  1151.       sAssumeMasterSeries1 + FXDataSeries.Name +
  1152.       sBecause + FXDataSeries.Name + sAssumeMasterSeries3);
  1153.  
  1154.   if (XPts <> FNoPts) then raise
  1155.     EComponentError.CreateFmt(Self.Name +
  1156.       ' (%d ' + sPoints + ')' + sAssumeMasterSeries4 + sWith + '%d ' + sPoints + ' !',
  1157.       [FNoPts, XPts]);
  1158.  
  1159. {this last is probably redundant because of test #2:}
  1160.   if (FDependentSeries.Count > 0) then raise
  1161.     EComponentError.Create(Self.Name +
  1162.       sAssumeMasterSeries4 + sBecause + sAssumeMasterSeries5);
  1163.  
  1164.   for i := 0 to AList.Count-1 do
  1165.   begin
  1166.     if (AList.Items[i] <> Self) then
  1167.     begin
  1168. {add these dependent series to our own list:}
  1169.       FDependentSeries.Add(AList.Items[i]);
  1170. {tell them that this series is now the Master:}
  1171.       TSeries(AList.Items[i]).ResetXDataSeries(OldMaster, Self);
  1172.     end;
  1173.   end;
  1174.  
  1175. {the X Data is now internal to this series:}
  1176.   FExternalXSeries := FALSE;
  1177.   FXDataSeries := nil;
  1178. {note that we already KNOW the location of the X Data: FXData !}
  1179.  
  1180.   AssumeMasterSeries := TRUE;
  1181. end;
  1182.  
  1183. {------------------------------------------------------------------------------
  1184.      Function: TSeries.ResetXDataSeries
  1185.   Description: When a new series Assumes X Data Master status, it has to tell
  1186.                all the dependent series
  1187.        Author: Mat Ballard
  1188.  Date created: 08/25/2000
  1189. Date modified: 08/25/2000 by Mat Ballard
  1190.       Purpose: data management
  1191.  Return Value: TRUE if successful
  1192.  Known Issues:
  1193.  ------------------------------------------------------------------------------}
  1194. function TSeries.ResetXDataSeries(OldSeries, NewSeries: TSeries): Boolean;
  1195. begin
  1196.   if (FXDataSeries = OldSeries) then
  1197.   begin
  1198.     FXDataSeries := NewSeries;
  1199.     ResetXDataSeries := TRUE;
  1200.   end
  1201.   else
  1202.     ResetXDataSeries := FALSE;
  1203. end;
  1204.  
  1205. {------------------------------------------------------------------------------
  1206.      Function: TSeries.PointToData
  1207.   Description: uses data from an externally-managed array
  1208.        Author: Mat Ballard
  1209.  Date created: 04/25/2000
  1210. Date modified: 04/25/2000 by Mat Ballard
  1211.       Purpose: data management
  1212.  Return Value: TRUE if successful
  1213.  Known Issues:
  1214.  ------------------------------------------------------------------------------}
  1215. function TSeries.PointToData(XPointer, YPointer: pSingleArray; NumberOfPoints: Integer): Boolean;
  1216. begin
  1217.   PointToData := FALSE;
  1218.   if (DataStatus = dsNone) then
  1219.   begin
  1220.     DataStatus := dsExternal;
  1221.     FXData := XPointer;
  1222.     FYData := YPointer;
  1223.     FNoPts := NumberOfPoints;
  1224.     GetBounds; {which calls ResetBounds}
  1225.     DoDataChange;
  1226.     PointToData := TRUE;
  1227.   end;
  1228. end;
  1229.  
  1230. {------------------------------------------------------------------------------
  1231.      Function: TSeries.AddDrawPoint
  1232.   Description: adds a point then draws it
  1233.        Author: Mat Ballard
  1234.  Date created: 04/25/2000
  1235. Date modified: 04/25/2000 by Mat Ballard
  1236.       Purpose: data management and screen display
  1237.  Return Value: the number of data points
  1238.  Known Issues:
  1239.  ------------------------------------------------------------------------------}
  1240. function TSeries.AddDrawPoint(X, Y: Single; ACanvas: TCanvas): Integer;
  1241. var
  1242.   iX, iY: Integer;
  1243.   TheResult: Integer;
  1244. begin
  1245. {Add the point; we don't fire any events, but we do adjust axes if required:
  1246.  this may trigger a re-draw if Min/Max are exceeded:}
  1247.   TheResult := AddPoint(X, Y, FALSE, TRUE);
  1248.   AddDrawPoint := TheResult;
  1249. {$IFDEF DELPHI3_UP}
  1250.   Assert(ACanvas <> nil, 'TSeries.AddDrawPoint: ' + sACanvasIsNil);
  1251. {$ENDIF}
  1252.   if ((not FVisible) or
  1253.       (TheResult < 0)) then exit;
  1254.  
  1255. {Draw from last to this point:}
  1256.   ACanvas.Pen.Assign(FPen);
  1257.   if (FNoPts > 1) then
  1258.   begin
  1259.     iX := FXAxis.FofX(FXData^[FNoPts-2])+ FDeltaX;
  1260.     iY := FYAxis.FofY(FYData^[FNoPts-2]) + FDeltaY;
  1261.     ACanvas.MoveTo(iX, iY);
  1262.     iX := FXAxis.FofX(FXData^[FNoPts-1]) + FDeltaX;
  1263.     iY := FYAxis.FofY(FYData^[FNoPts-1]) + FDeltaY;
  1264.     ACanvas.LineTo(iX, iY);
  1265.   end
  1266.   else
  1267.   begin
  1268.     iX := FXAxis.FofX(FXData^[FNoPts-1]) + FDeltaX;
  1269.     iY := FYAxis.FofY(FYData^[FNoPts-1]) + FDeltaY;
  1270.   end;
  1271.   if ((FSymbol <> syNone) and (FSymbolSize > 0)) then
  1272.   begin
  1273.     ACanvas.Brush.Assign(FBrush);
  1274.     DrawSymbol(ACanvas, iX, iY);
  1275.   end;
  1276. end;
  1277.  
  1278. {------------------------------------------------------------------------------
  1279.      Function: TSeries.AddPoint
  1280.   Description: adds a data point, increasing memory if required
  1281.        Author: Mat Ballard
  1282.  Date created: 04/25/2000
  1283. Date modified: 04/25/2000 by Mat Ballard
  1284.       Purpose: data management
  1285.  Return Value: the number of data points
  1286.  Known Issues:
  1287.  ------------------------------------------------------------------------------}
  1288. function TSeries.AddPoint(X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer;
  1289. begin
  1290.   AddPoint := -1;
  1291.  
  1292. {$IFDEF DELPHI3_UP}
  1293.   Assert(DataStatus <> dsExternal,
  1294.     'TSeries.AddPoint: ' + sAddPointAssert1 + Name + ' ' + sSeries +
  1295.       CRLF + sAddPointAssert2);
  1296. {$ENDIF}
  1297.  
  1298.   if (DataStatus = dsNone) then
  1299.   begin
  1300.     DataStatus := dsInternal;
  1301.     if (not IncMemSize) then exit;  {will return false and exit if not enough memory}
  1302.     ResetBounds;
  1303.   end;
  1304.  
  1305. {Check memory available:}
  1306.   if (FNoPts >= MemSize-2) then
  1307.     if (not IncMemSize) then exit; {will return false and exit if not enough memory}
  1308.  
  1309. {If the X data is in another series, then we do not add it:}
  1310.   if (FExternalXSeries) then
  1311.   begin
  1312. {check validity of the External X data:}
  1313.     if (FXDataSeries = nil) then raise
  1314.       EAccessViolation.Create('TSeries.AddPoint: ' + sAddPoint1 + Name +
  1315.         ' ' + sSeries + sBecause + sAddPoint2);
  1316.     if (FXDataSeries.NoPts <= FNoPts) then raise
  1317.       ERangeError.CreateFmt('TSeries.AddPoint: ' + sAddPoint3,
  1318.         [FXDataSeries.NoPts, FNoPts]);
  1319.     if (FXDataSeries.XData = nil) then  raise
  1320.       EAccessViolation.Create('TSeries.AddPoint: ' + sAddPoint1 + Name +
  1321.         ' ' + sSeries + sBecause  + sAddPoint4);
  1322.   end
  1323.   else
  1324.   begin
  1325. {save the X data:}
  1326.     FXData^[FNoPts] := X;
  1327.   end;
  1328.  
  1329. {save the Y data:}
  1330.   FYData^[FNoPts] := Y;
  1331.  
  1332. {Check the min and max X and Y properties of the series,
  1333.  and adjust axes as required:}
  1334.   CheckBounds(FNoPts, AdjustAxes);
  1335.   if (FireEvent) then
  1336.     DoDataChange;
  1337.  
  1338.   Inc(FNoPts);
  1339.   AddPoint := FNoPts;
  1340. end;
  1341.  
  1342. {------------------------------------------------------------------------------
  1343.      Function: TSeries.AddStringPoint
  1344.   Description: adds a data point with a String X value, increasing memory if required
  1345.        Author: Mat Ballard
  1346.  Date created: 11/16/2000
  1347. Date modified: 11/16/2000 by Mat Ballard
  1348.       Purpose: data management
  1349.  Return Value: the number of data points
  1350.  Known Issues:
  1351.  ------------------------------------------------------------------------------}
  1352. function TSeries.AddStringPoint(XString: String; X, Y: Single; FireEvent, AdjustAxes: Boolean): Integer;
  1353. begin
  1354. {$IFDEF DELPHI3_UP}
  1355.   Assert(DataStatus <> dsExternal,
  1356.     'TSeries.AddStringPoint: ' + sAddPointAssert1 + Name + ' ' + sSeries +
  1357.       CRLF + sAddPointAssert2);
  1358. {$ENDIF}
  1359.  
  1360.   AddStringPoint := -1;
  1361.  
  1362.   if (DataStatus = dsNone) then
  1363.   begin
  1364.     DataStatus := dsInternalString;
  1365.     if (not IncMemSize) then exit;  {will return false and exit if not enough memory}
  1366.     ResetBounds;
  1367.   end;
  1368.  
  1369.   AddStringPoint := AddPoint(X, Y, FireEvent, AdjustAxes);
  1370.  
  1371. {If the X data is in another series, then we do not add it:}
  1372.   if (not FExternalXSeries) then
  1373.   begin
  1374. {save the X string data:}
  1375.     FXStringData.Add(XString);
  1376.   end;
  1377. end;
  1378.  
  1379. {------------------------------------------------------------------------------
  1380.      Function: TSeries.DelPoint
  1381.   Description: deletes the point nearest to X and Y
  1382.        Author: Mat Ballard
  1383.  Date created: 04/25/2000
  1384. Date modified: 04/25/2000 by Mat Ballard
  1385.       Purpose: data management
  1386.  Return Value: the new number of points
  1387.  Known Issues:
  1388.  ------------------------------------------------------------------------------}
  1389. function TSeries.DelPoint(X, Y: Single; Confirm: Boolean): Integer;
  1390. {This deletes a single point, the closest one, from the xy data.}
  1391. var
  1392.   i, ThePoint: Integer;
  1393.   Distance, MinDistance: Single;
  1394. begin
  1395.   DelPoint := -1;
  1396.   if (FNoPts <= 0) then raise
  1397.     ERangeError.CreateFmt('TSeries.DelPoint: ' + sDelPoint1, [FName]);
  1398.  
  1399.   MinDistance := 3.4e38;
  1400.   ThePoint := -1;
  1401.   for i := 0 to FNoPts-1 do
  1402.   begin
  1403.     Distance := Abs(X - FXData^[i]) + Abs(Y - FYData^[i]);
  1404.     if (MinDistance > Distance) then
  1405.     begin
  1406.       ThePoint := i;
  1407.     end;
  1408.   end;
  1409.  
  1410.   if (ThePoint = -1) then
  1411.   begin
  1412.     exit;
  1413.   end;
  1414.  
  1415.   DelPoint := DelPointNumber(ThePoint, Confirm);
  1416. end;
  1417.  
  1418. {------------------------------------------------------------------------------
  1419.      Function: TSeries.DelPointNumber
  1420.   Description: deletes ThePoint by its index
  1421.        Author: Mat Ballard
  1422.  Date created: 04/25/2000
  1423. Date modified: 04/25/2000 by Mat Ballard
  1424.       Purpose: data management
  1425.  Return Value: the new number of points
  1426.  Known Issues:
  1427.  ------------------------------------------------------------------------------}
  1428. function TSeries.DelPointNumber(ThePoint: Integer; Confirm: Boolean): Integer;
  1429. {This deletes a single point, the Nth one, from the xy data.}
  1430. {}
  1431. {Note: this DOES NOT delete the X Value of externally-maintained X data
  1432.  values, so it shifts the upper half of the series one point to the left.}
  1433. var
  1434.   i: Integer;
  1435.   TheMessage: String;
  1436. begin
  1437.   DelPointNumber := -1;
  1438.  
  1439.   if (FNoPts <= 0) then raise
  1440.     ERangeError.CreateFmt('TSeries.DelPointNumber: ' + sDelPoint1,
  1441.       [FName]);
  1442.   if ((ThePoint < 0) or (ThePoint >= FNoPts)) then raise
  1443.     ERangeError.CreateFmt('TSeries.DelPointNumber: ' + sDelPointNumber1,
  1444.       [FName, FNoPts, ThePoint]);
  1445.   if (FDependentSeries.Count > 0) then
  1446.   begin
  1447.     ERangeError.CreateFmt(
  1448.       sDelPointNumber2,
  1449.       [FDependentSeries.Count]);
  1450.     exit;
  1451.   end;
  1452.  
  1453.   if (Confirm) then
  1454.   begin
  1455.     TheMessage := Format(sDelete + ' ' + sPoint +' %d: (%e.3, %e.3) ?',
  1456.       [ThePoint, FXData^[ThePoint], FYData^[ThePoint]]);
  1457.     if (mrNo = MessageDlg(
  1458. {$IFDEF LINUX}
  1459.     sDelete + ' ' + sPoint,
  1460. {$ENDIF}
  1461.       TheMessage,
  1462.       mtWarning,
  1463.       [mbYes, mbNo],
  1464.       0)) then
  1465.         exit;
  1466.   end;
  1467.  
  1468. {we now use the slower method to be more consistent with the
  1469.  dynamic array approach:}
  1470.   if (not FExternalXSeries) then
  1471.   begin
  1472.     for i := ThePoint to FNoPts-2 do
  1473.     begin
  1474.       FXData^[i] := FXData^[i+1];
  1475.     end;
  1476.     if ((FXStringData <> nil) and
  1477.         (FXStringData.Count > ThePoint)) then
  1478.       FXStringData.Delete(ThePoint);
  1479.   end;
  1480.  
  1481.   for i := ThePoint to FNoPts-1 do
  1482.   begin
  1483.     FYData^[i] := FYData^[i+1];
  1484.   end;
  1485.  
  1486.   Dec(FNoPts);
  1487.  
  1488.   DoDataChange;
  1489.   DelPointNumber := FNoPts;
  1490. end;
  1491.  
  1492. {------------------------------------------------------------------------------
  1493.      Function: TSeries.DelData
  1494.   Description: standard property Get function
  1495.        Author: Mat Ballard
  1496.  Date created: 04/25/2000
  1497. Date modified: 04/25/2000 by Mat Ballard
  1498.       Purpose: deletes an entire data set. It only works on internal data sets.
  1499.  Return Value: TRUE if successful
  1500.  Known Issues:
  1501.  ------------------------------------------------------------------------------}
  1502. function TSeries.DelData: Boolean;
  1503. begin
  1504.   DelData := FALSE;
  1505.   if (DataStatus = dsNone) then exit;
  1506.  
  1507.   if (FDependentSeries.Count > 0) then
  1508.   begin
  1509. {Merde ! this series is being destroyed, but other series depend on it !
  1510.  we therefore need to "pass the buck": pass the X Data that we've created, and
  1511.  the list of dependent series to another series:}
  1512.     TSeries(FDependentSeries.Items[0]).AssumeMasterSeries(FNoPts, Self, FDependentSeries);
  1513. {and now, the X Data is managed by an external series:}
  1514.     FExternalXSeries := TRUE;
  1515.     FDependentSeries.Clear;
  1516.   end;
  1517.  
  1518.   if (DataStatus = dsInternal) then
  1519.   begin
  1520.     if (not FExternalXSeries) then
  1521.     begin
  1522. {$IFDEF DELPHI1}
  1523.       FreeMem(FXData, MemSize * SizeOf(Single));
  1524.       if (FXStringData <> nil) then
  1525.       begin
  1526.         FXStringData.Free;
  1527.         FXStringData := nil;
  1528.         FXAxis.SetLabelSeries(nil);
  1529.       end;
  1530.     end;
  1531.     FreeMem(FYData, MemSize * SizeOf(Single));
  1532. {$ELSE}
  1533.       ReAllocMem(FXData, 0);
  1534.       if (FXStringData <> nil) then
  1535.       begin
  1536.         FXStringData.Free;
  1537.         FXStringData := nil;
  1538.         FXAxis.SetLabelSeries(nil);
  1539.       end;
  1540.     end;
  1541.     ReAllocMem(FYData, 0);
  1542. {$ENDIF}
  1543.     FXData := nil;
  1544.     FYData := nil;
  1545.     {FZData := nil;}
  1546.   end;
  1547.  
  1548.   FExternalXSeries := FALSE;
  1549.   FNoPts := 0;
  1550.   DataStatus := dsNone;
  1551.   MemSize := 0;
  1552.   ResetBounds;
  1553.  
  1554.   DelData := TRUE;
  1555.   DoDataChange;
  1556. end;
  1557.  
  1558. {------------------------------------------------------------------------------
  1559.     Procedure: TSeries.ClearHighsLows
  1560.   Description: frees the Highs and Lows, and their Counts and Capacities
  1561.        Author: Mat Ballard
  1562.  Date created: 04/25/2000
  1563. Date modified: 04/25/2000 by Mat Ballard
  1564.       Purpose: Series analysis
  1565.  Known Issues:
  1566.  ------------------------------------------------------------------------------}
  1567. procedure TSeries.ClearHighsLows;
  1568. begin
  1569.   if (FHighs <> nil) then
  1570.   begin
  1571.     FreeMem(FHighs, FHighCapacity * SizeOf(Integer));
  1572.     FHighs := nil;
  1573.   end;
  1574.   if (FLows <> nil) then
  1575.   begin
  1576.     FreeMem(FLows, FHighCapacity * SizeOf(Integer));
  1577.     FLows := nil;
  1578.   end;
  1579.   
  1580.   FHighLow := [];
  1581.   FHighCapacity := 10;
  1582.   FHighCount := 0;
  1583.   FLowCount := 0;
  1584. end;
  1585.  
  1586. {------------------------------------------------------------------------------
  1587.      Function: TSeries.Average
  1588.   Description: calculates the average of a series over a range
  1589.        Author: Mat Ballard
  1590.  Date created: 04/25/2000
  1591. Date modified: 04/25/2000 by Mat Ballard
  1592.       Purpose: numerical calculation
  1593.  Return Value: the average: single
  1594.  Known Issues:
  1595.  ------------------------------------------------------------------------------}
  1596. function TSeries.Average(TheLeft, TheRight: Single): Single;
  1597. var
  1598.   i,
  1599.   Start,
  1600.   Finish,
  1601.   Number: Integer;
  1602.   Sum: Single;
  1603. begin
  1604.   if (TheLeft > TheRight) then
  1605.   begin
  1606. {swap TheLeft and TheRight}
  1607.     Sum := TheLeft;
  1608.     TheLeft := TheRight;
  1609.     TheRight := Sum;
  1610.   end;
  1611.  
  1612. {get the TheLeft and TheRight points:}
  1613.   Start := GetNearestPointToX(TheLeft);
  1614.   Finish := GetNearestPointToX(TheRight);
  1615.  
  1616. {adjust TheLeft and TheRight:}
  1617.   if (FXData^[Start] < TheLeft) then
  1618.     Inc(Start);
  1619.   if (FXData^[Finish] > TheRight) then
  1620.     Dec(Finish);
  1621.  
  1622. {initialize:}
  1623.   Number := 0;
  1624.   Sum := 0;
  1625.   for i := Start to Finish do
  1626.   begin
  1627.     Sum := Sum + FYData^[i];
  1628.     Inc(Number);
  1629.   end;
  1630.  
  1631.   Average := Sum / Number;
  1632. end;
  1633.  
  1634. {------------------------------------------------------------------------------
  1635.     Procedure: TSeries.Linearize
  1636.   Description: Linearizes (turns into a straight line) the data of a series over a range
  1637.        Author: Mat Ballard
  1638.  Date created: 05/30/2001
  1639. Date modified: 05/30/2001 by Mat Ballard
  1640.       Purpose: numerical calculation
  1641.  Known Issues:
  1642.  ------------------------------------------------------------------------------}
  1643. procedure TSeries.Linearize(TheLeft, TheRight: Single);
  1644. var
  1645.   i,
  1646.   Start,
  1647.   Finish: Integer;
  1648.   Slope, Intercept: Single;
  1649. begin
  1650.   if (TheLeft > TheRight) then
  1651.   begin
  1652. {swap TheLeft and TheRight}
  1653.     Slope := TheLeft;
  1654.     TheLeft := TheRight;
  1655.     TheRight := Slope;
  1656.   end;
  1657.  
  1658. {get the TheLeft and TheRight points:}
  1659.   Start := GetNearestPointToX(TheLeft);
  1660.   Finish := GetNearestPointToX(TheRight);
  1661.  
  1662. {adjust TheLeft and TheRight:}
  1663.   if (FXData^[Start] < TheLeft) then
  1664.     Inc(Start);
  1665.   if (FXData^[Finish] > TheRight) then
  1666.     Dec(Finish);
  1667.  
  1668. {initialize:}
  1669.   Slope := (FYData^[Finish] - FYData^[Start]) /
  1670.     (FXData^[Finish] - FXData^[Start]);
  1671.   Intercept := FYData^[Finish] - Slope * FXData^[Finish];
  1672.   for i := Start+1 to Finish-1 do
  1673.     FYData^[i] := Slope * FXData^[i] + Intercept;
  1674. end;
  1675.  
  1676. {------------------------------------------------------------------------------
  1677.     Procedure: TSeries.Zero
  1678.   Description: zeros the data of a series over a range
  1679.        Author: Mat Ballard
  1680.  Date created: 05/30/2001
  1681. Date modified: 05/30/2001 by Mat Ballard
  1682.       Purpose: numerical calculation
  1683.  Known Issues:
  1684.  ------------------------------------------------------------------------------}
  1685. procedure TSeries.Zero(TheLeft, TheRight: Single);
  1686. var
  1687.   i,
  1688.   Start,
  1689.   Finish: Integer;
  1690.   TheTemp: Single;
  1691. begin
  1692.   if (TheLeft > TheRight) then
  1693.   begin
  1694. {swap TheLeft and TheRight}
  1695.     TheTemp := TheLeft;
  1696.     TheLeft := TheRight;
  1697.     TheRight := TheTemp;
  1698.   end;
  1699.  
  1700. {get the TheLeft and TheRight points:}
  1701.   Start := GetNearestPointToX(TheLeft);
  1702.   Finish := GetNearestPointToX(TheRight);
  1703.  
  1704. {adjust TheLeft and TheRight:}
  1705.   if (FXData^[Start] < TheLeft) then
  1706.     Inc(Start);
  1707.   if (FXData^[Finish] > TheRight) then
  1708.     Dec(Finish);
  1709.  
  1710. {initialize:}
  1711.   for i := Start to Finish do
  1712.     FYData^[i] := 0;
  1713. end;
  1714.  
  1715. {------------------------------------------------------------------------------
  1716.     Procedure: TSeries.MovingAverage
  1717.   Description: Calculates the movong average
  1718.        Author: Mat Ballard
  1719.  Date created: 04/25/2000
  1720. Date modified: 04/25/2000 by Mat Ballard
  1721.       Purpose: Smoothing
  1722.  Known Issues:
  1723.  ------------------------------------------------------------------------------}
  1724. procedure TSeries.MovingAverage(Span: Integer);
  1725. var
  1726.   i, j,
  1727.   Left, Right: Integer;
  1728.   AverageData: pSingleArray;
  1729. begin
  1730. {allocate memory for arrays:}
  1731.   GetMem(AverageData, FNoPts * SizeOf(Single));
  1732.  
  1733.   for i := 0 to FNoPts-1 do
  1734.   begin
  1735.     AverageData^[i] := 0;
  1736.     Left := i - Span;
  1737.     Right := i + Span;
  1738.  
  1739.     if (Left < 0) then
  1740.     begin
  1741.       Right := 2*i;
  1742.       Left := 0;
  1743.     end;
  1744.     if (Right >= FNoPts) then
  1745.     begin
  1746.       Left := i - (FNoPts-1 - i);
  1747.       Right := FNoPts-1;
  1748.     end;
  1749.  
  1750.     for j := Left to Right do
  1751.     begin
  1752.       AverageData^[i] := AverageData^[i] + FYData^[j];
  1753.     end;
  1754.     AverageData^[i] := AverageData^[i] / (1 + Right - Left);
  1755.   end;
  1756.  
  1757. {NB: the following generates gross access violations:
  1758.   System.Move(AverageData, FYData, FNoPts * SizeOf(Single));}
  1759.   for i := 0 to FNoPts-1 do
  1760.     FYData^[i] := AverageData^[i];
  1761.  
  1762.   FreeMem(AverageData, FNoPts * SizeOf(Single));
  1763. end;
  1764.  
  1765. {------------------------------------------------------------------------------
  1766.     Procedure: TSeries.DoSpline
  1767.   Description: Does the cubic spline of the data
  1768.        Author: Mat Ballard
  1769.  Date created: 04/25/2000
  1770. Date modified: 04/25/2000 by Mat Ballard
  1771.       Purpose: Places the cubic spline interpolation into X and Y
  1772.  Known Issues:
  1773.  ------------------------------------------------------------------------------}
  1774. procedure TSeries.DoSpline(Density: Integer; pSplineSeries: TSeries);
  1775. var
  1776.   i,
  1777.   j: Integer;
  1778.   dX,
  1779.   X: Single;
  1780. begin
  1781. {calculate the ...}
  1782.   SecondDerivative;
  1783.  
  1784. {Index of the new spline points:}
  1785.   for i := 0 to FNoPts-2 do
  1786.   begin
  1787.     pSplineSeries.AddPoint(FXData^[i], FYData^[i], FALSE, FALSE);
  1788.     dX := (FXData^[i+1] - FXData^[i]) / (Density+1);
  1789.     X := FXData^[i];
  1790.     for j := 0 to Density-1 do
  1791.     begin
  1792.       X := X + dX;
  1793.       pSplineSeries.AddPoint(X, SplineValue(X), FALSE, FALSE);
  1794.     end;
  1795.   end;
  1796.   pSplineSeries.AddPoint(FXData^[FNoPts-1], FYData^[FNoPts-1], FALSE, FALSE);
  1797. end;
  1798.  
  1799. {------------------------------------------------------------------------------
  1800.     Procedure: TSeries.SplineValue
  1801.   Description: Calculates the Y co-ordinate from the cubic spline
  1802.        Author: Mat Ballard
  1803.  Date created: 04/25/2000
  1804. Date modified: 04/25/2000 by Mat Ballard
  1805.       Purpose: data manipulation
  1806.  Known Issues: yet to be done
  1807.  ------------------------------------------------------------------------------}
  1808. function TSeries.SplineValue(X: Single): Single;
  1809. var
  1810.   iLeft,
  1811.   iRight,
  1812.   i: integer;
  1813.   dX,
  1814.   LeftX,
  1815.   RightX: Single;
  1816. begin
  1817. {in initialize left and right indices:}
  1818.   iLeft := 0;
  1819.   iRight := FNoPts-1;
  1820.  
  1821. {bracket the X value using binary search:}
  1822.   while (iRight - iLeft > 1) do
  1823.   begin
  1824.     i := (iRight+iLeft) div 2;
  1825.     if (FXData^[i] > X) then
  1826.       iRight := i
  1827.      else
  1828.       iLeft := i;
  1829.   end;
  1830. {width of bracketing interval is:}
  1831.   dX := FXData^[iRight] - FXData^[iLeft];
  1832.  
  1833. {should we chuck a loopy ?}
  1834.   if (dX = 0.0) then raise
  1835.     ERangeError.CreateFmt('TSeries.SplineValue: ' + sSplineValue1 + ' (dX = 0) !' + CRLF+
  1836.       'XData[%d] = %g, XData[%d] = %g', [iRight, FXData^[iRight], iLeft, FXData^[iLeft]]);
  1837.  
  1838. {the right and left portions are:}
  1839.   RightX := (FXData^[iRight]-X) / dX;
  1840.   LeftX := (X-FXData^[iLeft]) / dX;
  1841.  
  1842. {so the cubic spline estimate is:}
  1843.   SplineValue := RightX * FYData^[iLeft] + LeftX * FYData^[iRight] +
  1844.     ((IntPower(RightX, 3) - RightX) * Fd2Y_dX2^[iLeft] +
  1845.       (IntPower(LeftX, 3) - LeftX) * Fd2Y_dX2^[iRight]) *
  1846.         Sqr(dX) / 6.0;
  1847. end;
  1848.  
  1849. {------------------------------------------------------------------------------
  1850.     Procedure: TSeries.SecondDerivative
  1851.   Description: Does the cubic spline of the data
  1852.        Author: Mat Ballard
  1853.  Date created: 04/25/2000
  1854. Date modified: 04/25/2000 by Mat Ballard
  1855.       Purpose: Calculates the second derivatives for use by SplineValue
  1856.  Known Issues:
  1857.  ------------------------------------------------------------------------------}
  1858. procedure TSeries.SecondDerivative;
  1859. var
  1860.   i: integer;
  1861.   TempVar,
  1862.   LeftXFraction: Single;
  1863.   UpperTriangle: pSingleArray;
  1864. begin
  1865.   ClearSpline;
  1866.  
  1867. {allocate memory for the second derivatives:}
  1868.   Size2ndDeriv := FNoPts * SizeOf(Single);
  1869.   GetMem(Fd2Y_dX2, Size2ndDeriv);
  1870.  
  1871.   GetMem(UpperTriangle, FNoPts * SizeOf(Single));
  1872.  
  1873. {handle the first point: we use "natural" boundary condition of
  1874.  zero second derivative:}
  1875.   Fd2Y_dX2^[0] := 0;
  1876.   UpperTriangle^[0] := 0;
  1877.  
  1878. {do the loop over middle points:}
  1879.   for i := 1 to FNoPts-2 do begin
  1880.     LeftXFraction := (FXData^[i] - FXData^[i-1]) /
  1881.       (FXData^[i+1] - FXData^[i-1]);
  1882.     TempVar := LeftXFraction * Fd2Y_dX2^[i-1] + 2.0;
  1883.     Fd2Y_dX2^[i] := (LeftXFraction - 1.0) / TempVar;
  1884.     UpperTriangle^[i] := (FYData^[i+1] - FYData^[i]) / (FXData^[i+1] - FXData^[i]) -
  1885.       (FYData^[i] - FYData^[i-1]) / (FXData^[i] - FXData^[i-1]);
  1886.     UpperTriangle^[i] := (6.0 * UpperTriangle^[i] / (FXData^[i+1] - FXData^[i-1]) -
  1887.       LeftXFraction * UpperTriangle^[i-1]) / TempVar;
  1888.   end;
  1889.  
  1890. {handle the last point: we use "natural" boundary condition of
  1891.  zero second derivative:}
  1892.   Fd2Y_dX2^[FNoPts-1] := 0;
  1893.  
  1894.   for i := FNoPts-2 downto 0 do
  1895.   begin
  1896.     Fd2Y_dX2^[i] := Fd2Y_dX2^[i] * Fd2Y_dX2^[i+1] + UpperTriangle^[i];
  1897.   end;
  1898.   FreeMem(UpperTriangle, FNoPts * SizeOf(Single));
  1899. end;
  1900.  
  1901. {------------------------------------------------------------------------------
  1902.     Procedure: TSeries.ClearSpline
  1903.   Description: frees the second derivative memory
  1904.        Author: Mat Ballard
  1905.  Date created: 04/25/2000
  1906. Date modified: 04/25/2000 by Mat Ballard
  1907.       Purpose: Spline memory management
  1908.  Known Issues:
  1909.  ------------------------------------------------------------------------------}
  1910. procedure TSeries.ClearSpline;
  1911. begin
  1912.   if (Fd2Y_dX2 <> nil) then
  1913.   begin
  1914.     FreeMem(Fd2Y_dX2, Size2ndDeriv);
  1915.     Fd2Y_dX2 := nil;
  1916.     Size2ndDeriv := 0;
  1917.   end;
  1918. end;
  1919.  
  1920. {------------------------------------------------------------------------------
  1921.     Procedure: TSeries.Differentiate
  1922.   Description: Replaces the Series Y data with its differential
  1923.        Author: Mat Ballard
  1924.  Date created: 04/25/2000
  1925. Date modified: 04/25/2000 by Mat Ballard
  1926.       Purpose: Data manipulation
  1927.  Known Issues:
  1928.  ------------------------------------------------------------------------------}
  1929. procedure TSeries.Differentiate;
  1930. var
  1931.   i: Integer;
  1932.   Differential,
  1933.   YOld: Single;
  1934. begin
  1935. {we save the first data point:}
  1936.   YOld := FYData^[0];
  1937.  
  1938. {now do the first point by difference (1st order):}
  1939.   FYData^[0] :=
  1940.     (FYData^[1] - FYData^[0]) / (FXData^[1] - FXData^[0]);
  1941.  
  1942.   for i := 1 to FNoPts-2 do
  1943.   begin
  1944. {we calculate a mid-point (2nd order) differential}
  1945.     Differential :=
  1946.       (((FYData^[i] - YOld) / (FXData^[i] - FXData^[i-1])) +
  1947.        ((FYData^[i+1] - FYData^[i]) / (FXData^[i+1] - FXData^[i])))
  1948.        / 2;
  1949.     YOld := FYData^[i];
  1950.     FYData^[i] := Differential;
  1951.   end;
  1952.  
  1953. {now do the last point by difference (1st order):}
  1954.   FYData^[FNoPts-1] :=
  1955.     (FYData^[FNoPts-1] - YOld) / (FXData^[FNoPts-1] - FXData^[FNoPts-2]);
  1956.  
  1957. {re-scale:}    
  1958.   FDeltaX := 0;
  1959.   FDeltaY := 0;
  1960.   ResetBounds;
  1961.   GetBounds;
  1962. end;
  1963.  
  1964. {------------------------------------------------------------------------------
  1965.     Procedure: TSeries.Integrate
  1966.   Description: Replaces the Series Y data with its integral
  1967.        Author: Mat Ballard
  1968.  Date created: 04/25/2000
  1969. Date modified: 04/25/2000 by Mat Ballard
  1970.       Purpose: Data manipulation
  1971.  Known Issues:
  1972.  ------------------------------------------------------------------------------}
  1973. procedure TSeries.Integrate;
  1974. var
  1975.   i: Integer;
  1976.   Sum,
  1977.   YOld: Single;
  1978. begin
  1979.   Sum := 0;
  1980.   YOld := FYData^[0];
  1981.   for i := 1 to FNoPts-1 do
  1982.   begin
  1983.     Sum := Sum +
  1984.       (FYData^[i] + YOld) * (FXData^[i] - FXData^[i-1]) / 2;
  1985.     YOld := FYData^[i];
  1986.     FYData^[i] := Sum;
  1987.   end;
  1988. {we set the first data point:}
  1989.   FYData^[0] := 0;
  1990.  
  1991. {re-scale:}
  1992.   FDeltaX := 0;
  1993.   FDeltaY := 0;
  1994.   ResetBounds;
  1995.   GetBounds;
  1996. end;
  1997.  
  1998. {------------------------------------------------------------------------------
  1999.      Function: TSeries.Integral
  2000.   Description: standard property Get function
  2001.        Author: Mat Ballard
  2002.  Date created: 04/25/2000
  2003. Date modified: 04/25/2000 by Mat Ballard
  2004.       Purpose: gets the integral of the Series from Start to Finish
  2005.  Return Value: the real area
  2006.  Known Issues: see IntegralByPoint below
  2007.  ------------------------------------------------------------------------------}
  2008. function TSeries.Integral(TheLeft, TheRight: Single): Single;
  2009. var
  2010.   Start,
  2011.   Finish: Integer;
  2012.   Sum,
  2013.   YEst: Single;
  2014. begin
  2015.   if (TheLeft > TheRight) then
  2016.   begin
  2017. {swap TheLeft and TheRight}
  2018.     Sum := TheLeft;
  2019.     TheLeft := TheRight;
  2020.     TheRight := Sum;
  2021.   end;
  2022.  
  2023. {get the TheLeft and TheRight points:}
  2024.   Start := GetNearestPointToX(TheLeft);
  2025.   Finish := GetNearestPointToX(TheRight);
  2026.  
  2027. {adjust TheLeft and TheRight:}
  2028.   if (FXData^[Start] < TheLeft) then
  2029.     Inc(Start);
  2030.   if (FXData^[Finish] > TheRight) then
  2031.     Dec(Finish);
  2032.  
  2033. {Integrate the bulk:}
  2034.   Sum := IntegralByPoint(Start, Finish);
  2035.  
  2036. {Add the end bits:}
  2037.   if ((Start > 0) and
  2038.       (FXData^[Start] <> TheLeft)) then
  2039.   begin
  2040.     YEst := FYData^[Start-1] +
  2041.       (FYData^[Start] - FYData^[Start-1]) *
  2042.       (TheLeft - FXData^[Start-1]) / (FXData^[Start] - FXData^[Start-1]);
  2043.     Sum := Sum +
  2044.       (FXData^[Start] - TheLeft) *
  2045.       (FYData^[Start] + YEst) / 2;
  2046.   end;
  2047.   if ((Finish < FNoPts-1) and
  2048.       (FXData^[Finish] <> TheRight)) then
  2049.   begin
  2050.     YEst := FYData^[Finish] +
  2051.       (FYData^[Finish+1] - FYData^[Finish]) *
  2052.       (TheRight - FXData^[Finish]) / (FXData^[Finish+1] - FXData^[Finish]);
  2053.     Sum := Sum +
  2054.       (TheRight - FXData^[Finish]) *
  2055.       (FYData^[Finish] + YEst) / 2;
  2056.   end;
  2057.  
  2058.   Integral := Sum;
  2059. end;
  2060.  
  2061. {------------------------------------------------------------------------------
  2062.      Function: TSeries.IntegralByPoint
  2063.   Description: standard property Get function
  2064.        Author: Mat Ballard
  2065.  Date created: 04/25/2000
  2066. Date modified: 04/25/2000 by Mat Ballard
  2067.       Purpose: gets the integral of the Series from iStart to iFinish
  2068.  Return Value: the real area
  2069.  Known Issues: see Integral above
  2070.  ------------------------------------------------------------------------------}
  2071. function TSeries.IntegralByPoint(Start, Finish: Integer): Single;
  2072. var
  2073.   i: Integer;
  2074.   Sum: Single;
  2075. begin
  2076.   if (Start > Finish) then
  2077.   begin
  2078. {swap Start and Finish}
  2079.     i := Start;
  2080.     Start := Finish;
  2081.     Finish := i;
  2082.   end;
  2083.  
  2084.   Sum := 0;
  2085.   for i := Start+1 to Finish do
  2086.   begin
  2087.     Sum := Sum +
  2088.       (FYData^[i] + FYData^[i-1]) * (FXData^[i] - FXData^[i-1]) / 2;
  2089.   end;
  2090. {we set the first data point:}
  2091.   IntegralByPoint := Sum;
  2092. end;
  2093.  
  2094. {------------------------------------------------------------------------------
  2095.      Function: TSeries.IncMemSize
  2096.   Description: increases the available memory for data
  2097.        Author: Mat Ballard
  2098.  Date created: 04/25/2000
  2099. Date modified: 04/25/2000 by Mat Ballard
  2100.       Purpose: data and memory management
  2101.  Return Value: TRUE if successful
  2102.  Known Issues:
  2103.  ------------------------------------------------------------------------------}
  2104. function TSeries.IncMemSize: Boolean;
  2105. begin
  2106.   IncMemSize := AllocateNoPts(MemSize + FDefSize);
  2107.   if ((DataStatus = dsInternalString) and (FXStringData = nil)) then
  2108.   begin
  2109.     FXStringData := TStringList.Create;
  2110.     FXAxis.SetLabelSeries(Self);
  2111.   end;
  2112. end;
  2113.  
  2114. {------------------------------------------------------------------------------
  2115.      Function: TSeries.InsertPoint
  2116.   Description: inserts a data point
  2117.        Author: Mat Ballard
  2118.  Date created: 04/25/2000
  2119. Date modified: 04/25/2000 by Mat Ballard
  2120.       Purpose: gets the value of the ??? Property
  2121.  Return Value: new number of data points
  2122.  Known Issues:
  2123.  ------------------------------------------------------------------------------}
  2124. function TSeries.InsertPoint(X, Y: Single): Integer;
  2125. var
  2126.   i, ThePoint: Integer;
  2127. begin
  2128.   InsertPoint := -1;
  2129.  
  2130.   if ((DataStatus = dsNone) or (FNoPts = 0))then
  2131.   begin
  2132. {we add the point, firing events and adjusting axes as neccessary:}
  2133.     InsertPoint := AddPoint(X, Y, TRUE, TRUE);
  2134.     exit;
  2135.   end;
  2136.  
  2137. {Find out where to insert this point:}
  2138.   ThePoint := 0;
  2139.   {TheXPointer := FXData;}
  2140.   for i := 0 to FNoPts-1 do
  2141.   begin
  2142.     if (FXData^[i] > X) then
  2143.     begin
  2144.       ThePoint := i;
  2145.     end
  2146.     else if (ThePoint > 0) then
  2147.     begin
  2148.       break;
  2149.     end;
  2150.     {Inc(TheXPointer);}
  2151.   end;
  2152.  
  2153.   if (ThePoint = FNoPts-1) then
  2154.   begin
  2155. {we add the point, firing events and adjusting axes as neccessary:}
  2156.     InsertPoint := AddPoint(X, Y, TRUE, TRUE);
  2157.     exit;
  2158.   end;
  2159.  
  2160. {Check memory available:}
  2161.   if (FNoPts >= MemSize-2) then
  2162.     if (not IncMemSize) then exit; {will return false and exit if not enough memory}
  2163.  
  2164.   if (not FExternalXSeries) then
  2165.   begin
  2166.     for i := FNoPts downto ThePoint+1 do
  2167.     begin
  2168.       FXData^[i] := FXData^[i-1];
  2169.     end;
  2170.     FXData^[ThePoint] := X;
  2171.   end;
  2172.  
  2173.   for i := FNoPts downto ThePoint+1 do
  2174.   begin
  2175.     FYData^[i] := FYData^[i-1];
  2176.   end;
  2177.   FYData^[ThePoint] := Y;
  2178.  
  2179.   Inc(FNoPts);
  2180.  
  2181.   DoDataChange;
  2182.   InsertPoint := FNoPts;
  2183. end;
  2184.  
  2185. {------------------------------------------------------------------------------
  2186.     Procedure: TSeries.Smooth
  2187.   Description: smoothes the data using a modified Savitsky-Golay method 
  2188.        Author: Mat Ballard
  2189.  Date created: 04/25/2000
  2190. Date modified: 04/25/2000 by Mat Ballard
  2191.       Purpose: data manipulation
  2192.  Known Issues:
  2193.  ------------------------------------------------------------------------------}
  2194. procedure TSeries.Smooth(SmoothOrder: Integer);
  2195. var
  2196.   i, j, K: Integer;
  2197.   Start, Finish: Integer;
  2198.   IntSum: Integer;
  2199.   Sum, SumStart, SumFinish: Single;
  2200.   pSmoothData: pSingleArray;
  2201.   SCMatrix, pSCMatrix: pInteger; {Longword ?}
  2202. {define pSCMatrix(i, j) == pSCMatrix(i + (FNoPts+1) * j)}
  2203.   pSCSum: pIntegerArray;    {Longword ?}
  2204.   {Msg: String;}
  2205.  
  2206. {NOTE: Multidimensional dynamic arrays DON'T WORK !}
  2207.  
  2208.   procedure SetSCMatrixPointer(i, j: Integer);
  2209.   begin
  2210.     if (SmoothOrder < 2) then raise
  2211.       ERangeError.CreateFmt('SetSCMatrixPointer: SCMatrix(%d, %d) ' + sSetSCMatrixPointer1,
  2212.         [i, j]);
  2213.     pSCMatrix := SCMatrix;
  2214.     Inc(pSCMatrix, i + (SmoothOrder+1) * j);
  2215.   end;
  2216.  
  2217.   {procedure DisplayMatrix;
  2218.   var
  2219.     ii, jj: Integer;
  2220.     DMsg: String;
  2221.   begin
  2222.   //display the matrix:
  2223.     DMsg := Format('Smooth Order = %d, Start = %d, Finish = %d',
  2224.                   [SmoothOrder, Start, Finish]) + CRLF;
  2225.     DMsg := DMsg + CRLF + 'The smoothing Matrix is:';
  2226.     For ii := 0 To SmoothOrder do
  2227.     begin
  2228.       DMsg := DMsg + CRLF;
  2229.       For jj := 0 To SmoothOrder do
  2230.       begin
  2231.         SetSCMatrixPointer(ii, jj);
  2232.         DMsg := DMsg + IntToStr(pSCMatrix^) + ', '
  2233.       end;
  2234.     end;
  2235.  
  2236.     DMsg := DMsg + CRLF + CRLF+ 'The smoothing Sums are:' + #13+#10;
  2237.     pSCSum := SCSum;
  2238.     For ii := 0 To SmoothOrder do
  2239.     begin
  2240.       DMsg := DMsg + IntToStr(pSCSum^) + ', ';
  2241.       Inc(pSCSum);
  2242.     end;
  2243.  
  2244.     ShowMessage(DMsg);
  2245.   end;}
  2246.  
  2247. begin
  2248.   if ((SmoothOrder < 2) or (SmoothOrder > 20)) then raise
  2249.     ERangeError.CreateFmt('TSeries.Smooth: ' + sSmooth1,
  2250.       [SmoothOrder]);
  2251.  
  2252.   if (FNoPts <= SmoothOrder+1) then raise
  2253.     ERangeError.CreateFmt('TSeries.Smooth: ' + sSmooth2,
  2254.       [SmoothOrder, FNoPts]);
  2255.  
  2256. {allocate memory for arrays:}
  2257.   GetMem(pSmoothData, FNoPts * SizeOf(Single));
  2258.   GetMem(SCMatrix, (SmoothOrder+1) * (SmoothOrder+1) * SizeOf(Integer));
  2259.   GetMem(pSCSum, (SmoothOrder+1) * SizeOf(Integer));
  2260.  
  2261. {Zero the matrix:}
  2262.   For i := 0 To SmoothOrder do {i <=> Rows}
  2263.   begin
  2264.     For j := 0 to SmoothOrder do {j <=> Column}
  2265.     begin
  2266.       SetSCMatrixPointer(i, j);
  2267.       pSCMatrix^ := 0;
  2268.     end;
  2269.   end;
  2270.  
  2271. {set the first column and the diagonals to 1:}
  2272.   For i := 0 To SmoothOrder do
  2273.   begin
  2274.     SetSCMatrixPointer(i, 0);
  2275.     pSCMatrix^ := 1;
  2276.     SetSCMatrixPointer(i, i);
  2277.     pSCMatrix^ := 1;
  2278.   end;
  2279.  
  2280. {Calculate the Smoothing Coefficients:
  2281.  now columns 1, 2, ... SmoothOrder:}
  2282.   For i := 2 To SmoothOrder do {i <=> Rows}
  2283.   begin
  2284.     For j := 1 to i-1 do {j <=> Column}
  2285.     begin
  2286.       SetSCMatrixPointer(i - 1, j - 1);
  2287.       IntSum := pSCMatrix^;
  2288.       SetSCMatrixPointer(i - 1, j);
  2289.       IntSum := IntSum + pSCMatrix^;
  2290.       SetSCMatrixPointer(i, j);
  2291.       pSCMatrix^ := IntSum;
  2292.     end;
  2293.   end;
  2294. {   For j% = 1 To SmoothOrder%
  2295.         For i% = j% To SmoothOrder%
  2296.             Sum! = 0
  2297.             For K% = 0 To i% - 1
  2298.                 Sum! = Sum! + SC(K%, j% - 1)
  2299.             Next K%
  2300.             SC(i%, j%) = Sum!
  2301.         Next i%
  2302.     Next j%}
  2303.  
  2304. {Calculate the sums:}
  2305.   For i := 0 To SmoothOrder do {i <=> Rows}
  2306.   begin
  2307.     pSCSum^[i] := 0;
  2308.     For j := 0 To i do {j <=> Columns}
  2309.     begin
  2310.       SetSCMatrixPointer(i, j);
  2311.       pSCSum^[i] := pSCSum^[i] + pSCMatrix^;
  2312.     end;
  2313.   end;
  2314. {    For i% = 0 To SmoothOrder%
  2315.         SCSum(i%) = 0
  2316.         For j% = 0 To i%
  2317.             SCSum(i%) = SCSum(i%) + SC(i%, j%)
  2318.         Next j%
  2319.     Next i%}
  2320.  
  2321. {Calculate the starting and ending points:}
  2322.   Start := SmoothOrder div 2;
  2323.   Finish := FNoPts - Start;
  2324. {    Start% = Int(SmoothOrder% / 2)
  2325.     Finish% = Runs.No_Pts - Start%}
  2326.  
  2327.   {DisplayMatrix;}
  2328.  
  2329. {these first and last points don't change:}
  2330.   pSmoothData^[0] := FYData^[0];
  2331.   pSmoothData^[FNoPts-1] := FYData^[FNoPts-1];
  2332. {    Smooth_Data(0) = Y_Data(0)
  2333.     Smooth_Data(Runs.No_Pts) = Y_Data(Runs.No_Pts)}
  2334.  
  2335. {Do the messy points in between:}
  2336.   For K := 1 To (SmoothOrder - 2) div 2 do
  2337. { For i% = 2 To (SmoothOrder% - 2) Step 2}
  2338.   begin
  2339.     i := 2*K;
  2340.     SumStart := 0;
  2341.     SumFinish := 0;
  2342.     For j := 0 To i do
  2343.     begin
  2344.       SetSCMatrixPointer(i, j);
  2345.       SumStart := SumStart + FYData^[j] * pSCMatrix^;
  2346. {     SumStart& = SumStart& + CLng(Y_Data(j%)) * CLng(SC(i%, j%))}
  2347.       SumFinish := SumFinish + FYData^[FNoPts-1-j] * pSCMatrix^;
  2348. {     SumFinish& = SumFinish& + CLng(Y_Data(Runs.No_Pts - j%)) * CLng(SC(i%, j%))}
  2349.     end;
  2350.     pSmoothData^[K] := SumStart / pSCSum^[i];
  2351. {   Smooth_Data(i% / 2) = SumStart& / SCSum(i%)}
  2352.     pSmoothData^[FNoPts-1-K] := SumFinish / pSCSum^[i];
  2353. {   Smooth_Data(Runs.No_Pts - i% / 2) = SumFinish& / SCSum(i%)}
  2354.   end;
  2355.  
  2356. {    For i% = 2 To (SmoothOrder% - 2) Step 2
  2357.         SumStart& = 0
  2358.         SumFinish& = 0
  2359.         For j% = 0 To i%
  2360.             SumStart& = SumStart& + CLng(Y_Data(j%)) * CLng(SC(i%, j%))
  2361.             SumFinish& = SumFinish& + CLng(Y_Data(Runs.No_Pts - j%)) * CLng(SC(i%, j%))
  2362.         Next j%
  2363.         Smooth_Data(i% / 2) = SumStart& / SCSum(i%)
  2364.         Smooth_Data(Runs.No_Pts - i% / 2) = SumFinish& / SCSum(i%)
  2365.     Next i%}
  2366.  
  2367. {loop over the fully-smoothed points:}
  2368.   For K := Start To Finish-1 do
  2369.   begin
  2370.     Sum := 0;
  2371.     For j := 0 To SmoothOrder do
  2372.     begin
  2373.       SetSCMatrixPointer(SmoothOrder, j);
  2374.       Sum := Sum + FYData^[K+j-Start] * pSCMatrix^;
  2375. {     Sum! = Sum! + Y_Data(K% + j% - Start%) * CSng(SC(SmoothOrder%, j%))}
  2376.     end;
  2377.     pSmoothData^[K] := Sum / pSCSum^[SmoothOrder];
  2378. {   Smooth_Data(K%) = Sum! / SCSum(SmoothOrder%)}
  2379.   end;
  2380.  
  2381. {finally, update the Y data:}
  2382.   For i := 0 To FNoPts-1 do
  2383.     FYData^[i] := pSmoothData^[i];
  2384. {NB: this causes terminal access violations:
  2385.   System.Move(pSmoothData, FYData, FNoPts * SizeOf(Single));}
  2386.  
  2387.  
  2388. {$IFDEF DELPHI1}
  2389.   FreeMem(pSmoothData, FNoPts * SizeOf(Single));
  2390.   FreeMem(SCMatrix, (SmoothOrder+1) * (SmoothOrder+1) * SizeOf(Integer));
  2391.   FreeMem(pSCSum, (SmoothOrder+1) * SizeOf(Integer));
  2392. {$ELSE}
  2393.   FreeMem(pSmoothData);
  2394.   FreeMem(SCMatrix);
  2395.   FreeMem(pSCSum);
  2396. {$ENDIF}
  2397.  
  2398.   DoDataChange;
  2399. end;
  2400.  
  2401. {Sub Smooth (SmoothOrder%, X_Data() As Single, Y_Data() As Single)
  2402.  
  2403. '   This function smooths the data using a midpoint method
  2404. '   Keywords:
  2405. '       smooth
  2406. '   Input:
  2407. '
  2408. '   Modifies:
  2409. '       nothing
  2410. '   Output:
  2411. '       none
  2412. '   Returns:
  2413. '
  2414. '   Called From:
  2415. '
  2416. '   Calls:
  2417. '
  2418.  
  2419. Dim i%, j%, K%
  2420. Dim Start%, Finish%
  2421. Dim SumStart&, SumFinish&
  2422. Dim Sum!
  2423. Dim Msg$
  2424. ReDim Smooth_Data(0 To Runs.Array_Size) As Single
  2425.  
  2426.     On Error GoTo Smooth_ErrorHandler
  2427.  
  2428. '   declare the matrix of coefficients for smoothing:
  2429.     ReDim SC(0 To SmoothOrder%, 0 To SmoothOrder%) As Long
  2430.     ReDim SCSum(0 To SmoothOrder%) As Long
  2431.  
  2432. '   set the first column to 1:
  2433.     For i% = 0 To SmoothOrder%
  2434.         SC(i%, 0) = 1
  2435.     Next i%
  2436.  
  2437. '   Calculate the Smoothing Coefficients:
  2438. '   now columns 1, 2, ... SmoothOrder%:
  2439.     For j% = 1 To SmoothOrder%
  2440.         For i% = j% To SmoothOrder%
  2441.             Sum! = 0
  2442.             For K% = 0 To i% - 1
  2443.                 Sum! = Sum! + SC(K%, j% - 1)
  2444.             Next K%
  2445.             SC(i%, j%) = Sum!
  2446.         Next i%
  2447.     Next j%
  2448.  
  2449. '   Calculate the sums:
  2450.     For i% = 0 To SmoothOrder%
  2451.         SCSum(i%) = 0
  2452.         For j% = 0 To i%
  2453.             SCSum(i%) = SCSum(i%) + SC(i%, j%)
  2454.         Next j%
  2455.     Next i%
  2456.  
  2457. '    Msg$ = "Smoothing Matrix:"
  2458. '    For i% = 0 To SmoothOrder%
  2459. '        Msg$ = Msg$ & LF
  2460. '        For j% = 0 To SmoothOrder%
  2461. '            Msg$ = Msg$ & Str$(SC(i%, j%)) & ", "
  2462. '        Next j%
  2463. '    Next i%
  2464. '    Msg$ = Msg$ & LF & LF & "Smoothing Sums:"
  2465. '    For i% = 0 To SmoothOrder%
  2466. '        Msg$ = Msg$ & Str$(SCSum(i%)) & ", "
  2467. '    Next i%
  2468. '    MsgBox Msg$, MB_OK, "Smoothing"
  2469.  
  2470. '   Calculate the starting and ending points:
  2471.     Start% = Int(SmoothOrder% / 2)
  2472.     Finish% = Runs.No_Pts - Start%
  2473.  
  2474. '   Do the smooth; end points are not affected:
  2475.     Smooth_Data(0) = Y_Data(0)
  2476.     Smooth_Data(Runs.No_Pts) = Y_Data(Runs.No_Pts)
  2477. '   Do the messy points in between:
  2478.     For i% = 2 To (SmoothOrder% - 2) Step 2
  2479.         SumStart& = 0
  2480.         SumFinish& = 0
  2481.         For j% = 0 To i%
  2482.             SumStart& = SumStart& + CLng(Y_Data(j%)) * CLng(SC(i%, j%))
  2483.             SumFinish& = SumFinish& + CLng(Y_Data(Runs.No_Pts - j%)) * CLng(SC(i%, j%))
  2484.         Next j%
  2485.         Smooth_Data(i% / 2) = SumStart& / SCSum(i%)
  2486.         Smooth_Data(Runs.No_Pts - i% / 2) = SumFinish& / SCSum(i%)
  2487.     Next i%
  2488.  
  2489. '   loop over the fully-smoothed points:
  2490.     For K% = Start% To Finish%
  2491.         Sum! = 0
  2492.         For j% = 0 To SmoothOrder%
  2493.             Sum! = Sum! + Y_Data(K% + j% - Start%) * CSng(SC(SmoothOrder%, j%))
  2494.         Next j%
  2495.         Smooth_Data(K%) = Sum! / SCSum(SmoothOrder%)
  2496.     Next K%
  2497.  
  2498. '   finally, update the RI data:
  2499.     For i% = 0 To Runs.No_Pts
  2500.         Y_Data(i%) = Smooth_Data(i%)
  2501.     Next i%
  2502.  
  2503.  
  2504. Smooth_FINISHED:
  2505.     Refresh
  2506.  
  2507. Exit Sub
  2508.  
  2509. Smooth_ErrorHandler:   ' Error handler line label.
  2510.  
  2511.     Msg$ = "Panic in " & "Smooth_ErrorHandler !"
  2512.     Msg$ = Msg$ & LF & LF & "Error No. " & Str$(Err) & ": " & Error$
  2513.     Response% = Message(Msg$, MB_OK + MB_ICONEXCLAMATION, "Error !", NO, H_PANIC)
  2514.  
  2515.     Resume Smooth_FINISHED
  2516.  
  2517. End Sub
  2518. }
  2519.  
  2520. {------------------------------------------------------------------------------
  2521.     Procedure: TSeries.Sort
  2522.   Description: Sorts the data using the HeapSort method
  2523.        Author: Mat Ballard
  2524.  Date created: 04/25/2000
  2525. Date modified: 04/25/2000 by Mat Ballard
  2526.       Purpose: Data manipulation
  2527.  Known Issues:
  2528.  ------------------------------------------------------------------------------}
  2529. procedure TSeries.Sort;
  2530. {$IFDEF DELPHI1}
  2531. begin
  2532.   ShowMessage('Sorting is not supported under Delphi 1.');
  2533. end;
  2534. {$ELSE}
  2535. var
  2536.   i: Integer;
  2537.   pMem: Pointer;
  2538.   pPoint: pXYPoint;
  2539.   TheList: TList;
  2540. begin
  2541. {create and initialize the list of points:}
  2542.   TheList := TList.Create;
  2543.   TheList.Capacity := FNoPts;
  2544. {allocate one big block of memory:}
  2545.   GetMem(pMem, FNoPts * SizeOf(TXYPoint));
  2546. {point at the beginning:}
  2547.   pPoint := pMem;
  2548.  
  2549. {loop over all points:}
  2550.   for i := 0 to FNoPts-1 do
  2551.   begin
  2552.     pPoint^.X := FXData^[i];
  2553.     pPoint^.Y := FYData^[i];
  2554.     TheList.Add(pPoint);
  2555.     Inc(pPoint);
  2556.   end;
  2557.  
  2558. {do the dirty deed:}
  2559.   TheList.Sort(Compare);
  2560.  
  2561. {point at the beginning:}
  2562.   pPoint := pMem;
  2563. {loop over all points to save results:}
  2564.   for i := 0 to FNoPts-1 do
  2565.   begin
  2566.     FXData^[i] := pPoint^.X;
  2567.     FYData^[i] := pPoint^.Y;
  2568.     Inc(pPoint);
  2569.   end;
  2570.  
  2571.   TheList.Free;
  2572.   FreeMem(pMem, FNoPts * SizeOf(TXYPoint));
  2573. end;
  2574. {$ENDIF}
  2575.  
  2576. {------------------------------------------------------------------------------
  2577.      Function: Compare
  2578.   Description: comparison function for sorting
  2579.        Author: Mat Ballard
  2580.  Date created: 04/25/2000
  2581. Date modified: 04/25/2000 by Mat Ballard
  2582.       Purpose: compares the X ordinate of point for a TList quicksort
  2583.  Return Value: -1, 0 or 1
  2584.  Known Issues:
  2585.  ------------------------------------------------------------------------------}
  2586. function Compare(Item1, Item2: Pointer): Integer;
  2587. begin
  2588.   if (pXYPoint(Item1)^.X < pXYPoint(Item2)^.X) then
  2589.   begin
  2590.     Compare := -1;
  2591.   end
  2592.   else if (pXYPoint(Item1)^.X = pXYPoint(Item2)^.X) then
  2593.   begin
  2594.     Compare := 0;
  2595.   end
  2596.   else
  2597.   begin
  2598.     Compare := 1;
  2599.   end;
  2600. end;
  2601.  
  2602. {------------------------------------------------------------------------------
  2603.     Procedure: TSeries.GetPoint
  2604.   Description: returns the Nth (0..NoPts-1) point's X and Y values.
  2605.        Author: Mat Ballard
  2606.  Date created: 04/25/2000
  2607. Date modified: 04/25/2000 by Mat Ballard
  2608.       Purpose: data management
  2609.  Known Issues:
  2610.  ------------------------------------------------------------------------------}
  2611. procedure TSeries.GetPoint(N: Integer; var X, Y: Single);
  2612. begin
  2613.   if ((N < 0) or (N >= FNoPts)) then raise
  2614.     ERangeError.CreateFmt('TSeries.GetPoint: ' + sGetPoint1,
  2615.       [N, FNoPts]);
  2616.  
  2617.   X := FXData^[N];
  2618.   Y := FYData^[N];
  2619. end;
  2620.  
  2621. {------------------------------------------------------------------------------
  2622.      Function: TSeries.GetXYPoint
  2623.   Description: returns the Nth (0..NoPts-1) point's X and Y values.
  2624.        Author: Mat Ballard
  2625.  Date created: 04/25/2000
  2626. Date modified: 04/25/2000 by Mat Ballard
  2627.       Purpose: data management
  2628.  Return Value: XY
  2629.  Known Issues:
  2630.  ------------------------------------------------------------------------------}
  2631. function TSeries.GetXYPoint(N: Integer): TXYPoint;
  2632. {This returns the Nth (0..NoPts-1) point's X and Y values.}
  2633. var
  2634.   XY: TXYPoint;
  2635. begin
  2636.   if ((N < 0) or (N >= FNoPts)) then raise
  2637.     ERangeError.CreateFmt('TSeries.GetXYPoint: ' + sGetPoint1,
  2638.       [N, FNoPts]);
  2639.  
  2640.   XY.X := FXData^[N];
  2641.   XY.Y := FYData^[N];
  2642.   GetXYPoint := XY;
  2643. end;
  2644.  
  2645. {------------------------------------------------------------------------------
  2646.     Procedure: TSeries.Displace
  2647.   Description: Runs the "Displace" dialog box
  2648.        Author: Mat Ballard
  2649.  Date created: 04/25/2000
  2650. Date modified: 04/25/2000 by Mat Ballard
  2651.       Purpose: user management of Series displacement
  2652.  Known Issues:
  2653.  ------------------------------------------------------------------------------}
  2654. procedure TSeries.Displace(TheHelpFile: String);
  2655. var
  2656.   DisplacementForm: TDisplacementForm;
  2657. begin
  2658.   DisplacementForm := TDisplacementForm.Create(nil);
  2659.   DisplacementForm.TheSeries := TObject(Self);
  2660.   
  2661.   DisplacementForm.SeriesLabel.Caption := FName;
  2662.   DisplacementForm.DeltaXNEdit.AsInteger := FDeltaX;
  2663.   DisplacementForm.DeltaYNEdit.AsInteger := FDeltaY;
  2664.  
  2665.   DisplacementForm.HelpFile := TheHelpFile;
  2666.  
  2667.   if (DisplacementForm.ShowModal = mrOK) then
  2668.     ApplyDisplacementChange(DisplacementForm);
  2669.  
  2670.   DisplacementForm.Free;
  2671. end;
  2672.  
  2673. {------------------------------------------------------------------------------
  2674.     Procedure: TCustomPlot.ApplyDisplacementChange
  2675.   Description: This applies changes from the Displace Dialog.
  2676.        Author: Mat Ballard
  2677.  Date created: 03/28/2001
  2678. Date modified: 03/28/2001 by Mat Ballard
  2679.       Purpose: User interface management
  2680.  Known Issues:
  2681.  ------------------------------------------------------------------------------}
  2682. procedure TSeries.ApplyDisplacementChange(Sender: TObject);
  2683. begin
  2684.   with TDisplacementForm(Sender) do
  2685.   begin
  2686.     FDeltaX := DeltaXNEdit.AsInteger;
  2687.     FDeltaY := DeltaYNEdit.AsInteger;
  2688.   end;
  2689.   DoStyleChange;
  2690. end;
  2691.  
  2692. {------------------------------------------------------------------------------
  2693.     Procedure: TCustomPlot.EditData
  2694.   Description: Runs the Data Editor for the selected Series
  2695.        Author: Mat Ballard
  2696.  Date created: 03/13/2001
  2697. Date modified: 03/13/2001 by Mat Ballard
  2698.       Purpose:
  2699.  Known Issues:
  2700.  ------------------------------------------------------------------------------}
  2701. procedure TSeries.EditData(TheHelpFile: String);
  2702. var
  2703.   i: Integer;
  2704.   DataEditor: TDataEditorForm;
  2705. begin
  2706.   if (FNoPts > 1024) then
  2707.   begin
  2708.     ShowMessageFmt(sTooManyToEdit, [FNoPts]);
  2709.     exit;
  2710.   end;
  2711.  
  2712.   DataEditor := TDataEditorForm.Create(nil);
  2713.   DataEditor.HelpFile := TheHelpFile;
  2714.   DataEditor.ExternalXSeries := FExternalXSeries or (DataStatus = dsExternal);
  2715.   DataEditor.DependentXSeries := (FDependentSeries.Count > 0);
  2716.   DataEditor.TheSeries := TObject(Self);
  2717.   DataEditor.ExternalXSeries := Self.FExternalXSeries;
  2718.  
  2719.   DataEditor.SeriesnameLabel.Caption := Self.Name;
  2720.   {StatusBar1.SimpleText := Format('%d points', [FNoPts]);}
  2721.   DataEditor.ZDataNEdit.AsReal := ZData;
  2722.   DataEditor.DataStringGrid.RowCount := FNoPts + 1;
  2723.   for i := 0 to FNoPts - 1 do
  2724.   begin
  2725.     DataEditor.DataStringGrid.Cells[0, i+1] := IntToStr(i);
  2726.     DataEditor.DataStringGrid.Cells[1, i+1] := FloatToStr(FXData^[i]);
  2727.     DataEditor.DataStringGrid.Cells[2, i+1] := FloatToStr(FYData^[i]);
  2728.   end;
  2729.   if (XStringData <> nil) then
  2730.   begin
  2731.     for i := 0 to XStringData.Count-1 do
  2732.       DataEditor.DataStringGrid.Cells[3, i+1] := XStringData.Strings[i];
  2733.   end;
  2734.  
  2735.   if (DataEditor.ShowModal = mrOK) then
  2736.     ApplyDataChange(DataEditor);
  2737.  
  2738.   DataEditor.Free;
  2739. end;
  2740.  
  2741. {------------------------------------------------------------------------------
  2742.     Procedure: TCustomPlot.ApplyDataChange
  2743.   Description: This applies changes from the DataDialog.
  2744.        Author: Mat Ballard
  2745.  Date created: 03/28/2001
  2746. Date modified: 03/28/2001 by Mat Ballard
  2747.       Purpose: User interface management
  2748.  Known Issues:
  2749.  ------------------------------------------------------------------------------}
  2750. procedure TSeries.ApplyDataChange(Sender: TObject);
  2751. var
  2752.   i,
  2753.   TotalLength: Integer;
  2754.   StringData: TStringList;
  2755. begin
  2756.   with TDataEditorForm(Sender) do
  2757.   begin
  2758.     if (RowCountChanged) then
  2759.     begin
  2760.       if (DataStringGrid.RowCount >= MemSize) then
  2761.         IncMemSize;
  2762.       NumericDataChanged := TRUE;
  2763.       StringDataChanged := TRUE;
  2764.       FNoPts := DataStringGrid.RowCount-1;
  2765.     end;
  2766.     if (NumericDataChanged) then
  2767.     begin
  2768.       for i := 1 to DataStringGrid.RowCount - 1 do
  2769.       begin
  2770.         FXData^[i-1] := StrToFloat(DataStringGrid.Cells[1, i]);
  2771.         FYData^[i-1] := StrToFloat(DataStringGrid.Cells[2, i]);
  2772.       end;
  2773.     end;
  2774.     if (StringDataChanged) then
  2775.     begin
  2776.       StringData := TStringList.Create;
  2777.       StringData.Assign(DataStringGrid.Cols[3]);
  2778.       StringData.Delete(0);
  2779.       TotalLength := 0;
  2780.       for i := 0 to StringData.Count-1 do
  2781.         TotalLength := TotalLength + Length(StringData[i]);
  2782.       if (TotalLength > 0) then
  2783. {This assignment isn't what you think it is:
  2784. exercise: trace into it:}
  2785.         XStringData := StringData;
  2786.       StringData.Free;
  2787.     end;
  2788. {Find out the new X and Y Min and Max values:}    
  2789.     ResetBounds;
  2790.     GetBounds;
  2791.   end;
  2792.   DoDataChange;
  2793. end;
  2794.  
  2795. {------------------------------------------------------------------------------
  2796.     Procedure: TSeries.EditPoint
  2797.   Description: Runs the "EditPoint" dialog box
  2798.        Author: Mat Ballard
  2799.  Date created: 04/25/2000
  2800. Date modified: 04/25/2000 by Mat Ballard
  2801.       Purpose: user management of Series displacement
  2802.  Known Issues:
  2803.  ------------------------------------------------------------------------------}
  2804. procedure TSeries.EditPoint(ThePointNumber: Integer; TheHelpFile: String);
  2805. var
  2806.   PointEditorForm: TPointEditorForm;
  2807.   TheResult: TModalResult;
  2808. begin
  2809.   PointEditorForm := TPointEditorForm.Create(nil);
  2810.   PointEditorForm.TheSeries := Self;
  2811.   
  2812.   PointEditorForm.Init(FXData, FYData, FXStringData, FXAxis, FYAxis);
  2813.   PointEditorForm.PointSlideBar.Max := FNoPts-1;
  2814.   PointEditorForm.PointSlideBar.Frequency := Round(FNoPts/10);
  2815.   PointEditorForm.PointSlideBar.PageSize := PointEditorForm.PointSlideBar.Frequency;
  2816.  
  2817. {$IFDEF BCB}
  2818.   PointEditorForm.PointUpDown.Max := FNoPts-1;
  2819.   PointEditorForm.PointNEdit.Max := FNoPts-1;
  2820. {$ELSE}
  2821. {$IFDEF MSWINDOWS}
  2822.   PointEditorForm.PointSpinEdit.MaxValue := FNoPts-1;
  2823. {$ENDIF}
  2824. {$IFDEF LINUX}
  2825.   PointEditorForm.PointSpinEdit.Max := FNoPts-1;
  2826. {$ENDIF}
  2827. {$ENDIF}
  2828.   PointEditorForm.PointSlideBar.Position := ThePointNumber;
  2829.   PointEditorForm.FillData(ThePointNumber);
  2830.   PointEditorForm.DetailsLabel.Caption := FName;
  2831.  
  2832.   if (FExternalXSeries) then
  2833.   begin
  2834.     PointEditorForm.XDataNEdit.Enabled := FALSE;
  2835.     PointEditorForm.XScreenNEdit.Enabled := FALSE;
  2836.   end;
  2837.  
  2838.   PointEditorForm.HelpFile := TheHelpFile;
  2839.  
  2840.   TheResult := PointEditorForm.ShowModal;
  2841.   ApplyPointChange(PointEditorForm, TheResult);
  2842.  
  2843.   PointEditorForm.Free;
  2844. end;
  2845.  
  2846. {------------------------------------------------------------------------------
  2847.     Procedure: TCustomPlot.ApplyPointChange
  2848.   Description: This applies changes from the PointEditor Dialog.
  2849.        Author: Mat Ballard
  2850.  Date created: 03/28/2001
  2851. Date modified: 03/28/2001 by Mat Ballard
  2852.       Purpose: User interface management
  2853.  Known Issues:
  2854.  ------------------------------------------------------------------------------}
  2855. procedure TSeries.ApplyPointChange(Sender: TObject; TheResult: TModalResult);
  2856. var
  2857.   ThePointNumber: Integer;
  2858.   XNew, YNew: Single;
  2859. begin
  2860.   with TPointEditorForm(Sender) do
  2861.   begin
  2862.     if (TheResult <> mrCancel) then
  2863.     begin
  2864.       ThePointNumber := PointSlideBar.Position;
  2865.       if (DataGroupBox.Enabled) then
  2866.       begin
  2867.         XNew := XDataNEdit.AsReal;
  2868.         YNew := YDataNEdit.AsReal;
  2869.       end
  2870.       else
  2871.       begin {base on screen co-ords:}
  2872.         XNew := FXAxis.XofF(XScreenNEdit.AsInteger);
  2873.         YNew := FYAxis.YofF(YScreenNEdit.AsInteger);
  2874.       end;
  2875.  
  2876.       case TheResult of
  2877.         mrOK:
  2878.           begin
  2879.             ReplacePoint(ThePointNumber, XNew, YNew);
  2880.             if ((FXStringData <> nil) and
  2881.                 (FXStringData.Count > ThePointNumber)) then
  2882.               FXStringData.Strings[ThePointNumber] := XStringDataEdit.Text;
  2883.             CheckBounds(ThePointNumber, TRUE);
  2884.           end;
  2885.         mrYes:
  2886.           begin
  2887.             if (FXStringData <> nil) then
  2888.               AddStringPoint(XStringDataEdit.Text, XNew, YNew, TRUE, TRUE)
  2889.             else
  2890.               AddPoint(XNew, YNew, TRUE, TRUE);
  2891.             CheckBounds(FNoPts, TRUE);
  2892.           end;
  2893.         mrNo:
  2894.           DelPointNumber(ThePointNumber, TRUE);
  2895.       end;
  2896.     end; {if}
  2897.   end;
  2898.   DoDataChange;
  2899. end;
  2900.  
  2901. {------------------------------------------------------------------------------
  2902.     Procedure: TSeries.ReplacePoint
  2903.   Description: Replaces the Nth point with new values
  2904.        Author: Mat Ballard
  2905.  Date created: 04/25/2000
  2906. Date modified: 04/25/2000 by Mat Ballard
  2907.       Purpose: data management
  2908.  Known Issues:
  2909.  ------------------------------------------------------------------------------}
  2910. procedure TSeries.ReplacePoint(N: Integer; NewX, NewY: Single);
  2911. begin
  2912.   if (DataStatus <> dsInternal) then exit;
  2913.  
  2914.   if ((N < 0) or (N >= FNoPts)) then raise
  2915.     ERangeError.CreateFmt('TSeries.ReplacePoint: ' + sGetPoint1,
  2916.       [N, FNoPts]);
  2917.  
  2918.   if (not FExternalXSeries) then
  2919.     FXData^[N] := NewX;
  2920.   FYData^[N] := NewY;
  2921.  
  2922.   DoDataChange;
  2923. end;
  2924.  
  2925. {Odds and sods --------------------------------------------------------------}
  2926. {------------------------------------------------------------------------------
  2927.     Procedure: TSeries.Compress
  2928.   Description: reduces the size of the data set by local averaging
  2929.        Author: Mat Ballard
  2930.  Date created: 04/25/2000
  2931. Date modified: 10/15/2000 by Mat Ballard
  2932.       Purpose: data manipulation and management
  2933.  Known Issues: Tidied up: Contract originally compressed; now it really does contract.
  2934.  ------------------------------------------------------------------------------}
  2935. procedure TSeries.Compress(CompressRatio: Integer);
  2936. var
  2937.   i, j, k: Integer;
  2938.   XSum, YSum: Single;
  2939. begin
  2940.   if ((CompressRatio < 2) or (FNoPts div CompressRatio < 10 )) then
  2941.   begin
  2942. {we used to throw an exception here, but this roots CompressAllSeries}
  2943.     ShowMessage(Format('TSeries.Compress: ' + sCompressData1,
  2944.       [FName, FNoPts, CompressRatio]));
  2945.     exit;
  2946.   end;
  2947.  
  2948.   j := 0;
  2949.   k := 0;
  2950.   XSum := 0;
  2951.   YSum := 0;
  2952.   for i := 0 to FNoPts-1 do
  2953.   begin
  2954.     XSum := XSum + FXData^[i];
  2955.     YSum := YSum + FYData^[i];
  2956.     Inc(j);
  2957.     if (j = CompressRatio) then
  2958.     begin
  2959.       if (not FExternalXSeries) then
  2960.         FXData^[k] := XSum / j;
  2961.       FYData^[k] := YSum / j;
  2962.       j := 0;
  2963.       XSum := 0;
  2964.       YSum := 0;
  2965.       Inc(k);
  2966.     end;
  2967.   end; {for}
  2968.   if (j > 0) then
  2969.   begin
  2970.     if (not FExternalXSeries) then
  2971.       FXData^[k] := XSum / j;
  2972.     FYData^[k] := YSum / j;
  2973.     Inc(k);
  2974.   end;
  2975.   FNoPts := k;
  2976.  
  2977.   DoDataChange;
  2978. end;
  2979.  
  2980. {------------------------------------------------------------------------------
  2981.     Procedure: TSeries.Contract
  2982.   Description: reduces the size of the data set by throwing away the ends of the data set
  2983.        Author: Mat Ballard
  2984.  Date created: 10/15/2000
  2985. Date modified: 10/15/2000 by Mat Ballard
  2986.       Purpose: data manipulation and management
  2987.  Known Issues: Tidied up: Contract originally compressed; now it really does contract.
  2988.  ------------------------------------------------------------------------------}
  2989. procedure TSeries.Contract(TheStart, TheFinish: Integer);
  2990. var
  2991.   i: Integer;
  2992. begin
  2993.   if (TheStart > TheFinish) then
  2994.   begin
  2995.     i := TheStart;
  2996.     TheStart := TheFinish;
  2997.     TheFinish := i;
  2998.   end;
  2999.  
  3000.   if ((TheStart < 0) or (TheFinish > FNoPts)) then
  3001.   begin
  3002. {we used to throw an exception here, but this roots ContractAllSeries}
  3003.     ShowMessage(Format('TSeries.Contract: ' + sContractData1,
  3004.       [FName, TheStart, TheFinish]));
  3005.     exit;
  3006.   end;
  3007.  
  3008.   if (TheStart > 0) then
  3009.   begin
  3010.     for i := TheStart to TheFinish do
  3011.       FYData^[i-TheStart] := FYData^[i];
  3012.     if (not FExternalXSeries) then
  3013.       for i := TheStart to TheFinish do
  3014.         FXData^[i-TheStart] := FXData^[i];
  3015.   end;
  3016.  
  3017.   FNoPts := TheFinish - TheStart +1;
  3018.   Self.ResetBounds;
  3019.   Self.GetBounds;
  3020.  
  3021.   DoDataChange;
  3022. end;
  3023.  
  3024. {------------------------------------------------------------------------------
  3025.     Procedure: TSeries.CopyToClipBoard
  3026.   Description: Copies this Series to the clipboard as tab-delimited text
  3027.        Author: Mat Ballard
  3028.  Date created: 04/25/2000
  3029. Date modified: 04/25/2000 by Mat Ballard
  3030.       Purpose: moving data in and out of the application
  3031.  Known Issues:
  3032.  ------------------------------------------------------------------------------}
  3033. procedure TSeries.CopyToClipBoard;
  3034. var
  3035. {Q: which is more efficient: a String or a TStringList ?}
  3036.   TheData: String;
  3037.   i: Integer;
  3038. begin
  3039.   TheData := FName;
  3040.   TheData := TheData + CRLF + FXAxis.Title.Caption + #9 + FYAxis.Title.Caption;
  3041.   TheData := TheData + CRLF + FXAxis.Title.Units + #9 + FYAxis.Title.Units;
  3042.   for i := 0 to FNoPts-1 do
  3043.   begin
  3044.     TheData := TheData + CRLF + FloatToStr(FXData^[i]) + #9 + FloatToStr(FYData^[i]);
  3045.   end;
  3046.   Clipboard.AsText := TheData;
  3047. end;
  3048.  
  3049. {------------------------------------------------------------------------------
  3050.     Procedure: TSeries.CheckBounds
  3051.   Description: Checks if ThePointNo exceeds the Series Mins and Maxes
  3052.        Author: Mat Ballard
  3053.  Date created: 04/25/2000
  3054. Date modified: 04/25/2000 by Mat Ballard
  3055.       Purpose: many: data and screen management
  3056.  Known Issues:
  3057.  ------------------------------------------------------------------------------}
  3058. procedure TSeries.CheckBounds(ThePointNo: Integer; AdjustAxis: Boolean);
  3059. begin
  3060.   if (FXMin > FXData^[ThePointNo]) then
  3061.   begin
  3062.     FXMin := FXData^[ThePointNo];
  3063.     if (AdjustAxis) then
  3064.       FXAxis.SetMinFromSeries(FXMin);
  3065.     {if (assigned(FOnXMinChange) and FVisible) then OnXMinChange(Self, FXMin);}
  3066.   end;
  3067.   if (FXMax < FXData^[ThePointNo]) then
  3068.   begin
  3069.     FXMax := FXData^[ThePointNo];
  3070.     if (AdjustAxis) then
  3071.       FXAxis.SetMaxFromSeries(FXMax);
  3072.     {if (assigned(FOnXMaxChange) and FVisible) then OnXMaxChange(Self, FXMax);}
  3073.   end;
  3074.   if (FYMin > FYData^[ThePointNo]) then
  3075.   begin
  3076.     FYMin := FYData^[ThePointNo];
  3077.     if (AdjustAxis) then
  3078.       FYAxis.SetMinFromSeries(FYMin);
  3079.     {if (assigned(FOnYMinChange) and FVisible) then OnYMinChange(Self, FYMin);}
  3080.   end;
  3081.   if (FYMax < FYData^[ThePointNo]) then
  3082.   begin
  3083.     FYMax := FYData^[ThePointNo];
  3084.     if (AdjustAxis) then
  3085.       FYAxis.SetMaxFromSeries(FYMax);
  3086.     {if (assigned(FOnYMaxChange) and FVisible) then OnYMaxChange(Self, YMax);}
  3087.   end;
  3088. end;
  3089.  
  3090. {------------------------------------------------------------------------------
  3091.     Procedure: TSeries.GetBounds
  3092.   Description: Determines the Mins and Maxes of this Series
  3093.        Author: Mat Ballard
  3094.  Date created: 04/25/2000
  3095. Date modified: 04/25/2000 by Mat Ballard
  3096.       Purpose: sets the XMin, XMax, YMin and YMax  Properties
  3097.  Known Issues:
  3098.  ------------------------------------------------------------------------------}
  3099. procedure TSeries.GetBounds;
  3100. var
  3101.   i: Integer;
  3102. begin
  3103.   for i := 0 to FNoPts-1 do
  3104.   begin
  3105.     if (FXMin > FXData^[i]) then FXMin := FXData^[i];
  3106.     if (FXMax < FXData^[i]) then FXMax := FXData^[i];
  3107.     if (FYMin > FYData^[i]) then FYMin := FYData^[i];
  3108.     if (FYMax < FYData^[i]) then FYMax := FYData^[i];
  3109.   end;
  3110.   FXAxis.SetMinMaxFromSeries(FXMin, FXMax);
  3111.   FYAxis.SetMinMaxFromSeries(FYMin, FYMax);
  3112. end;
  3113.  
  3114. {------------------------------------------------------------------------------
  3115.     Procedure: TSeries.ResetBounds
  3116.   Description: Resets the Mins and Maxes
  3117.        Author: Mat Ballard
  3118.  Date created: 04/25/2000
  3119. Date modified: 04/25/2000 by Mat Ballard
  3120.       Purpose: data management
  3121.  Known Issues:
  3122.  ------------------------------------------------------------------------------}
  3123. procedure TSeries.ResetBounds;
  3124. begin
  3125.   FXMin := 3.4e38;
  3126.   FXMax := -3.4e38;
  3127.   FYMin := 3.4e38;
  3128.   FYMax := -3.4e38;
  3129. end;
  3130.  
  3131. {------------------------------------------------------------------------------
  3132.      Function: TSeries.GetNearestPointToFX
  3133.   Description: does what it says
  3134.        Author: Mat Ballard
  3135.  Date created: 04/25/2000
  3136. Date modified: 04/25/2000 by Mat Ballard
  3137.       Purpose: data management
  3138.  Return Value: Index of nearest point
  3139.  Known Issues:
  3140.  ------------------------------------------------------------------------------}
  3141. function TSeries.GetNearestPointToFX(FX: Integer): Integer;
  3142. {This uses a binary search method to find the point with an X value closest to X.}
  3143. begin
  3144.   GetNearestPointToFX :=
  3145.     GetNearestPointToX(Self.FXAxis.XofF(FX));
  3146. end;
  3147.  
  3148. {------------------------------------------------------------------------------
  3149.      Function: TSeries.GetNearestPointToX
  3150.   Description: does what it says
  3151.        Author: Mat Ballard
  3152.  Date created: 04/25/2000
  3153. Date modified: 04/25/2000 by Mat Ballard
  3154.       Purpose: data management
  3155.  Return Value: Index of nearest point
  3156.  Known Issues:
  3157.  ------------------------------------------------------------------------------}
  3158. function TSeries.GetNearestPointToX(X: Single): Integer;
  3159. {This uses a binary search method to find the point with an X value closest to X.}
  3160. var
  3161.   iEst, iLow, iHigh: Integer;
  3162. begin
  3163. {Is X outside the range of X Values ?}
  3164.   if (X >= FXMax) then
  3165.   begin
  3166.     GetNearestPointToX := FNoPts-1;
  3167.     exit;
  3168.   end;
  3169.   if (X <= FXMin) then
  3170.   begin
  3171.     GetNearestPointToX := 0;
  3172.     exit;
  3173.   end;
  3174.  
  3175. {The lowest and highest possible points are:}
  3176.   iLow := 0;
  3177.   iHigh := FNoPts - 1;
  3178. {Estimate a starting point:}
  3179.   iEst := Round(FNoPts * (X-FXMin)/(FXMax - FXMin));
  3180.  
  3181.   repeat
  3182.     if (X < FXData^[iEst]) then
  3183.     begin
  3184. {The point is lower:}
  3185.       iHigh := iEst;
  3186.       iEst := (iEst + iLow) div 2;
  3187.     end
  3188.     else
  3189.     begin
  3190. {The point is higher:}
  3191.       iLow := iEst;
  3192.       iEst := (iEst + iHigh) div 2;
  3193.     end;
  3194.   until ((iEst-iLow) <= 1) and ((iHigh-iEst) <= 1);
  3195. {find the X values just below and just above:}
  3196.   if ((X < FXData^[iLow]) or (X > FXData^[iHigh])) then
  3197.   begin
  3198.     raise EComponentError.CreateFmt(sGetNearestPointToX1
  3199.       + CRLF + 'X ' + sGetNearestPointToX2 + ' = %g/%g/%g',
  3200.         [X, FXData^[iLow], FXData^[iEst], FXData^[iHigh]]);
  3201.   end
  3202.   else if (X < FXData^[iEst]) then
  3203.   begin
  3204. {FXData^[iLow] < X < FXData^[iEst]}
  3205.     if ((X-FXData^[iLow]) < (FXData^[iEst]-X)) then
  3206.     begin
  3207.       iEst := iLow;
  3208.     end;
  3209.   end
  3210.   else
  3211.   begin
  3212. {FXData^[iEst] < X < FXData^[iHigh]}
  3213.     if ((FXData^[iEst]-X) > (FXData^[iHigh]-X)) then
  3214.     begin
  3215.       iEst := iHigh;
  3216.     end;
  3217.   end;
  3218.   GetNearestPointToX := iEst;
  3219. end;
  3220.  
  3221. {------------------------------------------------------------------------------
  3222.      Function: TSeries.GetNearestPieSlice
  3223.   Description: does what it says
  3224.        Author: Mat Ballard
  3225.  Date created: 01/25/2001
  3226. Date modified: 01/25/2001 by Mat Ballard
  3227.       Purpose: data management
  3228.  Return Value: Index of nearest point
  3229.  Known Issues:
  3230.  ------------------------------------------------------------------------------}
  3231. function TSeries.GetNearestPieSlice(
  3232.   iX, iY,
  3233.   PieLeft, PieTop, PieWidth, PieHeight: Integer;
  3234.   var MinDistance: Single): Integer;
  3235. var
  3236.   i, NearestN: Integer;
  3237.   Xi, Yi, Ri: Integer;
  3238.   Angle,
  3239.   AngleSum,
  3240.   TheAngle,
  3241.   Ratio,
  3242.   Sum: Single;
  3243.  
  3244. begin
  3245.   GetNearestPieSlice := 0;
  3246. {adjust for displacement:}
  3247.   Dec(iX, FDeltaX);
  3248.   Dec(iY, FDeltaY);
  3249. {et al:}
  3250.   if (MinDistance = 0) then
  3251.     MinDistance := 1.0e38;
  3252.   NearestN := 0;
  3253.  
  3254.   if ((iX < PieLeft) or
  3255.       (iX > (PieLeft + PieWidth)) or
  3256.       (iY < PieTop)  or
  3257.       (iY > (PieTop + PieHeight))) then
  3258.     exit;
  3259.  
  3260. {X and Y distances from centre of ellipse:}
  3261.   Xi := iX - (PieLeft + PieWidth div 2);
  3262.   Yi := iY - (PieTop + PieHeight div 2);
  3263.   MinDistance := Sqrt(Sqr(Xi) + Sqr(Yi));
  3264.   Ratio := PieWidth / PieHeight;
  3265.   Sum := Sqr(Xi) + Sqr(Ratio)*Sqr(Yi);
  3266.   Ri := Sqr(PieWidth div 2);
  3267.   if (Round(Sum) <= Ri) then
  3268.   begin
  3269.     TheAngle := GetAngle(Xi, Yi);
  3270.     AngleSum := 0;
  3271.     for i := 0 to FNoPts-1 do
  3272.     begin
  3273.   {angles are in radians, of course:}
  3274.       Angle := TWO_PI * FYData^[i] / YSum;
  3275.       AngleSum := AngleSum + Angle;
  3276.       if (AngleSum >= TheAngle) then
  3277.       begin
  3278.         NearestN := i;
  3279.         MinDistance := 0;
  3280.         break;
  3281.       end;
  3282.     end;
  3283.   end;
  3284.  
  3285.   GetNearestPieSlice := NearestN;
  3286. end;
  3287.  
  3288. {------------------------------------------------------------------------------
  3289.      Function: TSeries.GetNearestXYPoint
  3290.   Description: does what it says
  3291.        Author: Mat Ballard
  3292.  Date created: 04/25/2000
  3293. Date modified: 01/25/2001 by Mat Ballard
  3294.       Purpose: data management
  3295.  Return Value: Index of nearest point
  3296.  Known Issues:
  3297.  ------------------------------------------------------------------------------}
  3298. function TSeries.GetNearestXYPoint(
  3299.   iX, iY,
  3300.   StartPt, EndPt: Integer;
  3301.   var MinDistance: Single): Integer;
  3302. {The data may not be sorted, so we check every point:}
  3303. var
  3304.   Distance: Single;
  3305.   i, NearestN: Integer;
  3306.   Xi, Yi: Integer;
  3307. begin
  3308. {adjust for displacement:}
  3309.   Dec(iX, FDeltaX);
  3310.   Dec(iY, FDeltaY);
  3311. {check the incoming value:}
  3312.   if (MinDistance = 0) then
  3313.     MinDistance := 1.0e38;
  3314.   NearestN := 0;
  3315.   if (StartPt = EndPt) then
  3316.   begin
  3317.     StartPt := 0;
  3318.     EndPt := FNoPts-1;
  3319.   end;
  3320.  
  3321. {loop over points in each series:}
  3322.   for i := StartPt to EndPt do
  3323.   begin
  3324.     Xi := FXAxis.FofX(FXData^[i]);
  3325.     Yi := FYAxis.FofY(FYData^[i]);
  3326.     Distance := Sqrt(Sqr(Int(Xi-iX)) + Sqr(Int(Yi-iY)));
  3327.     if (MinDistance > Distance) then
  3328.     begin
  3329.       MinDistance := Distance;
  3330.       NearestN := i;
  3331.     end;
  3332.   end; {loop over points}
  3333.   //MinDistance := Sqrt(MinDistance);
  3334.  
  3335.   GetNearestXYPoint := NearestN;
  3336. end;
  3337.  
  3338.  
  3339. {------------------------------------------------------------------------------
  3340.      Function: TSeries.GetNearestXYPointFast
  3341.   Description: does what it says - a quick and dirty method
  3342.        Author: Mat Ballard
  3343.  Date created: 04/25/2000
  3344. Date modified: 04/25/2000 by Mat Ballard
  3345.       Purpose: data management
  3346.  Return Value: Index of nearest point
  3347.  Known Issues: will not work on very spiky data
  3348.  ------------------------------------------------------------------------------}
  3349. function TSeries.GetNearestXYPointFast(
  3350.   iX, iY: Integer;
  3351.   var MinDistance: Single): Integer;
  3352. var
  3353.   X: Single;
  3354.   N: Integer;
  3355.   StartPt, EndPt: Integer;
  3356. begin
  3357.   X := FXAxis.XofF(iX);
  3358.   N := GetNearestPointToX(X);
  3359.  
  3360.   StartPt := N - FNoPts div 20;
  3361.   if (StartPt < 0) then StartPt := 0;
  3362.   EndPt := N + FNoPts div 20;
  3363.   if (EndPt > FNoPts) then EndPt := FNoPts;
  3364.  
  3365.   GetNearestXYPointFast := GetNearestXYPoint(
  3366.     iX, iY,
  3367.     StartPt, EndPt,
  3368.     MinDistance);
  3369. end;
  3370.  
  3371. {TSeries the movie ! ----------------------------------------------------------}
  3372. {------------------------------------------------------------------------------
  3373.     Procedure: TSeries.Draw
  3374.   Description: standard Drawing procedure
  3375.        Author: Mat Ballard
  3376.  Date created: 04/25/2000
  3377. Date modified: 05/06/2001 by Mat Ballard
  3378.       Purpose: draws the Series on a given canvas
  3379.  Known Issues: PERFORMANCE stuff now removed - I know TPlot is quick !
  3380.  ------------------------------------------------------------------------------}
  3381. procedure TSeries.Draw(ACanvas: TCanvas; XYFastAt: Integer);
  3382. var
  3383.   i: Integer;
  3384.   iX, iXOld, iY, iYMin, iYMax: Integer;
  3385.   TheYMin, TheYMax: Single;
  3386. begin
  3387. {$IFDEF DELPHI3_UP}
  3388.   Assert(ACanvas <> nil, 'TSeries.Draw: ' + sACanvasIsNil);
  3389. {$ENDIF}
  3390.   if ((not FVisible) or
  3391.       (FNoPts = 0)) then exit;
  3392.  
  3393.   ACanvas.Pen.Assign(FPen);
  3394.   ACanvas.Brush.Assign(FBrush);
  3395.  
  3396.   if (ACanvas.Pen.Width > 0) then
  3397.   begin
  3398.     if (FNoPts < XYFastAt) then
  3399.     begin
  3400.       iX := FXAxis.FofX(FXData^[0])+ FDeltaX;
  3401.       iY := FYAxis.FofY(FYData^[0]) + FDeltaY;
  3402.       ACanvas.MoveTo(iX, iY);
  3403.  
  3404.       for i := 1 to FNoPts-1 do
  3405.       begin
  3406.         iX := FXAxis.FofX(FXData^[i]) + FDeltaX;
  3407.         iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  3408.         ACanvas.LineTo(iX, iY);
  3409.       end; {loop over points}
  3410.     end
  3411.     else
  3412.     begin
  3413. {There is a huge number of points (> 10000).
  3414.  We therefore adopt a new drawing procedure:
  3415.                        TPlot                      TChart
  3416.               Fast     Slow     Memory        Time     Memory
  3417.               (ms)     (ms)      (K)          (ms)       (K)
  3418.    9990 pts:  108      ----     2902          204      2668
  3419.   10001 pts:  13.9     16.2     2976
  3420.  100000 pts:  23.4     67.7     3628         2226      6000
  3421. 1000000 pts: 123 ms   592      10736        19271     41040
  3422.  
  3423. {This is the more accurate but slower algorithm:}
  3424.       i := 0;
  3425.       iX := FXAxis.FofX(FXData^[0])+ FDeltaX;
  3426.       iY := FYAxis.FofY(FYData^[0]) + FDeltaY;
  3427.       ACanvas.MoveTo(iX, iY);
  3428.       while i < FNoPts do
  3429.       begin
  3430.         iXOld := iX;
  3431.         TheYMin := FYData^[i];
  3432.         TheYMax := TheYMin;
  3433.         repeat
  3434.           iX := FXAxis.FofX(FXData^[i])+ FDeltaX;
  3435.           if (iX > iXOld) then break;
  3436.           if (TheYMin > FYData^[i]) then
  3437.             TheYMin := FYData^[i];
  3438.           if (TheYMax < FYData^[i]) then
  3439.             TheYMax := FYData^[i];
  3440.           Inc(i);  
  3441.         until (i = FNoPts-1);
  3442.         iYMin := FYAxis.FofY(TheYMin)+ FDeltaY;
  3443.         iYMax := FYAxis.FofY(TheYMax)+ FDeltaY;
  3444.         ACanvas.LineTo(iX, iYMax);
  3445.         ACanvas.LineTo(iX, iYMin);
  3446.         Inc(i);
  3447.       end;
  3448.  
  3449.     end; {if FNoPts < XYFastAt}
  3450.   end; {Pen.Width > 0}
  3451.  
  3452.   if ((FSymbol <> syNone) and
  3453.       (FSymbolSize > 0) and
  3454.       (FNoPts < XYFastAt)) then
  3455.   begin
  3456.     ACanvas.Brush.Assign(FBrush);
  3457.     for i := 0 to FNoPts-1 do
  3458.     begin
  3459.       iX := FXAxis.FofX(FXData^[i])+ FDeltaX;
  3460.       iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  3461.       DrawSymbol(ACanvas, iX, iY);
  3462.     end; {loop over points}
  3463.   end;
  3464.  
  3465.   if (FHighCount > 0) then
  3466.     if (FHighLow <> []) then
  3467.       DrawHighs(ACanvas);
  3468. end;
  3469.  
  3470. {------------------------------------------------------------------------------
  3471.     Procedure: TSeries.DrawShades
  3472.   Description: Drawing procedure exyension
  3473.        Author: Mat Ballard
  3474.  Date created: 07/23/2001
  3475. Date modified: 07/23/2001 by Mat Ballard
  3476.       Purpose: draws the portion of a Series that exceeds the axis limits on a given canvas
  3477.  Known Issues: 
  3478.  ------------------------------------------------------------------------------}
  3479. procedure TSeries.DrawShades(ACanvas: TCanvas; XYFastAt: Integer);
  3480. var
  3481.   i: Integer;
  3482.   iX, iXOld, iXCalc, iY, iYOld: Integer;
  3483.   LowerLimit, UpperLimit: Integer;
  3484.   Slope, Intercept: Single;
  3485. begin
  3486. {$IFDEF DELPHI3_UP}
  3487.   Assert(ACanvas <> nil, 'TSeries.DrawShades: ' + sACanvasIsNil);
  3488. {$ENDIF}
  3489.   if ((not FVisible) or
  3490.       (FNoPts = 0)) then exit;
  3491.       
  3492.   if (FNoPts >= XYFastAt) then exit;
  3493.  
  3494.   ACanvas.Pen.Assign(FPen);
  3495.   ACanvas.Brush.Assign(FBrush);
  3496.  
  3497.   if (FShadeLimits) then
  3498.   begin
  3499.     ACanvas.Pen.Style := psClear;
  3500.     LowerLimit := FYAxis.FofY(FYAxis.LimitLower);
  3501.     UpperLimit := FYAxis.FofY(FYAxis.LimitUpper);
  3502.     iX := FXAxis.FofX(FXData^[0])+ FDeltaX;
  3503.     iY := FYAxis.FofY(FYData^[0]) + FDeltaY;
  3504.     for i := 1 to FNoPts-1 do
  3505.     begin
  3506.       iXOld := iX;
  3507.       iYOld := iY;
  3508.       iX := FXAxis.FofX(FXData^[i]) + FDeltaX;
  3509.       iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  3510.       if (iY > LowerLimit) then
  3511.       begin
  3512.         if (iYOld >= LowerLimit) then
  3513.           ACanvas.Polygon([
  3514.             Point(iXOld, iYOld),
  3515.             Point(iXOld, LowerLimit),
  3516.             Point(iX, LowerLimit),
  3517.             Point(iX, iY)])
  3518.         else
  3519.         begin
  3520.           Slope := (iY-iYOld) / (iX-iXOld);
  3521.           Intercept := iY - Slope * iX;
  3522.           iXCalc := Round((LowerLimit - Intercept) / Slope);
  3523.           ACanvas.Polygon([
  3524.             Point(iXCalc, LowerLimit),
  3525.             Point(iX, LowerLimit),
  3526.             Point(iX, iY)])
  3527.         end;
  3528.       end
  3529.       else if (iY < UpperLimit) then
  3530.       begin
  3531.         if (iYOld <= UpperLimit) then
  3532.           ACanvas.Polygon([
  3533.             Point(iXOld, iYOld),
  3534.             Point(iXOld, UpperLimit),
  3535.             Point(iX, UpperLimit),
  3536.             Point(iX, iY)])
  3537.         else
  3538.         begin
  3539.           Slope := (iY-iYOld) / (iX-iXOld);
  3540.           Intercept := iY - Slope * iX;
  3541.           iXCalc := Round((UpperLimit - Intercept) / Slope);
  3542.           ACanvas.Polygon([
  3543.             Point(iXCalc, UpperLimit),
  3544.             Point(iX, UpperLimit),
  3545.             Point(iX, iY)])
  3546.         end
  3547.       end  
  3548.       else if (iYOld > LowerLimit) then
  3549.       begin
  3550.         Slope := (iY-iYOld) / (iX-iXOld);
  3551.         Intercept := iY - Slope * iX;
  3552.         iXCalc := Round((LowerLimit - Intercept) / Slope);
  3553.         ACanvas.Polygon([
  3554.           Point(iXCalc, LowerLimit),
  3555.           Point(iXOld, LowerLimit),
  3556.           Point(iXOld, iYOld)])
  3557.       end
  3558.       else if (iYOld < UpperLimit) then
  3559.       begin
  3560.           Slope := (iY-iYOld) / (iX-iXOld);
  3561.           Intercept := iY - Slope * iX;
  3562.           iXCalc := Round((UpperLimit - Intercept) / Slope);
  3563.           ACanvas.Polygon([
  3564.             Point(iXCalc, UpperLimit),
  3565.             Point(iXOld, UpperLimit),
  3566.             Point(iXOld, iYOld)])
  3567.       end; {iY or iYOld in Limits}
  3568.     end; {loop over points}
  3569.     ACanvas.Pen.Style := FPen.Style;
  3570.   end; {Shaded}
  3571. end;
  3572.  
  3573. {------------------------------------------------------------------------------
  3574.     Procedure: TSeries.DrawPie
  3575.   Description: standard Pie Drawing procedure
  3576.        Author: Mat Ballard
  3577.  Date created: 12/21/2000
  3578. Date modified: 12/21/2000 by Mat Ballard
  3579.       Purpose: draws the Series on a given canvas as a Pie
  3580.  Known Issues:
  3581.  ------------------------------------------------------------------------------}
  3582.  
  3583. { Equation of an ellipse, origin at (h, k), with x-radius a and y-radius b is:
  3584.  
  3585.     (x - h)^2     (y - k)^2
  3586.     ----------  +  ----------   =   1
  3587.        a^2            b^2
  3588.  
  3589.   The polar version of this equation is:
  3590.  
  3591.   r  =  1 / Sqrt(Cos^2╪/a^2 + Sin^2╪/b^2)
  3592.  
  3593.   where:
  3594.   a^2  = b^2 + c^2         c is the focus
  3595.   x  =  r Cos ╪
  3596.   y  =  r Sin ╪
  3597. }
  3598.  
  3599. procedure TSeries.DrawPie(
  3600.   ACanvas: TCanvas;
  3601.   PieLeft,
  3602.   PieTop,
  3603.   PieWidth,
  3604.   PieHeight: Integer);
  3605. var
  3606.   a, b, d,
  3607.   i, j, Index, TextIndex, WallIndex,
  3608.   iAngleSum, iOldAngleSum,
  3609.   NoTopPts,
  3610.   StartSolid, EndSolid,
  3611.   StringWidth: Integer;
  3612.   TextAngle,
  3613.   TheSin, TheCos: Extended;
  3614.   Angle, AngleSum, OldAngleSum,
  3615.   PolarAngle,
  3616.   Radius, Ratio: Single;
  3617.   IsWall, DoAmount: Boolean;
  3618.   Points: TPoints;
  3619.   pPoints: pTPoints;
  3620.   AngleSumPos, TextPos, DeltaPos: TPoint;
  3621.   TheText: String;
  3622.  
  3623.   {Note: this function only works for values of Angle between 0 and 360.}
  3624.   function PolarRadians(AnAngle: Extended): Extended;
  3625.   var
  3626.     TheResult: Extended;
  3627.   begin
  3628.     TheResult := 90.0 - AnAngle;
  3629.     if (TheResult < 0) then
  3630.       TheResult := TheResult + 360.0;
  3631.     PolarRadians := TWO_PI * TheResult / 360;
  3632.   end;
  3633.  
  3634. begin
  3635. {$IFDEF DELPHI3_UP}
  3636.   Assert(ACanvas <> nil, 'TSeries.Draw: ' + sACanvasIsNil);
  3637. {$ENDIF}
  3638.   if ((not FVisible) or
  3639.       (FNoPts = 0)) then exit;
  3640.  
  3641.   ACanvas.Pen.Assign(FPen);
  3642.   ACanvas.Brush.Assign(FBrush);
  3643.   ACanvas.Font.Assign(FXAxis.Labels.Font);
  3644.  
  3645.   if (Self.ExternalXSeries) then
  3646.     Self.FXStringData := Self.FXDataSeries.XStringData;
  3647.  
  3648. {Get the total; note Abs() - negative sectors are bad news:}
  3649.   YSum := 0;
  3650.   for i := 0 to FNoPts-1 do
  3651.     YSum := YSum + Abs(FYData^[i]);
  3652.  
  3653. {Points[0] is the centre of the ellipse:}
  3654.   Points[0].x := PieLeft + PieWidth div 2 + FDeltaX;
  3655.   Points[0].y := PieTop + PieHeight div 2 + FDeltaY;
  3656. {a is the horizontal major axis length}
  3657.   a := PieWidth div 2;
  3658. {b is the vertical minor axis length}
  3659.   b := PieHeight div 2;
  3660. {c is the distance of the focus from the centre:}
  3661.   d := a - b;
  3662.   if (d > PieHeight div 5) then
  3663.     d := PieHeight div 5;
  3664.   IsWall := FALSE;
  3665.  
  3666. {This is the angle, in degrees, from 12 o'clock, clockwise:}
  3667.   AngleSum := 0;
  3668.   OldAngleSum := 0;
  3669.   iOldAngleSum := 0;
  3670.   AngleSumPos.x := Points[0].x;
  3671.   AngleSumPos.y := Points[0].y - b;
  3672.  
  3673.   for i := 0 to FNoPts-1 do
  3674.   begin
  3675.     Index := 1;
  3676.     StartSolid := -1;
  3677.     EndSolid := -1;
  3678.     if (iOldAngleSum < OldAngleSum) then
  3679.     begin
  3680.       Points[Index].x := AngleSumPos.x;
  3681.       Points[Index].y := AngleSumPos.y;
  3682. {only angles between 90 and 270 - the lower side of the ellipse -
  3683.  can have a "wall":}
  3684.       if ((90 <= OldAngleSum) and (OldAngleSum <= 270)) then
  3685.         StartSolid := Index;
  3686.       Inc(Index);
  3687.     end;
  3688.     ACanvas.Brush.Color := MyColorValues[1 + i mod 15];
  3689.     Angle := 360.0 * Abs(FYData^[i]) / YSum;
  3690.     AngleSum := AngleSum + Angle;
  3691.     iAngleSum := Trunc(AngleSum);
  3692.     for j := iOldAngleSum to iAngleSum do
  3693.     begin
  3694. {we look for start of the "wall":}
  3695.       if ((StartSolid < 0) and (90 <= j) and (j <= 270)) then
  3696.         StartSolid := Index;
  3697. {gotta find the start before the end:}
  3698.       if ((StartSolid > 0) and (j <= 270)) then
  3699.         EndSolid := Index;
  3700.       PolarAngle := PolarRadians(j);
  3701.       SinCos(PolarAngle, TheSin, TheCos);
  3702.       Radius :=  1.0 / Sqrt(Sqr(TheCos/a) + Sqr(TheSin/b));
  3703.       AngleSumPos.x := Points[0].x + Round(Radius * TheCos);
  3704.       AngleSumPos.y := Points[0].y - Round(Radius * TheSin);
  3705.       Points[Index].x := AngleSumPos.x;
  3706.       Points[Index].y := AngleSumPos.y;
  3707.       Inc(Index);
  3708.     end;
  3709.     if (iAngleSum < AngleSum) then
  3710.     begin
  3711.       if ((StartSolid > 0) and (AngleSum <= 270)) then
  3712.         EndSolid := Index;
  3713.       PolarAngle := PolarRadians(AngleSum);
  3714.       SinCos(PolarAngle, TheSin, TheCos);
  3715.       Radius :=  1.0 / Sqrt(Sqr(TheCos/a) + Sqr(TheSin/b));
  3716.       AngleSumPos.x := Points[0].x + Round(Radius * TheCos);
  3717.       AngleSumPos.y := Points[0].y - Round(Radius * TheSin);
  3718.       Points[Index].x := AngleSumPos.x;
  3719.       Points[Index].y := AngleSumPos.y;
  3720.       Inc(Index);
  3721.     end;
  3722. {Draw the pie slice:}
  3723.     ACanvas.Polygon(Slice(Points, Index));
  3724.     TextAngle := OldAngleSum + Angle/2;
  3725.  
  3726. {Should we put the amounts in ?}
  3727.     j := Round(Sqrt(
  3728.       Sqr(Points[1].x - Points[Index-1].x) +
  3729.       Sqr(Points[1].y - Points[Index-1].y)));
  3730.     TheText := FloatToStrF(100 * FYData^[i] / YSum, ffFixed, 0, 0) + '%';
  3731.     StringWidth := ACanvas.TextWidth(TheText);
  3732.     DoAmount := (j > StringWidth);
  3733. {NB: we do this before the wall section because the latter changes the Points array,
  3734.  however, we do the textout after the wall because of brush and color issues.}
  3735.  
  3736. {Draw the bottom wall:}
  3737.     if ((d > 0) and (StartSolid > 0) and (EndSolid > 0)) then
  3738.     begin
  3739.       IsWall := TRUE;
  3740.       ACanvas.Brush.Color := Misc.GetDarkerColor(MyColorValues[1 + i mod 15], 50);
  3741.       pPoints := @Points[StartSolid];
  3742.       NoTopPts := EndSolid - StartSolid + 1;
  3743.       WallIndex := NoTopPts;
  3744.       for j := NoTopPts-1 downto 0 do
  3745.       begin
  3746.         pPoints^[WallIndex].x := pPoints^[j].x;
  3747.         pPoints^[WallIndex].y := pPoints^[j].y + d;
  3748.         Inc(WallIndex);
  3749.       end;
  3750. {Draw the pie wall:}
  3751.       ACanvas.Polygon(Slice(pPoints^, 2 * NoTopPts));
  3752.     end;
  3753.  
  3754. {Set brush up for text mode:}
  3755.     ACanvas.Brush.Style := bsClear;
  3756.  
  3757. {Should we put the amounts in ?
  3758.  See above}
  3759.     if (DoAmount) then
  3760.     begin
  3761.       ACanvas.Font.Color := GetInverseColor(MyColorValues[1 + i mod 15]);
  3762.       TextPos := Points[Index div 2];
  3763.       DeltaPos.x := Points[0].x - TextPos.x;
  3764.       DeltaPos.y := Points[0].y - TextPos.y;
  3765.       j := Round(Sqrt(Sqr(DeltaPos.x) + Sqr(DeltaPos.y)));
  3766.       Ratio := StringWidth / j;
  3767.       DeltaPos.x := Round(Ratio * DeltaPos.x);
  3768.       DeltaPos.y := Round(Ratio * DeltaPos.y);
  3769.       TextPos.x := TextPos.x + DeltaPos.x - StringWidth div 2;
  3770.       TextPos.y := TextPos.y + DeltaPos.y - ACanvas.TextHeight(TheText) div 2;
  3771.       ACanvas.TextOut(TextPos.x, TextPos.y, TheText);
  3772.     end;
  3773.  
  3774. {Is the X Data is in the form of a string ?}
  3775.     if (FXStringData <> nil) then
  3776.     begin
  3777.       if (i < FXStringData.Count) then
  3778.       begin
  3779.         if (Angle > 6.0) then {6 degrees}
  3780.         begin
  3781. {There is a large enough amount to denote:}
  3782.           ACanvas.Font.Color := MyColorValues[1 + i mod 15];
  3783.           TextPos := Points[Index div 2];
  3784. {put the text outside the circle:}
  3785.           if (TextAngle > 180) then
  3786.             Dec(TextPos.x, ACanvas.TextWidth(FXStringData.Strings[i]));
  3787.           if ((TextAngle < 90) or (TextAngle > 270)) then
  3788.             Dec(TextPos.y, ACanvas.TextHeight('Ap'))
  3789.           else if (IsWall) then
  3790.             Inc(TextPos.y, d);
  3791.           ACanvas.TextOut(TextPos.x, TextPos.y, FXStringData.Strings[i]);
  3792. {restore brush:}
  3793.           ACanvas.Brush.Style := FBrush.Style;
  3794.         end; {Angle > 0.1}
  3795.       end; {there is a string}
  3796.     end; {stringdata}
  3797.  
  3798.     iOldAngleSum := iAngleSum;
  3799.     OldAngleSum := AngleSum;
  3800.     //Application.ProcessMessages;
  3801.   end; {for i}
  3802. {restore the string pointer:}
  3803.   if (Self.ExternalXSeries) then
  3804.     Self.FXStringData := nil;
  3805. end;
  3806.  
  3807. {------------------------------------------------------------------------------
  3808.     Procedure: TSeries.DrawPolar
  3809.   Description: standard Drawing procedure
  3810.        Author: Mat Ballard
  3811.  Date created: 01/25/2001
  3812. Date modified: 01/25/2001 by Mat Ballard
  3813.       Purpose: draws the Series on a given canvas as a Polar graph
  3814.  Known Issues:
  3815.  ------------------------------------------------------------------------------}
  3816. procedure TSeries.DrawPolar(ACanvas: TCanvas; PolarRange: Single);
  3817. var
  3818.   i: Integer;
  3819.   iX, iY: Integer;
  3820.   Angle,
  3821.   X,
  3822.   Y: Single;
  3823.   SinTheta, CosTheta: Extended;
  3824. begin
  3825. {$IFDEF DELPHI3_UP}
  3826.   Assert(ACanvas <> nil, 'TSeries.Draw: ' + sACanvasIsNil);
  3827. {$ENDIF}
  3828.   if ((not FVisible) or
  3829.       (FNoPts = 0)) then exit;
  3830.  
  3831.   ACanvas.Pen.Assign(FPen);
  3832.   ACanvas.Brush.Assign(FBrush);
  3833.   if (ACanvas.Pen.Width > 0) then
  3834.   begin
  3835.     Angle := TWO_PI * FXData^[0] / PolarRange;
  3836.     SinCos(Angle, SinTheta, CosTheta);
  3837.     X := SinTheta * FYData^[0];
  3838.     Y := CosTheta * FYData^[0];
  3839.     iX := FXAxis.FofX(X);
  3840.     iY := FYAxis.FofY(Y);
  3841.     ACanvas.MoveTo(iX, iY);
  3842.  
  3843.     for i := 1 to FNoPts-1 do
  3844.     begin
  3845.       Angle := TWO_PI * FXData^[i] / PolarRange;
  3846.       SinCos(Angle, SinTheta, CosTheta);
  3847.       X := SinTheta * FYData^[i];
  3848.       Y := CosTheta * FYData^[i];
  3849.       iX := FXAxis.FofX(X);
  3850.       iY := FYAxis.FofY(Y);
  3851.       ACanvas.LineTo(iX, iY);
  3852.       if ((FSymbol <> syNone) and (FSymbolSize > 0)) then
  3853.         DrawSymbol(ACanvas, iX, iY);
  3854.     end; {loop over points}
  3855.   end;
  3856.  
  3857.   {if (FHighCount > 0) then
  3858.     if (FHighLow <> []) then
  3859.       DrawHighs(ACanvas);}
  3860. end;
  3861.  
  3862. {------------------------------------------------------------------------------
  3863.     Procedure: TSeries.Trace
  3864.   Description: Draws the series in an erasable mode
  3865.        Author: Mat Ballard
  3866.  Date created: 04/25/2000
  3867. Date modified: 04/25/2000 by Mat Ballard
  3868.       Purpose: rapidly changing screen display
  3869.  Known Issues:
  3870.  ------------------------------------------------------------------------------}
  3871. procedure TSeries.Trace(ACanvas: TCanvas);
  3872. var
  3873.   i: Integer;
  3874.   iX, iY: Integer;
  3875. begin
  3876. {$IFDEF DELPHI3_UP}
  3877.   Assert(ACanvas <> nil, 'TSeries.Trace: ' + sACanvasIsNil);
  3878. {$ENDIF}
  3879.  
  3880.   if ((not FVisible) or
  3881.       (FNoPts = 0)) then exit;
  3882.  
  3883.   ACanvas.Pen.Assign(FPen);
  3884.   ACanvas.Pen.Mode := pmNotXOR;
  3885.   iX := FXAxis.FofX(FXData^[0])+ FDeltaX;
  3886.   iY := FYAxis.FofY(FYData^[0]) + FDeltaY;
  3887.   ACanvas.MoveTo(iX, iY);
  3888.   for i := 1 to FNoPts-1 do
  3889.   begin
  3890.     iX := FXAxis.FofX(FXData^[i]) + FDeltaX;
  3891.     iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  3892.     ACanvas.LineTo(iX, iY);
  3893.   end; {loop over points}
  3894.  
  3895.   if ((FSymbol <> syNone) and (FSymbolSize > 0)) then
  3896.   begin
  3897.     ACanvas.Brush.Assign(FBrush);
  3898.     for i := 0 to FNoPts-1 do
  3899.     begin
  3900.       iX := FXAxis.FofX(FXData^[i])+ FDeltaX;
  3901.       iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  3902.       DrawSymbol(ACanvas, iX, iY);
  3903.     end; {loop over points}
  3904.   end;
  3905. end;
  3906.  
  3907. {------------------------------------------------------------------------------
  3908.     Procedure: TSeries.DrawHighs
  3909.   Description: standard Drawing procedure
  3910.        Author: Mat Ballard
  3911.  Date created: 04/25/2000
  3912. Date modified: 04/25/2000 by Mat Ballard
  3913.       Purpose: draws the Highs of the Series on a given canvas
  3914.  Known Issues:
  3915.  ------------------------------------------------------------------------------}
  3916. procedure TSeries.DrawHighs(ACanvas: TCanvas);
  3917. var
  3918.   i,
  3919.   iX,
  3920.   iY: Integer;
  3921.   TheValue: String;
  3922. {$IFDEF MSWINDOWS}
  3923.   LogRec: TLogFont;
  3924.   OldFontHandle, NewFontHandle: hFont;
  3925. {$ENDIF}
  3926. begin
  3927.   ACanvas.Font.Color := ACanvas.Pen.Color;
  3928.  
  3929. {$IFDEF MSWINDOWS}
  3930.   GetObject(ACanvas.Font.Handle, SizeOf(LogRec), Addr(LogRec));
  3931.   LogRec.lfEscapement := 900; {Up}
  3932.   LogRec.lfOrientation := LogRec.lfEscapement;
  3933.   {LogRec.lfOutPrecision := OUT_DEFAULT_PRECIS;}
  3934.   NewFontHandle := CreateFontIndirect(LogRec);
  3935. {select the new font:}
  3936.   OldFontHandle := SelectObject(ACanvas.Handle, NewFontHandle);
  3937. {$ENDIF}
  3938. {$IFDEF LINUX}
  3939. {$ENDIF}
  3940.  
  3941. {Loop over all Highs:}
  3942.   if (hlHigh in FHighLow) then
  3943.   begin
  3944.     for i := 0 to FHighCount-1 do
  3945.     begin
  3946.       iX := FXAxis.FofX(FXData^[FHighs^[i]]);
  3947.       iY := FYAxis.FofY(FYData^[FHighs^[i]]);
  3948.       ACanvas.MoveTo(iX, iY-2);
  3949.       ACanvas.LineTo(iX, iY + ACanvas.Font.Height);
  3950. {$IFDEF MSWINDOWS}
  3951.       ACanvas.TextOut(
  3952.         iX + ACanvas.Font.Height div 2,
  3953.         iY + ACanvas.Font.Height,
  3954.         FXAxis.LabelToStrF(FXData^[FHighs^[i]]));
  3955. {$ENDIF}
  3956. {$IFDEF LINUX}
  3957.       ACanvas.TextOut(
  3958.         iX + ACanvas.Font.Height div 2,
  3959.         iY + ACanvas.Font.Height + Abs(ACanvas.Font.Height),
  3960.         FXAxis.LabelToStrF(FXData^[FHighs^[i]]));
  3961. {$ENDIF}
  3962.     end;
  3963.   end;
  3964.  
  3965. {Loop over all Lows:}
  3966.   if (hlLow in FHighLow) then
  3967.   begin
  3968.     for i := 0 to FLowCount-1 do
  3969.     begin
  3970.       iX := FXAxis.FofX(FXData^[FLows^[i]]);
  3971.       iY := FYAxis.FofY(FYData^[FLows^[i]]);
  3972.       ACanvas.MoveTo(iX, iY+2);
  3973.       ACanvas.LineTo(iX, iY - ACanvas.Font.Height);
  3974.       TheValue := FXAxis.LabelToStrF(FXData^[FLows^[i]]);
  3975. {$IFDEF MSWINDOWS}
  3976.       ACanvas.TextOut(
  3977.         iX + ACanvas.Font.Height div 2,
  3978.         iY - ACanvas.Font.Height + ACanvas.TextWidth(TheValue),
  3979.         TheValue);
  3980. {$ENDIF}
  3981. {$IFDEF LINUX}
  3982.       ACanvas.TextOut(
  3983.         iX + ACanvas.Font.Height div 2,
  3984.         iY - ACanvas.Font.Height + ACanvas.TextWidth(TheValue) + Abs(ACanvas.Font.Height),
  3985.         TheValue);
  3986. {$ENDIF}
  3987.     end;
  3988.   end;
  3989.  
  3990. {$IFDEF MSWINDOWS}
  3991. {go back to original font:}
  3992.   NewFontHandle := SelectObject(ACanvas.Handle, OldFontHandle);
  3993. {and delete the old one:}
  3994.   DeleteObject(NewFontHandle);
  3995. {$ENDIF}
  3996. {$IFDEF LINUX}
  3997. {$ENDIF}
  3998. end;
  3999.  
  4000. {------------------------------------------------------------------------------
  4001.     Procedure: TSeries.DrawHistory
  4002.   Description: standard Drawing procedure
  4003.        Author: Mat Ballard
  4004.  Date created: 04/25/2000
  4005. Date modified: 04/25/2000 by Mat Ballard
  4006.       Purpose: draws the Series on a given canvas IN HISTORY MODE
  4007.  Known Issues:
  4008.  ------------------------------------------------------------------------------}
  4009. procedure TSeries.DrawHistory(ACanvas: TCanvas; HistoryX: Single);
  4010. var
  4011.   i: Integer;
  4012.   iX, iY: Integer;
  4013. begin
  4014. {$IFDEF DELPHI3_UP}
  4015.   Assert(ACanvas <> nil, 'TSeries.DrawHistory: ' + sACanvasIsNil);
  4016. {$ENDIF}
  4017.  
  4018.   if ((not FVisible) or
  4019.       (FNoPts = 0)) then exit;
  4020.  
  4021.   ACanvas.Pen.Assign(FPen);
  4022. {we set the pen mode so that a second call to DrawHistory
  4023. erases the curve on screen:}
  4024.   ACanvas.Pen.Mode := pmNotXOR;
  4025.   iX := FXAxis.FofX(0) + FDeltaX;
  4026.   iY := FYAxis.FofY(FYData^[FNoPts-1]) + FDeltaY;
  4027.   ACanvas.MoveTo(iX, iY);
  4028.   for i := FNoPts-2 downto 0 do
  4029.   begin
  4030.     iX := FXAxis.FofX(FXData^[i] - FXData^[FNoPts-1]) + FDeltaX;
  4031. {we leave this loop if this is the last point:}
  4032.     if (iX < FXAxis.Left) then break;
  4033.     iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  4034.     ACanvas.LineTo(iX, iY);
  4035.   end; {loop over points}
  4036.   if ((FSymbol <> syNone) and (FSymbolSize > 0)) then
  4037.   begin
  4038.     ACanvas.Brush.Assign(FBrush);
  4039.     for i := FNoPts-2 downto 0 do
  4040.     begin
  4041.       iX := FXAxis.FofX(FXData^[i] - FXData^[FNoPts-1])+ FDeltaX;
  4042. {we leave this loop if this is the last point:}
  4043.       if (iX < FXAxis.Left) then break;
  4044.       iY := FYAxis.FofY(FYData^[i]) + FDeltaY;
  4045.       DrawSymbol(ACanvas, iX, iY);
  4046.     end; {loop over points}
  4047.   end;
  4048. end;
  4049.  
  4050. {------------------------------------------------------------------------------
  4051.     Procedure: TSeries.DrawSymbol
  4052.   Description: Draws the selected Symbol at each point
  4053.        Author: Mat Ballard
  4054.  Date created: 04/25/2000
  4055. Date modified: 03/01/2001 by Mat Ballard
  4056.       Purpose: draws the Symbols of the Series on a given canvas
  4057.  Known Issues:
  4058.  ------------------------------------------------------------------------------}
  4059. procedure TSeries.DrawSymbol(ACanvas: TCanvas; iX, iY: Integer);
  4060. begin
  4061.   case FSymbol of
  4062.     syDash:
  4063.       begin
  4064.         ACanvas.MoveTo(iX - FSymbolSize, iY);
  4065.         ACanvas.LineTo(iX + FSymbolSize+1, iY);
  4066.       end;
  4067.     syVertDash:
  4068.       begin
  4069.         ACanvas.MoveTo(iX, iY - FSymbolSize);
  4070.         ACanvas.LineTo(iX, iY + FSymbolSize+1);
  4071.       end;
  4072.     syLeftDash:
  4073.       begin
  4074.         ACanvas.MoveTo(iX, iY - FSymbolSize);
  4075.         ACanvas.LineTo(iX, iY + FSymbolSize+1);
  4076.         ACanvas.MoveTo(iX, iY);
  4077.         ACanvas.LineTo(iX - FSymbolSize, iY);
  4078.       end;
  4079.     syRightDash:
  4080.       begin
  4081.         ACanvas.MoveTo(iX, iY - FSymbolSize);
  4082.         ACanvas.LineTo(iX, iY + FSymbolSize+1);
  4083.         ACanvas.MoveTo(iX, iY);
  4084.         ACanvas.LineTo(iX + FSymbolSize+1, iY);
  4085.       end;
  4086.     syPlus:
  4087.       begin
  4088.         ACanvas.MoveTo(iX - FSymbolSize, iY);
  4089.         ACanvas.LineTo(iX + FSymbolSize+1, iY);
  4090.         ACanvas.MoveTo(iX, iY - FSymbolSize);
  4091.         ACanvas.LineTo(iX, iY + FSymbolSize+1);
  4092.       end;
  4093.     syCross:
  4094.       begin
  4095.         ACanvas.MoveTo(iX - FSymbolSize, iY - FSymbolSize);
  4096.         ACanvas.LineTo(iX + FSymbolSize+1, iY + FSymbolSize+1);
  4097.         ACanvas.MoveTo(iX + FSymbolSize, iY - FSymbolSize);
  4098.         ACanvas.LineTo(iX - FSymbolSize-1, iY + FSymbolSize+1);
  4099.       end;
  4100.     syStar:
  4101.       begin
  4102.         ACanvas.MoveTo(iX - FSymbolSize, iY);
  4103.         ACanvas.LineTo(iX + FSymbolSize+1, iY);
  4104.         ACanvas.MoveTo(iX, iY - FSymbolSize);
  4105.         ACanvas.LineTo(iX, iY + FSymbolSize+1);
  4106.         ACanvas.MoveTo(iX - FSymbolSize, iY - FSymbolSize);
  4107.         ACanvas.LineTo(iX + FSymbolSize+1, iY + FSymbolSize+1);
  4108.         ACanvas.MoveTo(iX + FSymbolSize, iY - FSymbolSize);
  4109.         ACanvas.LineTo(iX - FSymbolSize-1, iY + FSymbolSize+1);
  4110.       end;
  4111.     sySquare:
  4112.       begin
  4113.         ACanvas.Rectangle(iX - FSymbolSize, iY - FSymbolSize,
  4114.           iX + FSymbolSize+1, iY + FSymbolSize+1)
  4115.       end;
  4116.     syCircle:
  4117.       begin
  4118.         ACanvas.Ellipse(iX - FSymbolSize, iY - FSymbolSize,
  4119.           iX + FSymbolSize+1, iY + FSymbolSize+1)
  4120.       end;
  4121.     syUpTriangle:
  4122.       begin
  4123.         ACanvas.Polygon([
  4124.           Point(iX - FSymbolSize, iY + FSymbolSize+1),
  4125.           Point(iX, iY - FSymbolSize),
  4126.           Point(iX + FSymbolSize, iY + FSymbolSize+1)]);
  4127.       end;
  4128.     syDownTriangle:
  4129.       begin
  4130.         ACanvas.Polygon([
  4131.           Point(iX - FSymbolSize, iY - FSymbolSize),
  4132.           Point(iX, iY + FSymbolSize+1),
  4133.           Point(iX + FSymbolSize, iY - FSymbolSize)]);
  4134.       end;
  4135.   end;
  4136.   ACanvas.MoveTo(iX, iY);
  4137. end;
  4138.  
  4139. {Moving TSeries on the screen -------------------------------------------------}
  4140. {------------------------------------------------------------------------------
  4141.     Procedure: TSeries.GenerateColumnOutline
  4142.   Description: calculates an Outline of the Series
  4143.        Author: Mat Ballard
  4144.  Date created: 02/26/2001
  4145. Date modified: 02/26/2001 by Mat Ballard
  4146.       Purpose: Screen display - highlighting a Series
  4147.  Known Issues: This records the position of a single "point"
  4148.  ------------------------------------------------------------------------------}
  4149. procedure TSeries.GenerateColumnOutline(X1, Y1, X2, Y2: Integer);
  4150. begin
  4151.   TheOutline[0].x := X1;
  4152.   TheOutline[0].y := Y1;
  4153.   TheOutline[1].x := X2;
  4154.   TheOutline[1].y := Y2;
  4155. end;
  4156.  
  4157. {------------------------------------------------------------------------------
  4158.     Procedure: TSeries.GeneratePieOutline
  4159.   Description: calculates an Outline of the Series
  4160.        Author: Mat Ballard
  4161.  Date created: 02/26/2001
  4162. Date modified: 02/26/2001 by Mat Ballard
  4163.       Purpose: Screen display - highlighting a Series
  4164.  Known Issues: This records the position of the entire ellipse
  4165.  ------------------------------------------------------------------------------}
  4166. procedure TSeries.GeneratePieOutline(
  4167.   PieLeft,
  4168.   PieTop,
  4169.   PieWidth,
  4170.   PieHeight,
  4171.   TheNearestPoint: Integer);
  4172. var
  4173.   i: Integer;
  4174.   Radius: Single;
  4175.   Angle,
  4176.   AngleSum,
  4177.   TheSin, TheCos: Extended;
  4178.   Centre: TPoint;
  4179. begin
  4180.   TheOutline[0].x := PieLeft;
  4181.   TheOutline[0].y := PieTop;
  4182.   TheOutline[1].x := PieLeft + PieWidth;
  4183.   TheOutline[1].y := PieTop + PieHeight;
  4184.  
  4185.   Centre.x := PieLeft + PieWidth div 2;
  4186.   Centre.y := PieTop + PieHeight div 2;
  4187.   Radius := PieWidth / 2.0;
  4188.  
  4189.   TheOutline[2].x := Centre.x;
  4190.   TheOutline[2].y := PieTop;
  4191.   AngleSum := 0;
  4192. {work our way around the circle:}
  4193.   for i := 0 to TheNearestPoint do
  4194.   begin
  4195.     TheOutline[3].x := TheOutline[2].x;
  4196.     TheOutline[3].y := TheOutline[2].y;
  4197. {angles are in radians, of course:}
  4198.     Angle := TWO_PI * FYData^[i] / YSum;
  4199.     AngleSum := AngleSum + Angle;
  4200.     SinCos(AngleSum, TheSin, TheCos);
  4201.     TheOutline[2].x := Centre.x + Round(Radius * TheSin);
  4202.     TheOutline[2].y := Centre.y - Round(Radius * TheCos);
  4203.     {ACanvas.Pie(
  4204.       PieLeft + FDeltaX, PieTop + FDeltaY,
  4205.       PieRight + FDeltaX, PieBottom + FDeltaY,
  4206.       TheOutline[2].x + FDeltaX, TheOutline[2].y + FDeltaY,
  4207.       TheOutline[3].x + FDeltaX, TheOutline[3].y + FDeltaY);}
  4208.   end;
  4209. end;
  4210.  
  4211. {------------------------------------------------------------------------------
  4212.     Procedure: TSeries.GenerateXYOutline
  4213.   Description: calculates an Outline of the Series
  4214.        Author: Mat Ballard
  4215.  Date created: 04/25/2000
  4216. Date modified: 04/25/2000 by Mat Ballard
  4217.       Purpose: Screen display - highlighting a Series
  4218.  Known Issues:
  4219.  ------------------------------------------------------------------------------}
  4220. procedure TSeries.GenerateXYOutline;
  4221. var
  4222.   i: Integer;
  4223.   StepSize: Integer;
  4224. begin
  4225.   if (FNoPts > OUTLINE_DENSITY) then
  4226.   begin
  4227. {initialize:}
  4228.     NoOutlinePts := OUTLINE_DENSITY+1; { = 21}
  4229.     StepSize := FNoPts div OUTLINE_DENSITY;
  4230. {loop over data points:}
  4231.     for i := 0 to NoOutlinePts-2 do    {0..19}
  4232.     begin
  4233.       TheOutline[i].x := FXAxis.FofX(FXData^[i*StepSize]);
  4234.       TheOutline[i].y := FYAxis.FofY(FYData^[i*StepSize]);
  4235.     end;
  4236. {do the end point:}
  4237.     TheOutline[OUTLINE_DENSITY].x := FXAxis.FofX(FXData^[FNoPts-1]);
  4238.     TheOutline[OUTLINE_DENSITY].y := FYAxis.FofY(FYData^[FNoPts-1]);
  4239.   end
  4240.   else
  4241.   begin
  4242. {not many points:}
  4243.     NoOutlinePts := FNoPts;
  4244.     for i := 0 to NoOutlinePts-1 do
  4245.     begin
  4246.       TheOutline[i].x := FXAxis.FofX(FXData^[i]);
  4247.       TheOutline[i].y := FYAxis.FofY(FYData^[i]);
  4248.     end;
  4249.   end;
  4250. end;
  4251.  
  4252. {------------------------------------------------------------------------------
  4253.     Procedure: TSeries.Outline
  4254.   Description: draws an Outline of the Series
  4255.        Author: Mat Ballard
  4256.  Date created: 04/25/2000
  4257. Date modified: 04/25/2000 by Mat Ballard
  4258.       Purpose: Screen display - highlighting a Series
  4259.  Known Issues:
  4260.  ------------------------------------------------------------------------------}
  4261. procedure TSeries.Outline(
  4262.   ACanvas: TCanvas;
  4263.   ThePlotType: TPlotType;
  4264.   TheOutlineWidth: Integer);
  4265. var
  4266.   i: Integer;
  4267.   {pOutlinePt: pPoint;}
  4268. begin
  4269.   ACanvas.Pen.Color := clLime;
  4270.   ACanvas.Pen.Width := TheOutlineWidth;
  4271.   ACanvas.Pen.Mode := pmNotXOR;
  4272.   {ACanvas.Pen.Style := psDash;}
  4273.  
  4274.   case ThePlotType of
  4275.     ptXY, ptError, ptMultiple, ptBubble:
  4276.       begin
  4277.         if (NoOutlinePts = 0) then exit;
  4278.  
  4279.         ACanvas.MoveTo(TheOutline[0].x + FDeltaX, TheOutline[0].y + FDeltaY);
  4280.         for i := 0 to NoOutlinePts-1 do
  4281.         begin
  4282.           ACanvas.LineTo(TheOutline[i].x + FDeltaX, TheOutline[i].y + FDeltaY);
  4283.         end;
  4284.       end;
  4285.     ptColumn, ptStack, ptNormStack:
  4286.       ACanvas.Rectangle(
  4287.         TheOutline[0].x,
  4288.         TheOutline[0].y,
  4289.         TheOutline[1].x,
  4290.         TheOutline[1].y);
  4291.     ptPie:
  4292.       begin
  4293.         ACanvas.Ellipse(
  4294.           TheOutline[0].x + FDeltaX,
  4295.           TheOutline[0].y + FDeltaY,
  4296.           TheOutline[1].x + FDeltaX,
  4297.           TheOutline[1].y + FDeltaY);
  4298.         ACanvas.Pen.Width := TheOutlineWidth div 2;  
  4299.         ACanvas.Pie(
  4300.           TheOutline[0].x + FDeltaX, TheOutline[0].y + FDeltaY,
  4301.           TheOutline[1].x + FDeltaX, TheOutline[1].y + FDeltaY,
  4302.           TheOutline[2].x + FDeltaX, TheOutline[2].y + FDeltaY,
  4303.           TheOutline[3].x + FDeltaX, TheOutline[3].y + FDeltaY);
  4304.       end;
  4305.   end;
  4306. end;
  4307.  
  4308. {------------------------------------------------------------------------------
  4309.     Procedure: TSeries.MoveBy
  4310.   Description: does what it says
  4311.        Author: Mat Ballard
  4312.  Date created: 04/25/2000
  4313. Date modified: 04/25/2000 by Mat Ballard
  4314.       Purpose: moves the clicked object Outline by (DX, DY) from its current point.
  4315.  Known Issues:
  4316.  ------------------------------------------------------------------------------}
  4317. procedure TSeries.MoveBy(ACanvas: TCanvas; ThePlotType: TPlotType; DX, DY, TheOutlineWidth: Integer);
  4318. begin
  4319. {erase the old Outline:}
  4320.   Outline(ACanvas, ThePlotType, TheOutlineWidth);
  4321. {save the new displacements:}
  4322.   Inc(FDeltaX, DX);
  4323.   Inc(FDeltaY, DY);
  4324.  
  4325. {create the new Outline:}
  4326.   Outline(ACanvas, ThePlotType, TheOutlineWidth);
  4327. end;
  4328.  
  4329. {------------------------------------------------------------------------------
  4330.     Procedure: TSeries.MoveTo
  4331.   Description: does what it says
  4332.        Author: Mat Ballard
  4333.  Date created: 04/25/2000
  4334. Date modified: 04/25/2000 by Mat Ballard
  4335.       Purpose: moves the clicked object Outline TO (X, Y).
  4336.  Known Issues:
  4337.  ------------------------------------------------------------------------------}
  4338. procedure TSeries.MoveTo(
  4339.   ACanvas: TCanvas;
  4340.   ThePlotType: TPlotType;
  4341.   TheOutlineWidth,
  4342.   X, Y: Integer);                  {by how much}
  4343. begin
  4344. {erase the old Outline:}
  4345.   Outline(ACanvas, ThePlotType, TheOutlineWidth);
  4346.  
  4347. {save the new displacements:}
  4348.   FDeltaX := X - FXAxis.FofX(FXData^[0]);
  4349.   FDeltaY := Y - FYAxis.FofY(FYData^[0]);
  4350.  
  4351. {create the new Outline:}
  4352.   Outline(ACanvas, ThePlotType, TheOutlineWidth);
  4353. end;
  4354.  
  4355. {------------------------------------------------------------------------------
  4356.     Procedure: TSeries.LineBestFit
  4357.   Description: Does what it says
  4358.        Author: Mat Ballard
  4359.  Date created: 04/25/2000
  4360. Date modified: 04/25/2000 by Mat Ballard
  4361.       Purpose: calculates the line of best fit from Start to Finish
  4362.  Known Issues: 
  4363.  ------------------------------------------------------------------------------}
  4364. procedure TSeries.LineBestFit(TheLeft, TheRight: Single;
  4365.   var NoLSPts: Integer;
  4366.   var SumX, SumY, SumXsq, SumXY, SumYsq: Double;
  4367.   var Slope, Intercept, Rsq: Single);
  4368. var
  4369.   i: Integer;
  4370.   Start, Finish: Integer;
  4371.   LnX, LnY: Double;
  4372. begin
  4373. {Determine the starting and ending points:}
  4374.   Start := GetNearestPointToX(TheLeft);
  4375.   Finish := GetNearestPointToX(TheRight);
  4376.  
  4377.   if ((not FXAxis.LogScale) and (not FYAxis.LogScale)) then
  4378.   begin
  4379. {normal linear fit:}
  4380.     for i := Start to Finish do
  4381.     begin
  4382.       Inc(NoLSPts);
  4383.       SumX := SumX + FXData^[i];
  4384.       SumY := SumY + FYData^[i];
  4385.       SumXsq := SumXsq + Sqr(FXData^[i]);
  4386.       SumXY := SumXY + FXData^[i] * FYData^[i];
  4387.       SumYsq := SumYsq + Sqr(FYData^[i]);
  4388.     end;
  4389.   end
  4390.   else if ((FXAxis.LogScale) and (not FYAxis.LogScale)) then
  4391.   begin
  4392. {logarithmic X Axis:}
  4393.     for i := Start to Finish do
  4394.     begin
  4395.       Inc(NoLSPts);
  4396.       LnX := Ln(FXData^[i]);
  4397.       SumX := SumX + LnX;
  4398.       SumY := SumY + FYData^[i];
  4399.       SumXsq := SumXsq + Sqr(LnX);
  4400.       SumXY := SumXY + LnX * FYData^[i];
  4401.       SumYsq := SumYsq + Sqr(FYData^[i]);
  4402.     end;
  4403.   end
  4404.   else if ((not FXAxis.LogScale) and (FYAxis.LogScale)) then
  4405.   begin
  4406. {logarithmic Y Axis:}
  4407.     for i := Start to Finish do
  4408.     begin
  4409.       Inc(NoLSPts);
  4410.       LnY := Ln(FYData^[i]);
  4411.       SumX := SumX + FXData^[i];
  4412.       SumY := SumY + LnY;
  4413.       SumXsq := SumXsq + Sqr(FXData^[i]);
  4414.       SumXY := SumXY + FXData^[i] * LnY;
  4415.       SumYsq := SumYsq + Sqr(LnY);
  4416.     end;
  4417.   end
  4418.   else if ((FXAxis.LogScale) and (FYAxis.LogScale)) then
  4419.   begin
  4420. {double logarithmic fit:}
  4421.     for i := Start to Finish do
  4422.     begin
  4423.       Inc(NoLSPts);
  4424.       LnX := Ln(FXData^[i]);
  4425.       LnY := Ln(FYData^[i]);
  4426.       SumX := SumX + LnX;
  4427.       SumY := SumY + LnY;
  4428.       SumXsq := SumXsq + Sqr(LnX);
  4429.       SumXY := SumXY + LnX * LnY;
  4430.       SumYsq := SumYsq + Sqr(LnY);
  4431.     end;
  4432.   end;
  4433.  
  4434. {so the slope and intercept are:}
  4435.   try
  4436.     Slope := (NoLSPts * SumXY - SumX * SumY) /
  4437.              (NoLSPts * SumXsq - Sqr(SumX));
  4438.     Intercept := (SumY / NoLSPts) - Slope * (SumX / NoLSPts);
  4439.     RSQ := Sqr(NoLSPts * SumXY - SumX * SumY) /
  4440.            ((NoLSPts * SumXsq - Sqr(SumX)) * (NoLSPts * SumYsq - Sqr(SumY)));
  4441.   except
  4442.     EMathError.CreateFmt('NoLSPts = %d' + CRLF +
  4443.       'SumX = %g' + CRLF +
  4444.       'SumY = %g' + CRLF +
  4445.       'SumXsq = %g' + CRLF +
  4446.       'SumXY = %g' + CRLF +
  4447.       'SumYsq = %g.',
  4448.       [NoLSPts, SumX, SumY, SumXsq, SumXY, SumYsq]);
  4449.   end;
  4450. end;
  4451.  
  4452. {Sub BestFit (iStart%, iFinish%, X_Data() As Single, Y_Data() As Single, Clear_Regs%, Slope!, Intercept!, RSQ!)
  4453. Dim i%
  4454. Dim Msg$
  4455. Static SumX!
  4456. Static SumY!
  4457. Static SumXsq!
  4458. Static SumXY!
  4459. Static SumYsq!
  4460. Static No_Pts%
  4461.  
  4462.     On Error GoTo BestFit_ErrorHandler
  4463.  
  4464. '   we initialise the sums for a least-squares fit:
  4465.     If (Clear_Regs% = True) Then
  4466.         No_Pts% = 0
  4467.         SumX! = 0
  4468.         SumY! = 0
  4469.         SumXsq! = 0
  4470.         SumXY! = 0
  4471.         SumYsq! = 0
  4472.     End If
  4473.  
  4474.     Select Case LogCase()
  4475.     Case 0      'neither axis is logged:
  4476. '   Do the summation:
  4477.         For i% = iStart% To iFinish%
  4478.             No_Pts% = No_Pts% + 1
  4479.             SumX! = SumX! + X_Data(i%)
  4480.             SumY! = SumY! + Y_Data(i%)
  4481.             SumXsq! = SumXsq! + X_Data(i%) ^ 2
  4482.             SumXY! = SumXY! + X_Data(i%) * Y_Data(i%)
  4483.             SumYsq! = SumYsq! + Y_Data(i%) ^ 2
  4484.         Next i%
  4485.     Case 1      'only the X-axis is logged:
  4486.         For i% = iStart% To iFinish%
  4487.             No_Pts% = No_Pts% + 1
  4488.             SumX! = SumX! + Log(X_Data(i%))
  4489.             SumY! = SumY! + Y_Data(i%)
  4490.             SumXsq! = SumXsq! + Log(X_Data(i%)) ^ 2
  4491.             SumXY! = SumXY! + Log(X_Data(i%)) * Y_Data(i%)
  4492.             SumYsq! = SumYsq! + Y_Data(i%) ^ 2
  4493.         Next i%
  4494.     Case 2      'only the Y-axis is logged:
  4495.         For i% = iStart% To iFinish%
  4496.             No_Pts% = No_Pts% + 1
  4497.             SumX! = SumX! + X_Data(i%)
  4498.             SumY! = SumY! + Log(Y_Data(i%))
  4499.             SumXsq! = SumXsq! + X_Data(i%) ^ 2
  4500.             SumXY! = SumXY! + X_Data(i%) * Log(Y_Data(i%))
  4501.             SumYsq! = SumYsq! + Log(Y_Data(i%)) ^ 2
  4502.         Next i%
  4503.     Case 3      'both axes are logged:
  4504.         For i% = iStart% To iFinish%
  4505.             No_Pts% = No_Pts% + 1
  4506.             SumX! = SumX! + Log(X_Data(i%))
  4507.             SumY! = SumY! + Log(Y_Data(i%))
  4508.             SumXsq! = SumXsq! + Log(X_Data(i%)) ^ 2
  4509.             SumXY! = SumXY! + Log(X_Data(i%)) * Log(Y_Data(i%))
  4510.             SumYsq! = SumYsq! + Log(Y_Data(i%)) ^ 2
  4511.         Next i%
  4512.     Case 4      'X axis is Log10'ed
  4513.         For i% = iStart% To iFinish%
  4514.             No_Pts% = No_Pts% + 1
  4515.             SumX! = SumX! + LOG10_E * Log(X_Data(i%))
  4516.             SumY! = SumY! + Y_Data(i%)
  4517.             SumXsq! = SumXsq! + (LOG10_E * Log(X_Data(i%))) ^ 2
  4518.             SumXY! = SumXY! + LOG10_E * Log(X_Data(i%)) * Y_Data(i%)
  4519.             SumYsq! = SumYsq! + Y_Data(i%) ^ 2
  4520.         Next i%
  4521.     Case 6      'X axis is Log10'ed, Y axis is ln'ed:
  4522.         For i% = iStart% To iFinish%
  4523.             No_Pts% = No_Pts% + 1
  4524.             SumX! = SumX! + LOG10_E * Log(X_Data(i%))
  4525.             SumY! = SumY! + Log(Y_Data(i%))
  4526.             SumXsq! = SumXsq! + (LOG10_E * Log(X_Data(i%))) ^ 2
  4527.             SumXY! = SumXY! + LOG10_E * Log(X_Data(i%)) * Log(Y_Data(i%))
  4528.             SumYsq! = SumYsq! + Log(Y_Data(i%)) ^ 2
  4529.         Next i%
  4530.     Case 8      'Y axis is Log10'ed:
  4531.         For i% = iStart% To iFinish%
  4532.             No_Pts% = No_Pts% + 1
  4533.             SumX! = SumX! + X_Data(i%)
  4534.             SumY! = SumY! + LOG10_E * Log(Y_Data(i%))
  4535.             SumXsq! = SumXsq! + X_Data(i%) ^ 2
  4536.             SumXY! = SumXY! + X_Data(i%) * LOG10_E * Log(Y_Data(i%))
  4537.             SumYsq! = SumYsq! + (LOG10_E * Log(Y_Data(i%))) ^ 2
  4538.         Next i%
  4539.     Case 9      'X axis is ln'ed, Y axis is Log10'ed:
  4540.         For i% = iStart% To iFinish%
  4541.             No_Pts% = No_Pts% + 1
  4542.             SumX! = SumX! + Log(X_Data(i%))
  4543.             SumY! = SumY! + LOG10_E * Log(Y_Data(i%))
  4544.             SumXsq! = SumXsq! + Log(X_Data(i%)) ^ 2
  4545.             SumXY! = SumXY! + Log(X_Data(i%)) * LOG10_E * Log(Y_Data(i%))
  4546.             SumYsq! = SumYsq! + (LOG10_E * Log(Y_Data(i%))) ^ 2
  4547.         Next i%
  4548.     Case 12      'both axes are Log10'ed:
  4549.         For i% = iStart% To iFinish%
  4550.             No_Pts% = No_Pts% + 1
  4551.             SumX! = SumX! + LOG10_E * Log(X_Data(i%))
  4552.             SumY! = SumY! + LOG10_E * Log(Y_Data(i%))
  4553.             SumXsq! = SumXsq! + (LOG10_E * Log(X_Data(i%))) ^ 2
  4554.             SumXY! = SumXY! + LOG10_E * Log(X_Data(i%)) * LOG10_E * Log(Y_Data(i%))
  4555.             SumYsq! = SumYsq! + (LOG10_E * Log(Y_Data(i%))) ^ 2
  4556.         Next i%
  4557.     End Select
  4558.  
  4559. '   so the slope and intercept are:
  4560.     Slope! = (No_Pts% * SumXY! - SumX! * SumY!) / (No_Pts% * SumXsq! - (SumX! ^ 2))
  4561.     Intercept! = (SumY! / No_Pts%) - Slope! * (SumX! / No_Pts%)
  4562.     RSQ! = (No_Pts% * SumXY! - SumX! * SumY!) ^ 2 / ((No_Pts% * SumXsq! - (SumX! ^ 2)) * (No_Pts% * SumYsq! - (SumY! ^ 2)))
  4563.  
  4564. BestFit_FINISHED:
  4565.  
  4566.  
  4567. Exit Sub
  4568.  
  4569. BestFit_ErrorHandler:   ' Error handler line label.
  4570.  
  4571.     Select Case Err
  4572.     Case 5
  4573.         Resume Next
  4574.     Case Else
  4575.         Msg$ = "Panic in " & "BestFit_ErrorHandler !"
  4576.         Msg$ = Msg$ & LF & LF & "Error No. " & Str$(Err) & ": " & Error$
  4577.         Response% = Message(Msg$, MB_OK + MB_ICONEXCLAMATION, "Error !", NO, H_PANIC)
  4578.     End Select
  4579.  
  4580.     Resume BestFit_FINISHED
  4581.  
  4582.  
  4583. End Sub
  4584. }
  4585.  
  4586. {------------------------------------------------------------------------------
  4587.      Function: TSeries.FindHighsLows
  4588.   Description: This function finds all the Highs (and troughs) in a region
  4589.        Author: Mat Ballard
  4590.  Date created: 04/25/2000
  4591. Date modified: 04/25/2000 by Mat Ballard
  4592.       Purpose: gets the value of the ??? Property
  4593.  Return Value: the number of Highs
  4594.  Known Issues:
  4595.  ------------------------------------------------------------------------------}
  4596. function TSeries.FindHighsLows(Start, Finish, HeightSensitivity: Integer): Integer;
  4597. var
  4598.   i,
  4599.   LastHigh,
  4600.   LastLow: Integer;
  4601.   Highseek: Boolean;
  4602.   Delta: Single;
  4603. begin
  4604. {this routine finds all the major Highs in a region;
  4605.  See "FindAHigh" for a single High finding routine.}
  4606.  
  4607. {set the sensitivity:}
  4608.   Delta := (HeightSensitivity / 100.0) * (FYMax - FYMin);
  4609.   ClearHighsLows;
  4610.  
  4611. {initialise variables:}
  4612.   LastHigh := Start;
  4613.   LastLow := Start;
  4614.   Highseek := TRUE;
  4615.  
  4616. {allocate memory for results:}
  4617.   GetMem(FHighs, FHighCapacity * SizeOf(Integer));
  4618.   GetMem(FLows, FHighCapacity * SizeOf(Integer));
  4619. {we set the first point to a low}
  4620.   Lows^[FLowCount] := LastLow;
  4621.   Inc(FLowCount);
  4622.  
  4623.   for i := Start to Finish do
  4624.   begin
  4625.     if (Highseek = TRUE) then
  4626.     begin
  4627.       if (FYData^[i] > FYData^[LastHigh]) then
  4628.         LastHigh := i;
  4629.       if (FYData^[i] < (FYData^[LastHigh] - Delta)) then
  4630.       begin
  4631. {The Last High was a real maximum:}
  4632.         Highs^[FHighCount] := LastHigh;
  4633.         Inc(FHighCount);
  4634.         if (FHighCount >= FHighCapacity-2) then
  4635.         begin
  4636. {add 10 more points:}
  4637. {$IFDEF DELPHI1}
  4638.           ReAllocMem(FHighs, FHighCapacity * SizeOf(Integer),
  4639.             (FHighCapacity+10) * SizeOf(Integer));
  4640.           ReAllocMem(FLows, FHighCapacity * SizeOf(Integer),
  4641.             (FHighCapacity+10) * SizeOf(Integer));
  4642.           Inc(FHighCapacity, 10);
  4643. {$ELSE}
  4644.           Inc(FHighCapacity, 10);
  4645.           ReAllocMem(FHighs, FHighCapacity * SizeOf(Integer));
  4646.           ReAllocMem(FLows, FHighCapacity * SizeOf(Integer));
  4647. {$ENDIF}
  4648.         end;
  4649.         Highseek := FALSE;
  4650.         LastLow := i;
  4651.       end;
  4652.     end
  4653.     else
  4654.     begin
  4655.       if (FYData^[i] < FYData^[LastLow]) then
  4656.         LastLow := i;
  4657.       if (FYData^[i] > (FYData^[LastLow] + Delta)) then
  4658.       begin
  4659. {The Last Low was a real minimum:}
  4660.         Lows^[FLowCount] := LastLow;
  4661.         Inc(FLowCount);
  4662.         Highseek := TRUE;
  4663.         LastHigh := i;
  4664.       end; {comparison}
  4665.     end; {seeking high or low}
  4666.   end; {for}
  4667.   Lows^[FLowCount] := LastLow;
  4668.  
  4669.   FindHighsLows := FHighCount;
  4670. end;
  4671.  
  4672.  
  4673. end.
  4674.