unit GraphUnit;
// -----------------------------------
// PICViewer - X-Y Graph Plotting form
// -----------------------------------

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, Maths, CurveFitter, XYPlotDisplay, StdCtrls, OleCtrls, SHDocVw,
  HTMLLabel, ExtCtrls, ViewUnit, math ;

type
  TGraphFrm = class(TForm)
    PlotControlsGrp: TGroupBox;
    GroupBox3: TGroupBox;
    Label2: TLabel;
    bXYFit: TButton;
    cbXYEquation: TComboBox;
    cbPlotNum: TComboBox;
    GroupBox9: TGroupBox;
    lbCursor: THTMLLabel;
    bNormalPlot: TButton;
    bPercentagePlot: TButton;
    PlotGrp: TGroupBox;
    bDeltaYPlot: TButton;
    bSetAxes: TButton;
    Fit: TCurveFitter;
    lbResults: THTMLLabel;
    Plot: TXYPlotDisplay;
    procedure FormResize(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure PlotCursorChange(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure bPercentagePlotClick(Sender: TObject);
    procedure bNormalPlotClick(Sender: TObject);
    procedure bDeltaYPlotClick(Sender: TObject);
    procedure bXYFitClick(Sender: TObject);
    procedure bSetAxesClick(Sender: TObject);
  private
    { Private declarations }
    LineStyle : TPenStyle ;

    // Plot cursors
    InitialisePlotCursors : Boolean ;
    PlotCursorNum : Integer ;
    PlotCursorNumPos : Single ;
    PlotCursor0 : Integer ;
    PlotCursor0Pos : Single ;
    PlotCursor1 : Integer ;
    PlotCursor1Pos : Single ;
    PlotCursor2 : Integer ;
    PlotCursor2Pos : Single ;
    FittedPlotNum : Integer ;

    xMin : Single ;           // Minimum X value
    xMax : Single ;           // Maximum X value
    FXUnits : String ;         // X axis units
    FYUnits : String ;         // Y axis units
    MaxLineNum : Integer ;    // Highest line # in use
    YScale : Array[0..MaxROIs] of Single ;
    YSubtract : Array[0..MaxROIs] of Single ;


    function GetXAxisLabel : String ;
    procedure SetXAxisLabel( Value : String )  ;
    function GetYAxisLabel : String ;
    procedure SetYAxisLabel( Value : String )  ;

  public
    { Public declarations }
    Index : Integer ;
    procedure CreateLine( LineNum : Integer ) ;
    procedure InitialiseCursors ;
    procedure AddPoint( LineNum : Integer ; x : Single ; y : Single ) ;
    procedure CopyImageToClipboard ;
    procedure CopyDataToClipboard ;
    procedure Print ;
    property XAxisLabel : String read GetXAxisLabel write SetXAxisLabel ;
    property YAxisLabel : String read GetYAxisLabel write SetYAxisLabel ;
    property XUnits : String read FXUnits write FXUnits ;
    property YUnits : String read FYUnits write FYUnits ;
  end;

var
  GraphFrm: TGraphFrm;

implementation



{$R *.dfm}

Uses Setfitpa, Printgra, Setaxes;


const
    NumFitPoints = 500 ;

procedure TGraphFrm.FormShow(Sender: TObject);
// --------------------------------------
// Initialisations when form is displayed
// --------------------------------------
var
     i : Integer ;
begin

     // Set up graph cursors
     Plot.ClearVerticalCursors ;
     PlotCursorNum := Plot.AddVerticalCursor( clGreen, '' ) ;
     //PlotCursorNumPos := NumFrames div 2 ;
     PlotCursor0 := Plot.AddVerticalCursor( clBlue, 'x=0' ) ;
     PlotCursor1 := Plot.AddVerticalCursor( clBlue, 'f' ) ;
     PlotCursor2 := Plot.AddVerticalCursor( clBlue, 'f' ) ;
     Plot.LinkVerticalCursors( PlotCursor1, PlotCursor2 ) ;

     InitialisePlotCursors := True ;

     FittedPlotNum := 0 ;

     xMin := 1E30 ;
     xMax := -1E30 ;
     XUnits := '' ;
     YUnits := '' ;
     MaxLineNum := 0 ;

     for i := 0 to High(YScale) do begin
         YScale[i] := 1.0 ;
         YSubtract[i] := 0.0 ;
         end ;

     bPercentagePlot.Enabled := True ;
     bDeltaYPlot.Enabled := True ;
     bNormalPlot.Enabled := False ;

     // X-Y plot fitting equation options
     cbXYEquation.Clear ;
     cbXYEquation.Items.AddObject( 'None', TObject(None)) ;
     cbXYEquation.Items.AddObject( 'Linear', TObject(Linear)) ;
     //cbXYEquation.Items.AddObject( 'Power', TObject(PowerFunc)) ;
     cbXYEquation.Items.AddObject( 'Exponential', TObject(Exponential)) ;
     cbXYEquation.Items.AddObject( '2 Exponentials', TObject(Exponential2)) ;
     cbXYEquation.Items.AddObject( 'Boltzmann', TObject(Boltzmann)) ;
     cbXYEquation.ItemIndex := 0 ;

     // Clear fitted plot selection list
     cbPlotNum.Clear ;

     Resize ;
     end;


procedure TGraphFrm.FormResize(Sender: TObject);
// ---------------------------------------------------------------
// Update control size/position when size of form has been changed
// ---------------------------------------------------------------
begin

     PlotControlsGrp.Top := Max( ClientHeight - PlotControlsGrp.Height - 5,2) ;
     PlotControlsGrp.Width := Max( ClientWidth - 2*PlotControlsGrp.Left,2) ;
     lbCursor.Height := (1+Plot.NumLines)*18 ;
     lbCursor.Top := Max(PlotControlsGrp.Top - lbCursor.Height - 2,2) ;

     Plot.Width := Max(ClientWidth - 2*Plot.Left,2) ;
     Plot.Height := Max(lbCursor.Top - 1 - Plot.Top,2) ;

     lbResults.Width := Max(PlotControlsGrp.ClientWidth - lbResults.Left - 5,2) ;
     lbResults.Height := Max(PlotControlsGrp.ClientHeight - lbResults.Top - 5,2) ;

     end;


procedure TGraphFrm.CreateLine( LineNum : Integer ) ;
// -----------------------------
// Create new line on graph plot
// -----------------------------
begin

    Plot.xAxisAutoRange := True ;
    Plot.yAxisAutoRange := True ;
    LineStyle := psSolid ;

    Plot.CreateLine( LineNum , clBlue, msNumber, LineStyle ) ;

    // Highest line # in use
    MaxLineNum := MaxInt( [MaxLineNum,LineNum] ) ;

    cbPlotNum.Items.AddObject(format('ROI #%d',[LineNum]),TObject(LineNum)) ;
    // Update ROI fit selection list
    //cbPlotNum.ItemIndex := cbPlotNum.Items.IndexOfObject(TObject(FitPlotNum)) ;

    Resize ;

    end ;


procedure TGraphFrm.InitialiseCursors ;
// ----------------------------------------------------
// Set readout and fitting cursors to default positions
// ----------------------------------------------------
begin
     PlotCursor0Pos := xMin ;
     PlotCursor1Pos := xMin ;
     PlotCursor2Pos := xMax ;
     PlotCursorNumPos := (xMin + xMax)*0.5 ;
     Plot.VerticalCursors[PlotCursorNum] := PlotCursorNumPos ;
     Plot.VerticalCursors[PlotCursor0] := PlotCursor0Pos ;
     Plot.VerticalCursors[PlotCursor1] := PlotCursor1Pos ;
     Plot.VerticalCursors[PlotCursor2] := PlotCursor2Pos ;

     end ;


procedure TGraphFrm.AddPoint(
          LineNum : Integer ;
          x : Single ;
          y : Single ) ;
// --------------------------
// Add point to line on graph
// --------------------------
begin

    Plot.AddPoint( LineNum, x, y ) ;

    // Find min./max. of X variable range
    xMin := MinFlt([x,xMin]) ;
    xMax := MaxFlt([x,xMax]) ;

    end ;

Function TGraphFrm.GetXAxisLabel : String ;
// ----------------
// Get X axis label
// ----------------
begin
     Result := Plot.XAxisLabel ;
     end ;


procedure TGraphFrm.SetXAxisLabel( Value : String ) ;
// ----------------
// Set X axis label
// ----------------
begin
     Plot.XAxisLabel := Value ;
     end ;


Function TGraphFrm.GetYAxisLabel : String ;
// ----------------
// Get Y axis label
// ----------------
begin
     Result := Plot.YAxisLabel ;
     end ;


procedure TGraphFrm.SetYAxisLabel( Value : String ) ;
// ----------------
// Set Y axis label
// ----------------
begin
     Plot.YAxisLabel := Value ;
     end ;


procedure TGraphFrm.PlotCursorChange(Sender: TObject);
// ---------------------------------
// ROI Intensity Plot cursor changed
// ---------------------------------
var
     x,y : Single ;
     i : Integer ;
     s : string ;
begin
     s := '' ;

     for i := 1 to MaxLineNum do if Plot.GetNumPointsInLine(i) > 0 then begin
         Plot.GetPoint( i, Plot.FindNearestIndex(i,PlotCursorNum),x,y ) ;
         if s = '' then s := format('%.0f %s', [x,FXUnits]) ;
         s := s + format('<br>ROI.%d=%.0f %s',[i,y,FYUnits]) ;
         end ;
     lbCursor.Caption := s ;

     PlotCursorNumPos := Round(Plot.VerticalCursors[PlotCursorNum]) ;
     lbCursor.Left := Plot.XToCanvasCoord(
                          Plot.VerticalCursors[PlotCursorNum] )
                          - (lbCursor.Width div 2) ;

     end;


procedure TGraphFrm.FormClose(Sender: TObject; var Action: TCloseAction);
// ---------------------------
// Tidy up when form is closed
// ---------------------------
begin
     Action := caFree ;
     end;


procedure TGraphFrm.bPercentagePlotClick(Sender: TObject);
// --------------------------------------
// Plot graph as % of Y value at x=0 cursor
// --------------------------------------
var
    x : Single ;
    Y : Single ;
    YOldScale : Single ;
    YOldSubtract : Single ;
    YAtX0 : Single ;
    Line : Integer ;
    i : Integer ;
begin

     Plot.yAxisAutoRange := True ;
     Plot.xAxisAutoRange := True ;

     for Line := 0 to MaxLineNum do if Plot.GetNumPointsInLine(Line) > 0 then begin

         // Old scaling values
         YOldScale := YScale[Line] ;
         YOldSubtract := YSubtract[Line] ;

         // Get and save Y intensity values at X=0 cursor position
         Plot.GetPoint( Line, Plot.FindNearestIndex(Line,PlotCursor0),x,YAtX0 ) ;
         YAtX0 := (YAtX0/YOldScale) + YOldSubtract ;
         if YAtX0 = 0.0 then Break ;

         YScale[Line] := 100.0 / YAtX0 ;
         YSubtract[Line] := 0.0 ;

         for i := 0 to Plot.GetNumPointsInLine(Line)-1 do begin
            Plot.GetPoint(Line,i,x,Y);
            // Undo existing scaling
            Y := (Y/YOldScale)  + YOldSubtract ;
            Y := YScale[Line] * ( Y - YSubtract[Line] )  ;
            Plot.SetPoint(Line,i,x,Y);
            end ;

         end ;

     Plot.Invalidate ;

     bPercentagePlot.Enabled := False ;
     bDeltaYPlot.Enabled := True ;
     bNormalPlot.Enabled := True ;

     end ;


procedure TGraphFrm.bNormalPlotClick(Sender: TObject);
// --------------------------------------
// Plot graph as Y intensity values
// --------------------------------------
var
    x : Single ;
    Y : Single ;
    YOldScale : Single ;
    YOldSubtract : Single ;
    YAtX0 : Single ;
    Line : Integer ;
    i : Integer ;
begin

     Plot.yAxisAutoRange := True ;
     Plot.xAxisAutoRange := True ;

     for Line := 0 to MaxLineNum do if Plot.GetNumPointsInLine(Line) > 0 then begin

         // Old scaling values
         YOldScale := YScale[Line] ;
         YOldSubtract := YSubtract[Line] ;

         YScale[Line] := 1.0 ;
         YSubtract[Line] := 0.0 ;

         for i := 0 to Plot.GetNumPointsInLine(Line)-1 do begin
            Plot.GetPoint(Line,i,x,Y);
            // Undo existing scaling
            Y := (Y/YOldScale)  + YOldSubtract ;
            Y := YScale[Line] * ( Y - YSubtract[Line] )  ;
            Plot.SetPoint(Line,i,x,Y);
            end ;

         end ;

     Plot.Invalidate ;

     bPercentagePlot.Enabled := True ;
     bDeltaYPlot.Enabled := True ;
     bNormalPlot.Enabled := False ;

     end ;


procedure TGraphFrm.bDeltaYPlotClick(Sender: TObject);
// --------------------------------------
// Plot graph as dY/Y(x=0) values
// --------------------------------------
var
    x : Single ;
    Y : Single ;
    YOldScale : Single ;
    YOldSubtract : Single ;
    YAtX0 : Single ;
    Line : Integer ;
    i : Integer ;
begin

     Plot.yAxisAutoRange := True ;
     Plot.xAxisAutoRange := True ;

     for Line := 0 to MaxLineNum do if Plot.GetNumPointsInLine(Line) > 0 then begin

         // Old scaling values
         YOldScale := YScale[Line] ;
         YOldSubtract := YSubtract[Line] ;

         // Get and save Y intensity values at X=0 cursor position
         Plot.GetPoint( Line, Plot.FindNearestIndex(Line,PlotCursor0),x,YAtX0 ) ;
         YAtX0 := (YAtX0/YOldScale) + YOldSubtract ;
         if YAtX0 = 0.0 then Break ;

         YScale[Line] := 1.0 / YAtX0 ;
         YSubtract[Line] := YAtX0 ;

         for i := 0 to Plot.GetNumPointsInLine(Line)-1 do begin
            Plot.GetPoint(Line,i,x,Y);
            // Undo existing scaling
            Y := (Y/YOldScale)  + YOldSubtract ;
            Y := YScale[Line] * ( Y - YSubtract[Line] )  ;
            Plot.SetPoint(Line,i,x,Y);
            end ;

         end ;

     Plot.Invalidate ;

     bPercentagePlot.Enabled := True ;
     bDeltaYPlot.Enabled := False ;
     bNormalPlot.Enabled := True ;
     end ;



procedure TGraphFrm.bXYFitClick(Sender: TObject);
// --------------------------------------
//  Fit mathematical functions to X-Y plot
//  --------------------------------------
var
   i,iStart,iEnd,nFit : Integer ;
   x,dx,xStart,xEnd,y,xZero : single ;
   FittedLine : Integer ;
   FitPlotNum : Integer ;
   Line : Integer ;
   OK : Boolean ;
begin

     // Quit if no ROI plots availables
     if cbPlotNum.Items.Count <= 0 then Exit ;
     if cbPlotNum.ItemIndex < 0 then Exit ;

     OK := True ;

     // Make X/Y data graph markers only
     FitPlotNum := Integer(cbPlotNum.Items.Objects[cbPlotNum.ItemIndex]) ;
     for Line := 1 to MaxLineNum do begin
         if Line = FitPlotNum then Plot.LineStyles[Line] := psClear
                              else Plot.LineStyles[Line] := psSolid ;
         end ;

     // Clear any existing fitted line / results
     FittedLine := 0 ;
     Plot.CreateLine( FittedLine, clRed, msNone, psSolid ) ;

     Fit.Equation := TCFEqnType(cbXYEquation.Items.Objects[cbXYEquation.ItemIndex]) ;
     if Fit.Equation = None then begin
        lbResults.Caption := Fit.FitResults ;
        Exit ;
        end ;

     { Copy data into fitting array }

     { Lower and upper x data limit set by display cursors }
     Plot.GetPoint( FitPlotNum,
                    Plot.FindNearestIndex(FitPlotNum,PlotCursor0),
                    xZero,y )  ;
     iStart := MinInt([ Plot.FindNearestIndex(FitPlotNum,PlotCursor1),
                        Plot.FindNearestIndex(FitPlotNum,PlotCursor2)]) ;
     iEnd := MaxInt([ Plot.FindNearestIndex(FitPlotNum,PlotCursor1),
                      Plot.FindNearestIndex(FitPlotNum,PlotCursor2)]) ;

     Fit.ClearPoints ;
     for i := iStart to iEnd do begin
         Plot.GetPoint( FitPlotNum,i,x,y) ;
         Fit.AddPoint(x-xZero,y);
         end ;

     { Abort curve fit, if not enough data points }
     if Fit.NumPoints < Fit.NumParameters then begin
        MessageDlg( format('%d points is insufficient for fit',[nFit]),
                           mtWarning, [mbOK], 0 ) ;
        Exit ;
        end ;

     { Let user create/modify initial parameter settings and/or
       fix parameters at constant values }
     SetFitParsFrm.Fit := Fit ;
     SetFitParsFrm.ShowModal ;
     if SetFitParsFrm.ModalResult <> mrOK then Exit ;

     // Find parameters of best fitting curve
     Fit.ParametersSet := True ;
     Fit.FitCurve ;
     // Copy results
     lbResults.Caption := Fit.FitResults ;

     // Plot equation on graph
     if Fit.GoodFit and (Fit.Equation <> None) then begin
        Plot.GetPoint(FitPlotNum,iEnd,xEnd,y) ;
        dx := (xEnd - xZero) / NumFitPoints ;
        x := xZero ;
        Plot.ShowLines := True ;
        for i := 0 to NumFitPoints-1 do begin
            Plot.AddPoint( FittedLine, x, Fit.EquationValue(x-xZero) ) ;
            x := x + dx ;
            end ;
        end ;

     { Make sure plot is updated with changes }
     Plot.Invalidate ;

     //FitAvailable := OK ;

     end ;


procedure TGraphFrm.CopyDataToClipboard ;
// -----------------------------------------------------------
// Copy the data in currently displayed graph to the clipboard
// -----------------------------------------------------------
begin
     Plot.CopyDataToClipboard
     end ;


procedure TGraphFrm.CopyImageToClipboard ;
{ -----------------------------------------------------
  Copy image of graph to clipboard as Windows metafile
  ----------------------------------------------------- }
begin

    PrintGraphFrm.Plot := Plot ;
    PrintGraphFrm.ToPrinter := False ;
    PrintGraphFrm.ShowModal ;
    if PrintGraphFrm.ModalResult = mrOK then Plot.CopyImageToClipboard ;

    end ;


procedure TGraphFrm.Print ;
{ ------------------
  Print graph
  ------------------ }
begin
       PrintGraphFrm.Plot := Plot ;
       PrintGraphFrm.ToPrinter := True ;
       PrintGraphFrm.ShowModal ;
       if PrintGraphFrm.ModalResult = mrOK then begin
          Plot.ClearPrinterTitle ;
          Plot.AddPrinterTitleLine(Caption);
          Plot.Print ;
          end ;
       end ;


procedure TGraphFrm.bSetAxesClick(Sender: TObject);
{ ------------------------------
  Set plot axes range/law/labels
  ------------------------------}
begin
     SetAxesFrm.Plot := plot ;
     SetAxesFrm.Histogram := False ;
     SetAxesFrm.ShowModal ;
     Plot.Invalidate ;
     end;


end.
