-- PlotterImpl.mesa
-- Last Modified On January 17, 1983 10:09 am By Paul Rovner

DIRECTORY
Graphics USING[MoveTo, DrawTo, Context, Rectangle, DrawStroke,
GetBounds, Box, SetFat, DrawBox, LineTo, DrawArea, GetDefaultFont,
Path, NewPath, SetCP],
GraphicsOps USING[TextBox, DrawText],
IO USING[PutFToRope, int, real],
Menus USING[MenuProc, Menu, CreateMenu, InsertMenuEntry, CreateEntry],
Plotter USING[PointShape, Connectivity],
Process USING[Detach],
Real USING[FixI, RoundI, RoundLI],
RealEvent USING[Handle, Object, StreamHandle],
RealFns USING[Power, Log, SqRt],
RealVec USING[Handle, LengthFault, All, IndexVec, SortOrder, Permute],
RefAnyStream USING[Error, CreateCoForkedConsumerStream],
Rope USING[ROPE, ToRefText],
SafeStorage USING[NewZone],
ViewerClasses USING[ViewerClass, ViewerClassRec, PaintProc, Viewer, InitProc],
ViewerMenus USING[Grow, Close, Left, Right],
ViewerOps USING[CreateViewer, DestroyViewer, PaintViewer, RegisterViewerClass, SetMenu];

PlotterImpl: MONITOR -- protects "plotters" list
IMPORTS Graphics, GraphicsOps, Menus, IO, Process, Real, RealFns, RealVec, RefAnyStream, Rope,
SafeStorage, ViewerMenus, ViewerOps
EXPORTS Plotter =
{ OPEN Real;

-- TYPEs
Object: PUBLIC TYPE = RECORD[eventSource: RealEvent.StreamHandle,
plotValueDifferences: BOOLEAN,
plotValuePerSecond: BOOLEAN,
autoRepaint: BOOLEAN,
valueAxis: RealVec.Handle,
timeAxis: RealVec.Handle,
viewer: ViewerClasses.Viewer,
nextIndex: NAT ← 0,
prevEvent: RealEvent.Object ← [0.0, 0.0],
maintainerProcess: PROCESSNIL,
toStop: BOOLEANFALSE];
Handle: TYPE = REF Object;

-- module variables
plotters: LIST OF Handle ← NIL;

vecQZone: ZONE = SafeStorage.NewZone[quantized];

plotClass: ViewerClasses.ViewerClass
← vecQZone.NEW[ViewerClasses.ViewerClassRec ← [paint: PlotPaint,
init: PlotInit]];
plotMenu: Menus.Menu ← Menus.CreateMenu[];


-- PUBLIC PROCs
Create: PUBLIC PROC
[label: Rope.ROPE,
eventSource: RealEvent.StreamHandle,
nEvents: NAT,
plotValueDifferences: BOOLEANFALSE,
plotValuePerSecond: BOOLEANFALSE,
autoRepaint: BOOLEANFALSE,
iconic: BOOLEANTRUE,
connectivity: Plotter.Connectivity ← solid,
pointShape: Plotter.PointShape ← none
]
RETURNS[h: Handle] =
{valueAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];
timeAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];

h ← vecQZone.NEW[Object ← [eventSource: eventSource,
plotValueDifferences: plotValueDifferences,
plotValuePerSecond: plotValuePerSecond,
autoRepaint: autoRepaint,
valueAxis: valueAxis,
timeAxis: timeAxis,
viewer: CreatePlotViewer[label: label,
verticalAxis: valueAxis,
horizontalAxis: timeAxis,
iconic: iconic,
connectivity: connectivity,
pointShape: pointShape
]
]
];
NewPlotter[h];
Process.Detach[FORK PlotMaintainer[h]];
};

CoCreate: PUBLIC PROC
[label: Rope.ROPE,
nEvents: NAT,
plotValueDifferences: BOOLEANFALSE,
plotValuePerSecond: BOOLEANFALSE,
autoRepaint: BOOLEANFALSE,
iconic: BOOLEANTRUE,
connectivity: Plotter.Connectivity ← solid,
pointShape: Plotter.PointShape ← none
]
RETURNS[plotter: Handle, eventSink: RealEvent.StreamHandle] =
{valueAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];
timeAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];

plotter ← vecQZone.NEW[Object ← [eventSource: NIL,
plotValueDifferences: plotValueDifferences,
plotValuePerSecond: plotValuePerSecond,
autoRepaint: autoRepaint,
valueAxis: valueAxis,
timeAxis: timeAxis,
viewer: CreatePlotViewer[label: label,
verticalAxis: valueAxis,
horizontalAxis: timeAxis,
iconic: iconic,
connectivity: connectivity,
pointShape: pointShape
]
]
];
NewPlotter[plotter];
RETURN[plotter, RefAnyStream.CreateCoForkedConsumerStream[PlotCoMaintainer, plotter]];
};


NewPlotter: ENTRY PROC[h: Handle] = {ENABLE UNWIND => NULL; plotters ← vecQZone.CONS[h, plotters]};

-- this guy is CoFORKED
PlotCoMaintainer: PROC[self: REF ANY, source: RealEvent.StreamHandle] =
{firstEvent: BOOLEANTRUE;
me: Handle = NARROW[self];
UNTIL me.toStop
DO
event: RealEvent.Handle = NARROW[source.procs.get
[source
! RefAnyStream.Error
=> IF ec = EndOfStream
OR ec = StreamClosed
THEN GOTO stop]];
IF me.toStop THEN EXIT;
RecordPlotterEvent[me, event^, firstEvent];
firstEvent ← FALSE;
IF me.autoRepaint THEN Paint[me];
ENDLOOP;
EXITS stop => NULL};

-- this guy is FORKED
PlotMaintainer: PROC[self: Handle] =
{firstEvent: BOOLEANTRUE;
UNTIL self.toStop
DO
source: RealEvent.StreamHandle = GetEventSource[self];
event: RealEvent.Handle;
IF source = NIL THEN EXIT;
event ← NARROW[source.procs.get[source ! RefAnyStream.Error
=> IF ec = EndOfStream
OR ec = StreamClosed
THEN GOTO stop]];
IF self.toStop THEN EXIT;
RecordPlotterEvent[self, event^, firstEvent];
firstEvent ← FALSE;
IF self.autoRepaint THEN Paint[self];
ENDLOOP;
EXITS stop => NULL};

GetEventSource: ENTRY PROC[self: Handle] RETURNS[RealEvent.StreamHandle] =
INLINE {ENABLE UNWIND => NULL; RETURN[self.eventSource]};

RecordPlotterEvent: ENTRY PROC[self: Handle, event: RealEvent.Object, firstEvent: BOOLEAN] =
{ ENABLE UNWIND => NULL;
valueToPlot: REAL;

IF firstEvent AND self.plotValueDifferences THEN self.prevEvent ← event;

IF self.nextIndex = self.timeAxis.length THEN self.nextIndex ← 0; -- circular buffer

self.timeAxis.elements[self.nextIndex] ← event.time; -- horizontal axis value

valueToPlot ← (IF self.plotValueDifferences
THEN event.sampleValue - self.prevEvent.sampleValue
ELSE event.sampleValue);
valueToPlot ← (IF self.plotValuePerSecond
THEN IF firstEvent
THEN 0.0
ELSE valueToPlot/(event.time - self.prevEvent.time)
ELSE valueToPlot);
self.valueAxis.elements[self.nextIndex] ← valueToPlot; -- vertical axis value

IF self.plotValueDifferences THEN self.prevEvent ← event;
self.nextIndex ← self.nextIndex + 1;
};


Paint: PUBLIC ENTRY PROC[self: Handle] =
{ENABLE UNWIND => NULL;
v: ViewerClasses.Viewer = self.viewer;
IF v # NIL THEN ViewerOps.PaintViewer[v, all]};


FindPlotterForViewer: ENTRY PROC[v: ViewerClasses.Viewer] RETURNS[Handle] =
{ENABLE UNWIND => NULL;
FOR p: LIST OF Handle ← plotters, p.rest UNTIL p = NIL
DO IF p.first.viewer = v THEN RETURN[p.first]
ENDLOOP;
RETURN[NIL]};

Destroy: PUBLIC ENTRY PROC[self: Handle] =
{ENABLE UNWIND => NULL;
v: ViewerClasses.Viewer = self.viewer;
self.viewer ← NIL;
IF NOT self.eventSource = NIL THEN self.eventSource.procs.close[self.eventSource];
self.eventSource ← NIL;
ViewerOps.DestroyViewer[v];
IF plotters.first = self
THEN plotters ← plotters.rest
ELSE FOR p: LIST OF Handle ← plotters, p.rest UNTIL p.rest = NIL
DO IF p.rest.first = self THEN {p.rest ← p.rest.rest; EXIT}
ENDLOOP;
--kill the maintainer process
self.toStop ← TRUE};

CreatePlotViewer: PUBLIC PROC[
label: Rope.ROPE,
verticalAxis: RealVec.Handle,
horizontalAxis: RealVec.Handle ← NIL,
iconic: BOOLEANTRUE,
connectivity: Plotter.Connectivity ← solid,
pointShape: Plotter.PointShape ← none]
RETURNS[v: ViewerClasses.Viewer] =
{ IF verticalAxis = NIL THEN ERROR RealVec.LengthFault[verticalAxis, 0];
IF horizontalAxis = NIL THEN horizontalAxis ← RealVec.IndexVec[verticalAxis.length];

v ← ViewerOps.CreateViewer
[flavor: $Plot,
info: [name: label,
column: left,
data: vecQZone.NEW[PlotDataRec ←
[vertical: verticalAxis,
horizontal: horizontalAxis,
-- below six are set properly by the printproc
dx: 0.0,
dy: 0.0,
xScale: 1.0,
yScale: 1.0,
xScaleMin: 1.0,
yScaleMin: 1.0,
pointShape: pointShape,
connectivity: connectivity]],
iconic: iconic]]};


-- Create a "plot" viewer class

PlotData: TYPE = REF PlotDataRec;
PlotDataRec: TYPE = RECORD [
vertical: RealVec.Handle,
horizontal: RealVec.Handle,
dx, dy: REAL, -- offset of inner box from context box
xScale, xScaleMin, yScale, yScaleMin: REAL,
-- transform data to inner box coords
pointShape: Plotter.PointShape,
connectivity: Plotter.Connectivity
];

PlotInit: ViewerClasses.InitProc = TRUSTED
{ViewerOps.SetMenu[viewer: self, menu: plotMenu, paint: FALSE--workaround Viewers bug--]};

PlotPaint: ViewerClasses.PaintProc = TRUSTED BEGIN
box: Graphics.Box ← Graphics.GetBounds[context];
originX: REAL ← box.xmin;
originY: REAL ← box.ymin;
data: PlotData ← NARROW[self.data];
v: RealVec.Handle ← data.vertical;
h: RealVec.Handle ← data.horizontal;
maxh: REAL ← h.elements[0]; -- data values
minh: REAL ← h.elements[0];
maxv: REAL ← v.elements[0];
minv: REAL ← v.elements[0];
l: NATMIN[v.length, h.length];
x0, y0, x1, y1: REAL;
monotonic: BOOLEANTRUE;

[] ← Graphics.SetFat[context, TRUE]; -- Groan

FOR i: NAT IN [0..l)
DO IF h.elements[i] < maxh THEN monotonic ← FALSE;
maxv ← MAX[maxv, v.elements[i]];
minv ← MIN[minv, v.elements[i]];
maxh ← MAX[maxh, h.elements[i]];
minh ← MIN[minh, h.elements[i]];
ENDLOOP;

[data.dx, data.dy, data.xScaleMin, data.xScale, data.yScaleMin, data.yScale]
← DisplayAxes[context, box, minh, maxh, minv, maxv];

IF data.pointShape # none
THEN FOR i: NAT IN [0..l) -- plot the points
DO x0 ← originX + data.dx + (h.elements[i] - data.xScaleMin)*data.xScale;
y0 ← originY + data.dy + (v.elements[i] - data.yScaleMin)*data.yScale;
DrawPoint[context, x0, y0, data.pointShape]
ENDLOOP;

IF NOT monotonic THEN
{perms: RealVec.Handle = RealVec.SortOrder[h];
h ← RealVec.Permute[h, perms];
v ← RealVec.Permute[v, perms]};

x0 ← originX + data.dx + (h.elements[0] - data.xScaleMin)*data.xScale;
y0 ← originY + data.dy + (v.elements[0] - data.yScaleMin)*data.yScale;
FOR i: NAT IN [1..l)
DO x1 ← originX + data.dx + (h.elements[i] - data.xScaleMin)*data.xScale;
y1 ← originY + data.dy + (v.elements[i] - data.yScaleMin)*data.yScale;
IF data.connectivity = vertical
THEN {x0 ← x1; y0 ← originY + data.dy};
DrawLineSeg[context, x0, y0, x1, y1, data.connectivity];
x0 ← x1;
y0 ← y1;
ENDLOOP;
END;


DrawPoint: PROC[context: Graphics.Context, x, y: REAL, p: Plotter.PointShape] =
{ OPEN Graphics;
box: Box = GetBounds[context];
xSize: REAL ← box.xmax-box.xmin; -- of bounding graphic box
ySize: REAL ← box.ymax-box.ymin;
side: REALMIN[1.0, 3.0*(MIN[ySize, xSize]/1000.0)]; -- pixels
SELECT p FROM
dot, filledBox =>
context.DrawBox[box: [xmin: x-side,
ymin: y-side,
xmax: x+side,
ymax: y+side]];
circle, emptyBox =>
{p: Path = NewPath[size: 4];
Rectangle[self: p, x0: x-side, y0: y-side, x1: x+side, y1: y+side];
context.DrawStroke[path: p, width: 1, closed: TRUE]};
emptyDiamond =>
{p: Path = NewPath[size: 4];
MoveTo[p, x-side, y];
LineTo[p, x, y + side];
LineTo[p, x+side, y];
LineTo[p, x, y-side];
context.DrawStroke[path: p, width: 1, closed: TRUE]};
filledDiamond =>
{p: Path = NewPath[size: 4];
MoveTo[p, x-side, y];
LineTo[p, x, y + side];
LineTo[p, x+side, y];
LineTo[p, x, y-side];
DrawArea[context, p]};
ENDCASE;
};

InnerBox: PROC[box: Graphics.Box, xMargin, yMargin: REAL] RETURNS[innerBox: Graphics.Box] =
{ innerBox.xmin ← box.xmin + xMargin;
innerBox.ymin ← box.ymin + yMargin;
innerBox.xmax ← box.xmax - xMargin;
innerBox.ymax ← box.ymax - yMargin;

IF (innerBox.xmax - innerBox.xmin) < 0.0
OR (innerBox.ymax - innerBox.ymin) < 0.0
THEN RETURN[box]};

textXpos: TYPE = {left, center, right};
textYpos: TYPE = {bottom, center, top};
micasPerPixel: REAL = (2540.0/72.0);
textHeightFudge: REAL = 2.0;
textWidthFudge: REAL = 6.0;

DisplayAxes: PROC[context: Graphics.Context,
box: Graphics.Box, -- lower left, upper right
xMin, xMax, yMin, yMax: REAL] -- data value range
RETURNS[dx, dy, xScaleMin, xScale, yScaleMin, yScale: REAL --for transforming data values to context.box coords--] =
{ xStep, yStep: REAL; -- between ticks
nLabelsX, nLabelsY: NAT;
labelWidth, labelHeight: REAL;
xSize: REAL = box.xmax-box.xmin; -- of bounding graphic box
ySize: REAL = box.ymax-box.ymin;
innerBox: Graphics.Box;
p: Graphics.Path = Graphics.NewPath[size: 4];

[labelWidth, labelHeight] ← GetTextBox[context, formatNumber[MAX[ABS[xMin], ABS[xMax], ABS[yMin], ABS[yMax]] * 10]];

innerBox ← InnerBox[box, labelWidth + textWidthFudge, labelHeight + textHeightFudge];
dx ← innerBox.xmin - box.xmin;
dy ← innerBox.ymin - box.ymin;

nLabelsX ← MAX[1, MIN[10, Real.RoundI[(((innerBox.xmax-innerBox.xmin)/labelWidth)*5)/8]]];
nLabelsY ← MAX[1, MIN[10, Real.RoundI[(((innerBox.ymax-innerBox.ymin)/labelHeight)*5)/8]]];

[xScaleMin, xMax, xStep, xScale] ← scaleAxis[xMin, xMax, innerBox.xmax-innerBox.xmin, nLabelsX];

[yScaleMin, yMax, yStep, yScale] ← scaleAxis[yMin, yMax, innerBox.ymax-innerBox.ymin, nLabelsY];

Graphics.MoveTo[p, innerBox.xmin, innerBox.ymin];
Graphics.Rectangle[p, innerBox.xmin, innerBox.ymin, innerBox.xmax, innerBox.ymax];
context.DrawStroke[path: p, width: 1, closed: TRUE];

HorizontalAxisLabels[context, innerBox, xScaleMin, xMax, xScale, xStep];
VerticalAxisLabels[context, innerBox, yScaleMin, yMax, yScale, yStep]};

scaleAxis: PROC[minDataValue, maxDataValue, innerBoxSize: REAL, nLabels: NAT]
RETURNS[min, max, step, scale: REAL] =
{step ← findStepSize[maxDataValue - minDataValue, nLabels];
min ← alignEnd[minDataValue, step, FALSE];
max ← alignEnd[maxDataValue, step, TRUE];
IF almost[min, max] THEN {max ← max + 50.0; min ← min - 50.0};
scale ← innerBoxSize/(max - min)};

findStepSize: PROC[range: REAL, nSteps: NAT] RETURNS[step: REAL] =
{RETURN[NEILfindStepSize[range, nSteps]]};

NEILfindStepSize: PROC[range: REAL, nSteps: NAT] RETURNS[step: REAL] =
{logRange: REAL;
mantissa, minStep: REAL;
characteristic: INTEGER;
steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0];

IF almost[range, 0.0] THEN range ← 100.0;

logRange ← RealFns.Log[10.0, range];
characteristic ← Real.FixI[logRange];
mantissa ← logRange - characteristic;
IF logRange < 0.0 THEN
{ characteristic ← characteristic - 1;
mantissa ← mantissa + 1.0};
minStep ← RealFns.Power[10.0, mantissa]/nSteps;
FOR i: NAT IN [0..5)
DO step ← steps[i];
IF step > minStep OR almost[step, minStep] THEN EXIT
ENDLOOP;
IF characteristic >= 0
THEN THROUGH [1..characteristic] DO step ← step*10.0 ENDLOOP
ELSE THROUGH [1..-characteristic] DO step ← step/10.0 ENDLOOP;
step ← MAX[1.0, step]};

alignEnd: PROC[e, step: REAL, roundUp: BOOLEAN] RETURNS[ae: REAL] =
{absE: REAL = ABS[e];
nSteps: LONG INTEGER;
xend: REAL;

IF e = 0.0 THEN RETURN[0.0];
IF e < 0.0 THEN roundUp ← ~roundUp;
nSteps ← Real.RoundLI[absE/step - 0.5];
xend ← step*nSteps;
IF almost[nSteps, absE/step]
THEN ae ← xend
ELSE IF roundUp
THEN
IF xend >= absE
THEN ae ← xend
ELSE ae ← step*(nSteps + 1)
ELSE
IF xend <= absE
THEN ae ← xend
ELSE ae ← step*(nSteps - 1);
IF e < 0.0 THEN ae ← -ae};

almost: PROC[p, q: REAL] RETURNS[BOOLEAN] = INLINE
{RETURN[MAX[ABS[p], ABS[q]] = 0.0 OR ABS[p - q]/MAX[ABS[p], ABS[q]] < 0.00001]};

HorizontalAxisLabels: PROC[context: Graphics.Context,
box: Graphics.Box,
minDataValue, maxDataValue: REAL,
scale, step: REAL] =
{ tickLen: REAL ← 200.0/micasPerPixel;
tickCount: NAT = Real.RoundI[(maxDataValue - minDataValue)/step];

FOR i: NAT IN [0..tickCount]
DO tick: REAL = step*scale*i;
label: Rope.ROPE = formatNumber[step*i + minDataValue];
MyDrawText[context, label, box.xmin + tick, box.ymin, center, top];
IF i # 0 AND i # tickCount
THEN {context.SetCP[box.xmin + tick, box.ymin];
context.DrawTo[box.xmin + tick, box.ymin + tickLen];
context.SetCP[box.xmin + tick, box.ymax];
context.DrawTo[box.xmin + tick, box.ymax - tickLen]};
ENDLOOP};

VerticalAxisLabels: PROC[context: Graphics.Context,
box: Graphics.Box,
minDataValue, maxDataValue: REAL,
scale, step: REAL] =
{ tickLen: REAL ← 200.0/micasPerPixel;
tickCount: NAT = Real.RoundI[(maxDataValue - minDataValue)/step];

FOR i: NAT IN [0..tickCount]
DO tick: REAL = step*scale*i;
label: Rope.ROPE = formatNumber[step*i + minDataValue];
MyDrawText[context, label, box.xmin - textWidthFudge, box.ymin + tick, right, center];
IF i # 0 AND i # tickCount
THEN {context.SetCP[box.xmin, box.ymin + tick];
context.DrawTo[box.xmin + tickLen, box.ymin + tick];
context.SetCP[box.xmax, box.ymin + tick];
context.DrawTo[box.xmax - tickLen, box.ymin + tick]};
ENDLOOP};

formatNumber: PROC[value: REAL, forceInteger: BOOLEANTRUE]
RETURNS[Rope.ROPE] =
{RETURN[IF value = 0.0
THEN "0"
ELSE IF forceInteger
THEN IO.PutFToRope["%g ", IO.int[Real.RoundLI[value]]]
ELSE IO.PutFToRope["%-10.2f ", IO.real[value]]]};

MyDrawText: PROC[context: Graphics.Context,
text: Rope.ROPE,
x0, y0: REAL,
xPos: textXpos, yPos: textYpos] =
-- the textYpos args specify where within the text the specified coords are
{dx, dy: REAL;

[dx, dy] ← GetTextBox[context, text];
IF xPos = center
THEN x0 ← x0 - dx/2
ELSE IF xPos = right THEN x0 ← x0 - dx;
IF yPos = center
THEN y0 ← y0 - dy/2
ELSE IF yPos = top THEN y0 ← y0 - dy;
context.SetCP[x0, y0];
GraphicsOps.DrawText[context, Rope.ToRefText[text]]};

GetTextBox: PROC[context: Graphics.Context, text: Rope.ROPE]
RETURNS[dx, dy: REAL] =
{xmin,ymin,xmax,ymax: REAL;
[xmin,ymin,xmax,ymax] ← GraphicsOps.TextBox[
Graphics.GetDefaultFont[context],
Rope.ToRefText[text]];
dx ← xmax - xmin;
dy ← ymax - ymin};

DrawLineSeg: PROC[context: Graphics.Context,
x0, y0, x1, y1: REAL,
lineStyle: Plotter.Connectivity] =
{minSize: REAL = 7.0;
IF almost[x0, x1] AND almost[y0, y1] THEN RETURN;

IF lineStyle = solid OR lineStyle = vertical
THEN {context.SetCP[x0, y0]; context.DrawTo[x1, y1]}
ELSE { max: REAL = (SELECT lineStyle FROM
dotted => 300/micasPerPixel,
dashed => 600/micasPerPixel,
dotDash => 900/micasPerPixel,
solid => 0.0
ENDCASE => ERROR);
dx: REAL = x1 - x0;
dy: REAL = y1 - y0;
s: REAL ← RealFns.SqRt[dx*dx + dy*dy];
lineState: REAL ← 0.0;
dxds: REAL ← dx/s;
dyds: REAL ← dy/s;

UNTIL almost[s, 0]
DO space: BOOLEAN;
inc: REAL;
SELECT lineStyle FROM
dotted =>
IF lineState < 100/micasPerPixel
THEN {space ← FALSE;
inc ← MIN[s, 100/micasPerPixel - lineState]}
ELSE {space ← TRUE;
inc ← MIN[s, 300/micasPerPixel - lineState]};
dashed =>
IF lineState < 400/micasPerPixel
THEN {space ← FALSE;
inc ← MIN[s, 400/micasPerPixel - lineState]}
ELSE {space ← TRUE;
inc ← MIN[s, 600/micasPerPixel - lineState]};
dotDash =>
IF lineState < 400/micasPerPixel
THEN {space ← FALSE;
inc ← MIN[s, 400/micasPerPixel - lineState]}
ELSE IF lineState < 600/micasPerPixel
THEN {space ← TRUE;
inc ← MIN[s, 600/micasPerPixel - lineState]}
ELSE IF lineState < 700/micasPerPixel
THEN {space ← FALSE;
inc ← MIN[s, 700/micasPerPixel
- lineState]}
ELSE {space ← TRUE;
inc ← MIN[s, 900/micasPerPixel
- lineState]};
ENDCASE => ERROR;
s ← s - inc;
x1 ← x0 + dxds*inc;
y1 ← y0 + dyds*inc;
IF ~space THEN {context.SetCP[x0, y0]; context.DrawTo[x1, y1]};
x0 ← x1;
y0 ← y1;
lineState ← lineState + inc;
IF lineState >= max THEN lineState ← 0
ENDLOOP}}; -- end big ELSE clause and DrawLineSeg

-- use this routine to set the plot to new values (NOTEfor laytah)
PlotSet: PROC [plot: ViewerClasses.Viewer, v, h: RealVec.Handle] = BEGIN
myData: PlotData = NARROW[plot.data];
myData.vertical ← v;
myData.horizontal ← h;
IF ~plot.parent.iconic THEN ViewerOps.PaintViewer[plot, all];
END;

DestroyPlotViewer: Menus.MenuProc = TRUSTED BEGIN
h: Handle = FindPlotterForViewer[NARROW[parent]];
IF h = NIL THEN ViewerOps.DestroyViewer[NARROW[parent]] ELSE Destroy[h];
END;

PaintPlotViewer: Menus.MenuProc = TRUSTED BEGIN
ViewerOps.PaintViewer[NARROW[parent], all];
END;


-- START HERE

Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Destroy", DestroyPlotViewer]];
Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Grow", ViewerMenus.Grow]];
Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Close", ViewerMenus.Close]];
Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Left", ViewerMenus.Left]];
Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Right", ViewerMenus.Right]];
Menus.InsertMenuEntry[plotMenu, Menus.CreateEntry["Paint", PaintPlotViewer]];

ViewerOps.RegisterViewerClass[$Plot, plotClass]; -- plug into Viewers

}.