-- PlotterImpl.mesa
Jlarson, July 10, 1985 5:48:34 pm PDT
DIRECTORY
Imager USING[Box, Context, MaskBox, MaskFill, MaskStroke, PathProc, SetFont, SetStrokeWidth, SetXY, ShowRope],
ImagerBackdoor USING[GetBounds],
ImagerFont USING[RopeBoundingBox],
IO USING[PutFR, 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],
Rope USING[ROPE],
Vector2 USING[VEC],
VFonts USING[DefaultFont],
ViewerClasses USING[ViewerClass, ViewerClassRec, PaintProc, Viewer, InitProc],
ViewerMenus USING[Grow, Close, Left, Right],
ViewerOps USING[CreateViewer, DestroyViewer, PaintViewer, RegisterViewerClass, SetMenu];
PlotterImpl: CEDAR MONITOR -- protects "plotters" list
IMPORTS Menus, Imager, ImagerBackdoor, ImagerFont,IO, Process, Real, RealFns, RealVec, RefAnyStream, VFonts, 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;
VEC: TYPE = Vector2.VEC;
module variables
plotters: LIST OF Handle ← NIL;
plotClass: ViewerClasses.ViewerClass
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] = TRUSTED
{valueAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];
timeAxis: RealVec.Handle = RealVec.All[nEvents, 0.0];
h ← 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]];
};
NewPlotter: ENTRY PROC[h: Handle] = {ENABLE UNWIND => NULL; plotters ← CONS[h, plotters]};
this guy is FORKED
PlotMaintainer: PROC[self: Handle] = TRUSTED
{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] = TRUSTED
{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] = TRUSTED
{ 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: 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: Imager.Box;
width, height: REAL;
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;
[[box.xmin, box.ymin, width, height]] ← ImagerBackdoor.GetBounds[context];
box.xmax ← box.xmin + width;
box.ymax ← box.ymin + height;
originX ← box.xmin;
originY ← box.ymin;
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;
Draw4pt: PROC[context: Imager.Context, p0,p1,p2,p3: VEC, w: REAL] ~ {
Path: Imager.PathProc ~ {moveTo[p0]; lineTo[p1]; lineTo[p2]; lineTo[p3]};
Imager.SetStrokeWidth[context, w];
Imager.MaskStroke[context: context, path: Path, closed: TRUE]};
DrawFilled4pt: PROC[context: Imager.Context, p0,p1,p2,p3: VEC, w: REAL] ~ {
Path: Imager.PathProc ~ {moveTo[p0]; lineTo[p1]; lineTo[p2]; lineTo[p3]};
Imager.SetStrokeWidth[context, w];
Imager.MaskFill[context, Path]};
DrawLine: PROC[context: Imager.Context, p0,p1: VEC, w: REAL] ~ {
Path: Imager.PathProc ~ {moveTo[p0]; lineTo[p1]};
Imager.SetStrokeWidth[context, w];
Imager.MaskStroke[context, Path]};
DrawPoint: PROC[context: Imager.Context, x, y: REAL, p: Plotter.PointShape] =
{
box: Imager.Box;
xSize, ySize, side: REAL;
[[box.xmin, box.ymin, xSize, ySize]] ← ImagerBackdoor.GetBounds[context];
box.xmax ← box.xmin + xSize;
box.ymax ← box.ymin + ySize;
side ← MIN[1.0, 3.0*(MIN[ySize, xSize]/1000.0)]; -- pixels
SELECT p FROM
dot, filledBox =>
Imager.MaskBox[context: context, box: [xmin: x-side,
ymin: y-side,
xmax: x+side,
ymax: y+side]];
circle, emptyBox => Draw4pt[context, [x-side, y-side],[x+side,y-side],[x+side, y+side],[x-side, y+side],1];
emptyDiamond => Draw4pt[context, [x-side,y],[x,y + side],[x+side,y],[x,y-side],1];
filledDiamond => DrawFilled4pt[context, [x-side,y],[x,y + side],[x+side,y],[x,y-side],1];
ENDCASE;
};
InnerBox: PROC[box: Imager.Box, xMargin, yMargin: REAL] RETURNS[innerBox: Imager.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: Imager.Context,
box: Imager.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: Imager.Box;
[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];
Draw4pt[context, [innerBox.xmin, innerBox.ymin],[innerBox.xmax,innerBox.ymin],[innerBox.xmax,innerBox.ymax],[innerBox.xmin,innerBox.ymax],1];
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: Imager.Context,
box: Imager.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 {
DrawLine[context,[box.xmin + tick, box.ymin],[box.xmin + tick, box.ymin + tickLen],1];
DrawLine[context,[box.xmin + tick, box.ymax],[box.xmin + tick, box.ymax - tickLen],1]};
ENDLOOP};
VerticalAxisLabels: PROC[context: Imager.Context,
box: Imager.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 {
DrawLine[context,[box.xmin, box.ymin + tick],[box.xmin + tickLen, box.ymin + tick],1];
DrawLine[context,[box.xmax, box.ymin + tick],[box.xmax - tickLen, box.ymin + tick],1]};
ENDLOOP};
formatNumber: PROC[value: REAL, forceInteger: BOOLEANTRUE]
RETURNS[Rope.ROPE] =
{RETURN[IF value = 0.0
THEN "0"
ELSE IF forceInteger
THEN IO.PutFR["%g ", IO.int[Real.RoundLI[value]]]
ELSE IO.PutFR["%-10.2f ", IO.real[value]]]};
MyDrawText: PROC[context: Imager.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;
Imager.SetFont[context,VFonts.DefaultFont[]];
Imager.SetXY[context, [x0, y0]];
Imager.ShowRope[context, text]};
GetTextBox: PROC[context: Imager.Context, text: Rope.ROPE]
RETURNS[dx, dy: REAL] =
{leftExtent, rightExtent, descent, ascent: REAL;
[[leftExtent, rightExtent, descent, ascent]] ← ImagerFont.RopeBoundingBox[VFonts.DefaultFont[],text];
dx ← rightExtent - leftExtent;
dy ← ascent + descent};
DrawLineSeg: PROC[context: Imager.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 DrawLine[context,[x0, y0],[x1, y1],1]
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 DrawLine[context,[x0, y0],[x1, y1],1];
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
}.