unit u_LogGraph;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, Forms, Controls,
  Graphics, Dialogs, StdCtrls, ComCtrls;

type
  tProc = procedure;
  tTrace = array of real;
  TFrmLogGraph = class(TForm)
    CbxWhiteBkgd: TCheckBox;
    Label54: TLabel;
    Label55: TLabel;
    Label56: TLabel;
    Label57: TLabel;
    Label58: TLabel;
    Label61: TLabel;
    Label62: TLabel;
    TbxAmplitudeOffset: TEdit;
    TbxAmplitudePerDiv: TEdit;
    TbxNDivs: TEdit;
    TbxPhaseOffset: TEdit;
    TbxPhasePerDiv: TEdit;
    UdAmplitudeOffset: TUpDown;
    UdAmplitudePerDiv: TUpDown;
    UdnDivs: TUpDown;
    UdPhaseOffset: TUpDown;
    UdPhasePerDiv: TUpDown;
    procedure FormDestroy(Sender: TObject);
    procedure InitLogDisplay(StartFrequency, nDecades: integer; Cb:tProc);
		procedure DispAmplitudeTrace(Trace: tTrace; nFrequencies, Colour: integer);
		procedure DispPhaseTrace(Trace: tTrace; nFrequencies, Colour: integer);
    procedure CbxWhiteBkgdClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure UdAmplitudeOffsetClick(Sender: TObject);
    procedure UdAmplitudePerDivClick(Sender: TObject);
    procedure UdnDivsClick(Sender: TObject);
    procedure UdPhaseOffsetClick(Sender: TObject);
    procedure UdPhasePerDivClick(Sender: TObject);

  private
    { private declarations }
  public
    { public declarations }
  end;

const
	SettingsFileName = 'LogGraph.settings';
var
  FrmLogGraph: TFrmLogGraph;
  FrgndColor, BkgndColor: integer;
  TopText: string;

implementation

uses u_complex;
var
  AmplitudeScale, _AmplitudeOffset, PhaseScale, _PhaseOffset: integer;
  nDivs, AmplitudePerDiv, AmplitudeOffset: integer;
  PhasePerDiv, PhaseOffset: integer;
	LogGraphReady, WhiteBackGround: boolean;
  CallPlot: tProc;

// Scaling
procedure TFrmLogGraph.UdAmplitudePerDivClick(Sender: TObject);
begin
  case UdAmplitudePerDiv.position of
    0: AmplitudePerDiv:= 1;
    1: AmplitudePerDiv:= 3;
    2: AmplitudePerDiv:= 10;
    3: AmplitudePerDiv:= 20;
    4: AmplitudePerDiv:= 50;
    5: AmplitudePerDiv:= 100;
  end;
 TbxAmplitudePerDiv.text:= Format('%2d', [AmplitudePerDiv]);
 if LogGraphReady then CallPlot;
end;

procedure TFrmLogGraph.UdAmplitudeOffsetClick(Sender: TObject);
begin
 AmplitudeOffset:= UdAmplitudeOffset.position;
 TbxAmplitudeOffset.text:= Format('%2d', [AmplitudeOffset]);
 if LogGraphReady then CallPlot;
end;

procedure TFrmLogGraph.UdnDivsClick(Sender: TObject);
begin
 nDivs:= UdnDivs.position;
 TbxNDivs.text:= Format('%2d', [nDivs]);
 if LogGraphReady then CallPlot;
end;

procedure TFrmLogGraph.UdPhasePerDivClick(Sender: TObject);
begin
  case UdPhasePerDiv.position of
    0: PhasePerDiv:= 5;
    1: PhasePerDiv:= 10;
    2: PhasePerDiv:= 20;
    3: PhasePerDiv:= 45;
  end;
 TbxPhasePerDiv.text:= Format('%2d', [PhasePerDiv]);
 if LogGraphReady then CallPlot;
end;

procedure TFrmLogGraph.UdPhaseOffsetClick(Sender: TObject);
begin
  PhaseOffset:= UdPhaseOffset.position;
  TbxPhaseOffset.text:= Format('%2d', [PhaseOffset]);
  if LogGraphReady then CallPlot;
end;

procedure TFrmLogGraph.CbxWhiteBkgdClick(Sender: TObject);
begin
	WhiteBackground := CbxWhiteBkgd.Checked = true;
	if LogGraphReady then CallPlot;
end;

procedure InitPlot(StartFrequency, nDecades: integer);
var
  X, Y, I, J: integer;
begin
  FrmLogGraph.caption:= TopText;
  AmplitudeScale:= nDivs * AmplitudePerDiv;
  _AmplitudeOffset:= AmplitudeOffset * AmplitudePerDiv;
  PhaseScale:= nDivs * PhasePerDiv;
  _PhaseOffset:= PhaseOffset * PhasePerDiv;
  With FrmLogGraph do
	begin
    Width:= 1000;
	  With Canvas do
  	begin
   	  if WhiteBackground then
   	  begin
        Brush.Color:= clWhite;
        Pen.Color:= clBlack;
        Font.Color:= clblack;
      end
      else
   	  begin
   	    Brush.Color:= clBlack;
   	    Pen.Color:= clWhite;
        Font.Color:= clWhite;
   	  end;
      Pen.Style := psSolid;
      Rectangle(0, 0, 1000, 700);
      AutoReDraw:= true;
   	end;
    For J:= 0 To nDecades do  // vertical lines for n decades
  	  For I:= 1 To 10 do
  	  begin
        X:= 30 + round(Log10((I)) * 940/nDecades + J * 940/nDecades);
        Canvas.Line (X, 9, X, 675);
        If (I = 1) Or (I = 2) Or (I = 4) Or (I = 6) Or (I = 8) Or (I = 10) Then
    	  begin
     	    Canvas.TextOut(X-8, 680, Format('%2d', [round(I * Ten2Power(J) * StartFrequency)]));
     	  end;
      end;
    For I:= 0 To nDivs do // horizontal lines and scale values
    begin
  	  Y:= trunc(666 / nDivs * I);
  	  Canvas.Line (30, 675 - Y, 970, 675 - Y);
      Canvas.TextOut(3, 670 - Y , Format('%2d', [(I - AmplitudeOffset) * AmplitudePerDiv]));
      Canvas.TextOut(975, 670 - Y , Format('%2d', [(I - PhaseOffset) * PhasePerDiv]));
    end;
	end;
end;

procedure TFrmLogGraph.InitLogDisplay(StartFrequency, nDecades: integer; Cb:Tproc);
begin
  CallPlot := Cb; // set callback procedure
  InitPlot(StartFrequency, nDecades);
end;

procedure TFrmLogGraph.DispAmplitudeTrace(Trace: tTrace; nFrequencies, Colour: integer);
var n, X, Y: integer;
begin
  With FrmLogGraph.Canvas do
  begin
	  Pen.Color:= Colour;
    Pen.Style := psSolid;
	  For n:= 0 To nFrequencies do
    begin
      X:= round(n * 940 / nFrequencies);
      Y:= round((Trace[n] +_AmplitudeOffset) / AmplitudeScale * 666 );
      // clip the graph
      If Y < 0 Then Y:= 0;
      If Y > 666 Then Y:= 666;
      if n = 0 then Moveto(X + 30, 675 - Y)else Lineto(X + 30, 675 - Y);
    end;
  end;
end;

procedure TFrmLogGraph.DispPhaseTrace(Trace: tTrace; nFrequencies, Colour: integer);
var n, X, Y: integer;
begin
  With FrmLogGraph.Canvas do
  begin
	  Pen.Color:= Colour;
    Pen.Style := psSolid;
    // Pen.Style := psDash; appears not to work in Linux so we change
    // from psSolid to psClear every 10 pixels to get a dashed line
	  For n:= 0 To nFrequencies do
    begin
      X:= round(n * 940 / nFrequencies);
      Y:= round((Trace[n] +_PhaseOffset) / PhaseScale * 666 );
      // clip the graph
      If Y < 0 Then Y:= 0;
      If Y > 666 Then Y:= 666;
      if (n mod 10) = 0 then if pen.Style = psSolid then Pen.Style:= psClear else Pen.style:= psSolid;
      if n = 0 then Moveto(X + 30, 675 - Y)else Lineto(X + 30, 675 - Y);
    end;
  end;
end;

procedure ReadSettings;
var DataFile: text; D: integer;
begin
	assign(DataFile, SettingsFileName);
	reset(DataFile);
  with FrmLogGraph do
  begin
		readln(DataFile, D);
    UdAmplitudePerDiv.position:= D;
		readln(DataFile, D);
    UdAmplitudeOffset.position:= D;
		readln(DataFile, D);
    UdnDivs.position:= D;
		readln(DataFile, D);
    UdPhasePerDiv.position:= D;
		readln(DataFile, D);
    UdPhaseOffset.position:= D;
  end;
  close(DataFile);
end;

procedure WriteSettings;
var DataFile: text;
begin
	assign(DataFile, SettingsFileName);
	rewrite(DataFile);
  with FrmLogGraph do
  begin
		writeln(DataFile, UdAmplitudePerDiv.position);
		writeln(DataFile, UdAmplitudeOffset.position);
		writeln(DataFile, UdnDivs.position);
		writeln(DataFile, UdPhasePerDiv.position);
		writeln(DataFile, UdPhaseOffset.position);
  end;
  close(DataFile);
end;

procedure TFrmLogGraph.FormDestroy(Sender: TObject);
begin
  WriteSettings;
end;

procedure TFrmLogGraph.FormCreate(Sender: TObject);
begin
  if FileExists (SettingsFileName) then ReadSettings;
  UdnDivsClick(Sender);
  UdAmplitudePerDivClick(Sender);
  UdAmplitudeOffsetClick(Sender);
  UdPhasePerDivClick(Sender);
  UdPhaseOffsetClick(Sender);
	LogGraphReady:= true;
end;

{$R *.lfm}

end.

