2010. szeptember 27., hétfő

A Component that plots graphs


Problem/Question/Abstract:

A component for creating graphs

Answer:

Here is a component that draws graphs. You can zoom in and out of the graph. The code is shown below. Copy the code to .pas file and install the component. I will add a demo to show how to use this component soon.

unit UGraph;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Math;

type
  TOnMouseMove = procedure(Shift: TShiftState; x, y: integer) of object;
  TOnMouseDown = procedure(Button: TMouseButton; Shift: TShiftState; x, y: integer) of
    object;
  TOnMouseUp = procedure(Button: TMouseButton; Shift: TShiftState; x, y: integer) of
    object;

  TState = (fplotted, fjoined);
  TGraph = class;
  TPlots = class;

  TPoints = class(Tlist)
  private
    fplots: TPlots;
    fptcolor, fcrvcolor: TColor;
    fstate: set of Tstate;
    procedure fPlot;
    procedure fJoin;
  protected
    function Get(index: integer): PPoint;
  public
    procedure Plot;
    procedure Join;
    constructor Create(aplots: TPlots);
    function Add(x, y: integer): PPoint;
    procedure HideDots;
    procedure HideJoins;
    procedure Clear; override;
    property CurveColor: Tcolor read fcrvcolor write fcrvColor;
    property DotColor: Tcolor read fptcolor write fptColor;
    property Items[index: integer]: PPoint read Get; default;
  end;

  TPlots = class(Tlist)
  private
    fgraph: TGraph;
  protected
    function Get(index: integer): TPoints;
  public
    constructor Create(agraph: TGraph);
    function Add: TPoints;
    procedure Clear; override;
    procedure PlotAllDots;
    procedure PlotAllJoins;
    procedure HideAllDots;
    procedure HideAllJoins;
    property Items[index: integer]: TPoints read Get; default;
  end;

  TGraph = class(TGraphicControl)
  private
    faxcolor, fbkcolor, fgridcolor: Tcolor;
    fMouseDown: TOnMouseDown;
    fMouseMove: TOnMouseMove;
    fMouseUp: TOnMouseUp;
    fspc: extended;
    ldiv, sdiv: integer;
    xaxis, yaxis: integer;
    xlc, ylc: integer;
    fmag: integer;
    fplots: TPlots;
    function Translate(x, y: integer): Tpoint;
    function GetScale: Extended;
    procedure DrawGrid;
    procedure DrawAxes;
    procedure GetXLineRect(y: integer; var arect: trect);
    procedure GetYLineRect(x: integer; var arect: trect);
    procedure SetGridColor(acolor: Tcolor);
    procedure SetBackColor(acolor: Tcolor);
    procedure SetAxisColor(acolor: TColor);
  protected
    procedure loaded; override;
    procedure Paint; override;
    {procedure MsgHandler(var msg:TMessage);}
    procedure MouseDown(Button: TMouseButton; shift: TShiftState; x, y: integer);
      override;
    procedure MouseMove(shift: TShiftState; x, y: integer); override;
    procedure MouseUp(Button: TMouseButton; shift: TShiftState; x, y: integer);
      override;
  public
    constructor Create(AComponent: TComponent); override;
    destructor Destroy; override;
    procedure OffSetAxes(x, y: integer);
    procedure ResetAxes;
    procedure Zoom(mag: integer);
    property Plots: TPlots read fplots;
  published
    property OnMouseDown: TOnMouseDown read fMouseDown write fMouseDown;
    property OnMouseMove: TOnMouseMove read fMouseMove write fMouseMove;
    property OnMouseUp: TOnMouseUp read fMouseUp write fMouseUp;
    property GridColor: Tcolor read fgridcolor write SetGridColor;
    property BackColor: Tcolor read fbkcolor write SetBackColor;
    property AxisColor: Tcolor read faxcolor write SetAxisColor;
    property Scale: extended read GetScale;
    property ZoomFactor: integer read fmag;
  end;

procedure Register;

implementation

procedure TGraph.MouseDown(Button: TMouseButton; shift: TShiftState; x, y: integer);
var
  tp: Tpoint;
begin
  tp.x := x - left;
  tp.y := y - top;
  tp.x := trunc(tp.x / fspc - yaxis);
  tp.y := trunc(xaxis - tp.y / fspc);
  if (assigned(fMouseDown)) then
    fMouseDown(button, shift, tp.x, tp.y);
  inherited;
end;

procedure TGraph.MouseMove(shift: TShiftState; x, y: integer);
var
  tp: Tpoint;
begin
  tp.x := x - left;
  tp.y := y - top;
  tp.x := trunc(tp.x / fspc - yaxis);
  tp.y := trunc(xaxis - tp.y / fspc);
  if (assigned(fMousemove)) then
    fMousemove(shift, tp.x, tp.y);
  inherited;
end;

procedure TGraph.MouseUp(Button: TMouseButton; shift: TShiftState; x, y: integer);
var
  tp: Tpoint;
begin
  tp.x := x - left;
  tp.y := y - top;
  tp.x := trunc(tp.x / fspc - yaxis);
  tp.y := trunc(xaxis - tp.y / fspc);
  if (assigned(fMouseUp)) then
    fMouseup(button, shift, tp.x, tp.y);
  inherited;
end;

constructor TPoints.Create(aplots: TPlots);
begin
  if aplots = nil then
    raise Exception.Create('Not a valid Graph object.');
  fplots := aplots;
end;

constructor TPlots.Create(agraph: Tgraph);
begin
  if agraph = nil then
    raise Exception.Create('Not a valid Graph object.');
  fgraph := agraph;
end;

procedure TPoints.HideDots;
begin
  fstate := fstate - [fplotted];
end;

procedure TPoints.HideJoins;
begin
  fstate := fstate - [fjoined];
end;

procedure TPoints.Plot;
begin
  fstate := fstate + [fplotted];
  fplots.fgraph.invalidate;
end;

procedure TPoints.fPlot;
var
  i: integer;
  tmp: tpoint;
begin
  if count <= 0 then
    exit;
  with fplots.fgraph do
  begin
    canvas.pen.color := fptcolor;
    canvas.pen.width := 1;
    for i := 0 to count - 1 do
    begin
      tmp := Translate(items[i].x, items[i].y);
      canvas.Ellipse(rect(tmp.x - 1, tmp.y - 1, tmp.x + 1, tmp.y + 1));
    end;
  end;
end;

procedure TPoints.Join;
begin
  fstate := fstate + [fjoined];
  fplots.fgraph.invalidate;
end;

procedure TPoints.fJoin;
var
  i: integer;
  tmp: tpoint;
begin
  if count <= 0 then
    exit;
  with fplots.fgraph do
  begin
    canvas.pen.color := fcrvcolor;
    canvas.pen.width := 1;
    tmp := Translate(items[0].x, items[0].y);
    canvas.moveto(tmp.x, tmp.y);
    for i := 1 to count - 1 do
    begin
      tmp := Translate(items[i].x, items[i].y);
      canvas.lineto(tmp.x, tmp.y);
    end;
  end;
end;

procedure TPlots.PlotAllDots;
var
  i: integer;
begin
  for i := 0 to count - 1 do
    items[i].Plot;
end;

procedure TPlots.PlotAllJoins;
var
  i: integer;
begin
  for i := 0 to count - 1 do
    items[i].join
end;

procedure TPlots.HideAllDots;
var
  i: integer;
  inv: boolean;
begin
  inv := false;
  for i := 0 to count - 1 do
    if (fplotted in items[i].fstate) then
    begin
      items[i].fstate := items[i].fstate - [fplotted];
      inv := true;
    end;
  if inv then
    fgraph.invalidate;
end;

procedure TPlots.HideAllJoins;
var
  i: integer;
  inv: boolean;
begin
  inv := false;
  for i := 0 to count - 1 do
    if (fjoined in items[i].fstate) then
    begin
      items[i].fstate := items[i].fstate - [fjoined];
      inv := true;
    end;
  if inv then
    fgraph.invalidate;
end;

function TPlots.Get(index: integer): TPoints;
begin
  result := TPoints(inherited Get(index));
end;

function TPlots.Add: TPoints;
begin
  result := TPoints.create(self);
  inherited Add(result);
end;

procedure TPlots.Clear;
var
  i: integer;
  tmp: Tpoints;
begin
  for i := 0 to count - 1 do
  begin
    tmp := items[i];
    freeandnil(tmp);
  end;
  inherited;
end;

procedure TPoints.Clear;
var
  i: integer;
begin
  for i := 0 to count - 1 do
    dispose(items[i]);
  inherited;
end;

function TPoints.Get(index: integer): PPoint;
begin
  result := PPoint(inherited Get(index));
end;

function TPoints.Add(x, y: integer): PPoint;
begin
  new(result);
  result.x := x;
  result.y := y;
  inherited Add(result);
end;

function TGraph.GetScale: extended;
begin
  if fspc result := sdiv / fspc
else
  result := 1;
end;

destructor TGraph.Destroy;
begin
  freeandnil(fplots);
  inherited;
end;

constructor TGraph.Create(AComponent: TComponent);
begin
  fplots := TPlots.create(self);
  fmag := 100;
  fbkcolor := clwhite;
  faxcolor := clnavy;
  fgridcolor := RGB(214, 244, 254);
  ldiv := 10;
  sdiv := 5;
  fspc := 1;
  inherited;
end;

procedure TGraph.GetXLineRect(y: integer; var arect: trect);
begin
  arect.left := left;
  arect.right := arect.left + width;
  arect.top := top + trunc(y * fspc);
  arect.bottom := arect.top + 2;
end;

procedure TGraph.GetYLineRect(x: integer; var arect: trect);
begin
  arect.top := top;
  arect.bottom := arect.top + height;
  arect.left := left + trunc(x * fspc);
  arect.right := arect.left + 2;
end;

procedure TGraph.SetGridColor(acolor: Tcolor);
begin
  fgridcolor := acolor;
  Invalidate;
end;

procedure TGraph.SetBackColor(acolor: Tcolor);
begin
  fbkcolor := acolor;
  Invalidate;
end;

procedure TGraph.SetAxisColor(acolor: TColor);
begin
  faxcolor := acolor;
  Invalidate;
end;

procedure TGraph.Zoom(mag: integer);
begin
  if mag <= 0 then
    mag := 1;
  if mag > 100000 then
    mag := 100000;
  fspc := (mag / 20);
  if fspc > 1 then
    fspc := trunc(fspc);
  fmag := mag;
  xlc := Trunc(width / fspc);
  ylc := Trunc(height / fspc);
  xaxis := Trunc(ylc / 2);
  yaxis := Trunc(xlc / 2);
  Invalidate;
end;

function TGraph.Translate(x, y: integer): Tpoint;
begin
  result.x := trunc((x + yaxis) * fspc);
  result.y := trunc((xaxis - y) * fspc);
end;

procedure TGraph.loaded;
begin
  Zoom(fmag);
end;

procedure TGraph.ResetAxes;
begin
  Zoom(fmag);
end;

procedure TGraph.OffSetAxes(x, y: integer);
var
  tmp: trect;
  tmpx, tmpy: integer;
begin
  canvas.Pen.color := faxcolor;
  canvas.Pen.Width := 1;
  tmpx := xaxis;
  tmpy := yaxis;
  xaxis := xaxis - y;
  yaxis := yaxis + x;
  if (tmpx = xaxis) and (tmpy = yaxis) then
    exit;
  GetXlineRect(tmpx, tmp);
  InvalidateRect(parent.handle, @tmp, false);
  GetYlineRect(tmpy, tmp);
  InvalidateRect(parent.handle, @tmp, false);

  GetXlineRect(xaxis, tmp);
  InvalidateRect(parent.handle, @tmp, false);
  GetYlineRect(yaxis, tmp);
  InvalidateRect(parent.handle, @tmp, false);
end;

procedure TGraph.DrawAxes;
begin
  canvas.Pen.color := faxcolor;
  canvas.Pen.Width := 1;
  canvas.MoveTo(0, trunc(fspc * xaxis));
  canvas.lineto(width, trunc(fspc * xaxis));
  canvas.MoveTo(trunc(fspc * yaxis), 0);
  canvas.lineto(trunc(fspc * yaxis), height);
end;

procedure TGraph.DrawGrid;
var
  i, t: integer;
  t1, t2: Tpoint;
begin
  i := 0;
  t := 0;
  canvas.pen.color := fbkcolor;
  canvas.Brush.color := fbkcolor;
  canvas.rectangle(0, 0, width, height);
  canvas.Pen.color := fgridcolor;
  canvas.Pen.Width := 1;
  while i <= width do
  begin
    if (t mod ldiv) = 0 then
      canvas.pen.width := 2
    else
      canvas.pen.width := 1;
    t1.x := i;
    t1.y := 0;
    canvas.moveto(t1.x, t1.y);
    t2.x := i;
    t2.y := height;
    canvas.lineto(t2.x, t2.y);
    i := i + max(trunc(fspc), sdiv);
    t := t + 1;
  end;
  i := 0;
  t := 0;
  while i <= height do
  begin
    if (t mod ldiv) = 0 then
      canvas.pen.width := 2
    else
      canvas.pen.width := 1;
    t1.x := 0;
    t1.y := i;
    canvas.moveto(t1.x, t1.y);
    t2.x := width;
    t2.y := i;
    canvas.lineto(t2.x, t2.y);
    i := i + max(trunc(fspc), sdiv);
    t := t + 1;
  end;
end;

procedure TGraph.Paint;
var
  i: integer;
begin
  DrawGrid;
  for i := 0 to fplots.count - 1 do
  begin
    if (fplotted in fplots[i].fstate) then
      fplots[i].fplot;
    if fjoined in fplots[i].fstate then
      fplots[i].fjoin;
  end;
  DrawAxes;
end;

procedure Register;
begin
  RegisterComponents('My Components', [TGraph]);
end;

end.

2 megjegyzés:

  1. please show some sample code

    VálaszTörlés
  2. This is wrong

    function TGraph.GetScale: extended;
    begin
    if fspc result := sdiv / fspc
    else
    result := 1;
    end;

    I assume you mean

    if fspc<>0 then
    result := sdiv / fspc

    VálaszTörlés