home *** CD-ROM | disk | FTP | other *** search
/ Chip 2001 December / Chip_2001-12_cd1.bin / zkuste / delphi / kolekce / d56 / DM2KVCL.ZIP / PLOT.PAS < prev    next >
Pascal/Delphi Source File  |  2001-03-05  |  72KB  |  1,658 lines

  1. {****************************************************************************}
  2. {                            Data Master 2000                                }
  3. {****************************************************************************}
  4. unit Plot;
  5. {$B-,X+}
  6. interface
  7.  
  8. uses
  9.   Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  10.   ExtCtrls, ClipBrd, Printers, Common, Data, Parser;
  11.  
  12. type
  13.   TPointType=(ptSquare, ptCircle, ptCross, ptXCross, ptAsterisk); {shape type}
  14.  
  15. const   {there's many series in the plot! so dfm size will be greatly reduced}
  16.   DefLineVisible=true; DefShowBestFit=false; DefXColumn=0; DefYColumn=0;
  17.   DefFirstLine=0; DefLastLine=-1; DefPointSize=5; DefPointVisible=true;
  18.   DefInterleave=1; DefPointType=ptSquare; DefIsFunction=false;
  19.  
  20. type
  21.   TPlot = class;
  22.   {note that forward and define type declarations MUST be in
  23.   the same <type> section! Else D5 produce weird error messages...}
  24.  
  25.   TAxis=class(TPersistent)                                   {axis attributes}
  26.   private
  27.     FPlot: TPlot;
  28.     FWidth, FDecimals: integer;
  29.     FFType: TFloatFormat;
  30.     FMin, FMax, FMargins: extended;
  31.     FPen: TPen;
  32.     FFont: TFont;
  33.     FMajorTicks,FMinorTicks: integer;
  34.     FAutoScale: boolean;
  35.     FShowGrid: boolean;
  36.     FTitle: string;
  37.     FExpression: string;
  38.     procedure OnChanged(Sender: TObject);  {called by font/brush when changed}
  39.     procedure SetMin(M: extended);
  40.     procedure SetMax(M: extended);
  41.     procedure SetPen(P: TPen);
  42.     procedure SetFont(F: TFont);
  43.     procedure SetMinorTicks(T: integer);
  44.     procedure SetMajorTicks(T: integer);
  45.     procedure SetWidth(W: integer);
  46.     procedure SetDecimals(D: integer);
  47.     procedure SetFType(T: TFloatFormat);
  48.     procedure SetFormat(F: TFormat);
  49.     function  GetFormat: TFormat;
  50.     procedure SetAutoScale(A: boolean);
  51.     procedure SetShowGrid(G: boolean);
  52.     procedure SetMargins(M: extended);
  53.     procedure SetTitle(T: string);
  54.     procedure SetExpression(const Value: string);
  55.     procedure Update;        {called by Changed() and when properties changed}
  56.   public
  57.     property Format: TFormat read GetFormat write SetFormat;
  58.     procedure Assign(A: TPersistent); override;
  59.     constructor Create(APlot: TPlot);
  60.     destructor Destroy; override;
  61.   published
  62.     property Min: extended read FMin write SetMin;
  63.     property Max: extended read FMax write SetMax;
  64.     property Pen: TPen read FPen write SetPen;
  65.     property Font: TFont read FFont write SetFont;
  66.     property MinorTicks: integer read FMinorTicks write SetMinorTicks;
  67.     property MajorTicks: integer read FMajorTicks write SetMajorTicks;
  68.     property LabelWidth: integer read FWidth write SetWidth;
  69.     property LabelDecimals: integer read FDecimals write SetDecimals;
  70.     property LabelType: TFloatFormat read FFType write SetFType;
  71.     property AutoScale: boolean read FAutoScale write SetAutoScale;
  72.     property ShowGrid: boolean read FShowGrid write SetShowGrid;
  73.     property Margins: extended read FMargins write SetMargins;
  74.     property Title: string read FTitle write SetTitle;
  75.     property Expression: string read FExpression write SetExpression;
  76.   end;
  77.  
  78.   TSerie=class(TCollectionItem)                             {serie attributes}
  79.   private
  80.     FText: string;
  81.     FPointType: TPointType;
  82.     FPointVisible,FLineVisible,FShowBestFit,FIsFunction: boolean;
  83.     FXColumn,FYColumn,FFirstLine,FLastLine,FInterleave: integer;
  84.     FPointSize: integer;
  85.     FPen: TPen;
  86.     FBrush: TBrush;
  87.     FXExpression, FYExpression: string;
  88.     FContainer: TContainer;
  89.     procedure OnChanged(Sender: TObject);  {called by font/brush when changed}
  90.     procedure SetPointType(T: TPointType);            {methods for properties}
  91.     procedure SetPointVisible(B: boolean);
  92.     procedure SetLineVisible(B: boolean);
  93.     procedure SetIsFunction(B: boolean);
  94.     procedure SetXColumn(X: integer);
  95.     procedure SetYColumn(Y: integer);
  96.     procedure SetFirstLine(F: integer);
  97.     procedure SetLastLine(L: integer);
  98.     procedure SetPointSize(S: integer);
  99.     procedure SetInterleave(I: integer);
  100.     procedure SetShowBestFit(B: boolean);
  101.     procedure SetPen(P: TPen);
  102.     procedure SetBrush(B: TBrush);
  103.     procedure SetXExpression(E: string);
  104.     procedure SetYExpression(E: string);
  105.     procedure SetContainer(C: TContainer);
  106.     procedure SetText(Value: string);
  107.   protected
  108.     function GetDisplayName: string; override;
  109.   public
  110.     X1,X2,Y1,Y2,bfA,bfB: extended;             {need for BestFit & scale info}
  111.     Scaled: boolean;      {set by Plot.Paint() if serie scale info ^ is valid}
  112.     Locked: boolean;        {used to add points without repainting whole plot}
  113.     constructor Create(Collection: TCollection); override;
  114.     destructor Destroy; override;
  115.     procedure Assign(Source: TPersistent); override;
  116.     function Empty: boolean;                         {true, if block is empty}
  117.     procedure ClearBlock;                              {set block coords to 0}
  118.   published
  119.     property PointType: TPointType read FPointType write SetPointType
  120.              default DefPointType;
  121.     property PointVisible: boolean read FPointVisible write SetPointVisible
  122.              default DefPointVisible;
  123.     property PointSize: integer read FPointSize write SetPointSize
  124.              default DefPointSize;
  125.     property LineVisible: boolean read FLineVisible write SetLineVisible
  126.              default DefLineVisible;
  127.     property FirstLine: integer read FFirstLine write SetFirstLine
  128.              default DefFirstLine;
  129.     property LastLine: integer read FLastLine write SetLastLine
  130.              default DefLastLine;
  131.     property Interleave: integer read FInterleave write SetInterleave
  132.              default DefInterleave;
  133.     property ShowBestFit: boolean read FShowBestFit write SetShowBestFit
  134.              default DefShowBestFit;
  135.     property IsFunction: boolean read FIsFunction write SetIsFunction
  136.              default DefIsFunction;
  137.     property XColumn: integer read FXColumn write SetXColumn
  138.              default DefXColumn;
  139.     property YColumn: integer read FYColumn write SetYColumn
  140.              default DefYColumn;
  141.     property Pen: TPen read FPen write SetPen;
  142.     property Brush: TBrush read FBrush write SetBrush;
  143.     property Text: string read FText write SetText;
  144.     property XExpression: string read FXExpression write SetXExpression;
  145.     property YExpression: string read FYExpression write SetYExpression;
  146.     property Container: TContainer read FContainer write SetContainer;
  147.   end;
  148.  
  149.   TSeries=class(TCollection)                            {array of plot series}
  150.   private
  151.     FPlot: TPlot;
  152.     function GetItem(Index: Integer): TSerie;
  153.     procedure SetItem(Index: Integer; Value: TSerie);
  154.   protected
  155.     function GetOwner: TPersistent; override;
  156.     procedure Update(Item: TCollectionItem); override;
  157.   public
  158.     constructor Create(APlot: TPlot);
  159.     function Add: TSerie;
  160.     property Items[Index: Integer]:TSerie read GetItem write SetItem; default;
  161.     property Plot: TPlot read FPlot;
  162.   end;
  163.  
  164.   TPlotHintEvent=procedure(Sender: TObject; H: string) of object;
  165.   TGetPointEvent=function(D:TData; CX,CY:integer; const XExpr,YExpr: string;
  166.                           var X,Y:extended): boolean of object;
  167.   TClickedAt=(claPlot, claXAxis, claYAxis);
  168.   TPlotMouseMode=(pmNone, pmAutoZoom, pmZoom, pmRuler, pmUnZoom,
  169.     pmSelect, pmPointClick, pmPointEdit, pmPointDelete, pmTranslate);
  170.   TPlotCopyMode=(pcmPage, pcmPoints, pcmItems);
  171.   TTranslateMode=(ptmNo,ptmL,ptmR,ptmB,ptmT,ptmTL,ptmTR,ptmBL,ptmBR,ptmMove);
  172.   TPointClickEvent=procedure(Sender:TObject; Point,Serie:integer) of object;
  173.  
  174.   TPlot = class(TPaintBox)
  175.   private
  176.     { Private declarations }
  177.     FPen: TPen;
  178.     FBrush: TBrush;
  179.     FSerieIndex: integer;
  180.     FXAxis, FYAxis: TAxis;
  181.     FSeries: TSeries;
  182.     FBorderStyle: TBorderStyle;
  183.     FClickedAt: TClickedAt;
  184.     FTransparent: boolean;
  185.     FOnHint,FOnError: TPlotHintEvent;
  186.     FGetPoint: TGetPointEvent;
  187.     FOnSelectionChange: TNotifyEvent;
  188.     FOnPointClick: TPointClickEvent;
  189.     FParser: TMathParser;    {these 2 need for parsing serie's X,YExpressions}
  190.     FParserParams: TRealArray;
  191.     FMouseMode: TPlotMouseMode;                  {determine how mouse is used}
  192.     {next integer variables used together by Paint() and RealToIntCoords()!!!}
  193.     XAxisGap, YAxisGap, XLabelW, XTickLen, YTickLen,
  194.     XLabelH, YLabelW, YLabelH, XAxisLen, YAxisLen: integer;
  195.     Zooming: boolean;                            {next 5 fields used for zoom}
  196.     ZoomX,ZoomY,ZoomXo,ZoomYo: integer;
  197.     Ruling: boolean;                                     {flag of using ruler}
  198.     RulerX,RulerY: integer;                              {ruler center coords}
  199.     RulerFi: extended;                                           {ruler angle}
  200.     FCanvas: TCanvas;                                 {buffer used from Paint}
  201.     FWidth, FHeight: integer;                     {used for printing in Paint}
  202.     Printing: boolean;                                                   {-#-}
  203.     FCanUnZoom: boolean;                     {these vars needed for Undo Zoom}
  204.     OldX1,OldX2,OldY1,OldY2,           {Undo buffers for previous coordinates}
  205.     FX,FY,FN,FNA: TReal;    {buffers for X,Y,Num,AbsNum expression parameters}
  206.     Editing: boolean;                        {these nine used by point editor}
  207.     EditX, EditY, EditX1, EditY1, EditX2, EditY2, EditSer, EditPnt: integer;
  208.     FSelectionVisible: boolean;            {these 5 keep selection properties}
  209.     FSelectionTop,FSelectionBottom,FSelectionLeft,FSelectionRight: extended;
  210.     Translating: TTranslateMode;             {keep selection translation mode}
  211.     TransX1,TransX2,TransY1,TransY2: extended; {keep old selection coordinate}
  212.     TransBuf: array of TRealPoint;  {next 2 used to paint translation preview}
  213.     TransPointCount: integer;
  214.     function BelongMarker(rX,rY: extended; X,Y: integer): boolean;
  215.     procedure DrawSelection;
  216.     procedure SetXAxis(Value: TAxis);
  217.     procedure SetYAxis(Value: TAxis);
  218.     procedure SetSeries(const Value: TSeries);
  219.     procedure SetBorderStyle(B: TBorderStyle);
  220.     procedure SetTransparent(B: boolean);
  221.     procedure Changed(Sender: TObject); {called indirectly by canvas, axes,..}
  222.     procedure DrawRuler(X,Y: integer);                       {show/hide ruler}
  223.     procedure DrawEdit(X,Y: integer);                   {used by point editor}
  224.     procedure SetSerieIndex(const Value: integer);
  225.     function GetThisSerie: TSerie;
  226.     function GetSelection(const Index: Integer): extended;
  227.     procedure SetSelection(const Index: Integer; const Value: extended);
  228.     procedure SetSelectionVisible(const Value: boolean);
  229.     procedure SetPen(P: TPen);
  230.     procedure SetBrush(B: TBrush);
  231.     procedure SetMouseMode(M: TPlotMouseMode);
  232.   protected
  233.     { Protected declarations }
  234.     procedure Paint; override;                                   {paints plot}
  235.     procedure ShowPlotHint(H: string); virtual;
  236.     procedure ShowPlotError(H: string); virtual;
  237.     procedure MouseDown(Btn: TMouseButton;        {add zoom & other functions}
  238.               Shift: TShiftState; X,Y: Integer); override;
  239.     procedure MouseUp(Btn: TMouseButton;
  240.               Shift: TShiftState; X,Y: Integer); override;
  241.     procedure MouseMove(Shift: TShiftState; X,Y: Integer);  override;
  242.   public
  243.     { Public declarations }
  244.     constructor Create(AOwner: TComponent); override;
  245.     destructor Destroy; override;
  246.     function GetDataPoint(D: TData; CX,CY: integer; const XExpr,YExpr: string;
  247.              var X,Y: extended): boolean; virtual;
  248.     procedure SetDataPoint(D: TData; CX,CY: integer; X,Y: extended); virtual;
  249.     {these 2 methods may be overridden for custom datatype support!!!        }
  250.     procedure DrawLine(X1,Y1,X2,Y2: extended);               {drawing methods}
  251.     procedure DrawPoint(X,Y: extended; T: TPointType; S: integer);
  252.     procedure PaintPoint(X,Y: integer; T: TPointType; S: integer; C: TCanvas);
  253.     function RealToIntCoords(X, Y: extended; var iX,iY: integer): boolean;
  254.     function IntToRealCoords(X,Y:integer; var rX,rY: extended): boolean;
  255.     procedure CopyToClipboard(Mode: TPlotCopyMode; UseTabs: boolean);
  256.     procedure Delete;                        {delete items from selected area}
  257.     procedure Print(W,H: integer);                               {prints plot}
  258.     procedure UndoZoom;         {restore coordinates changed by built-in Zoom}
  259.     property ClickedAt: TClickedAt read FClickedAt;       {for use in OnClick}
  260.     property Parser: TMathParser read FParser;       {may add some parameters}
  261.     property CanUnZoom: boolean read FCanUnZoom;     {true when undo possible}
  262.     procedure SaveToFile(FileName: string);
  263.     procedure SaveToMetafile(WMF: TMetafile);
  264.     property SelectionVisible: boolean read FSelectionVisible
  265.              write SetSelectionVisible;           {if true, selection painted}
  266.     property SelectionTop: extended index 1 read GetSelection
  267.              write SetSelection;
  268.     property SelectionBottom: extended index 2 read GetSelection
  269.              write SetSelection;
  270.     property SelectionLeft: extended index 3 read GetSelection
  271.              write SetSelection;
  272.     property SelectionRight: extended index 4 read GetSelection
  273.              write SetSelection;
  274.   published
  275.     { Published declarations }
  276.     property Pen: TPen read FPen write SetPen;
  277.     property Brush: TBrush read FBrush write SetBrush;
  278.     property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle;
  279.     property MouseMode: TPlotMouseMode read FMouseMode write SetMouseMode
  280.              default pmNone;
  281.     property Transparent: boolean read FTransparent write SetTransparent
  282.              default true;
  283.     property XAxis: TAxis read FXAxis write SetXAxis;
  284.     property YAxis: TAxis read FYAxis write SetYAxis;
  285.     property Series: TSeries read FSeries write SetSeries;
  286.     property SerieIndex: integer read FSerieIndex write SetSerieIndex;
  287.     property ThisSerie: TSerie read GetThisSerie stored false;
  288.     property OnHint: TPlotHintEvent read FOnHint write FOnHint;
  289.     property OnError: TPlotHintEvent read FOnError write FOnError;
  290.     property OnGetPoint: TGetPointEvent read FGetPoint write FGetPoint;
  291.     property OnSelectionChange: TNotifyEvent read FOnSelectionChange
  292.              write FOnSelectionChange;
  293.     property OnPointClick: TPointClickEvent read FOnPointClick
  294.              write FOnPointClick;
  295.   end;
  296.  
  297. procedure Register;
  298.  
  299. resourcestring
  300.   errSerieBlock='Invalid data range in serie %d!';
  301.   errSerieExpr='Parsing error in serie %d: %s';
  302.   errSerieCols='Missed data in serie %d, line %d!';
  303.   msgScaling='Scaling...';
  304.   msgPlotting='Plotting...';
  305.   msgScanning='Scanning data...';
  306.   strMoving='Moving points...';
  307.   errEditPoint1='Unable to move point: serie %d has nonempty %s expression!';
  308.   errEditPoint2='Unable to move point: %s axis has nonempty expression!';
  309.   errTranslation='Unable to translate points: nonempty expressions!';
  310.  
  311. implementation
  312.  
  313. const PPI=85;            {points per inch; => [w,pel]=[pel/"]/[Pnt/"]*[w,pnt]}
  314.       MSZ=5;                                           {selection marker size}
  315.  
  316. { TAxis }
  317.  
  318. procedure TAxis.OnChanged(Sender: TObject);
  319. begin Update; end;
  320.  
  321. procedure TAxis.Update;
  322. begin if Assigned(FPlot) then FPlot.Changed(Self); end;
  323.  
  324. procedure TAxis.Assign(A: TPersistent);
  325. begin
  326.   if A is TAxis then
  327.   begin
  328.     LabelWidth:=(A as TAxis).LabelWidth;
  329.     LabelDecimals:=(A as TAxis).LabelDecimals;
  330.     LabelType:=(A as TAxis).LabelType;
  331.     Min:=(A as TAxis).Min;
  332.     Max:=(A as TAxis).Max;
  333.     Pen.Assign((A as TAxis).Pen);
  334.     Font.Assign((A as TAxis).Font);
  335.     MajorTicks:=(A as TAxis).MajorTicks;
  336.     MinorTicks:=(A as TAxis).MinorTicks;
  337.     AutoScale:=(A as TAxis).AutoScale;
  338.     ShowGrid:=(A as TAxis).ShowGrid;
  339.     Margins:=(A as TAxis).Margins;
  340.     Title:=(A as TAxis).Title;
  341.     Expression:=(A as TAxis).Expression;
  342.   end else inherited Assign(A);
  343. end;
  344.  
  345. procedure TAxis.SetMin(M: extended);
  346. begin if M<>FMin then begin FMin:=M; Update; end; end;
  347.  
  348. procedure TAxis.SetMax(M: extended);
  349. begin if M<>FMax then begin FMax:=M; Update; end; end;
  350.  
  351. procedure TAxis.SetPen(P: TPen);
  352. begin FPen.Assign(P); end;
  353.  
  354. procedure TAxis.SetFont(F: TFont);
  355. begin FFont.Assign(F); end;
  356.  
  357. procedure TAxis.SetMinorTicks(T: integer);
  358. begin
  359.   if (T>1) and (T<50) and (T<>FMinorTicks) then
  360.   begin FMinorTicks:=T; Update; end;
  361. end;
  362.  
  363. procedure TAxis.SetMajorTicks(T: integer);
  364. begin
  365.   if (T>1) and (T<50) and (T<>FMajorTicks) then
  366.   begin FMajorTicks:=T; Update; end;
  367. end;
  368.  
  369. procedure TAxis.SetWidth(W: integer);
  370. begin if FWidth<>W then begin FWidth:=W; Update; end; end;
  371.  
  372. procedure TAxis.SetDecimals(D: integer);
  373. begin if D<>FDecimals then begin FDecimals:=D; Update; end; end;
  374.  
  375. procedure TAxis.SetFType(T: TFloatFormat);
  376. begin if T<>FFType then begin FFType:=T; Update; end; end;
  377.  
  378. procedure TAxis.SetFormat(F: TFormat);
  379. begin LabelWidth:=F.Width; LabelDecimals:=F.Decimals; LabelType:=F.FType; end;
  380.  
  381. function TAxis.GetFormat;
  382. begin
  383.   with Result do begin Width:=FWidth; Decimals:=FDecimals; FType:=FFType; end;
  384. end;
  385.  
  386. procedure TAxis.SetAutoScale(A: boolean);
  387. begin if A<>FAutoScale then begin FAutoScale:=A; Update; end; end;
  388.  
  389. procedure TAxis.SetShowGrid(G: boolean);
  390. begin if G<>FShowGrid then begin FShowGrid:=G; Update; end; end;
  391.  
  392. procedure TAxis.SetMargins(M: extended);
  393. begin if M<>FMargins then begin FMargins:=M; Update; end; end;
  394.  
  395. procedure TAxis.SetTitle(T: string);
  396. begin if T<>FTitle then begin FTitle:=T; Update; end; end;
  397.  
  398. procedure TAxis.SetExpression(const Value: string);
  399. begin if FExpression<>Value then begin FExpression:=Value; Update; end; end;
  400.  
  401. constructor TAxis.Create(APlot: TPlot);
  402. begin
  403.   inherited Create; FPlot:=APlot;
  404.   FWidth:=5; FDecimals:=2; FFType:=ffGeneral;
  405.   FMin:=0; FMax:=10; FMajorTicks:=10; FMinorTicks:=5;
  406.   FPen:=TPen.Create; FPen.OnChange:=OnChanged;
  407.   FFont:=TFont.Create; FFont.OnChange:=OnChanged;
  408.   FMargins:=0; FAutoScale:=false; FShowGrid:=false;
  409. end;
  410.  
  411. destructor TAxis.Destroy;
  412. begin FPen.Free; FFont.Free; inherited Destroy; end;
  413.  
  414. { TSerie }
  415.  
  416. constructor TSerie.Create(Collection: TCollection);
  417. begin
  418.   inherited;
  419.   FPointType:=DefPointType; FPointVisible:=DefPointVisible;
  420.   FLineVisible:=DefLineVisible; FShowBestFit:=DefShowBestFit;
  421.   FXColumn:=DefXColumn; FYColumn:=DefYColumn;
  422.   FFirstLine:=DefFirstLine; FLastLine:=DefLastLine;
  423.   FPointSize:=DefPointSize; FInterleave:=DefInterleave;
  424.   FPen:=TPen.Create; FPen.OnChange:=OnChanged;
  425.   FBrush:=TBrush.Create; FBrush.OnChange:=OnChanged;
  426.   FXExpression:=''; FYExpression:=''; FContainer:=nil;
  427. end;
  428.  
  429. destructor TSerie.Destroy;
  430. begin FPen.Free; FBrush.Free; inherited Destroy; end;
  431.  
  432. procedure TSerie.Assign(Source: TPersistent);
  433. begin
  434.   if Source is TSerie then
  435.   begin                   {note! we modify properties => Change MAY be called}
  436.     Text:=TSerie(Source).Text;
  437.     PointType:=(Source as TSerie).PointType;
  438.     PointVisible:=(Source as TSerie).PointVisible;
  439.     LineVisible:=(Source as TSerie).LineVisible;
  440.     XColumn:=(Source as TSerie).XColumn;
  441.     YColumn:=(Source as TSerie).YColumn;
  442.     FirstLine:=(Source as TSerie).FirstLine;
  443.     LastLine:=(Source as TSerie).LastLine;
  444.     PointSize:=(Source as TSerie).PointSize;
  445.     Interleave:=(Source as TSerie).Interleave;
  446.     ShowBestFit:=(Source as TSerie).ShowBestFit;
  447.     Pen.Assign((Source as TSerie).Pen);
  448.     Brush.Assign((Source as TSerie).Brush);
  449.     Container:=(Source as TSerie).Container;
  450.     XExpression:=(Source as TSerie).XExpression;
  451.     YExpression:=(Source as TSerie).YExpression;
  452.   end else inherited Assign(Source);
  453. end;
  454.  
  455. procedure TSerie.OnChanged(Sender: TObject);
  456. begin Changed(false); end;
  457.  
  458. function TSerie.GetDisplayName: string;
  459. begin Result:=Text; if Result='' then Result:=inherited GetDisplayName; end;
  460.  
  461. procedure TSerie.SetShowBestFit(B: boolean);
  462. begin if B<>FShowBestFit then begin FShowBestFit:=B; Changed(false); end; end;
  463.  
  464. procedure TSerie.SetBrush(B: TBrush);
  465. begin FBrush.Assign(B); end;
  466.  
  467. procedure TSerie.SetPen(P: TPen);
  468. begin FPen.Assign(P); end;
  469.  
  470. procedure TSerie.SetContainer(C: TContainer);
  471. begin if FContainer<>C then begin FContainer:=C; Changed(false); end; end;
  472.  
  473. procedure TSerie.SetFirstLine(F: integer);
  474. begin if F<>FFirstLine then begin FFirstLine:=F; Changed(false); end; end;
  475.  
  476. procedure TSerie.SetLastLine(L: integer);
  477. begin if L<>FLastLine then begin FLastLine:=L; Changed(false); end; end;
  478.  
  479. procedure TSerie.SetInterleave(I: integer);
  480. begin if I<>FInterleave then begin FInterleave:=I; Changed(false); end; end;
  481.  
  482. procedure TSerie.SetLineVisible(B: boolean);
  483. begin if B<>FLineVisible then begin FLineVisible:=B; Changed(false); end; end;
  484.  
  485. procedure TSerie.SetPointSize(S: integer);
  486. begin if S<>FPointSize then begin FPointSize:=S; Changed(false); end; end;
  487.  
  488. procedure TSerie.SetPointType(T: TPointType);
  489. begin if T<>FPointType then begin FPointType:=T; Changed(false); end; end;
  490.  
  491. procedure TSerie.SetPointVisible(B: boolean);
  492. begin if B<>FPointVisible then begin FPointVisible:=B; Changed(false);end;end;
  493.  
  494. procedure TSerie.SetXColumn(X: integer);
  495. begin if X<>FXColumn then begin FXColumn:=X; Changed(false); end; end;
  496.  
  497. procedure TSerie.SetXExpression(E: string);
  498. begin if E<>FXExpression then begin FXExpression:=E; Changed(false); end; end;
  499.  
  500. procedure TSerie.SetYColumn(Y: integer);
  501. begin if Y<>FYColumn then begin FYColumn:=Y; Changed(false); end; end;
  502.  
  503. procedure TSerie.SetYExpression(E: string);
  504. begin if E<>FYExpression then begin FYExpression:=E; Changed(false); end; end;
  505.  
  506. procedure TSerie.SetText(Value: string);
  507. begin if FText<>Value then begin FText:=Value; Changed(False); end; end;
  508.  
  509. procedure TSerie.SetIsFunction(B: boolean);
  510. begin
  511.   if B<>FIsFunction then
  512.   begin FXColumn:=0; FYColumn:=0; FIsFunction:=B; Changed(false); end;
  513. end;
  514.  
  515. procedure TSerie.ClearBlock;
  516. begin
  517.   FXColumn:=DefXColumn; FYColumn:=DefYColumn;
  518.   FFirstLine:=DefFirstLine; FLastLine:=DefLastLine; Changed(false);
  519. end;
  520.  
  521. function TSerie.Empty: boolean;
  522. begin
  523.   Result:=(FXColumn<=DefXColumn) or (FYColumn<=DefYColumn) or
  524.   (FFirstLine<DefFirstLine) or (FLastLine=DefLastLine) or
  525.   (FLastLine<FFirstLine) or (not Assigned(FContainer));
  526. end;
  527.  
  528. { TSeries }
  529.  
  530. constructor TSeries.Create(APlot: TPlot);
  531. begin inherited Create(TSerie); FPlot:=APlot; end;
  532.  
  533. function TSeries.Add: TSerie;
  534. begin Result:=TSerie(inherited Add); end;
  535.  
  536. function TSeries.GetItem(Index: Integer): TSerie;
  537. begin Result:=TSerie(inherited GetItem(Index)); end;
  538.  
  539. procedure TSeries.SetItem(Index: Integer; Value: TSerie);
  540. begin inherited SetItem(Index, Value); end;
  541.  
  542. function TSeries.GetOwner: TPersistent;
  543. begin Result:=FPlot; end;
  544.  
  545. procedure TSeries.Update(Item: TCollectionItem);
  546. begin
  547.   if Assigned(FPlot) then
  548.      if Assigned(Item) then
  549.         if not ((Item as TSerie).Locked) then FPlot.Changed(Item) else
  550.      else FPlot.Changed(Self);
  551. end;
  552.  
  553. { TPlot }
  554.  
  555. function CorRect(x1,y1,x2,y2: integer): TRect;           {check: x1<x2, y1,y2}
  556. var i: integer;
  557. begin
  558.   if x1>x2 then begin i:=x1; x1:=x2; x2:=i; end;
  559.   if y1>y2 then begin i:=y1; y1:=y2; y2:=i; end;
  560.   Result:=Rect(x1,y1,x2,y2);
  561. end;
  562.  
  563. procedure TPlot.Changed(Sender: TObject);
  564. begin
  565.   Zooming:=false; Ruling:=false; Editing:=false; Translating:=ptmNo;
  566.   Invalidate;
  567. end;
  568.  
  569. procedure TPlot.SaveToMetafile(WMF: TMetafile);
  570. var  WMFC: TMetafileCanvas;
  571. begin
  572.   WMF.Width:=Width; WMF.Height:=Height;
  573.   try
  574.     WMFC:=TMetafileCanvas.Create(WMF,0);
  575.     FCanvas:=WMFC; Paint; FCanvas:=Canvas; Refresh;
  576.   finally          {^ NOTE! here may be a lot of exeptions!}
  577.     WMFC.Free;
  578.   end;
  579. end;
  580.  
  581. procedure TPlot.SaveToFile(FileName: string);
  582. var WMF: TMetafile; 
  583. begin
  584.   WMF:=TMetafile.Create;
  585.   try
  586.     SaveToMetafile(WMF);
  587.     WMF.SaveToFile(FileName);
  588.   finally
  589.     WMF.Free;
  590.   end;
  591. end;
  592.  
  593. procedure TPlot.CopyToClipboard(Mode: TPlotCopyMode; UseTabs: boolean);
  594. var WMF: TMetafile;
  595.     I,J: integer; S: TStringList; X,Y: TReal; Tab: char; R: string; D: TData;
  596.     Data: pointer; HData: THandle; MS: TMemoryStream;
  597. begin
  598.   case Mode of
  599.     pcmPage:
  600.     begin
  601.       WMF:=TMetafile.Create;
  602.       try
  603.         SaveToMetafile(WMF); Clipboard.Assign(WMF);
  604.       finally
  605.         WMF.Free;
  606.       end;
  607.     end;
  608.     pcmPoints,pcmItems:
  609.     with ThisSerie do
  610.     begin
  611.       if Empty then Exit; if UseTabs then Tab:=#9 else Tab:=' ';
  612.       S:=TStringList.Create; Screen.Cursor:=crHourGlass;
  613.       MS:=TMemoryStream.Create; J:=0; MS.Write(J, SizeOf(J));
  614.       try
  615.         for I:=FirstLine to LastLine do
  616.         begin
  617.           if LastLine<>FirstLine then Container.ShowProgress(  // check for /0
  618.           Round((I-FirstLine)/(LastLine-FirstLine)*100));
  619.           FNA:=I; FN:=I-FirstLine;         // enable Num and AbsNum parameters
  620.           D:=Container.Items[I];
  621.           GetDataPoint(D, XColumn, YColumn, XExpression, YExpression, X, Y);
  622.           if (SelectionLeft<=X) and (SelectionRight>=X) and
  623.           (SelectionBottom<=Y) and (SelectionTop>=Y) then
  624.           if Mode=pcmItems then
  625.             if (D is TRealData) then with D as TRealData do
  626.             begin                                    // force TAB delimiter!!!
  627.               R:=''; for J:=1 to Size do R:=R+GetItemText(J)+Tab; S.Add(R);
  628.               J:=Size; MS.Write(J, SizeOf(J));
  629.               for J:=1 to Size do
  630.               begin X:=RData[J]; MS.Write(X, SizeOf(X)); end;
  631.               integer(MS.Memory^):=integer(MS.Memory^)+1;
  632.             end else S.Add(D.Data)
  633.           else S.Add(FloatToStr(X)+Tab+FloatToStr(Y));   // X,Y-NOT real data!
  634.         end;
  635.         ClipBoard.Open;                                    {copy to clipboard}
  636.         try
  637.           Clipboard.AsText:=S.Text;
  638.           if integer(MS.Memory^)>0 then
  639.           begin
  640.             HData:=GlobalAlloc(GMEM_MOVEABLE+GMEM_DDESHARE, MS.Size);
  641.             try
  642.               Data:=GlobalLock(HData);
  643.               try
  644.                 Move(MS.Memory^, Data^, MS.Size);
  645.                 SetClipboardData(TRealData.GetClipboardFormat, HData);
  646.               finally
  647.                 GlobalUnlock(HData);
  648.               end;
  649.             except
  650.               GlobalFree(HData); raise;
  651.             end;
  652.           end;
  653.         finally
  654.           ClipBoard.Close;
  655.         end;
  656.       finally
  657.         S.Free; MS.Free; Screen.Cursor:=crDefault;
  658.       end;
  659.     end;{with}
  660.   end;{case}
  661. end;
  662.  
  663. procedure TPlot.Delete;
  664. var J,K,P,DP: integer; X,Y: TReal; M: boolean;
  665. begin
  666.   with ThisSerie do
  667.   try
  668.     Screen.Cursor:=crHourGlass; if Empty then Exit; M:=false;
  669.     K:=FirstLine; P:=0; DP:=LastLine-FirstLine;
  670.     while K<=LastLine do
  671.     begin
  672.       if DP>0 then Container.ShowProgress(Round(P/DP*100)); Inc(P);
  673.       GetDataPoint(Container.Items[K], XColumn, YColumn,
  674.       XExpression, YExpression, X, Y);
  675.       if (SelectionLeft<=X) and (SelectionRight>=X) and
  676.       (SelectionBottom<=Y) and (SelectionTop>=Y) then
  677.       begin
  678.         for J:=0 to Series.Count-1 do     {find & correct ALL affected series}
  679.         if Series[J].Container=Container then with Series[J] do
  680.         begin
  681.           if K<=LastLine then LastLine:=LastLine-1;
  682.           if K<FirstLine then FirstLine:=FirstLine-1;
  683.         end;
  684.         with Container do begin TData(Items[K]).Free; Items.Delete(K); end;
  685.         M:=true;
  686.       end else Inc(K);
  687.     end;{while}
  688.   finally
  689.     Screen.Cursor:=crDefault;
  690.     if M then Container.Modified:=true;   // only if data really were modified
  691.   end;
  692. end;
  693.  
  694. constructor TPlot.Create(AOwner: TComponent);
  695. begin
  696.   inherited Create(AOwner);
  697.   FPen:=TPen.Create; FPen.OnChange:=Changed;
  698.   FBrush:=TBrush.Create; FBrush.OnChange:=Changed;
  699.   FXAxis:=TAxis.Create(Self); FYAxis:=TAxis.Create(Self);
  700.   FSeries:=TSeries.Create(Self);
  701.   FCanvas:=Canvas;            {copy "native" canvas to buffer used in Paint()}
  702.   FParser:=TMathParser.Create;       {create and initialize expression parser}
  703.   with FParser do
  704.   begin
  705.     Init(90); AddGonio; AddLogic; AddMath; AddMisc; AddSpec;
  706.     AddStdParams(@FParserParams);
  707.     AddObject(@FX,'CX',tfp_realvar); AddObject(@FY,'CY',tfp_realvar);
  708.     AddObject(@FN,'NUM',tfp_realvar); AddObject(@FNA,'ABSNUM',tfp_realvar);
  709.   end;
  710.   Zooming:=false; Ruling:=false; FMouseMode:=pmNone; FSelectionVisible:=false;
  711.   FTransparent:=true; Editing:=false; FSerieIndex:=-1; Translating:=ptmNo;
  712. end;
  713.  
  714. destructor TPlot.Destroy;
  715. begin
  716.   FXAxis.Free; FYAxis.Free; FSeries.Free; FParser.Free; TransBuf:=nil;{!}
  717.   FPen.Free; FBrush.Free; inherited Destroy;
  718. end;
  719.  
  720. procedure TPlot.SetPen(P: TPen);
  721. begin FPen.Assign(P); end;
  722.  
  723. procedure TPlot.SetBrush(B: TBrush);
  724. begin FBrush.Assign(B); end;
  725.  
  726. procedure TPlot.DrawLine(X1, Y1, X2, Y2: extended);
  727. var iX1,iY1,iX2,iY2: integer;          {DrawXXX should be called from OnPaint}
  728. begin
  729.   if RealToIntCoords(X1,Y1,iX1,iY1)                     {invalid coordinates!}
  730.      or RealToIntCoords(X2,Y2,iX2,iY2) then Exit;
  731.   FCanvas.MoveTo(iX1, iY1); FCanvas.LineTo(iX2, iY2);
  732. end;
  733.  
  734. procedure TPlot.DrawPoint(X, Y: extended; T: TPointType; S: integer);
  735. var iX, iY: integer;
  736. begin
  737.   if not RealToIntCoords(X,Y,iX,iY) then PaintPoint(iX,iY,T,S,FCanvas);
  738. end;
  739.  
  740. function TPlot.GetDataPoint(D: TData; CX, CY: integer; const XExpr,
  741.   YExpr: string; var X, Y: extended): boolean;
  742. var I: byte;
  743. begin
  744.   if Assigned(FGetPoint) then                  {may display another datatypes}
  745.   begin Result:=FGetPoint(D,CX,CY,XExpr,YExpr,X,Y); Exit; end;
  746.   Result:=true;                                             {true if error!!!}
  747.   if (D is TFunction) then
  748.   begin
  749.     FX:=(D as TFunction).X; FY:=(D as TFunction).Y; {copy to use x,y in exp-s}
  750.     begin
  751.       for I:=1 to MaxCols do FParserParams[I]:=1;             {cleanup buffer}
  752.       FParserParams[1]:=(D as TFunction).X;                      {copy values}
  753.       FParserParams[2]:=(D as TFunction).Y;
  754.     end;
  755.     if XExpr<>'' then X:=FParser.Parse(XExpr)                       {PARSE...}
  756.     else if CX=1 then X:=(D as TFunction).X else X:=(D as TFunction).Y;
  757.     if YExpr<>'' then Y:=FParser.Parse(YExpr)
  758.     else if CY=1 then Y:=(D as TFunction).X else Y:=(D as TFunction).Y;
  759.   end else
  760.   if D is TRealData then
  761.   begin
  762.    (D as TRealData).GetRData(FParserParams);
  763.    FX:=(D as TRealData).RData[CX]; if XExpr<>'' then FX:=FParser.Parse(XExpr);
  764.    FY:=(D as TRealData).RData[CY]; if YExpr<>'' then FY:=FParser.Parse(YExpr);
  765.   end else Exit;
  766.   if XAxis.Expression<>'' then X:=FParser.Parse(XAxis.Expression) else X:=FX;
  767.   if YAxis.Expression<>'' then Y:=FParser.Parse(YAxis.Expression) else Y:=FY;
  768.   Result:=false;                                  {NOTE! successfully exit!!!}
  769. end;
  770.  
  771. function TPlot.IntToRealCoords(X,Y: integer; var rX,rY: extended): boolean;
  772. begin
  773.   if (X<YAxisGap) or (X>YAxisGap+XAxisLen) or       {NOTE: returns TRUE if ok}
  774.      (Y>FHeight-XAxisGap) or (Y<FHeight-XAxisGap-YAxisLen)
  775.   then begin Result:=false; Exit; end else Result:=true;
  776.   with XAxis do rX:=FMin+(X-YAxisGap)/XAxisLen*(FMax-FMin);
  777.   with YAxis do rY:=FMin+(FHeight-XAxisGap-Y)/YAxisLen*(FMax-FMin);
  778. end;
  779.  
  780. function TPlot.BelongMarker(rX,rY: extended; X,Y: integer): boolean;
  781. var iX,iY: integer;
  782. begin
  783.   if RealToIntCoords(rX,rY,iX,iY) then Result:=false else
  784.   Result:=(X>=iX-MSZ) and (X<=iX+MSZ) and (Y>=iY-MSZ) and (Y<=iY+MSZ);
  785. end;
  786.  
  787. procedure TPlot.MouseDown(Btn: TMouseButton; Shift:TShiftState; X,Y: Integer);
  788. var a,b,rx,ry:extended; Ser,Pnt,ix,iy,I: integer; S: string[3];
  789. begin
  790.   if X<=YAxisGap then FClickedAt:=claYAxis;
  791.   if Y>=Height-XAxisGap then FClickedAt:=claXAxis;
  792.   if (X>YAxisGap) and (Y<Height-XAxisGap) then FClickedAt:=claPlot;
  793.   inherited MouseDown(Btn, Shift, X, Y);
  794.   if (ssLeft in Shift) and IntToRealCoords(X,Y,a,b) then     {process action:}
  795.   begin
  796.     if (MouseMode=pmPointClick) or (MouseMode=pmPointEdit) or
  797.        (MouseMode=pmPointDelete) then
  798.     begin                                             {seek for clicked point}
  799.       Screen.Cursor:=crHourGlass;
  800.       try
  801.         ShowPlotHint(msgScanning);                           {display message}
  802.         for Ser:=0 to Series.Count-1 do with Series[Ser] do
  803.         begin
  804.           if Empty then Continue;                     {empty serie; goto next}
  805.           for Pnt:=FirstLine to LastLine do                      {scan points}
  806.           begin
  807.             FNA:=Pnt; FN:=Pnt-FirstLine;
  808.             if GetDataPoint(Container.Items[Pnt],XColumn,YColumn,XExpression,
  809.             YExpression, RX, RY) or RealToIntCoords(rx,ry,ix,iy) then Continue
  810.             else if (abs(x-ix)<=PointSize div 2)
  811.                     and (abs(y-iy)<=PointSize div 2)
  812.                  then begin                                         {FOUND!!!}
  813.                         if Assigned(FOnPointClick)
  814.                         then FOnPointClick(Self,Pnt,Ser);
  815.                         if MouseMode=pmPointDelete then
  816.                         begin
  817.                           for I:=0 to Series.Count-1 do
  818.                           if Series[I].Container=Series[Ser].Container then
  819.                           with Series[I] do           // try to correct blocks
  820.                           begin
  821.                             if Pnt<=LastLine then LastLine:=LastLine-1;
  822.                             if Pnt<FirstLine then FirstLine:=FirstLine-1;
  823.                           end;
  824.                           with Series[Ser].Container do
  825.                           begin                   // free data and delete item
  826.                             TData(Items[Pnt]).Free; Items.Delete(Pnt);
  827.                             Modified:=true; // MessageBeep($ffffffff);
  828.                           end;
  829.                         end;{PointDelete}
  830.                         if MouseMode=pmPointEdit then
  831.                         begin
  832. {check for ALL expressions because we can't calculate inverse functions!}
  833.                           if (XAxis.Expression<>'') or
  834.                              (YAxis.Expression<>'') then
  835.                           begin
  836.                             if XAxis.Expression<>'' then S:='X';
  837.                             if YAxis.Expression<>'' then S:='Y';
  838.                             if (XAxis.Expression<>'') and
  839.                                (YAxis.Expression<>'') then S:='X,Y';
  840.                             Screen.Cursor:=crDefault; ShowPlotHint(' ');
  841.                             ShowPlotError(Format(errEditPoint2,[S]));
  842.                             Break;
  843.                           end;
  844.                           if (XExpression<>'') or (YExpression<>'') then
  845.                           begin
  846.                             if XExpression<>'' then S:='X';
  847.                             if YExpression<>'' then S:='Y';
  848.                             if (XExpression<>'') and (YExpression<>'')
  849.                             then S:='X,Y';
  850.                             Screen.Cursor:=crDefault; ShowPlotHint(' ');
  851.                             ShowPlotError(Format(errEditPoint1,[Ser,S]));
  852.                             Break;
  853.                           end;
  854.                           Editing:=true; EditX:=iX; EditY:=iY; {exact coord-s}
  855.                           if Pnt>FirstLine then      {remember previous point}
  856.                           begin
  857.                             GetDataPoint(Container.Items[Pnt-1],XColumn,
  858.                             YColumn, XExpression, YExpression, rx, ry);
  859.                             RealToIntCoords(rx,ry,EditX1,EditY1);  {NO CHECK!}
  860.                           end else begin EditX1:=0; EditY1:=0; end; {no such!}
  861.                           if Pnt<LastLine then           {remember next point}
  862.                           begin
  863.                             GetDataPoint(Container.Items[Pnt+1],XColumn,
  864.                             YColumn, XExpression, YExpression, rx, ry);
  865.                             RealToIntCoords(rx,ry,EditX2,EditY2);
  866.                           end else begin EditX2:=0; EditY2:=0; end;
  867.                           EditSer:=Ser; EditPnt:=Pnt;   {use to change point!}
  868.                           DrawEdit(iX,iY);              {show "rubber thread"}
  869.                           {MessageBeep($ffffffff); warn user on point capture}
  870.                         end;{PointEdit}
  871.                         Break;
  872.                       end;{find point cycle}
  873.           end;{for Pnt}
  874.           if Editing then Break;                 {prevent capture of >1 point}
  875.         end;{for Ser}
  876.       finally
  877.         Screen.Cursor:=crDefault; ShowPlotHint(' ');
  878.       end;
  879.     end;{pointclick process}
  880.     if ((ssShift in Shift) and (MouseMode=pmAutoZoom)) or    {turn on zooming}
  881.     (MouseMode=pmZoom) or (MouseMode=pmUnZoom) or (MouseMode=pmSelect) then
  882.     begin                                               {remember first point}
  883.       Zooming:=true; ZoomX:=X; ZoomY:=Y; ZoomXo:=X; ZoomYo:=Y;
  884.     end;
  885.     if MouseMode=pmRuler then                                 {turn on ruling}
  886.     begin                                               {remember first point}
  887.       Ruling:=true; RulerX:=X; RulerY:=Y; RulerFi:=0; DrawRuler(X,Y);
  888.     end;
  889.     if (ssLeft in Shift) and (MouseMode=pmTranslate) and SelectionVisible then
  890.     begin
  891.       if (XAxis.Expression<>'') or (YAxis.Expression<>'') or
  892.       (ThisSerie.XExpression<>'') or (ThisSerie.YExpression<>'') then
  893.       ShowPlotError(errTranslation) else
  894.       if BelongMarker(SelectionLeft,SelectionTop,X,Y)
  895.       then Translating:=ptmTL else
  896.       if BelongMarker(SelectionRight,SelectionTop,X,Y)
  897.       then Translating:=ptmTR else
  898.       if BelongMarker((SelectionLeft+SelectionRight)/2,SelectionTop,X,Y)
  899.       then Translating:=ptmT else
  900.       if BelongMarker((SelectionLeft+SelectionRight)/2,SelectionBottom,X,Y)
  901.       then Translating:=ptmB else
  902.       if BelongMarker(SelectionLeft,SelectionBottom,X,Y)
  903.       then Translating:=ptmBL else
  904.       if BelongMarker(SelectionRight,SelectionBottom,X,Y)
  905.       then Translating:=ptmBR else
  906.       if BelongMarker(SelectionLeft,(SelectionTop+SelectionBottom)/2,X,Y)
  907.       then Translating:=ptmL else
  908.       if BelongMarker(SelectionRight,(SelectionTop+SelectionBottom)/2,X,Y)
  909.       then Translating:=ptmR else
  910.       if (a>SelectionLeft) and (a<SelectionRight) and (b<SelectionTop) and
  911.       (b>SelectionBottom) then Translating:=ptmMove else Translating:=ptmNo;
  912.       ZoomX:=X; ZoomY:=Y;               {use the same buffers as for zooming!}
  913.       TransX1:=FSelectionLeft; TransX2:=FSelectionRight;
  914.       TransY1:=FSelectionBottom; TransY2:=FSelectionTop;
  915.       if (Translating<>ptmNo) and (not ThisSerie.Empty) then
  916.       begin                      {fill buffer for drawing translation preview}
  917.         Screen.Cursor:=crHourGlass;
  918.         try
  919.           ShowPlotHint(msgScanning);                         {display message}
  920.           with ThisSerie do
  921.           begin
  922.             SetLength(TransBuf,LastLine-FirstLine+1); TransPointCount:=0;
  923.             for I:=FirstLine to LastLine do
  924.             begin
  925.               GetDataPoint(Container.Items[I],XColumn,YColumn,XExpression,
  926.               YExpression, rX, rY);
  927.               if (TransX1<=rX) and (TransX2>=rX) and
  928.               (TransY1<=rY) and (TransY2>=rY) then
  929.               begin
  930.                 with TransBuf[TransPointCount] do begin X:=rX; Y:=rY; end;
  931.                 Inc(TransPointCount);
  932.               end;
  933.             end;
  934.           end;
  935.         finally
  936.           Screen.Cursor:=crDefault; ShowPlotHint(' ');
  937.         end;
  938.       end;{Translating}
  939.     end;{Translate}
  940.   end;{if IntToRealCoords()...}
  941. end;
  942.  
  943. procedure TPlot.DrawEdit(X, Y: integer);                {move "rubber thread"}
  944. begin
  945.   Canvas.Pen.Mode:=pmXor; Canvas.Pen.Style:=psSolid; Canvas.Pen.Width:=1;
  946.   Canvas.Pen.Color:=Color;
  947.   if (EditX1<>0) and (EditY1<>0) then
  948.   begin
  949.     if (X<>EditX) or (Y<>EditY)     {hide previous line if it is not the same}
  950.     then Canvas.PolyLine([Point(EditX1,EditY1),Point(EditX,EditY)]);
  951.     Canvas.PolyLine([Point(EditX1,EditY1),Point(X,Y)]);
  952.   end;
  953.   if (EditX2<>0) and (EditY2<>0) then
  954.   begin
  955.     if (X<>EditX) or (Y<>EditY)
  956.     then Canvas.PolyLine([Point(EditX2,EditY2),Point(EditX,EditY)]);
  957.     Canvas.PolyLine([Point(EditX2,EditY2),Point(X,Y)]);
  958.   end;
  959.   EditX:=X; EditY:=Y;
  960.   Canvas.Pen.Mode:=pmCopy;                  {we need to restore ONLY pen mode}
  961. end;
  962.  
  963. procedure TPlot.MouseMove(Shift: TShiftState; X, Y: Integer);
  964. var rX,rY,a,rXo,rYo: extended; s: string;
  965. begin
  966.   if Ruling then
  967.   begin
  968.     DrawRuler(RulerX,RulerY); RulerY:=Y;                {move or rotate ruler}
  969.     if (ssAlt in Shift) then RulerFi:=(RulerX-X)/Height*Pi else RulerX:=X;
  970.     DrawRuler(RulerX,RulerY);
  971.     a:=sin(RulerFi)/cos(RulerFi)*(YAxis.Max-YAxis.Min)/YAxisLen/
  972.     (XAxis.Max-XAxis.Min)*XAxisLen;
  973.     if IntToRealCoords(X,Y,rX,rY) then S:=Format('%7.4g*X+%-7.4g',[a,rY-rX*a])
  974.     else S:='';
  975.     if Pos('+-',S)<>0 then System.Delete(S, Pos('+-',S),1); {delete extra "+"}
  976.     ShowPlotHint(S);
  977.     inherited MouseMove(Shift, X, Y); Exit;                       {!!!!!!!!!!}
  978.   end;
  979.   if IntToRealCoords(X,Y,rX,rY) then
  980.   begin
  981.     if Editing then DrawEdit(X,Y);
  982.     if Zooming then
  983.     begin
  984.       Canvas.Brush.Color:=Color; {1) not Pen, but Brush?! 2) inverts plot bkg}
  985.       Canvas.DrawFocusRect(CorRect(ZoomXo,ZoomYo,ZoomX,ZoomY));    {erase old}
  986.       Canvas.DrawFocusRect(CorRect(ZoomXo,ZoomYo,X,Y));             {draw new}
  987.       ZoomX:=X; ZoomY:=Y;                            {remember for next cycle}
  988.     end;
  989.     if (MouseMode=pmTranslate) and SelectionVisible then
  990.     begin
  991.       IntToRealCoords(ZoomX,ZoomY,rXo,rYo); {real coordinates of previous pos}
  992.       if Translating<>ptmNo then            {if translating, update selection}
  993.       begin
  994.         DrawSelection;                               {hide previous selection}
  995.         if Translating=ptmMove then ShowPlotHint('dX : '+
  996.         FloatToStrF(FSelectionLeft-TransX1,XAxis.FFType,XAxis.FWidth,
  997.         XAxis.FDecimals)+' dY : '+FloatToStrF(FSelectionBottom-TransY1,
  998.         YAxis.FFType,YAxis.FWidth,YAxis.FDecimals)) else ShowPlotHint('dX : '+
  999.         FloatToStrF(Abs((FSelectionRight-FSelectionLeft)/(TransX2-TransX1)),
  1000.         ffFixed,7,4)+' dY : '+FloatToStrF(Abs((FSelectionTop-FSelectionBottom)
  1001.         /(TransY2-TransY1)),ffFixed,7,4));
  1002.         case Translating of
  1003.           ptmMove:
  1004.           begin
  1005.             FSelectionTop:=FSelectionTop+(rY-rYo);
  1006.             FSelectionBottom:=FSelectionBottom+(rY-rYo);
  1007.             FSelectionLeft:=FSelectionLeft+(rX-rXo);
  1008.             FSelectionRight:=FSelectionRight+(rX-rXo);
  1009.           end;
  1010.           ptmT: FSelectionTop:=FSelectionTop+(rY-rYo);
  1011.           ptmB: FSelectionBottom:=FSelectionBottom+(rY-rYo);
  1012.           ptmL: FSelectionLeft:=FSelectionLeft+(rX-rXo);
  1013.           ptmR: FSelectionRight:=FSelectionRight+(rX-rXo);
  1014.           ptmTL:
  1015.           begin
  1016.             FSelectionTop:=FSelectionTop+(rY-rYo);
  1017.             FSelectionLeft:=FSelectionLeft+(rX-rXo);
  1018.           end;
  1019.           ptmTR:
  1020.           begin
  1021.             FSelectionTop:=FSelectionTop+(rY-rYo);
  1022.             FSelectionRight:=FSelectionRight+(rX-rXo);
  1023.           end;
  1024.           ptmBL:
  1025.           begin
  1026.             FSelectionBottom:=FSelectionBottom+(rY-rYo);
  1027.             FSelectionLeft:=FSelectionLeft+(rX-rXo);
  1028.           end;
  1029.           ptmBR:
  1030.           begin
  1031.             FSelectionBottom:=FSelectionBottom+(rY-rYo);
  1032.             FSelectionRight:=FSelectionRight+(rX-rXo);
  1033.           end;
  1034.         end;{case}
  1035.         DrawSelection;                        {show selection at new position}
  1036.         ZoomX:=X; ZoomY:=Y;     {remember position to use in next MouseMove()}
  1037.       end;
  1038.       with Screen do                 {change cursor shape in translation mode}
  1039.       if BelongMarker(SelectionLeft,SelectionTop,X,Y)
  1040.       then Cursor:=crSizeNWSE else
  1041.       if BelongMarker(SelectionRight,SelectionTop,X,Y)
  1042.       then Cursor:=crSizeNESW else
  1043.       if BelongMarker((SelectionLeft+SelectionRight)/2,SelectionTop,X,Y) or
  1044.       BelongMarker((SelectionLeft+SelectionRight)/2,SelectionBottom,X,Y)
  1045.       then Cursor:=crSizeNS else
  1046.       if BelongMarker(SelectionLeft,SelectionBottom,X,Y)
  1047.       then Cursor:=crSizeNESW else
  1048.       if BelongMarker(SelectionRight,SelectionBottom,X,Y)
  1049.       then Cursor:=crSizeNWSE else
  1050.       if BelongMarker(SelectionLeft,(SelectionTop+SelectionBottom)/2,X,Y) or
  1051.       BelongMarker(SelectionRight,(SelectionTop+SelectionBottom)/2,X,Y)
  1052.       then Cursor:=crSizeWE else
  1053.       if (rX>SelectionLeft) and (rX<SelectionRight) and (rY<SelectionTop)
  1054.       and (rY>SelectionBottom) then Cursor:=crSize else Cursor:=crDefault;
  1055.     end else ShowPlotHint('X : '+FloatToStrF(rX, XAxis.FFType, XAxis.FWidth,
  1056.       XAxis.FDecimals)+' Y : '+FloatToStrF(rY, YAxis.FFType, YAxis.FWidth,
  1057.       YAxis.FDecimals));                            {display real coordinates}
  1058.   end else ShowPlotHint(' ');
  1059.   inherited MouseMove(Shift, X, Y);
  1060. end;
  1061.  
  1062. function Translate(x,x1,x2,x1o,x2o: extended): extended;
  1063. begin
  1064.   if x=x2o then Result:=x2
  1065.   else Result:=(x1+(x-x1o)/(x2o-x)*x2)/(1+(x-x1o)/(x2o-x));
  1066. end;
  1067.  
  1068. procedure TPlot.MouseUp(Btn: TMouseButton; Shift: TShiftState; X,Y: Integer);
  1069. var a,rx,ry,rx1,ry1: extended; R: TRect; I: integer; M: boolean;
  1070. begin
  1071.   if Zooming then
  1072.   begin
  1073.     Zooming:=false;
  1074.     if (ZoomXo=X) or (ZoomYo=Y) or (not IntToRealCoords(X,Y,rx,ry)) then Exit;
  1075.     R:=Correct(ZoomXo,ZoomYo,X,Y);                                      {sort}
  1076.     IntToRealCoords(R.Left,R.Top,rx,ry);
  1077.     IntToRealCoords(R.Right,R.Bottom,rx1,ry1);
  1078.     if MouseMode=pmSelect then
  1079.     begin
  1080.       SelectionTop:=ry; SelectionBottom:=ry1;               {update selection}
  1081.       SelectionLeft:=rx; SelectionRight:=rx1;
  1082.       if Assigned(FOnSelectionChange) then FOnSelectionChange(Self);
  1083.       inherited MouseUp(Btn, Shift, X, Y); Exit;                 {don't zoom!}
  1084.     end;
  1085.     XAxis.AutoScale:=false; YAxis.AutoScale:=false;          {reset autoscale}
  1086.     OldX1:=XAxis.Min; OldX2:=XAxis.Max; OldY1:=YAxis.Min; OldY2:=YAxis.Max;
  1087.     FCanUnZoom:=true;             {save coordinates in buffer and enable undo}
  1088.     if ((ssAlt in Shift) and (MouseMode=pmAutoZoom)) or (MouseMode=pmUnZoom)
  1089.     then begin                                                        {UnZoom}
  1090.       XAxis.Min:=XAxis.Min-(XAxis.Max-XAxis.Min)*(rx-XAxis.Min)/(rx1-rx);
  1091.       XAxis.Max:=XAxis.Max+(XAxis.Max-XAxis.Min)*(XAxis.Max-rx1)/(rx1-rx);
  1092.       YAxis.Min:=YAxis.Min-(YAxis.Max-YAxis.Min)*(ry1-YAxis.Min)/(ry-ry1);
  1093.       YAxis.Max:=YAxis.Max+(YAxis.Max-YAxis.Min)*(YAxis.Max-ry)/(ry-ry1);
  1094.     end else                                                            {Zoom}
  1095.     begin XAxis.Min:=rx; XAxis.Max:=rx1; YAxis.Min:=ry1; YAxis.Max:=ry; end;
  1096.   end;
  1097.   if Ruling then
  1098.   begin Ruling:=false; DrawRuler(RulerX,RulerY); end;             {hide ruler}
  1099.   if Editing then
  1100.   begin                                     {ok: valid final poin coordinates}
  1101.     Editing:=false; Invalidate;{erase lost "rubber thread" OR old moved point}
  1102.     if IntToRealCoords(X,Y,rx,ry) then                              {CHECK!!!}
  1103.     with Series[EditSer] do
  1104.     begin
  1105.       SetDataPoint(Container.Items[EditPnt], XColumn, YColumn, rx, ry);
  1106.       Container.Modified:=true;
  1107.     end;
  1108.   end;
  1109.   if Translating<>ptmNo then
  1110.   begin
  1111.     Translating:=ptmNo;
  1112.     if FSelectionLeft>FSelectionRight then
  1113.     begin
  1114.       a:=FSelectionLeft; FSelectionLeft:=FSelectionRight; FSelectionRight:=a;
  1115.     end;
  1116.     if FSelectionBottom>FSelectionTop then
  1117.     begin
  1118.       a:=FSelectionBottom; FSelectionBottom:=FSelectionTop; FSelectionTop:=a;
  1119.     end;
  1120.     if Assigned(FOnSelectionChange) then FOnSelectionChange(Self); M:=false;
  1121.     with ThisSerie do if not Empty then
  1122.     try
  1123.       Screen.Cursor:=crHourGlass; ShowPlotHint(strMoving);
  1124.       for I:=FirstLine to LastLine do
  1125.       begin
  1126.         if LastLine<>FirstLine then Container.ShowProgress(
  1127.         Round((I-FirstLine)/(LastLine-FirstLine)*100));
  1128.         GetDataPoint(Container.Items[I], XColumn, YColumn,
  1129.         XExpression, YExpression, rX, rY);
  1130.         if (TransX1<=rX) and (TransX2>=rX) and (TransY1<=rY) and (TransY2>=rY)
  1131.         then begin
  1132.           M:=true;
  1133.           rX:=Translate(rX,FSelectionLeft,FSelectionRight,TransX1,TransX2);
  1134.           rY:=Translate(rY,FSelectionBottom,FSelectionTop,TransY1,TransY2);
  1135.           SetDataPoint(Container.Items[I], XColumn, YColumn, rx, ry);
  1136.         end;
  1137.       end;{cycle}
  1138.       if M then begin Invalidate;{!} Container.Modified:=true; end;
  1139.     finally
  1140.       Screen.Cursor:=crDefault; ShowPlotHint(' ');
  1141.     end;
  1142.   end;
  1143.   inherited MouseUp(Btn, Shift, X, Y);
  1144. end;
  1145.  
  1146. procedure TPlot.SetDataPoint(D: TData; CX, CY: integer; X, Y: extended);
  1147. begin
  1148.   if D is TFunction then
  1149.   begin
  1150.     if CX=1 then (D as TFunction).X:=X else (D as TFunction).Y:=X;
  1151.     if CY=1 then (D as TFunction).X:=Y else (D as TFunction).Y:=Y;
  1152.   end;
  1153.   if D is TRealData then
  1154.   begin (D as TRealData).RData[CX]:=X; (D as TRealData).RData[CY]:=Y; end;
  1155. end;
  1156.  
  1157. procedure TPlot.PaintPoint(X, Y: integer; T: TPointType; S: integer;
  1158.   C: TCanvas);
  1159. var psize: integer;  {this method is useful to paint legends,dialog boxes,etc}
  1160. begin
  1161.   if Printing then S:=round(S*FCanvas.Font.PixelsPerInch/PPI);    {CORRECT!!!}
  1162.   psize:=S div 2;                                         {calculate halfsize}
  1163.   if psize<1 then with C do Pixels[X,Y]:=Brush.Color else         {one pixel!}
  1164.   case T of
  1165.     ptCircle: C.Ellipse(X-psize, Y-psize, X+psize, Y+psize);
  1166.     ptSquare: C.Rectangle(X-psize, Y-psize, X+psize, Y+psize);
  1167.     ptCross : begin
  1168.                 C.MoveTo(X,Y-psize); C.LineTo(X,Y+psize);               {vert}
  1169.                 C.MoveTo(X-psize,Y); C.LineTo(X+psize,Y);               {horz}
  1170.               end;
  1171.     ptXCross: begin
  1172.                 C.MoveTo(X-psize,Y-psize); C.LineTo(X+psize,Y+psize);      {\}
  1173.                 C.MoveTo(X-psize,Y+psize); C.LineTo(X+psize,Y-psize);      {/}
  1174.               end;
  1175.     ptAsterisk: begin
  1176.                   C.MoveTo(X,Y-psize); C.LineTo(X,Y+psize);                {|}
  1177.                   C.MoveTo(X-psize,Y); C.LineTo(X+psize,Y);                {-}
  1178.                   C.MoveTo(X-psize,Y-psize); C.LineTo(X+psize,Y+psize);    {\}
  1179.                   C.MoveTo(X-psize,Y+psize); C.LineTo(X+psize,Y-psize);    {/}
  1180.                 end;
  1181.   end;{case}
  1182. end;
  1183.  
  1184. procedure TPlot.DrawRuler(X, Y: integer);
  1185. const WFactor=40; LFactor=3;                {size of ruler relatively to plot}
  1186. var A: array[0..4] of TPoint; Fi,Len: extended;
  1187. begin
  1188.   if RulerFi>Pi/2.1 then RulerFi:=Pi/2.1;             {correct rotation angle}
  1189.   if RulerFi<-Pi/2.1 then RulerFi:=-Pi/2.1;
  1190.   Fi:=arctan(Height/WFactor/Width*LFactor);
  1191.   Len:=sqrt(sqr(Width/LFactor)+sqr(Height/WFactor));
  1192.   A[0]:=Point(Round(X-Len*cos(RulerFi+Fi)), Round(Y+Len*sin(RulerFi+Fi)));
  1193.   A[1]:=Point(Round(X-Len*cos(RulerFi-Fi)), Round(Y+Len*sin(RulerFi-Fi)));
  1194.   A[2]:=Point(Round(X+Len*cos(RulerFi+Fi)), Round(Y-Len*sin(RulerFi+Fi)));
  1195.   A[3]:=Point(Round(X+Len*cos(RulerFi-Fi)), Round(Y-Len*sin(RulerFi-Fi)));
  1196.   A[4]:=A[0];
  1197.   Canvas.Pen.Mode:=pmXor; Canvas.Pen.Width:=1;            {set pen attributes}
  1198.   Canvas.Pen.Color:=Color; Canvas.Pen.Style:=psSolid;
  1199.   Canvas.PolyLine(A); Canvas.Pen.Mode:=pmNotXor;
  1200.   Canvas.MoveTo(X-Round(Len*cos(RulerFi)), Y+Round(Len*sin(RulerFi)));
  1201.   Canvas.LineTo(X+Round(Len*cos(RulerFi)), Y-Round(Len*sin(RulerFi)));
  1202.   Canvas.Pen.Mode:=pmCopy;                                 {restore pen mode!}
  1203. end;
  1204.  
  1205. procedure TPlot.Print(W, H: integer);
  1206. begin
  1207.   FCanvas:=Printer.Canvas; FWidth:=W; FHeight:=H; Printing:=true;
  1208.   try
  1209.     Paint;
  1210.   finally
  1211.     Printing:=false; FCanvas:=Canvas; FWidth:=Width; FHeight:=Height; Refresh;
  1212.   end;
  1213. end;
  1214.  
  1215. function TPlot.RealToIntCoords(X,Y: extended; var iX,iY: integer): boolean;
  1216. {^ returns true if coordinates are out of range}
  1217.   function Belong(A,B,X: TReal): boolean;              {returns true if A<X<B}
  1218.   var T: TReal;
  1219.   begin
  1220.     if B<A then begin T:=A; A:=B; B:=T; end;
  1221.     if (A<=X) and (X<=B) then Belong:=true else Belong:=false;
  1222.   end;
  1223. begin
  1224.   if Belong(XAxis.Min, XAxis.Max, X) and Belong(YAxis.Min, YAxis.Max, Y) then
  1225.   begin
  1226.     Result:=false;
  1227.     with XAxis do if Min=Max then iX:=YAxisGap+(XAxisLen div 2)
  1228.     else iX:=YAxisGap+Round((X-Min)/(Max-Min)*XAxisLen);
  1229.     with YAxis do if Min=Max then iY:=FHeight-XAxisGap-(YAxisLen div 2)
  1230.     else iY:=FHeight-XAxisGap-Round((Y-Min)/(Max-Min)*YAxisLen);
  1231.   end else Result:=true;                  {real coordinates are out of range!}
  1232. end;
  1233.  
  1234. procedure TPlot.SetBorderStyle(B: TBorderStyle);
  1235. begin if FBorderStyle<>B then begin FBorderStyle:=B; Changed(Self); end; end;
  1236.  
  1237. procedure TPlot.SetSerieIndex(const Value: integer);
  1238. begin if (Value>-2) and (Value<Series.Count) then FSerieIndex:=Value; end;
  1239.  
  1240. function TPlot.GetThisSerie: TSerie;
  1241. begin
  1242.   if (SerieIndex>=0) and (SerieIndex<Series.Count)
  1243.   then Result:=Series[SerieIndex] else Result:=nil;
  1244. end;
  1245.  
  1246. procedure TPlot.SetSeries(const Value: TSeries);
  1247. begin FSeries.Assign(Value); end;
  1248.  
  1249. procedure TPlot.SetTransparent(B: boolean);
  1250. begin if FTransparent<>B then begin FTransparent:=B; Changed(Self); end; end;
  1251.  
  1252. procedure TPlot.SetXAxis(Value: TAxis);
  1253. begin FXAxis.Assign(Value); end;
  1254.  
  1255. procedure TPlot.SetYAxis(Value: TAxis);
  1256. begin FYAxis.Assign(Value); end;
  1257.  
  1258. procedure TPlot.ShowPlotHint(H: string);
  1259. begin if Assigned(FOnHint) then FOnHint(Self, H); end;
  1260.  
  1261. procedure TPlot.ShowPlotError(H: string);
  1262. begin if Assigned(FOnError) then FOnError(Self, H); end;
  1263.  
  1264. procedure TPlot.UndoZoom;
  1265. begin
  1266.   if FCanUnZoom then
  1267.   begin
  1268.     FCanUnZoom:=false; XAxis.Min:=OldX1; XAxis.Max:=OldX2;
  1269.     YAxis.Min:=OldY1; YAxis.Max:=OldY2;
  1270.   end;
  1271. end;
  1272.  
  1273. function TPlot.GetSelection(const Index: integer): extended;
  1274. begin
  1275.   case Index of
  1276.     1: Result:=FSelectionTop;
  1277.     2: Result:=FSelectionBottom;
  1278.     3: Result:=FSelectionLeft;
  1279.     4: Result:=FSelectionRight;
  1280.   end;
  1281. end;
  1282.  
  1283. procedure TPlot.SetSelection(const Index: Integer; const Value: extended);
  1284. begin
  1285.   case Index of
  1286.     1: if Value<>FSelectionTop then
  1287.        begin FSelectionTop:=Value; Changed(Self); end;
  1288.     2: if Value<>FSelectionBottom then
  1289.        begin FSelectionBottom:=Value; Changed(Self); end;
  1290.     3: if Value<>FSelectionLeft then
  1291.        begin FSelectionLeft:=Value; Changed(Self); end;
  1292.     4: if Value<>FSelectionRight then
  1293.        begin FSelectionRight:=Value; Changed(Self); end;
  1294.   end;
  1295. end;
  1296.  
  1297. procedure TPlot.SetSelectionVisible(const Value: boolean);
  1298. begin
  1299.   if FSelectionVisible<>Value then
  1300.   begin FSelectionVisible:=Value; Changed(Self); end;
  1301. end;
  1302.  
  1303. procedure TPlot.SetMouseMode(M: TPlotMouseMode);
  1304. begin
  1305.   if ((M=pmTranslate) or (FMouseMode=pmTranslate)) and (FMouseMode<>M) and
  1306.   SelectionVisible then begin DrawSelection; FMouseMode:=M; DrawSelection; end
  1307.   else FMouseMode:=M;
  1308. end;
  1309.  
  1310. procedure TPlot.DrawSelection;                          {draw selection frame}
  1311. var Buf: array of TPoint; I,J: integer; rX,rY: extended;
  1312. begin
  1313.   with FCanvas.Pen do
  1314.   begin Color:=clBlack; Width:=1; Mode:=pmNotXor; Style:=psDot; end;
  1315.   with FCanvas.Brush do begin Color:=clWhite; Style:=bsSolid; end;
  1316.   DrawLine(SelectionLeft,SelectionTop,SelectionRight,SelectionTop);
  1317.   DrawLine(SelectionLeft,SelectionBottom,SelectionRight,SelectionBottom);
  1318.   DrawLine(SelectionLeft,SelectionTop,SelectionLeft,SelectionBottom);
  1319.   DrawLine(SelectionRight,SelectionTop,SelectionRight,SelectionBottom);
  1320.   if MouseMode=pmTranslate then                {and markers in translate mode}
  1321.   begin
  1322.     FCanvas.Pen.Style:=psSolid; FCanvas.Pen.Width:={1}2{slow!!!};
  1323.     FCanvas.Brush.Color:=clBlack;
  1324.     DrawPoint(SelectionLeft,SelectionTop,ptSquare,MSZ);
  1325.     DrawPoint(SelectionRight,SelectionTop,ptSquare,MSZ);
  1326.     DrawPoint((SelectionLeft+SelectionRight)/2,SelectionTop,ptSquare,MSZ);
  1327.     DrawPoint(SelectionLeft,SelectionBottom,ptSquare,MSZ);
  1328.     DrawPoint(SelectionRight,SelectionBottom,ptSquare,MSZ);
  1329.     DrawPoint((SelectionLeft+SelectionRight)/2,SelectionBottom,ptSquare,MSZ);
  1330.     DrawPoint(SelectionLeft,(SelectionTop+SelectionBottom)/2,ptSquare,MSZ);
  1331.     DrawPoint(SelectionRight,(SelectionTop+SelectionBottom)/2,ptSquare,MSZ);
  1332.     if (Translating<>ptmNo) and (TransPointCount>0) then
  1333.     begin                                           {draw translation preview}
  1334.       SetLength(Buf,TransPointCount); J:=0;
  1335.       for I:=0 to TransPointCount-1 do
  1336.       begin
  1337.         rX:=Translate(TransBuf[I].X,FSelectionLeft,
  1338.         FSelectionRight,TransX1,TransX2);
  1339.         rY:=Translate(TransBuf[I].Y,FSelectionBottom,
  1340.         FSelectionTop,TransY1,TransY2);
  1341.         if not RealToIntCoords(rX,rY,Buf[J].X,Buf[J].Y) then Inc(J);
  1342.       end;
  1343.       if J>0 then begin SetLength(Buf,J); FCanvas.PolyLine(Buf); end;
  1344.     end;
  1345.   end;
  1346. end;
  1347.  
  1348. {----------------------------------------------------------------}
  1349. {--- next method is the heart of TPlot - it does all stuff!!! ---}
  1350. {----------------------------------------------------------------}
  1351. procedure TPlot.Paint;
  1352. type TPointCache=array [1..MaxInt div SizeOf(TPoint)-1] of TPoint;
  1353. var S: string[250]; I,J,tmp,tmp2: integer;                   {general service}
  1354.     X,Xo,Y,Yo: extended; First: boolean;                     {for draw series}
  1355.     bfXY,bfY,bfX,bfX2: extended;                            {for bestfit line}
  1356.     PointCache: ^TPointCache;
  1357.     PointCacheSize: integer;
  1358.     fX1,fX2: extended;                          {for functional series limits}
  1359. begin
  1360.   Screen.Cursor:=crHourGlass;
  1361.   if not Printing then begin FWidth:=Width; FHeight:=Height; end;
  1362.   {SCALE series:}
  1363.   ShowPlotHint(msgScaling);
  1364.   if XAxis.AutoScale or YAxis.AutoScale then
  1365.   for I:=0 to Series.Count-1 do        {calculate SERIE SCALES (if possible!)}
  1366.   begin
  1367.     with Series[I] do                                        {I-serie counter}
  1368.     begin
  1369.       Scaled:=false; tmp:=0;                            {point counter for BF}
  1370.       bfXY:=0; bfX:=0; bfY:=0; bfX2:=0;     {clear best fit sums accumulators}
  1371.       if Empty then Continue;                         {empty serie; goto next}
  1372.       for J:=FirstLine to LastLine do
  1373.       begin
  1374.         if ((J mod Interleave)<>0) and (LastLine<>J) then Continue;
  1375.         try
  1376.           FN:=J-FirstLine; FNA:=J;    {initialize expression pseudoparameters}
  1377.           if GetDataPoint(Container.Items[J], XColumn, YColumn,
  1378.           XExpression, YExpression, X, Y) then Continue;
  1379.           bfX:=bfX+X; bfY:=bfY+Y; bfXY:=bfXY+(X*Y); bfX2:=bfX2+(X*X);   {BF..}
  1380.           Inc(tmp);
  1381.           if Scaled then
  1382.           begin
  1383.             if X<X1 then X1:=X; if X>X2 then X2:=X;       {X,Y1-min; X,Y2-max}
  1384.             if Y<Y1 then Y1:=Y; if Y>Y2 then Y2:=Y;
  1385.           end else begin Scaled:=true; X1:=X; Y1:=Y; X2:=X; Y2:=Y; end;
  1386.         except                                 {no data in list or trealdata!}
  1387.           on EListError do
  1388.           begin
  1389.             Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1390.             ShowPlotError(Format(errSerieBlock,[I])); Break;
  1391.           end;
  1392.           on ERealDataError do
  1393.           begin
  1394.             Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1395.             ShowPlotError(Format(errSerieCols,[I,J])); Break;
  1396.           end;
  1397.           on E:EMathParser do
  1398.           begin
  1399.             Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1400.             ShowPlotError(Format(errSerieExpr,[I,E.Message])); Break;
  1401.           end;
  1402.         end;{try}
  1403.       end;{for J}
  1404.       if Scaled and (tmp>0) then             {calculate best fit coefficients}
  1405.       begin
  1406.         bfA:=tmp*bfX2-bfX*bfX; if bfA<>0 then
  1407.         begin bfA:=(tmp*bfXY-bfX*bfY)/bfA; bfB:=(bfY-bfA*bfX)/tmp; end
  1408.         else FShowBestFit:=false;                                    {unable!}
  1409.       end else FShowBestFit:=false;
  1410.     end;{with}
  1411.   end;{scale series}
  1412.   First:=true;                              {now calculate Min-Max BY SERIES!}
  1413.   for I:=0 to Series.Count-1 do with Series[I] do
  1414.   begin
  1415.     if not (XAxis.AutoScale or YAxis.AutoScale) then Scaled:=false; {clear!!!}
  1416.     if not Scaled then Continue;
  1417.     if First then begin First:=false; Xo:=X1; X:=X2; Yo:=Y1; Y:=Y2; end else
  1418.     begin
  1419.       if X1<Xo then Xo:=X1; if X2>X then X:=X2;            {X,Yo-min, X,Y-max}
  1420.       if Y1<Yo then Yo:=Y1; if Y2>Y then Y:=Y2;
  1421.     end;
  1422.   end;
  1423.   if not First then                         {set axes scale (w/o side effect)}
  1424.   begin
  1425.     if YAxis.AutoScale then with YAxis do
  1426.     begin FMin:=Yo-(Y-Yo)*FMargins; FMax:=Y+(Y-Yo)*FMargins; end;
  1427.     if XAxis.AutoScale then with XAxis do
  1428.     begin FMin:=Xo-(X-Xo)*FMargins; FMax:=X+(X-Xo)*FMargins; end;
  1429.     fX1:=Xo; fX2:=X;                    {remember scale for functional series}
  1430.   end else begin fX1:=XAxis.Min; fX2:=XAxis.Max; end;
  1431.   {START PAINT:}
  1432.   ShowPlotHint(msgPlotting);
  1433.   {}
  1434.   with FCanvas.Pen do        {Set pen attributes (frame MUST be always black)}
  1435.   begin Color:=clBlack; Width:=1; Mode:=pmCopy; Style:=psSolid; end;
  1436.   if Transparent or Printing then FCanvas.Brush.Style:=bsClear  {transparent?}
  1437.   else with FCanvas do begin Brush.Style:=bsSolid; Brush.Color:=Color; end;
  1438.   if BorderStyle=bsSingle then FCanvas.Rectangle(0,0,FWidth,FHeight)   {frame}
  1439.   else FCanvas.FillRect(Rect(0,0,FWidth,FHeight));
  1440.   {}
  1441.   FCanvas.Brush.Style:=bsClear;           {update brush style (for textouts!)}
  1442.   {Calculate gaps, labels & axes sizes:}
  1443.   FCanvas.Font:=XAxis.Font;{X}
  1444.   S:='.'; for I:=1 to XAxis.Format.Width do S:=S+'E';         {simulate label}
  1445.   if XAxis.Format.FType=ffExponent then                              {correct}
  1446.   begin S:=S+'+E0'; if XAxis.Format.Decimals=3 then S:=S+'00'; end;
  1447.   XLabelW:=FCanvas.TextWidth(S); XLabelH:=FCanvas.TextHeight(S);
  1448.   FCanvas.Font:=YAxis.Font;{Y}
  1449.   S:='.'; for I:=1 to YAxis.Format.Width do S:=S+'E';
  1450.   if YAxis.Format.FType=ffExponent then
  1451.   begin S:=S+'+E0'; if YAxis.Format.Decimals=3 then S:=S+'00'; end;
  1452.   YLabelW:=FCanvas.TextWidth(S); YLabelH:=FCanvas.TextHeight(S);
  1453.   {derive:}
  1454.   if XAxis.Title='' then XAxisGap:=XLabelH+YLabelH else
  1455.   XAxisGap:=2*XLabelH+YLabelH;
  1456.   YAxisGap:=YLabelW+2*YlabelH;
  1457.   XAxisLen:=FWidth-YAxisGap-(Round(XLabelW/2)+XLabelH);
  1458.   if YAxis.Title='' then YAxisLen:=FHeight-XAxisGap-(YLabelH div 2)
  1459.   else YAxisLen:=FHeight-XAxisGap-YLabelH;
  1460.   XTickLen:=YLabelH; YTickLen:=XLabelH;
  1461.   {Draw Y-Axis:}
  1462.   FCanvas.Font:=YAxis.Font; FCanvas.Pen:=YAxis.Pen;
  1463.   if Printing then FCanvas.Pen.Width:=          {when printing - in points!!!}
  1464.     round(FCanvas.Pen.Width*FCanvas.Font.PixelsPerInch/PPI);
  1465.   with FCanvas do
  1466.   begin
  1467.     MoveTo(YAxisGap, FHeight-XAxisGap);                            {main axis}
  1468.     LineTo(YAxisGap, FHeight-XAxisGap-YAxisLen);
  1469.     if YAxis.ShowGrid then                  {duplicate at the right of X-axis}
  1470.     begin
  1471.       MoveTo(YAxisGap+XAxisLen, FHeight-XAxisGap);
  1472.       LineTo(YAxisGap+XAxisLen, FHeight-XAxisGap-YAxisLen);
  1473.     end;
  1474.     for I:=0 to YAxis.MajorTicks do                  {Note! paint N+1 dashes!}
  1475.     begin
  1476.       tmp:=FHeight-XAxisGap-Round(YAxisLen/(YAxis.MajorTicks)*I);{Y of majtik}
  1477.       MoveTo(YAxisGap, tmp); LineTo(YAxisGap-YTickLen, tmp);      {major tick}
  1478.       if YAxis.ShowGrid then Lineto(YAxisGap+XAxisLen, tmp);       {grid line}
  1479.       with YAxis do
  1480.       S:=FloatToStrF(Min+(Max-Min)/MajorTicks*I,
  1481.          Format.FType, Format.Width, Format.Decimals);
  1482.       TextOut(YAxisGap-YTickLen-TextWidth(S)-2,tmp-(YlabelH div 2), S);
  1483.       if I<YAxis.MajorTicks then
  1484.       for J:=1 to YAxis.MinorTicks-1 do                         {NOTE! n-1!!!}
  1485.       begin                                                      {minor ticks}
  1486.         tmp2:=tmp-Round(YAxisLen/(YAxis.MajorTicks*YAxis.MinorTicks)*J);
  1487.         MoveTo(YAxisGap, tmp2); LineTo(YAxisGap-(YTickLen div 2), tmp2);
  1488.       end;
  1489.     end;
  1490.     S:=YAxis.Title;
  1491.     if YAxis.Expression<>'' then S:=S+' <'+YAxis.Expression+'>';
  1492.     if YAxis.Title<>'' then TextOut(YAxisGap,0,S);
  1493.   end;
  1494.   {Draw X-Axis:}
  1495.   FCanvas.Font:=XAxis.Font; FCanvas.Pen:=XAxis.Pen;
  1496.   if Printing then FCanvas.Pen.Width:=          {when printing - in points!!!}
  1497.     round(FCanvas.Pen.Width*FCanvas.Font.PixelsPerInch/PPI);
  1498.   with FCanvas do
  1499.   begin
  1500.     MoveTo(YAxisGap, FHeight-XAxisGap);                            {main axis}
  1501.     LineTo(YAxisGap+XAxisLen, FHeight-XAxisGap);
  1502.     if XAxis.ShowGrid then                    {duplicate at the top of Y-axis}
  1503.     begin
  1504.       MoveTo(YAxisGap, FHeight-XAxisGap-YAxisLen);
  1505.       LineTo(YAxisGap+XAxisLen, FHeight-XAxisGap-YAxisLen);
  1506.     end;
  1507.     for I:=0 to XAxis.MajorTicks do
  1508.     begin
  1509.       tmp:=YAxisGap+Round(XAxisLen/(XAxis.MajorTicks)*I);    {X of major tick}
  1510.       MoveTo(tmp, FHeight-XAxisGap); LineTo(tmp, FHeight-XAxisGap+XTickLen);
  1511.       if XAxis.ShowGrid then LineTo(tmp, FHeight-XAxisGap-YAxisLen);    {grid}
  1512.       with XAxis do
  1513.       S:=FloatToStrF(Min+(Max-Min)/MajorTicks*I,
  1514.          Format.FType, Format.Width, Format.Decimals);
  1515.       S:=Trim(S);
  1516.       TextOut(tmp-(TextWidth(S)div 2), FHeight-XAxisGap+XTickLen, S);
  1517.       if I<XAxis.MajorTicks then
  1518.       for J:=1 to XAxis.MinorTicks-1 do
  1519.       begin
  1520.         tmp2:=tmp+Round(XAxisLen/(XAxis.MajorTicks*XAxis.MinorTicks)*J);
  1521.         MoveTo(tmp2, FHeight-XAxisGap);
  1522.         LineTo(tmp2, FHeight-XAxisGap+(XTickLen div 2));
  1523.       end;
  1524.     end;
  1525.     S:=XAxis.Title;
  1526.     if XAxis.Expression<>'' then S:=S+' <'+XAxis.Expression+'>';
  1527.     if XAxis.Title<>'' then TextOut((FWidth-TextWidth(S)) div 2,
  1528.     FHeight-XAxisGap+XTickLen+XLabelH, S);
  1529.   end;
  1530.   {Draw series:}
  1531.   for I:=0 to Series.Count-1 do
  1532.   begin
  1533.     with Series[I] do
  1534.     begin
  1535.       if Empty then Continue;                         {empty serie; goto next}
  1536.       FCanvas.Pen:=Series[I].Pen;
  1537.       FCanvas.Brush:=Series[I].Brush;                              {set tools}
  1538.       if Printing then FCanvas.Pen.Width:=      {when printing - in points!!!}
  1539.          round(FCanvas.Pen.Width*FCanvas.Font.PixelsPerInch/PPI);
  1540.       try
  1541.         tmp:=1; PointCacheSize:=LastLine-FirstLine+1;
  1542.         Scaled:=false; tmp2:=0;                         {point counter for BF}
  1543.         bfXY:=0; bfX:=0; bfY:=0; bfX2:=0;   {clear best fit sums accumulators}
  1544.         GetMem(PointCache,PointCacheSize*SizeOf(TPoint));    {for ALL points!}
  1545.         for J:=FirstLine to LastLine do                     {fill point cache}
  1546.         begin
  1547.           if ((J mod Interleave)<>0) and (LastLine<>J) then Continue;
  1548.           try
  1549.             FN:=J-FirstLine; FNA:=J;
  1550.             if GetDataPoint(Container.Items[J], XColumn, YColumn,
  1551.             XExpression, YExpression, X, Y) then Continue;
  1552.             bfX:=bfX+X; bfY:=bfY+Y; bfXY:=bfXY+(X*Y); bfX2:=bfX2+(X*X); {BF..}
  1553.             Inc(tmp2);
  1554.             if Scaled then
  1555.             begin
  1556.               if X<X1 then X1:=X; if X>X2 then X2:=X;     {X,Y1-min; X,Y2-max}
  1557.               if Y<Y1 then Y1:=Y; if Y>Y2 then Y2:=Y;
  1558.             end else begin Scaled:=true; X1:=X; Y1:=Y; X2:=X; Y2:=Y; end;
  1559.           except                               {no data in list or trealdata!}
  1560.             on EListError do
  1561.             begin
  1562.              Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1563.              ShowPlotError(Format(errSerieBlock,[I])); Break;
  1564.             end;
  1565.             on ERealDataError do
  1566.             begin
  1567.              Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1568.              ShowPlotError(Format(errSerieCols,[I,J])); Break;
  1569.             end;
  1570.             on E:EMathParser do
  1571.             begin
  1572.               Scaled:=false; ClearBlock; Screen.Cursor:=crDefault;
  1573.               ShowPlotError(Format(errSerieExpr,[I,E.Message])); Break;
  1574.             end;
  1575.           end;
  1576.           if not RealToIntCoords(X,Y,PointCache^[tmp].X,PointCache^[tmp].Y)
  1577.           then inc(tmp);                 {remember in cache only valid points}
  1578.         end;{for J}
  1579.       finally                {now data in cache valid; ready to paint points!}
  1580.         if LineVisible then Polyline(FCanvas.Handle,PointCache^,tmp-1); {line}
  1581.         if PointVisible then
  1582.         for J:=1 to tmp-1 do PaintPoint(PointCache^[J].X,PointCache^[J].Y,
  1583.         PointType,PointSize,FCanvas);                             {and points}
  1584.         FreeMem(PointCache,PointCacheSize*SizeOf(TPoint));  {deallocate cache}
  1585.       end;
  1586.       if Scaled and (tmp2>0) then            {calculate best fit coefficients}
  1587.       begin
  1588.         bfA:=tmp2*bfX2-bfX*bfX; if bfA<>0 then
  1589.         begin bfA:=(tmp2*bfXY-bfX*bfY)/bfA; bfB:=(bfY-bfA*bfX)/tmp2; end
  1590.         else FShowBestFit:=false;                                    {unable!}
  1591.       end else FShowBestFit:=false;
  1592.       if ShowBestFit and Scaled and (bfA<>0) then         {draw best fit line}
  1593.       begin
  1594.         if X1<XAxis.Min then X1:=XAxis.Min;      // correct fbf line rectangle
  1595.         if X2>XAxis.Max then X2:=XAxis.Max;
  1596.         if Y1<YAxis.Min then Y1:=YAxis.Min;
  1597.         if Y2>YAxis.Max then Y2:=YAxis.Max;
  1598.         Xo:=X1; Yo:=bfA*X1+bfB;
  1599.         if Yo<Y1 then begin Yo:=Y1; Xo:=(Y1-bfB)/bfA; end;
  1600.         if Yo>Y2 then begin Yo:=Y2; Xo:=(Y2-bfB)/bfA; end;
  1601.         X:=X2; Y:=bfA*X2+bfB;
  1602.         if Y<Y1 then begin Y:=Y1; X:=(Y1-bfB)/bfA; end;
  1603.         if Y>Y2 then begin Y:=Y2; X:=(Y2-bfB)/bfA; end;
  1604.         DrawLine(Xo,Yo,X,Y);
  1605.       end;
  1606.     end;{with}
  1607.   end;{for}
  1608.   {Draw functional series: this cycle slightly differs from above!}
  1609.   for I:=0 to Series.Count-1 do
  1610.   begin
  1611.     with Series[I] do
  1612.     if IsFunction and (LastLine>FirstLine){!!!} and (YExpression<>'') then
  1613.     begin
  1614.       FCanvas.Pen:=Series[I].Pen;
  1615.       FCanvas.Brush:=Series[I].Brush;
  1616.       if Printing then FCanvas.Pen.Width:=
  1617.          round(FCanvas.Pen.Width*FCanvas.Font.PixelsPerInch/PPI);
  1618.       PointCacheSize:=LastLine-FirstLine+1;
  1619.       tmp:=1; GetMem(PointCache,PointCacheSize*SizeOf(TPoint));
  1620.       try
  1621.         for J:=FirstLine to LastLine do
  1622.         try
  1623.           FX:=fX1+(fX2-fX1)/(LastLine-FirstLine)*(J-FirstLine);
  1624.           FY:=FParser.Parse(YExpression);   {note: use ONLY YAxis expression!}
  1625.           if YAxis.Expression<>'' then FY:=FParser.Parse(YAxis.Expression);
  1626.           if not RealToIntCoords(FX,FY,PointCache^[tmp].X,PointCache^[tmp].Y)
  1627.           then inc(tmp);                   {as for "normal" serie (see above)}
  1628.         except
  1629.           on E:EMathParser do
  1630.           begin
  1631.             Screen.Cursor:=crDefault; YExpression:='';
  1632.             ShowPlotError(Format(errSerieExpr,[I,E.Message])); Break;
  1633.           end;
  1634.         end;
  1635.         if LineVisible then Polyline(FCanvas.Handle,PointCache^,tmp-1);
  1636.         if PointVisible then
  1637.         for J:=1 to tmp-1 do PaintPoint(PointCache^[J].X,PointCache^[J].Y,
  1638.         PointType,PointSize,FCanvas);
  1639.       finally
  1640.         FreeMem(PointCache,PointCacheSize*SizeOf(TPoint));
  1641.       end;
  1642.     end;{with if}
  1643.   end;{for}
  1644.   FCanvas.Font:=Font; FCanvas.Pen:=Pen; FCanvas.Brush:=Brush;
  1645.   if Assigned(OnPaint) then OnPaint(Self);         {allow additional painting}
  1646.   if SelectionVisible then DrawSelection;     {draw selection frame after all}
  1647.   ShowPlotHint(' '); Screen.Cursor:=crDefault;
  1648. end;
  1649. {-------------------------------------------------------------}
  1650.  
  1651. {component registration - this unit may be used separately from dm2000}
  1652. procedure Register;
  1653. begin
  1654.   RegisterComponents('DM2000', [TPlot]);
  1655. end;
  1656.  
  1657. end.
  1658.