-- PlotterImpl.mesa 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; 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; VEC: TYPE = Vector2.VEC; 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] = 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]}; PlotMaintainer: PROC[self: Handle] = TRUSTED {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] = 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; 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] = 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, 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: 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: NAT _ MIN[v.length, h.length]; x0, y0, x1, y1: REAL; monotonic: BOOLEAN _ TRUE; [[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: 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: Imager.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; 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 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 }. €Jlarson, July 10, 1985 5:48:34 pm PDT TYPEs module variables PUBLIC PROCs 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 Κ4˜šœ˜Icode™%—J˜šΟk ˜ Jšœœb˜nJšœœ ˜ Jšœ œ˜"Jšœœ˜Jšœœ;˜FJšœœ˜(Jšœœ ˜Jšœœ˜"Jšœ œ˜.Jšœœ˜ Jšœœ9˜FJšœ œ˜Jšœœœ˜Jšœœœ˜Jšœœ˜Jšœœ;˜NJšœ œ˜,Jšœ œI˜XJ˜—šœ œΟc˜8Jšœœœ œO˜ƒJšœ ˜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šŸ œœœœœœ œ˜Z˜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˜——š Ÿœœœœ˜3šœœœœ˜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šœ˜Jšœœ˜Jšœ œ ˜Jšœ œ ˜Jšœœ ˜#J˜"J˜$Jšœœž˜+Jšœœ˜Jšœœ˜Jšœœ˜Jšœœœ˜!Jšœœ˜Jšœ œœ˜J˜J˜JšœJ˜JJšœ˜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šŸœœ&œœ˜EJšœ¬œ˜³J˜JšŸ œœ&œœš˜ΰšŸœœ œœ„˜ΏJ˜—JšŸ œœ œ˜Mšœ(˜.JšœJ˜JJšœ˜Jšœ˜Jšœœ œž ˜;J˜šœ˜˜šœ4˜4J˜J˜J˜——Jšœk˜kJšœR˜RJšœY˜YJšœ˜——J˜J˜JšŸœœ$œœ˜W˜%J˜#J˜#J˜#J˜šœ&˜(Jšœ&˜(Jšœœ˜J˜——Jšœ œ˜(Jšœ œ˜(Jšœœ˜$Jšœœ˜Jšœœ˜J˜šŸ œœ˜+Jšœž˜3Jšœœž˜1Jšœ/œž6œ˜ušœœž˜(Jšœœ˜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˜—šŸœœ˜3Jšœ˜Jšœœ˜"Jšœ œ˜—šœ œ˜'Jšœ œ3˜AJ˜šœœœ˜šœœ˜Jšœ œ'˜7J˜Cšœœ˜Jšœ˜JšœW˜WJšœX˜X——Jšœ˜ J˜——šŸœœ˜1Jšœ˜Jšœœ˜"Jšœ œ˜—šœ œ˜'Jšœ œ3˜AJ˜šœœœ˜šœœ˜Jšœ œ'˜7J˜Všœœ˜Jšœ˜JšœW˜WJšœX˜X——Jšœ˜ J˜——š œœ œœœ˜@Jšœœ˜šœœœ ˜Jšœ˜šœœ ˜Jšœœœ˜1Jšœœœ˜,J˜———šŸ œœ˜*Jšœ œ˜Jšœœ˜ J˜!JšœH™HJšœ œ˜˜J˜%šœ˜Jšœ˜Jšœœœ˜'—šœ˜Jšœ˜Jšœœ œ˜%—Jšœ-˜-Jšœ ˜ J˜ J˜——šŸ œœ%œ˜:Jšœ œ˜šœ+œ˜0Jšœe˜eJšœ˜Jšœ˜J˜——šŸ œœ˜+Jšœœ˜J˜"šœ œ˜Jšœœœœ˜1J˜šœœ˜,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˜ J˜J˜Jšœœ'˜5J˜J˜J˜Jšœœ˜&—Jšœž&˜2J˜—————Jšœ?™?šŸœœ6˜HJšœœ ˜%J˜J˜Jšœœ"˜=Jšœ˜J˜—šœ$œ˜1Jšœ!œ ˜1Jš œœœœ œ ˜HJšœ˜J˜—šœ"œ˜/Jšœœ˜+Jšœ˜J˜J˜—Jšœ ™ ˜J˜QJ˜MJ˜OJ˜MJ˜OJ˜MJ˜Jšœ2ž˜FJ˜J˜J˜J˜J˜J˜——…—FΎ\r