-- 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: PROCESS _ NIL, toStop: BOOLEAN _ FALSE]; 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: BOOLEAN _ FALSE, plotValuePerSecond: BOOLEAN _ FALSE, autoRepaint: BOOLEAN _ FALSE, iconic: BOOLEAN _ TRUE, 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: BOOLEAN _ FALSE, plotValuePerSecond: BOOLEAN _ FALSE, autoRepaint: BOOLEAN _ FALSE, iconic: BOOLEAN _ TRUE, 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: BOOLEAN _ TRUE; 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: BOOLEAN _ TRUE; 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: BOOLEAN _ TRUE, 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: NAT _ MIN[v.length, h.length]; x0, y0, x1, y1: REAL; monotonic: BOOLEAN _ TRUE; [] _ 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: REAL _ MIN[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: BOOLEAN _ TRUE] 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 }.