{**************************************************************************
 *** WOW!                                                               ***
 ***                       TEXT GRAPH VIEWS!                            ***
 ***                                                                    ***
 **************************************************************************}
{$I compflgs}
{Provides a view for displaying graphs in text mode.... hopefully....}

unit TuiGrphs;

INTERFACE

uses views, chains, objects,tui;


type
	TNumType = integer;

	PPointData = ^TPointData;
	TPointData = object(TObject)
		X,Y : longint;
		Ch : char;   {character to mark point with}
		Next : PPointData;
		constructor Init(NX,NY : longint; NCh : char);
		destructor Done; virtual;
	end;

	PGraphView = ^TGraphView;
	TGraphView = object(TView)

		XLabel, YLabel : string;
		DataDiv : TPoint; {divisions for axis}

		FirstPoint,LastPoint : PPointData;

		DataMax : TPoint;
		DataMin : TPoint;

		Scaling : record
			X : real;
			Y : real;
		end;

		AxisChanged : boolean;

		constructor Init(Bounds : TRect; NXLabel, NYLabel : string);
		destructor Done; virtual;
		procedure AddPoint(const X,Y : longint; const Ch : char);
		procedure SetScale;
		procedure Draw; virtual;

		procedure DrawLabels;
		procedure DrawAxis;
		procedure DrawPoints;
		procedure DrawPoint(const Point : PPointData);

	private
		procedure WriteVert(const AtX : integer; AtY : integer; const S : string);

		{given data, where to plot on screen}
		function PlotY(Const YDat : integer) : integer;
		function PlotX(Const XDat : integer) : integer;


	end;

	PGraphWindow = ^TGraphWindow;
	TGraphWindow = object(TWindow)
		Graph : PGraphView;
		constructor Init(Bounds : Trect; NTitle,XLabel,YLabel : string);
	end;



IMPLEMENTATION

uses minilib;

{************************************
 ***      POINT DATA              ***
 ************************************}
constructor TPointData.Init;
begin
	inherited Init;
	X := NX;
	Y := NY;
	Ch := NCh;
end;

destructor TPointData.Done;
begin
	if Next<>nil then dispose(Next, done);
	inherited Done;
end;

{************************************
 ***         GRAPH WINDOW         ***
 ************************************}
constructor TGraphWindow.Init(Bounds : Trect; NTitle,XLabel,YLabel : string);
begin
	inherited Init(Bounds, NTitle, 0);
	Bounds.Grow(-1,-1); {move off frame}
	New(Graph, init(Bounds, XLabel, YLabel));
	Insert(Graph);

end;


{***************************************************************
 ***                      GRAPH VIEW                         ***
 ***************************************************************}
const
	Delta : TPoint = (X : 2; Y : 2); {offset of graph axis from view edge}


constructor TGraphView.Init;
begin
	inherited Init(BOunds);
	XLabel := NXLabel;
	YLabel := NYLabel;

	{grow exactly relative to window frame}
	GrowMode := gfGrowHiX + gfGrowHiY;

	FIrstPoint := nil;
	LastPoint := nil;

	DataMax.X := 0;
	DataMax.Y := 0;

	DataDiv.X := 0;
	DataDiv.Y := 0;

	Scaling.X := 0;
	Scaling.Y := 0;

	AxisChanged := False;
end;

destructor TGraphView.Done;
begin
	if FIrstPoint<>nil then dispose(FirstPoint, done);
	inherited Done;
end;

procedure TGraphView.AddPoint;
var Point : PPointData;
begin
	if X>DataMax.X then DataMax.X := X;
	if Y>DataMax.Y then DataMax.Y := Y;

	Point := New(PPointData, init(X,Y,Ch));

	if FirstPoint = nil then begin
		FirstPoint := Point;
	end else
		LastPoint^.Next := Point;

	LastPoint := Point;
end;

procedure TGraphView.SetScale;
var S : string;
		OldScale : record
			X : real;
			Y : real;
		end;

	{set divisions, units of 1,2 or 5 and a number of zeros}
	function WholeDivs(N : integer) : integer;
	var S : string;
	begin
		if N>1 then begin
			S := N2Str(N);
			case S[1] of
				'1' : WholeDivs := 2*Exp10(length(S)-1);
				'2'..'4' : WholeDivs := 5*Exp10(Length(S)-1);
				'5'..'9' : WholeDivs := Exp10(Length(S));
			else
				WholeDivs := 1;
			end;
		end else
			WholeDivs := 1;
	end;


begin
	OldScale.X := Scaling.X;  OldScale.Y := Scaling.Y;
	if DataMax.Y<>0 then Scaling.Y := (Size.Y-Delta.Y)/DataMax.Y;
	if DataMax.X<>0 then Scaling.X := (Size.X-Delta.X)/DataMax.X;
	if (OldScale.X<>Scaling.X) or (OldScale.Y<>Scaling.Y) then begin
		AxisChanged := True;
		{set divisions, units of 1,2 or 5 and a number of zeros}
		DataDiv.X := trunc(DataMax.X*5/(Size.X-Delta.X)); {division every 5th line}
		DataDiv.X := WholeDivs(DataDiv.X);

		DataDiv.Y := trunc(DataMax.Y*5/(Size.Y-Delta.Y));
		DataDiv.Y := WholeDivs(DataDiv.Y);
	end;
end;

procedure TGraphView.Draw;
begin
	inherited Draw; {blanks}

	SetScale;

	DrawLabels;
	DrawAxis;
	DrawPoints;
end;

function TGraphView.PlotX(const XDat : integer) : integer;
begin PlotX := trunc(XDat * Scaling.X)+Delta.X; end;

function TGraphView.PlotY(const YDat : integer) : integer;
begin PlotY := Size.Y - (trunc(YDat * Scaling.Y)+Delta.Y)-1; end;

procedure TGraphView.WriteVert(const AtX : integer; AtY : integer; const S : string);
var B : byte;
begin
	for B := 1 to length(S) do begin
		WriteCHar(AtX, AtY, S[B], 1,1);
		inc(AtY);
	end;
end;

procedure TGraphView.DrawLabels;
begin
	{draw x axis label}
	WriteStr((Size.X - length(XLabel)) div 2, Size.Y - 1, XLabel, 1);
	{draw y axis label - vertically}
	WriteVert(0,(Size.Y - length(YLabel)) div 2, YLabel);
end;

procedure TGraphView.DrawAxis;
var X,Y : integer;
begin
	{draw axis}
	WriteChar(Delta.X,Size.Y - Delta.Y -1, #196,1,Size.X-2); {x axis}
	for Y := 0 to Size.Y-Delta.Y-1 do WriteChar(Delta.X,Y,#179,1,1); {y axis}

	{add divisions}
	if DataDiv.X<>0 then begin
		X := 0;
		while X<=DataMax.X do begin
			WriteChar(PlotX(X),Size.Y-Delta.Y-1,#194,1,1);
			WriteStr(PlotX(X)-((length(N2Str(X))-1) div 2), Size.Y-Delta.Y, N2Str(X), 1);
			inc(X,DataDiv.X);
		end;
	end;

	if DataDiv.Y<>0 then begin
		Y := 0;
		while Y<=DataMax.Y do begin
			WriteChar(Delta.X,PlotY(Y),#180,1,1);
			WriteVert(Delta.X-1, PlotY(Y)-((length(N2Str(Y))-1) div 2), N2Str(Y));
			inc(Y,DataDiv.Y);
		end;
	end;

	AxisChanged := False; {mark as axis not changed since last draw}
end;

procedure TGraphView.DrawPoints;
var Point : PPOintData;
begin
	{mark data points}
	Point := FirstPoint;
	while Point<>nil do begin
		DrawPoint(Point);
		Point := Point^.Next;
	end;
end;


procedure TGraphView.DrawPoint(const Point : PPointData);
begin
		WriteChar(PlotX(Point^.X), PlotY(Point^.Y), Point^.Ch, 1,1);
end;

end.


