DIRECTORY Graphics USING[MoveTo, DrawTo, Context, Rectangle, DrawStroke, GetBounds, Box, SetFat, DrawBox, LineTo, DrawArea, GetDefaultFont, Path, NewPath, SetCP], GraphicsOps USING[TextBox, DrawText], 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, CreateCoForkedConsumerStream], Rope USING[ROPE, ToRefText], 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, ViewerMenus, ViewerOps EXPORTS Plotter = { OPEN Real; 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; plotters: LIST OF Handle _ NIL; plotClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [paint: PlotPaint, init: PlotInit]]; plotMenu: Menus.Menu _ Menus.CreateMenu[]; 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 _ 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 _ 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 _ CONS[h, plotters]}; 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}; 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; 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: NEW[PlotDataRec _ [vertical: verticalAxis, horizontal: horizontalAxis, dx: 0.0, dy: 0.0, xScale: 1.0, yScale: 1.0, xScaleMin: 1.0, yScaleMin: 1.0, pointShape: pointShape, connectivity: connectivity]], iconic: iconic]]}; 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, 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.PutFR["%g ", IO.int[Real.RoundLI[value]]] ELSE IO.PutFR["%-10.2f ", IO.real[value]]]}; MyDrawText: PROC[context: Graphics.Context, text: Rope.ROPE, x0, y0: REAL, xPos: textXpos, yPos: textYpos] = {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 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; 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 }. ºPlotterImpl.mesa Last Modified On December 16, 1983 2:16 pm By Paul Rovner TYPEs module variables PUBLIC PROCs this guy is CoFORKED this guy is FORKED kill the maintainer process below six are set properly by the printproc Create a "plot" viewer class transform data to inner box coords the textYpos args specify where within the text the specified coords are use this routine to set the plot to new values (NOTEfor laytah) START HERE Ê2˜J˜Jšœ™Jšœ9™9J˜šÏk ˜ šœ œ0˜>J˜BJ˜—Jšœ œ˜%Jšœœ˜Jšœœ;˜FJšœœ˜(Jšœœ ˜Jšœœ˜"Jšœ œ˜.Jšœœ˜ Jšœœ9˜FJšœ œ&˜8Jšœœœ ˜Jšœœ;˜NJšœ œ˜,Jšœ œI˜XJ˜—šœ œÏc˜1JšœœM˜uJšœ ˜Jšœœ˜ J˜—šœ™šœœœœ%˜AJšœœ˜Jšœœ˜Jšœ œ˜J˜J˜J˜Jšœ œ˜J˜)Jšœœœ˜!Jšœœœ˜—Jšœœœ˜J˜—šœ™Jšœ œœ œ˜J˜˜$šœœ2˜7J˜——J˜*J˜J˜—šœ ™ šÏnœœ˜šœ œ˜J˜$Jšœ œ˜ Jšœœœ˜&Jšœœœ˜$Jšœ œœ˜Jšœœœ˜J˜+J˜%—J˜Jšœ˜˜7J˜5J˜šœœ$˜+J˜+˜'J˜—J˜J˜˜&J˜J˜J˜J˜J˜J˜—J˜J˜—J˜Jšœœ˜'—J˜J˜—šŸœœ˜šœ œ˜Jšœ œ˜ Jšœœœ˜&Jšœœœ˜$Jšœ œœ˜Jšœœœ˜J˜+J˜%—J˜Jšœ6˜=˜7J˜5J˜šœ œœ˜)J˜+˜'J˜—J˜J˜˜&J˜J˜J˜J˜J˜J˜—J˜J˜—J˜JšœP˜V—J˜J˜J˜—JšŸ œœœœœœ œ˜Z˜Jšœ™—šŸœœœœ#˜Gšœ œœ˜Jšœ œ˜šœ ˜š˜šœœ˜1˜˜šœœ˜Jšœ˜Jšœœ˜————Jšœ œœ˜J˜+Jšœ œ˜Jšœœ ˜!—Jšœ˜—Jšœ œ˜J˜—Jšœ™—šŸœœ˜$šœ œœ˜šœ ˜š˜J˜6J˜Jšœ œœœ˜šœœ-˜;šœœ˜Jšœ˜Jšœœ˜——Jšœ œœ˜J˜-Jšœ œ˜Jšœœ ˜%—Jšœ˜—Jšœ œ˜J˜——JšŸœœœœ˜JJš œœœœœ˜9J˜šŸœœœ4œ˜\šœœœœ˜Jšœ œ˜J˜Jšœ œœ˜HJ˜Jšœ'œž˜UJ˜Jšœ6ž˜NJ˜šœœ˜+Jšœ/˜3Jšœ˜—šœœ˜)šœœ ˜Jšœ˜Jšœ/˜3—Jšœ˜—Jšœ8ž˜NJ˜Jšœœ˜9J˜$—J˜J˜J˜—šŸœœœœ˜(šœœœœ˜J˜&Jšœœœ ˜/J˜J˜——šŸœœœœ ˜Kšœœœœ˜š œœœœ˜6Jšœœœœ ˜-Jšœ˜—Jšœœ˜ J˜——šŸœœœœ˜*šœœœœ˜J˜&Jšœœ˜Jšœœœœ0˜RJšœœ˜J˜šœ˜Jšœ˜š œœœœœ ˜@Jšœœœœ˜;Jšœ˜——Jšœ™Jšœœ˜J˜——šŸœœœ˜Jšœ œ˜J˜Jšœ!œ˜%Jšœœœ˜J˜+J˜&Jšœ˜"š œœœœœ&˜HJšœœœ8˜TJ˜˜˜˜J˜ šœœ˜˜J˜————šœ+™+J˜J˜J˜ J˜ J˜J˜J˜J˜J˜J˜J˜—————Jšœ™J˜Jšœ œœ ˜!šœ œœ˜J˜J˜Jšœœž'˜6šœ&œ˜+Jšœ"™"—J˜J˜"J˜J˜—šœ#˜*Jšœ8žœ˜ZJ˜—šœ%œ˜2J˜0Jšœ œ ˜Jšœ œ ˜Jšœœ ˜#J˜"J˜$Jšœœž˜+Jšœœ˜Jšœœ˜Jšœœ˜Jšœœœ˜!Jšœœ˜Jšœ œœ˜J˜Jšœœž˜.J˜šœœœ˜šœœœ œ˜2Jšœœ˜ Jšœœ˜ Jšœœ˜ Jšœœ˜ —Jšœ˜J˜—˜LJ˜4J˜—šœ˜š œœœœ ž˜-šœG˜IJ˜FJ˜+Jšœ˜J˜———šœœ ˜˜.J˜J˜˜J˜F———J˜Fšœœœ˜šœG˜IJ˜Fšœ˜Jšœ#˜'—J˜8J˜J˜—Jšœ˜—Jšœ˜J˜J˜—JšŸ œœ"œ˜Ošœœ ˜J˜Jšœœž˜J˜"J˜——š œœ œ œœœ˜EJšœœ#˜*J˜—š Ÿœœ œ œœœ˜Išœ œ˜Jšœœ˜Jšœœ˜Jšœœœœ#˜?J˜Jšœœ˜)J˜J˜$˜%J˜%šœ˜˜&J˜——J˜/šœœœ˜šœ˜Jšœœœ˜4—Jšœ˜—Jšœ˜šœœœ˜—Jšœœ ˜J˜———š œ œ œ œœœ˜Gšœœœ˜Jšœ œœ˜Jšœœ˜ J˜Jšœ œœ˜Jšœ œ˜#J˜'J˜šœ˜Jšœ ˜šœœ˜š˜šœ ˜Jšœ ˜Jšœ˜—Jš˜šœ ˜Jšœ ˜Jšœ˜——Jšœ œ ˜J˜————š œ œœœœ˜4Jšœœœœœ œœœœœ˜PJ˜—šŸœœ˜5J˜Jšœœ˜"Jšœ œ˜—šœ œ˜'Jšœ œ3˜AJ˜šœœœ˜šœœ˜Jšœ œ'˜7J˜Cšœœ˜šœ+˜/J˜4J˜)J˜5———Jšœ˜ J˜——šŸœœ˜3J˜Jšœœ˜"Jšœ œ˜—šœ œ˜'Jšœ œ3˜AJ˜šœœœ˜šœœ˜Jšœ œ'˜7J˜Všœœ˜šœ+˜/J˜4J˜)J˜5———Jšœ˜ J˜——š œœ œœœ˜@Jšœœ˜šœœœ ˜Jšœ˜šœœ ˜Jšœœœ˜1Jšœœœ˜,J˜———šŸ œœ˜,Jšœ œ˜Jšœœ˜ J˜!JšœH™HJšœ œ˜˜J˜%šœ˜Jšœ˜Jšœœœ˜'—šœ˜Jšœ˜Jšœœ œ˜%—J˜J˜5J˜——šŸ œœ'œ˜