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