Graph drawing

The following text is prepared for explanation, how to create a graph, step by step.

You can (and should) to try it; in the next text is assumed, that you will make all the changes in just one button; my program had eight of them to show the development. You need just button, image and the FontDialog (on the Dialogs ribbon):

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Image1: TImage;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    Button5: TButton;
    Button6: TButton;
    Button7: TButton;
    Button8: TButton;
    FontDialog1: TFontDialog;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
    procedure Button6Click(Sender: TObject);
    procedure Button7Click(Sender: TObject);
    procedure Button8Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{comment: the eight steps to draw a sine function}
procedure TForm1.Button1Click(Sender: TObject);
var
   i : integer;
 begin
   for i:=0 to 360 do
     image1.canvas.lineto(i+10,100 - round (50*sin(i) ));
 end;

{the line is strange, because Pascal calculates with radians;
   there are one line more, because before drawing a function
     we need to move the drawing cursor first}

procedure TForm1.Button2Click(Sender: TObject);
 var
   i : integer;
 begin
   image1.canvas.MOVEto(0+10,100 - round (50*sin(0) ));
   for i:=0 to 360 do
     image1.canvas.lineto(i+10,100 - round (50*sin(i/180*pi) ));
 end;

{sinus, but with wrong size - we need to convert to size of our image (in "h" variable)}

 procedure TForm1.Button3Click(Sender: TObject);
 var
   i,h : integer;
 begin
   h := image1.height;
   image1.canvas.moveto(0+10,h div 2 - round (h/2*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(i+10,h div 2 - round (h/2*sin(i/180*pi) ));
 end;

{colored function would be nice, and thick line looks better}

procedure TForm1.Button4Click(Sender: TObject);
 var
   i,h : integer;
 begin
   h := image1.height;
   image1.canvas.pen.color := clred;
   image1.canvas.pen.width := 3;
   image1.canvas.moveto(0+10,h div 2 - round (h*0.4*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(i+10,h div 2 - round (h*0.4*sin(i/180*pi) ));
 end;

{graph should be resampled to all the width of the picture}

procedure TForm1.Button5Click(Sender: TObject);
 var
   i,h,w : integer;
 begin
   h := image1.height;
   w := image1.width;
   image1.canvas.pen.color := clred;
   image1.canvas.pen.width := 3;
   image1.canvas.moveto(0+10,h div 2 - round (h*0.4*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(round(i/360*(w-20))+10,h div 2 - round (h*0.4*sin(i/180*pi) ));
 end;

{we are still missing axis... important is to set back the pen width and color}

procedure TForm1.Button6Click(Sender: TObject);
 var
   i,h,w : integer;
 begin
   h := image1.height;
   w := image1.width;
   image1.canvas.pen.color := clred;
   image1.canvas.pen.width := 3;
   image1.canvas.moveto(0+10,h div 2 - round (h*0.4*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(round(i/360*(w-20))+10,h div 2 - round (h*0.4*sin(i/180*pi) ));
   image1.canvas.pen.color := clBlack;
   image1.canvas.pen.width := 1;
   image1.canvas.moveto(0+10, h div 2);
   image1.canvas.lineto(w-10, h div 2);
   image1.canvas.moveto(0+10, h div 2 - round (h*0.45*1 ));
   image1.canvas.lineto(0+10, h div 2 + round (h*0.45*1 ));
 end;

{some marks on horizontal axis (vertical would be the same) }

procedure TForm1.Button7Click(Sender: TObject);
 var
   i,h,w : integer;
 begin
   h := image1.height;
   w := image1.width;
   image1.canvas.pen.color := clred;
   image1.canvas.pen.width := 3;
   image1.canvas.moveto(0+10,h div 2 - round (h*0.4*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(round(i/360*(w-20))+10,h div 2 - round (h*0.4*sin(i/180*pi) ));
   image1.canvas.pen.color := clBlack;
   image1.canvas.pen.width := 1;
   image1.canvas.moveto(0+10, h div 2);
   image1.canvas.lineto(w-10, h div 2);
   image1.canvas.moveto(0+10, h div 2 - round (h*0.45*1 ));
   image1.canvas.lineto(0+10, h div 2 + round (h*0.45*1 ));

   for i:= 1 to 36 do
     begin
       image1.canvas.moveto(round(i/36*(w-20))+10,h div 2 - 3);
       image1.canvas.lineto(round(i/36*(w-20))+10,h div 2 + 3);
     end;
 end;

{the last step is the axis labeling}

procedure TForm1.Button8Click(Sender: TObject);
 var
   i,h,w : integer;
 begin
   h := image1.height;
   w := image1.width;
   image1.canvas.pen.color := clred;
   image1.canvas.pen.width := 3;
   image1.canvas.moveto(0+10,h div 2 - round (h*0.4*sin(0/180*pi) ));
   for i:=0 to 360 do
     image1.canvas.lineto(round(i/360*(w-20))+10,h div 2 - round (h*0.4*sin(i/180*pi) ));
   image1.canvas.pen.color := clBlack;
   image1.canvas.pen.width := 1;
   image1.canvas.moveto(0+10, h div 2);
   image1.canvas.lineto(w-10, h div 2);
   image1.canvas.moveto(0+10, h div 2 - round (h*0.45*1 ));
   image1.canvas.lineto(0+10, h div 2 + round (h*0.45*1 ));

   for i:= 1 to 36 do
     begin
       image1.canvas.moveto(round(i/36*(w-20))+10,h div 2 - 3);
       image1.canvas.lineto(round(i/36*(w-20))+10,h div 2 + 3);
     end;

   for i:= 0 to 17 do     {note: we can label the zero, but there are no place to write "360"}
      image1.canvas.textout(round(i/18*(w-20))+10+2,h div 2 - 3 +4,IntToStr(i*20));
   {caption of the graph}
   if fontdialog1.execute then image1.canvas.font:=fontdialog1.font;
   image1.canvas.textout(w div 2, 3, 'y=sin(x)');

 end;

end.

By use the last button, you can get a picture like this::

It would be better to draw a graph after axis, because the "180" number damages the curve.

Note: to erase the picture, the Image1.Canvas.FillRect method can be used:

 procedure TForm1.Button9Click(Sender: TObject);
 begin
   with Image1.Canvas do begin
     Brush.Style := bsSolid;
     Brush.Color := clWhite;
     FillRect( Rect(0,0,Image1.Width-1,Image1.Height-1) );
   end;
 end;

If the program (runtime) detects bsSolid use, it uses special very fast procedure to draw a rectangle. No faster method of erase canvas is availaible.

Tasks:

  1. Write a scale to the y axis
  2. Draw a different function, for example some polynom

Last comment: drawing graph using values, solved in an array (measured data, more complex function):

Draw a graph from values in the memory

The values can be in the global variable, declared as:

var
  a : array[0..100] of real;

for testing, we can fill it by random functions, or by a math function:

for i:=0 to 100 do
  a[i]:=sin(i/100*pi*2);

To draw a graph, we need to find the smallest and the biggest value, to calculate the magnification (c variable):
(note - from the algorithms theory: to find maximum/minimum, take the first value, and start to compare with the each other, from the second)

max:=a[0];
min:=a[0];
for i:=1 to 100 do begin
  if max<a[i] then max:=a[i];
  if min>a[i] then min:=a[i];
end;
c:=(image1.height*0.9)/(max-min);

To use this, we will need to create an image1 component.

The drawing will be simple:

image1.picture.canvas.pen.color:=clred;
image1.picture.canvas.moveto(10,
  image1.height-10-round((a[0]-min)*c));
for i:=0 to 100 do 
  image1.picture.canvas.lineto(10+i*3,
    image1.height-10-round((a[0]-min)*c));

Remember: the [0,0] point is in the upper left corner, so the value to be drawen should be subtracted from the zero position (see the red "-").
Instead of " *3 ", there should be horizontal magnification. This can be calculated the same way as the vertical:

d:=(image1.width*0.9)/(100);

d is (and should be declared as) real number. After use this, the round function should be used.