back to article

unit Unit1;
{    paint graph of a function
     shows scales handling
     avoid plotting of asymptotes by use of 2nd derivative
}

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Buttons, ExtCtrls;

type
  TForm1 = class(TForm)
    plotbox: TPaintBox;
    centerYtext: TStaticText;
    centerXtext: TStaticText;
    yscaletext: TStaticText;
    xscaletext: TStaticText;
    Label1: TLabel;
    Label2: TLabel;
    Label3: TLabel;
    Label4: TLabel;
    formula1btn: TSpeedButton;
    Label5: TLabel;
    formula2btn: TSpeedButton;
    formula4btn: TSpeedButton;
    formula5btn: TSpeedButton;
    plotbtn: TBitBtn;
    clearBtn: TBitBtn;
    Timer1: TTimer;
    SpeedButton1: TSpeedButton;
    formula3Btn: TSpeedButton;
    autoplotcheck: TCheckBox;
    procedure clearBtnClick(Sender: TObject);
    procedure FormPaint(Sender: TObject);
    procedure plotboxPaint(Sender: TObject);
    procedure centerXtextMouseDown(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure Timer1Timer(Sender: TObject);
    procedure centerXtextMouseUp(Sender: TObject; Button: TMouseButton;
      Shift: TShiftState; X, Y: Integer);
    procedure SpeedButton1Click(Sender: TObject);
    procedure formula1btnClick(Sender: TObject);
    procedure plotbtnClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

const maxscalecode = 9;
      minscalecode = 0;
      maxCenter     = 1000;
      minCenter     = -1000;
      scalesBase : array[0..2] of single = (1, 2, 5);
      scalesExp  : array[0..3] of single = (0.01, 0.1, 1, 10);

var centerX : single = 0;
    centerY : single = 0;
    scaleX  : single = 1;
    scaleY  : single = 1;
    scaleCodeX : byte = 6;
    scaleCodeY : byte = 6;
    timercode : byte = 0;
    formulaNr : byte = 1;

procedure showScales;
const fstring = '##0.0##';
begin
 with form1 do
  begin
   Xscaletext.caption := formatfloat(fstring,scaleX);
   Yscaletext.caption := formatfloat(fstring,scaleY);
   centerXtext.Caption := formatfloat(fstring,centerX);
   centerYtext.Caption := formatfloat(fstring,centerY);
  end;
end;

procedure scalecode2scales;
// scaleCode = scaleBaseIndex + 3*scalesExpIndex
var i,j: byte;
begin
 i := scalecodeX mod 3;
 j := scalecodeX div 3;
 scaleX := scalesBase[i] * scalesExp[j];
 centerX := round(centerX/scaleX) * scaleX;  //make center multiple of scale
 i := scaleCodeY mod 3;
 j := scaleCodeY div 3;
 scaleY := scalesBase[i] * scalesExp[j];
 centerY := round(centerY/scaleY) * scaleY;  //make center multiple of scale
end;

procedure resetScales;
begin
 scaleCodeX := 6;
 scaleCodeY := 6;
 centerX := 0;
 centerY := 0;
 scaleCode2Scales;
 showscales;
end;

procedure clearPlotBox;
var i : word;
    zX,zY : smallInt;
begin
 with form1.plotbox do with canvas do
  begin
   brush.color := $f0ffff;
   brush.style := bsSolid;
   fillrect(rect(0,0,width,height));
   i := 10;
   while i < width do
    begin
     if i = 410 then pen.Style := psDot else pen.Style := psSolid;
     if (i-10) mod 40 = 0 then pen.Color := $ffc0c0 else pen.Color := $ffe0e0;
     moveto(i,10);
     lineto(i,height-10);
     inc(i,20);
    end;
   i := 10;
   while i < height do
    begin
     if i = 330 then pen.Style := psDot else pen.Style := psSolid;
     if (i-10) mod 40 = 0 then pen.color := $ffc0c0 else pen.Color := $ffe0e0;
     moveto(10,i);
     lineto(width-10,i);
     inc(i,20);
    end;
   zX := 410 - trunc(40*centerXscaleX);
   if (zX>=10) and (zX <= width-10) then
    begin
     pen.color := $ff;
     moveto(zX,10);
     lineto(zX,height-10);
    end;
   zY := 330 + trunc(40*centerYscaleY);
   if (zY>=10) and (zY<=height-10) then
    begin
     pen.color := $ff;
     moveto(10,zY);
     lineto(width-10,zY);
    end;
  end;
end;

function pix2X(px : smallInt) : single;
//pixel value px to x value
begin
 result := centerX+0.025*(px-410)*scaleX;
end;

function getValue(x : single; var v : boolean) : single;
//calculate function value
var sqx : single;
begin
 result := 0;
 try
  case formulaNr of
   1 : result := 0.1*x*x-6;
   2 : result := sqrt(64-sqr(x));
   3 : begin
        sqx := x*x;
        result := sqx*(-0.01*sqx +0.5);
       end;
   4 : result := 1/(x-2.001);
   5 : result := 5/((x-4.001)*(x+3.999));
  end;//case
  v := true;
 except
  v := false;
 end;
end;

function getPixelValue(y : single) : smallInt;
var pixY : single;
begin
 PixY := 330 - 40*(y-centerY)/scaleY;
 if PixY < 0 then pixY := -1;       //avoid integer overflowing
 if PixY >= 660 then PixY := 660;
 result := round(PixY);
end;

// --- events ---

procedure TForm1.clearBtnClick(Sender: TObject);
begin
 clearPlotbox;
end;

procedure TForm1.FormPaint(Sender: TObject);
//paint edge around plotbox
var x1,y1,x2,y2 : word;
    i : byte;
begin
 showscales;
 with plotbox do
  begin
   x1 := Left-2;
   y1 := Top - 2;
   x2 := Left + Width+1;
   y2 := top + height+1;
  end;
 with canvas do    //form1.canvas
  begin
   pen.Width := 1;
   for i := 0 to 1 do
    begin
     pen.color := $000000;
     moveto(x2-i,y1+i);
     lineto(x1+i,y1+i);
     lineto(x1+i,y2-i);
     pen.color := $808080;
     lineto(x2-i,y2-i);
     lineto(x2-i,y1+i);
    end;
  end;
end;

procedure TForm1.plotboxPaint(Sender: TObject);
begin
 clearPlotBox;
end;

procedure TForm1.centerXtextMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
 timercode := Tstatictext(sender).Tag;
 if button = mbRight then inc(timercode,4);
 timer1.Interval := 300;
 timer1timer(self);
 timer1.Enabled := true;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
 case timercode of
  1 : if scaleCodeX < maxscaleCode then inc(scaleCodeX);
  2 : if scaleCodeY < maxscaleCode then inc(scaleCodeY);
  3 : if centerX < maxCenter then centerX := centerX + scaleX;
  4 : if centerY < maxCenter then centerY := centerY + scaleY;
  5 : if scaleCodeX > minscaleCode then dec(scaleCodeX);
  6 : if scaleCodeY > minscaleCode then dec(scaleCodeY);
  7 : if centerX > minCenter then centerX := centerX - scaleX;
  8 : if centerY > minCenter then centerY := centery - scaleY;
 end;//case
 scalecode2scales;
 showscales;
 clearplotbox;
 if autoplotcheck.Checked then plotBtnClick(self);
 with timer1 do if Interval > 160 then interval := interval-20;
end;

procedure TForm1.centerXtextMouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
 timer1.Enabled := false;
end;

procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
 resetscales;
 clearPlotBox;
 if autoplotcheck.Checked then plotBtnClick(self);
end;

procedure TForm1.formula1btnClick(Sender: TObject);
begin
 formulaNr := TStatictext(sender).Tag;
end;

procedure TForm1.plotbtnClick(Sender: TObject);
var x,y,prevY,dY,prevdY,ddY : single;
    valid,OK : boolean;
    plotcode : byte;
    px,py : smallInt;
begin
 plotcode := 0;
 py := 0;
 prevY := 0; prevdY := 0; ddY := 0;
 with form1.plotbox.Canvas do
  begin
   pen.Color := $000000;
   pen.Width := 1;
  end;
 for px := 10 to 809 do
  begin
   X := pix2X(px);
   Y := getValue(x,valid);
   if valid then py := getPixelvalue(y);
   dy := Y-prevY;
   if dY*prevdY >= 0 then OK := true        //asymptote suppression
    else OK := dy*ddy >= 0;
//   OK := true;                            //OK=true allows drawing asymptotes
   valid := valid and OK;
   plotcode := (plotcode shl 1) and $3;
   if valid then plotcode := plotcode or $1;
   with form1.plotbox.Canvas do
    case plotcode of
     1 : MoveTo(px,py);
     3 : lineto(px,py);
   end;//case
   ddY := dY - prevdY;
   prevdY := dY;
   prevY := Y;
  end; //for px
end;

end.