unit EZPlot;

interface

	type

		realPoint = record
				x, y: extended;
			end;

		datray = array[1..1] of realPoint;
		datPtr = ^datray;

		dotItem = (plus, cross, points, square, circle, dot, lines, hist);
		dotType = set of dotItem;

		lineType = (solid, dotted);		{, dash, dotDash}

		frameItem = (labels, numbers, ticks, majorGrid, minorGrid, box);
		frameType = set of frameItem;

		frame = record
				fRect: rect;
				xmin, xmax, ymin, ymax: extended;
				xOrderOfMag, yOrderOfMag: integer;
				dx, dy, delX, delY: extended;
				xLabel, yLabel: str255;
			end;

	procedure extremaOr (frameA, frameB: frame;
									var frameOut: frame);

	procedure chooseFrame (data: datPtr;
									numPts: integer;
									plotRect: rect;
									xLabel, yLabel: str255;
									var theFrame: frame;
									frameFormat: frameType;
									useGivenExtrema: boolean);

	procedure findExtremes (data: datPtr;
									numPts: integer;
									var theFrame: frame);

	procedure setTickIncrements (var theFrame: frame);

	procedure plotFrame (theFrame: frame;
									frameFormat: frameType;
									plotRect: rect);

	procedure plotData (data: datPtr;
									numPts: integer;
									theFrame: frame;
									dotFormat: dotType;
									lineFormat: lineType);

	procedure plot (data: datPtr;
									numPts: integer;
									plotRect: rect;
									xLabel, yLabel: str255;
									useGivenFrame: boolean;
									var theFrame: frame;
									frameFormat: frameType;
									dotFormat: dotType;
									lineFormat: lineType);


implementation

	const
		lineSpacing = 1.5;				{ for text: double space = 2 }
		numLabelDigits = 4;				{ number of digits in numeric lables }
		majorTickDefault = 3;			{ default number of major ticks on x axis (number on y axis determined from aspect ratio }
		majTickPercentRatio = 1.0;	{ percent length (of longest plot side) of major tick }
		minTickPercentRatio = 0.5;	{ percent length (of longest plot side) of minor tick }
		crossPercentRatio = 1;			{ percent width of crosses/plusses plotted }
		dotPercentRatio = 1.3;			{ percent width of dots/circles/squares plotted }

	function frac (x: extended): extended;
{ returns the fractional portion of x }
	begin
		frac := x - trunc(x);
	end;

	function power (base, exponent: extended): extended;
{ returns base^exponent }
	begin
		power := exp(exponent * ln(base));
	end;

	function log10 (x: extended): extended;
		const
			ln10 = 2.302585093;
	begin
		log10 := ln(x) / ln10;
	end;

	function screenX (x, xmax, xmin: extended;
									scrXmax, scrXmin: integer): integer;
	begin
		screenX := scrXmin + round((x - xmin) / (xmax - xmin) * (scrXmax - scrXmin));
	end;

	function screenY (y, ymax, ymin: extended;
									scrYmax, scrYmin: integer): integer;
	begin
		screenY := scrYmin - round((y - ymin) / (ymax - ymin) * (scrYmin - scrYmax));
	end;

	procedure StringDraw (theStr: str255;
									where: point;
									alignment: integer);
{ draws the string with the following alignment at point where: }
{ 781 }
{ 602 }
{ 543 }
{ i.e. alignment = 0 centers the string horizontally and vertically at where, while }
{ alignment = 1 left justifies above where and alignment = 5 right justifies below where }
{ NOTE: modify to allow for right angle orientation, too!  }
		var
			width, fontHeight: integer;
			info: FontInfo;
	begin
		moveTo(where.h, where.v);		{ move to draw location }
		width := stringWidth(theStr);
		GetFontInfo(info);
		fontHeight := info.ascent;
		case alignment of		{ first, adjust vertical position }
			7, 8, 1: 
				;
			6, 0, 2: 
				move(0, fontHeight div 2 - 1);
			5, 4, 3: 
				move(0, fontHeight);
			otherwise { error }
				;
		end;
		case alignment of		{ next, adjust the horizontal position }
			7, 6, 5: 
				move(-width, 0);
			8, 0, 4: 
				move(-width div 2, 0);
			1, 2, 3: 
				;
			otherwise { error }
				;
		end;
		DrawString(theStr);
	end;

	procedure extremaOr (frameA, frameB: frame;
									var frameOut: frame);

		function max (a, b: extended): extended;
		begin
			if a > b then
				max := a
			else
				max := b;
		end;

		function min (a, b: extended): extended;
		begin
			if a < b then
				min := a
			else
				min := b;
		end;

	begin	{ extremaOr }
		with frameOut do begin
				xmin := min(frameA.xmin, frameB.xmin);
				xmax := max(frameA.xmax, frameB.xmax);
				ymin := min(frameA.ymin, frameB.ymin);
				ymax := max(frameA.ymax, frameB.ymax);
			end;
	end;		{ extremaOr }

	procedure findExtremes (data: datPtr;
									numPts: integer;
									var theFrame: frame);
{ finds the extremes of the data, then }
{ updates the xmin...ymax fields of theFrame accordingly }
		const
			smallReal = 1e-6;
			bigReal = 1e20;
		var
			i: integer;
			margin: extended;
	begin
		with theFrame do begin
				xmin := bigReal;
				xmax := -xmin;
				ymin := xmin;
				ymax := xmax;
				for i := 1 to numPts do begin				{ find the extrema of the data }
						if data^[i].x > xmax then
							xmax := data^[i].x;
						if data^[i].y > ymax then
							ymax := data^[i].y;
						if data^[i].x < xmin then
							xmin := data^[i].x;
						if data^[i].y < ymin then
							ymin := data^[i].y;
					end;		{ for }
				if abs(xmax - xmin) < smallReal then	{ in case there's no variation }
					begin
						xmax := xmax + 1;
						xmin := xmin - 1;
					end;
				if abs(ymax - ymin) < smallReal then begin
						ymax := ymax + 1;
						ymin := ymin - 1;
					end;
			end;			{ with }
	end;				{ findExtremes }

	procedure setTickIncrements (var theFrame: frame);
{ setTickIncrements finalizes the state of theFrame record.  Values of dx..dely are decided upon }
{ to be the major/minor tick increments on the x and y axes.  xmin..ymax are updated to fall		}
{ on exact multiples of the minor tick increment.  The frame fields xOrderOfMag and 				}
{ yOrderOfMag are also determined and set. }

		var
			numMajorTicks: integer;
			aspectRatio: extended;

		procedure chooseTickIncrement (max, min: extended;
										numMajorTicks: integer;
										var del, d: extended;
										var OrderOfMag: integer);
			var
				tickLength, tickMag, tempReal: extended;
		begin
			tickLength := max - min;			{ choose appropriate del and d: numMajorTicks major ticks }
			tickLength := tickLength / numMajorTicks;
			if tickLength > 1 then
				tickMag := power(10, trunc(log10(tickLength)))
			else
				tickMag := power(10, trunc(log10(tickLength)) - 1);
			tickLength := tickLength / tickMag;
			if tickLength <= 1.5 then begin
					del := tickMag;
					d := del / 5;
				end
			else if (tickLength > 1.5) and (tickLength <= 4) then begin
					del := 2 * tickMag;
					d := del / 4;
				end
			else if (tickLength > 4) and (tickLength <= 7) then begin
					del := 5 * tickMag;
					d := tickMag;
				end
			else		{ 7.5 < tickLength < 10 }
				begin
					tickMag := 10 * tickMag;
					del := tickMag;
					d := del / 5;
				end;
			tempReal := log10((abs(max) + abs(min)) / 2);
			if tempReal > 0 then
				OrderOfMag := trunc(tempReal)
			else
				OrderOfMag := trunc(tempReal - 1);
		end;

	begin { setTickIncrements }
		with theFrame do begin
{ first choose appropriate tick increments automatically }
				numMajorTicks := majorTickDefault;
				chooseTickIncrement(xmax, xmin, numMajorTicks, delx, dx, xOrderOfMag);
				aspectRatio := (fRect.right - fRect.left) / (fRect.bottom - fRect.top);	{ aspectRatio > 1 => landscape }
				numMajorTicks := round(numMajorTicks / aspectRatio);
				if numMajorTicks < 3 then
					numMajorTicks := 3;
				chooseTickIncrement(ymax, ymin, numMajorTicks, dely, dy, yOrderOfMag);

{ then adjust mins and maxs so that they fall on an even multiple of the minor tick length 	}
				xmin := dx * round(xmin / dx - 1.4);
				xmax := dx * round(xmax / dx + 1.4);
				ymin := dy * round(ymin / dy - 1.4);
				ymax := dy * round(ymax / dy + 1.4);
			end;
	end;

	procedure chooseFrame (data: datPtr;
									numPts: integer;
									plotRect: rect;
									xLabel, yLabel: str255;
									var theFrame: frame;
									frameFormat: frameType;
									useGivenExtrema: boolean);

		procedure chooseFrameRect (plotRect: rect;
										var theFrame: frame;
										frameFormat: frameType);
{ chooses a fRect based on whether the frameFormat includes labels, numeric labels, both }
{ or neither.  The fRect's upper right corner coincides with the that of the plotRect, while }
{ the lower left corner of the fRect may lie within the plotRect if space is required for      }
{ labels }

			var
				info: FontInfo;
				fontWidth, fontHeight: integer;
				xOffset, yOffset: integer;

		begin
			GetFontInfo(info);
			with info do
				fontHeight := ascent + descent + leading;
			fontWidth := info.widMax;
			xOffset := 0;
			yOffset := 0;
			if labels in frameFormat then		{ make space in left and lower margin for axis lables }
				begin
					xOffset := round(lineSpacing * fontHeight);
					yOffset := round(lineSpacing * fontHeight);
				end;
			if numbers in frameFormat then	{ make space for numeric lables }
				begin
					xOffset := xOffset + round((lineSpacing - 1) * fontHeight + numLabelDigits * fontWidth);
					yOffset := yOffset + round(lineSpacing * fontHeight);
				end;
			if (numbers in frameFormat) or (labels in frameFormat) then	{ add an extra Å half line }
				begin
					xOffset := xOffset + round((lineSpacing - 1) * fontHeight);
					yOffset := yOffset + round((lineSpacing - 1) * fontHeight);
				end;
			with plotRect do
				if (xOffset > right - left) or (yOffset > bottom - top) then
					;	{ error - plotRect is too small for frameFormat, fontsize,  &tc.}
			theFrame.fRect := plotRect;
			with theFrame.fRect do begin
					left := left + xOffset;
					bottom := bottom - yOffset;
				end;
		end;

		procedure chooseFrameLabels;
		begin
			if (labels in frameFormat) and (numbers in frameFormat) then begin
					if theFrame.xOrderOfMag <> 0 then
						theFrame.xLabel := concat(xLabel, '  [*10^', StringOf(theFrame.xOrderOfMag : 1), ']')
					else
						theFrame.xLabel := xLabel;
					if theFrame.yOrderOfMag <> 0 then
						theFrame.yLabel := concat(yLabel, ' *10^', StringOf(theFrame.yOrderOfMag : 1))
					else
						theFrame.yLabel := yLabel;
				end
			else begin
					theFrame.xLabel := xLabel;
					theFrame.yLabel := yLabel;
				end; 	{ else }
		end;

	begin  { chooseFrame }
		chooseFrameRect(plotRect, theFrame, frameFormat);
		if not useGivenExtrema then
			findExtremes(data, numPts, theFrame);
		setTickIncrements(theFrame);
		chooseFrameLabels;
	end;  		{ chooseFrame }

	procedure plotFrame (theFrame: frame;
									frameFormat: frameType;
									plotRect: rect);

		procedure plotGridNTicks;
			var
				majTickLength, minTickLength, longSide, height, width: integer;
				x, y, temp: extended;
				diagStripe: pattern;

			procedure initPat;
{ initialize the pattern needed for the major and minor grid lines }
				var
					i: integer;
			begin
				for i := 0 to 7 do begin
						if i mod 4 = 0 then
							diagStripe[i] := 136
						else
							diagStripe[i] := diagStripe[i - 1] div 2;
					end;
			end;

		begin
			if (ticks in frameFormat) or (majorGrid in frameFormat) or (minorGrid in frameFormat) then begin
					if (minorGrid in frameFormat) then
						initPat;

					with theFrame.fRect do begin
							if (bottom - top) > (right - left) then  { portrait mode }
								longSide := bottom - top
							else	{ landscape mode -- choose tickLength based on longer side! }
								longSide := right - left;
						end;
					majTickLength := round(majTickPercentRatio / 100 * longSide);
					minTickLength := round(minTickPercentRatio / 100 * longSide);

					with theFrame do begin
{ plot X ticks }
							height := fRect.bottom - fRect.top;
							moveTo(fRect.left, fRect.bottom);
							x := xmin;
							while x <= xmax + dx / 10 do begin
									temp := abs(frac(x / delx));
									if (temp < 0.01) or (temp > 0.99) then  { plot a major tick }
										begin
											if minorGrid in frameFormat then begin
													PenPat(diagStripe);
													line(0, -height);
													move(0, height);
													PenPat(black);
												end;
											if majorGrid in frameFormat then begin
													PenPat(gray);
													line(0, -height);
													move(0, height);
													PenPat(black);
												end;
											if ticks in frameFormat then begin
													line(0, -majTickLength);							{ draw lower tick mark }
													move(0, -height + majTickLength);
													line(0, majTickLength);							{ draw upper tick mark }
													move(0, height - majTickLength);
												end
										end	{ if... plot a major tick mark }
									else	{ plot a minor tick }
										begin
											if minorGrid in frameFormat then begin
													PenPat(diagStripe);
													line(0, -height);
													move(0, height);
													PenPat(black);
												end;
											if ticks in frameFormat then begin
													line(0, -minTickLength);							{ draw lower tick mark }
													move(0, -height + minTickLength);
													line(0, minTickLength);							{ draw upper tick mark }
													move(0, height - minTickLength);
												end;
										end;	{ plot a minor tick mark }
									x := x + dx;
									moveTo(screenX(x, xmax, xmin, fRect.right, fRect.left), fRect.bottom);
								end;	{ while x < xmax }

{ plot Y ticks }
							width := fRect.right - fRect.left;
							moveTo(fRect.left, fRect.bottom);
							y := ymin;
							while y <= ymax + dy / 10 do begin
									temp := abs(frac(y / dely));
									if (temp < 0.01) or (temp > 0.99) then begin { plot a major tick }
											if minorGrid in frameFormat then begin
													PenPat(diagStripe);
													line(width, 0);
													move(-width, 0);
													PenPat(black);
												end;
											if majorGrid in frameFormat then begin
													PenPat(gray);
													line(width, 0);
													move(-width, 0);
													PenPat(black);
												end;
											if ticks in frameFormat then begin
													line(majTickLength, 0);
													move(width - majTickLength, 0);
													line(-majTickLength, 0);
													move(-width + majTickLength, 0);
												end;
										end
									else	{ plot a minor tick }
										begin
											if minorGrid in frameFormat then begin
													PenPat(diagStripe);
													line(width, 0);
													move(0, -width);
													PenPat(black);
												end;
											if ticks in frameFormat then begin
													line(minTickLength, 0);
													move(width - minTickLength, 0);
													line(-minTickLength, 0);
													move(-width + minTickLength, 0);
												end;
										end;
									y := y + dy;
									moveTo(fRect.left, screenY(y, ymax, ymin, fRect.top, fRect.bottom));
								end;	{ while y < ymax }
						end;		{ with  theFrame }
				end;		{ if ticks in frameFormat }
		end;			{ plotGridNTicks }

		procedure plotFrameBox;
		begin
			if box in frameFormat then
				with theFrame.fRect do
					FrameRect(top, left, bottom + 1, right + 1);
		end;

		procedure plotFrameNumbers;
			var
				x, y: extended;
				info: FontInfo;
				fontHeight: integer;
				where: point;

			procedure plotXnumbers;
			begin
				with theFrame do begin
						x := delx * round(xmin / delx);
						if x < xmin then
							x := x + delx;
						where.h := screenX(x, xmax, xmin, fRect.right, fRect.left);
						where.v := fRect.bottom + round(fontHeight * lineSpacing);
						while x <= xmax do begin
								if labels in frameFormat then
									StringDraw(StringOf(x / power(10, xOrderOfMag) : 3 : 2), where, 8)
								else if abs(xOrderOfMag) > 2 then
									StringDraw(StringOf(x), where, 8)
								else
									StringDraw(StringOf(x : 5 : 2), where, 8);
								x := x + delx;
								where.h := screenX(x, xmax, xmin, fRect.right, fRect.left);
							end;		{ while }
					end;			{ with theFrame }
			end;				{ plotXnumbers }

			procedure plotYnumbers;
			begin
				with theFrame do begin
						y := dely * round(ymin / dely);
						if y < ymin then
							y := y + dely;
						where.h := fRect.left - round((lineSpacing - 1) * fontHeight);
						where.v := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
						while y <= ymax do begin
								if labels in frameFormat then
									StringDraw(StringOf(y / power(10, yOrderOfMag) : 3 : 2), where, 6)
								else if abs(yOrderOfMag) > 2 then
									StringDraw(StringOf(y), where, 6)
								else
									StringDraw(StringOf(y : 5 : 2), where, 6);
								y := y + dely;
								where.v := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
							end;		{ while }
					end;			{ with theFrame }
			end;				{ plotXnumbers }

		begin	{ plotFramNumbers }
			if numbers in frameFormat then begin
					GetFontInfo(info);
					with info do
						fontHeight := ascent;
					plotXnumbers;
					plotYnumbers;
				end;
		end;	{ plotFramNumbers }

		procedure plotFrameLabels;
			var
				i, strLength, strPixLength, fontHeight, fontWidth: integer;
				info: FontInfo;
				where: point;
				x, y: extended;
		begin
			if labels in frameFormat then begin
					with theFrame do begin
							GetFontInfo(info);
							fontWidth := info.widMax;
							with info do
								fontHeight := ascent;

{ draw x Label }
							x := (xmax + xmin) / 2;
							where.h := screenX(x, xmax, xmin, fRect.right, fRect.left);
							if numbers in frameFormat then
								where.v := fRect.bottom + round(2 * fontHeight * lineSpacing)
							else
								where.v := fRect.bottom + round(fontHeight * lineSpacing);
							StringDraw(xLabel, where, 8);

{ draw y label }
							y := (ymax + ymin) / 2;
							strLength := length(yLabel);
							strPixLength := fontHeight * strLength;
							where.v := screenY(y, ymax, ymin, fRect.top, fRect.bottom) - strPixLength div 2;
							if numbers in frameFormat then
								where.h := fRect.left - round(lineSpacing * fontHeight + numLabelDigits * fontWidth)
							else
								where.h := fRect.left - round(lineSpacing * fontHeight);
							for i := 1 to strLength do begin
									StringDraw(yLabel[i], where, 2);
									where.v := where.v + fontHeight;
								end;
						end;		{ with theFrame...	}
				end;		{ if labels in ...		}
		end;			{ plotFrameLabels 	}

	begin  { plotFrame }
		EraseRect(plotRect);
		plotGridNTicks;
		plotFrameBox;
		plotFrameNumbers;
		plotFrameLabels;
	end;  { plotFrame }

	procedure plotData (data: datPtr;
									numPts: integer;
									theFrame: frame;
									dotFormat: dotType;
									lineFormat: lineType);
		var
			scrx, scry: integer;
			x, y: extended;
			saveRgn: RgnHandle;

		procedure plotLineNHist;
			var
				i: integer;
		begin
			case lineFormat of
				solid: 
					PenPat(black);
				dotted: 
					PenPat(gray);
			end;
			with theFrame do begin
					x := data^[1].x;
					y := data^[1].y;
					scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
					scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
					moveTo(scrx, scry);
					if lines in dotFormat then
						for i := 2 to numPts do begin
								x := data^[i].x;
								y := data^[i].y;
								scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
								scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
								LineTo(scrx, scry);
							end;
					x := data^[1].x;
					y := data^[1].y;
					scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
					scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
					moveTo(scrx, scry);
					if hist in dotFormat then begin
							for i := 2 to numPts do begin
									x := data^[i].x;
									y := data^[i].y;
									scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
									LineTo(scrx, scry);
									scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
									LineTo(scrx, scry);
								end;
						end;		{ if hist }
				end;		{ with theFrame }
			PenPat(black);
		end;			{ plotLineHist }

		procedure plotSquareNCircleNDot;
			var
				i, dotSize, longSide, offset: integer;
				dotRect: rect;
		begin
			with theFrame do begin
					with fRect do begin
							if (bottom - top) > (right - left) then  { portrait mode }
								longSide := bottom - top
							else	{ landscape mode -- choose dotSize based on longer side! }
								longSide := right - left;
						end;
					dotSize := round(dotPercentRatio / 100 * longSide);
					if not odd(dotSize) then
						dotSize := dotSize + 1;
					offset := dotSize div 2;
					for i := 1 to numPts do begin
							x := data^[i].x;
							y := data^[i].y;
							scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
							scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
							with dotRect do begin
									top := scry - offset;
									bottom := top + dotSize;
									right := scrx + offset + 1;
									left := right - dotSize;
								end;
							if dot in dotFormat then begin
									PenPat(Black);
									PaintOval(dotRect);
								end;
							if circle in dotFormat then begin
									EraseOval(dotRect);
									FrameOval(dotRect);
								end;
							if square in dotFormat then begin
									EraseRect(dotRect);
									FrameRect(dotRect);
								end;
						end;		{ for i...	}
				end;		{ with theFrame }
		end;			{ plotCircles }

		procedure plotPlusNCrossNPoint;
			var
				i, longSide, offset, crossLength: integer;
		begin
			with theFrame do begin
					with fRect do begin
							if (bottom - top) > (right - left) then  { portrait mode }
								longSide := bottom - top
							else	{ landscape mode -- choose crossLength based on longer side! }
								longSide := right - left;
						end;
					crossLength := round(crossPercentRatio / 100 * longSide);
					if odd(crossLength) then		{ want our crosses to be symmetrical }
						crossLength := crossLength + 1;
					offset := crossLength div 2;
					for i := 1 to numPts do begin
							x := data^[i].x;
							y := data^[i].y;
							scrx := screenX(x, xmax, xmin, fRect.right, fRect.left);
							scry := screenY(y, ymax, ymin, fRect.top, fRect.bottom);
							if plus in dotFormat then begin
									moveTo(scrx - offset, scry);
									line(crossLength, 0);
									moveTo(scrx, scry - offset);
									line(0, crosslength);
								end;
							if cross in dotFormat then begin
									moveTo(scrx - offset, scry - offset);
									line(crossLength, crossLength);
									moveTo(scrx + offset, scry - offset);
									line(-crossLength, crossLength);
								end;
							if points in dotFormat then begin
									PenMode(patXor);
									moveTo(scrx, scry);
									Line(0, 0);
									PenMode(patCopy);
								end;	{ if points... }
						end;		{ for i ... }
				end;		{ with theFrame }
		end;			{ plotPlusNCrossNPoint }

	begin	{ plotData }
		saveRgn := NewRgn;
		GetClip(saveRgn);
		ClipRect(theFrame.fRect);	{ to make sure plotting is clipped outside the fRect }

		if (lines in dotFormat) or (hist in dotFormat) then
			plotLineNHist;
		if (square in dotFormat) or (circle in dotFormat) or (dot in dotFormat) then
			plotSquareNCircleNDot;
		if (plus in dotFormat) or (cross in dotFormat) or (points in dotFormat) then
			plotPlusNCrossNPoint;

		SetClip(saveRgn);
	end;		{ plotData }

	procedure plot (data: datPtr;
									numPts: integer;
									plotRect: rect;
									xLabel, yLabel: str255;
									useGivenFrame: boolean;
									var theFrame: frame;
									frameFormat: frameType;
									dotFormat: dotType;
									lineFormat: lineType);
		const
			useGivenExtrema = false;
	begin
		if not useGivenFrame then
			chooseFrame(data, numPts, plotRect, xLabel, yLabel, theFrame, frameFormat, useGivenExtrema);
		plotFrame(theFrame, frameFormat, plotRect);
		plotData(data, numPts, theFrame, dotFormat, lineFormat);
	end;

end.	{ unit EZPlot }