DIRECTORY BasicTime USING [GMT, nullGMT], ColorMap USING [SetRGBColor], Graphics USING [black, Box, Color, Context, DrawBox, DrawRope, DrawStroke, FontBox, FontRef, GetBounds, GetCP, LineTo, MakeFont, Mark, MoveTo, NewPath, Path, Rectangle, Restore, RopeBox, Rotate, Save, Scale, SetColor, SetCP, SetFat, SetPaintMode, Translate, white], GraphicsColor USING [blue, cyan, green, magenta, red, RGBToColor, yellow], GraphicsToPress USING [Close, NewContext, SetPageSize], IO USING [int, PutFR, real, rope, time], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], MessageWindow USING [Append, Blink], Plot USING [Curves, Vector, PlotSpec, PlotSpecRec, RopeSequence, WritePlotFile], PlotOps USING [Handle, HandleData, Lock, PathSequence, Unlock], Real USING [FixI, RoundI, RoundLI], RealFns USING [Log, Power], Rope USING [Concat, IsEmpty, ROPE], ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [--ComputeColumn, ChangeColumn, CloseViewer, CreateViewer, EnumerateViewers, EnumProc, PaintViewer, RegisterViewerClass]; PlotScreen: CEDAR PROGRAM IMPORTS BasicTime, ColorMap, Graphics, GraphicsColor, GraphicsToPress, IO, Menus, MessageWindow, Plot, PlotOps, Real, RealFns, Rope, ViewerOps--, ViewerPaintImpl EXPORTS Plot, PlotOps SHARES ViewerOps = { OPEN Plot, PlotOps; RGB: TYPE = RECORD[r,g,b: REAL]; OutputType: TYPE = {screen, pressFile}; FontType: TYPE = {title, normal}; RefNumVector: TYPE = REF NVector; NVector: TYPE = RECORD[nVector: CARDINAL]; TextHeightFudge: REAL = 6.0; TextWidthFudge: REAL = 8.0; minLineWidth: ARRAY OutputType OF REAL = [0, 2.0]; nColors: INTEGER = 16; plotViewerClass: ViewerClasses.ViewerClass _ NIL; t12: Graphics.FontRef = Graphics.MakeFont["TimesRoman12"]; tD36: Graphics.FontRef = Graphics.MakeFont["TimesRomanD36"]; h8: Graphics.FontRef = Graphics.MakeFont["Helvetica8"]; hD24: Graphics.FontRef = Graphics.MakeFont["HelveticaD24"]; textFont: ARRAY FontType OF ARRAY OutputType OF Graphics.FontRef = [[t12, tD36], [h8, hD24]]; rgb: ARRAY[0..nColors) OF RGB; color: ARRAY [0..nColors) OF Graphics.Color; fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL; textScale: ARRAY FontType OF ARRAY OutputType OF REAL; CreateViewer: PUBLIC PROC [spec: PlotSpec _ NIL, column: ViewerClasses.Column _ left, iconic: BOOL _ TRUE] RETURNS [viewer: ViewerClasses.Viewer _ NIL] = { handle: Handle _ NEW[HandleData _ [ plotSpec: spec, paths: NEW[PathSequence[spec.nCurvesMax]] ] ]; FOR i: CARDINAL IN [0..spec.nCurvesMax) DO handle.paths[i] _ Graphics.NewPath[2]; ENDLOOP; IF column = color AND NOT iconic THEN { MyEnum: ViewerOps.EnumProc = { IF v.column = color AND NOT v.iconic THEN { ViewerOps.CloseViewer[v, FALSE]; ViewerOps.PaintViewer[v, all, TRUE, NIL]; }; }; ViewerOps.EnumerateViewers[MyEnum]; }; viewer _ ViewerOps.CreateViewer[ flavor: $Plot, info: [ name: Rope.Concat["Plot of ", spec.file], menu: MakeMenu[], column: column, iconic: iconic, data: handle], paint: FALSE]; ViewerOps.PaintViewer[viewer, all, TRUE, NIL]; }; -- CreateViewer AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer _ NIL, vector: Vector _ NIL] = { IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE { shouldPaint: BOOL _ FALSE; z: Curves; expectedSize: CARDINAL; oldNumVector: CARDINAL; handle: Handle _ NARROW[viewer.data]; Lock[handle]; oldNumVector _ handle.nVector; z _ handle.curves; expectedSize _ handle.plotSpec.nCurvesMax + 1; IF vector.size # expectedSize THEN MessageWindow.Append[ IO.PutFR["Only %g elements in vector when %g expected.", IO.int[vector.size], IO.int[expectedSize]], TRUE] ELSE { handle.nVector _ handle.nVector + 1; IF z = NIL THEN handle.curves _ CONS[vector, NIL] ELSE { UNTIL z.rest = NIL DO z _ z.rest ENDLOOP; z.rest _ CONS[vector, NIL]; shouldPaint _ TRUE; }; }; Unlock[handle]; IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, NEW[NVector _ [oldNumVector + 1]]]; }; }; -- AddVector CreateSpec: PUBLIC PROC[file, title: Rope.ROPE _ NIL, time: BasicTime.GMT _ BasicTime.nullGMT, bounds: Graphics.Box _ [0, 0, 0, 0], -- [xmin, xmax, ymin, ymax] nCurvesMax: CARDINAL _ 0, legendEntries: REF RopeSequence _ NIL ] RETURNS[spec: PlotSpec _ NIL] = { spec _ NEW[PlotSpecRec _ [file, title, time, bounds, nCurvesMax, legendEntries]]; }; -- CreateSpec SetSpec: PUBLIC PROC [viewer: ViewerClasses.Viewer _ NIL, newSpec: PlotSpec _ NIL] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; Lock[handle]; IF handle.plotSpec.nCurvesMax # newSpec.nCurvesMax THEN { handle.paths _ NEW[PathSequence[newSpec.nCurvesMax]]; handle.nVector _ 0; handle.curves _ NIL; }; handle.plotSpec _ newSpec; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; -- update the display. }; }; -- SetSpec Clear: PUBLIC PROC [viewer: ViewerClasses.Viewer] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; Lock[handle]; handle.plotSpec^ _ []; handle.paths _ NIL; handle.nVector _ 0; handle.curves _ NIL; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- Clear Zoom: PUBLIC PROC [viewer: ViewerClasses.Viewer, newBounds: Graphics.Box] = { IF IsPlotViewer[viewer] THEN { handle: Handle _ NARROW[viewer.data]; Lock[handle]; handle.plotSpec.bounds _ newBounds; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- Zoom IsPlotViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [BOOL] = { IF viewer = NIL THEN RETURN[FALSE]; RETURN[viewer.class.flavor = $Plot]; }; -- IsPlotViewer SwapBackground: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; Lock[handle]; handle.foreground _ IF handle.foreground = Graphics.white THEN Graphics.black ELSE Graphics.white; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL] }; -- SwapBackground Press: Menus.MenuProc = { context: Graphics.Context; viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; Lock[handle]; context _ GraphicsToPress.NewContext[handle.plotSpec.file.Concat[".press"]]; GraphicsToPress.SetPageSize[context]; context.Rotate[90]; DrawMe[context, handle, NIL, pressFile]; Unlock[handle]; GraphicsToPress.Close[context]; }; -- Press Save: Menus.MenuProc = { msg: Rope.ROPE; viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; Lock[handle]; msg _ WritePlotFile[handle.plotSpec, handle.curves]; Unlock[handle]; IF msg # NIL THEN { MessageWindow.Blink[]; MessageWindow.Append[message: msg, clearFirst: TRUE]; }; }; -- Save Display: ViewerClasses.PaintProc = { IF IsPlotViewer[self] AND NOT self.iconic THEN { handle: Handle _ NARROW[self.data]; Lock[handle]; DrawMe[context, handle, whatChanged, screen]; Unlock[handle]; }; }; -- Display DrawMe: PROC [context: Graphics.Context, handle: Handle, whatChanged: REF ANY, output: OutputType] = { BoundsValid: PROC [box: Graphics.Box] RETURNS [BOOL] = INLINE { RETURN[box.xmax > box.xmin AND box.ymax > box.ymin]; }; -- BoundsValid box: Graphics.Box _ Graphics.GetBounds[context]; width: REAL _ box.xmax - box.xmin; height: REAL _ box.ymax - box.ymin; marginRatio: REAL = 0.05; innerRatio: REAL = 0.9; marginX: REAL _ width*marginRatio; marginY: REAL _ height*marginRatio; maxX: REAL _ box.xmax - marginX; maxY: REAL _ box.ymax - marginY; minX: REAL _ box.xmin + marginX; minY: REAL _ box.ymin + marginY; innerWidth: REAL _ width * innerRatio; innerHeight: REAL _ height * innerRatio; tenthHeight: REAL _ innerHeight/10.0; textLineHeight: REAL _ fontHeight[normal][screen] + TextHeightFudge; windowTooLow: BOOL _ tenthHeight < textLineHeight; legendTop: REAL _ IF windowTooLow THEN minY ELSE minY + tenthHeight*1.5; axesTop: REAL _ IF windowTooLow THEN maxY ELSE maxY - tenthHeight; titleBox: Graphics.Box _ [minX, axesTop, maxX, maxY]; axesBox: Graphics.Box _ [minX, legendTop, maxX, axesTop]; legendBox: Graphics.Box _ [minX, minY, maxX, legendTop]; foreground: Graphics.Color _ IF output = screen THEN handle.foreground ELSE Graphics.black; background: Graphics.Color _ IF foreground = Graphics.white THEN Graphics.black ELSE Graphics.white; [] _ context.SetPaintMode[opaque]; [] _ Graphics.SetFat[context, FALSE]; IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN { IF output = screen THEN { -- clear the screen context.SetColor[background]; context.DrawBox[box]; }; context.SetColor[foreground]; IF NOT windowTooLow THEN { footNote: Rope.ROPE _ IO.PutFR["File: %g; Time: %g.", IO.rope[handle.plotSpec.file], IO.time[handle.plotSpec.time]]; DrawTitle[context, titleBox, handle.plotSpec.title, output]; DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries, footNote, output]; }; IF BoundsValid[handle.plotSpec.bounds] THEN { [handle.curvesBox, handle.realBounds] _ DrawAxes[context, axesBox, handle.plotSpec.bounds, output]; DrawCurves[context, handle.curvesBox, handle.realBounds, handle.curves, output]; }; } ELSE IF ISTYPE[whatChanged, RefNumVector] THEN { newNumVector: RefNumVector _ NARROW[whatChanged]; nVector: CARDINAL _ newNumVector.nVector; curves: Curves _ handle.curves; index: CARDINAL _ 1; m0, m1: Vector; IF nVector <= 1 THEN {Unlock[handle]; RETURN}; DO index _ index + 1; IF index = nVector THEN { m0 _ curves.first; m1 _ curves.rest.first; EXIT; }; curves _ curves.rest; ENDLOOP; { -- begin to plot the last step curveWidth: REAL = handle.curvesBox.xmax - handle.curvesBox.xmin; curveHeight: REAL = handle.curvesBox.ymax - handle.curvesBox.ymin; tMin: REAL = handle.realBounds.xmin; tFactor: REAL = curveWidth/(handle.realBounds.xmax - tMin); vMin: REAL = handle.realBounds.ymin; vFactor: REAL = curveHeight/(handle.realBounds.ymax - vMin); t0: REAL = (m0[0] - tMin)*tFactor; t1: REAL = (m1[0] - tMin)*tFactor; size: CARDINAL = m0.size; -- should be equal to m1.size context.Translate[handle.curvesBox.xmin, handle.curvesBox.ymin]; FOR index IN [1..size) DO ipath: CARDINAL _ index - 1; Graphics.MoveTo[handle.paths[ipath], t0, (m0[index]-vMin)*vFactor, TRUE]; Graphics.LineTo[handle.paths[ipath], t1, (m1[index]-vMin)*vFactor]; context.SetColor[color[ipath MOD 16]]; ColorMap.SetRGBColor[ index: 100+ ipath, r: rgb[ipath].r, g: rgb[ipath].g, b: rgb[ipath].b]; context.DrawStroke[handle.paths[ipath], minLineWidth[output]]; ENDLOOP; }; }; }; -- DrawMe DrawTitle: PROC [context: Graphics.Context, box: Graphics.Box, title: Rope.ROPE, output: OutputType] = { xPos: REAL _ box.xmin + (box.xmax - box.xmin) / 2.0; boxH: REAL _ box.ymax - box.ymin - fontHeight[normal][screen]; IF title.IsEmpty[] THEN RETURN; IF boxH >= fontHeight[title][screen] THEN [] _ MyDrawText[context, title, xPos, box.ymax, center, top, title, output] ELSE IF boxH >= fontHeight[normal][screen] THEN [] _ MyDrawText[context, title, xPos, box.ymax, center, top, normal, output]; }; -- DrawTitle DrawAxes: PROC [context: Graphics.Context, box, bounds: Graphics.Box, output: OutputType] RETURNS [innerBox, realBounds: Graphics.Box] = { HorizontalAxisLabels: PROC[context: Graphics.Context, box: Graphics.Box, min, max, scale, step: REAL] = { tickLen: REAL _ (box.ymax - box.ymin)/50.0; tickCount: CARDINAL = Real.RoundI[(max - min)/step]; textTop: REAL _ box.ymin; -- In practice, no need to deduct TextHeightFudge. tick: REAL; FOR i: CARDINAL IN [0..tickCount] DO tick _ step*scale*i; [] _ MyDrawText[context, IO.PutFR["%g", IO.real[step*i + min]], box.xmin + tick, textTop, center, top, normal, output]; IF i # 0 AND i # tickCount THEN { Graphics.MoveTo[path, box.xmin + tick, box.ymin, TRUE]; Graphics.LineTo[path, box.xmin + tick, box.ymin + tickLen]; context.DrawStroke[path, minLineWidth[output]]; Graphics.MoveTo[path, box.xmin + tick, box.ymax, TRUE]; Graphics.LineTo[path, box.xmin + tick, box.ymax - tickLen]; context.DrawStroke[path, minLineWidth[output]]; }; ENDLOOP; }; -- HorizontalAxisLabels VerticalAxisLabels: PROC[context: Graphics.Context, box: Graphics.Box, min, max, scale, step: REAL] = { tickLen: REAL _ (box.ymax - box.ymin)/50.0; tickCount: CARDINAL = Real.RoundI[(max - min)/step]; textRight: REAL _ box.xmin - TextWidthFudge; tick: REAL; FOR i: CARDINAL IN [0..tickCount] DO tick _ step*scale*i; [] _ MyDrawText[context, IO.PutFR["%g", IO.real[step*i + min]], textRight, box.ymin + tick, right, bottom, normal, output]; IF i # 0 AND i # tickCount THEN { Graphics.MoveTo[path, box.xmin, box.ymin + tick, TRUE]; Graphics.LineTo[path, box.xmin + tickLen, box.ymin + tick]; context.DrawStroke[path, minLineWidth[output]]; Graphics.MoveTo[path, box.xmax, box.ymin + tick, TRUE]; Graphics.LineTo[path, box.xmax - tickLen, box.ymin + tick]; context.DrawStroke[path, minLineWidth[output]]; }; ENDLOOP; }; -- VerticalAxisLabels xSize: REAL = box.xmax-box.xmin; ySize: REAL = box.ymax-box.ymin; path: Graphics.Path = Graphics.NewPath[size: 5]; xStep, yStep: REAL; -- between ticks xScale, yScale: REAL; nLabelsX, nLabelsY: CARDINAL; hLabelWidth, hLabelHeight, vLabelWidth, vLabelHeight, tLabelWidth, tLabelHeight: REAL; inBoxW, inBoxH: REAL; [tLabelWidth, tLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmin]], normal, output]; [hLabelWidth, hLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmax]], normal, output]; hLabelWidth _ MAX[tLabelWidth, hLabelWidth]; hLabelHeight _ MAX[tLabelHeight, hLabelHeight]; [tLabelWidth, tLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymin]], normal, output]; [vLabelWidth, vLabelHeight] _ GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymax]], normal, output]; vLabelWidth _ MAX[tLabelWidth, vLabelWidth]; vLabelHeight _ MAX[tLabelHeight, vLabelHeight]; innerBox _ [ xmin: box.xmin + vLabelWidth + TextWidthFudge, ymin: box.ymin + hLabelHeight + TextHeightFudge, xmax: box.xmax, ymax: box.ymax]; IF innerBox.xmin > innerBox.xmax THEN innerBox.xmin _ box.xmin; IF innerBox.ymin > innerBox.ymax THEN innerBox.ymin _ box.ymin; Graphics.MoveTo[path, innerBox.xmin, innerBox.ymin]; Graphics.Rectangle[path, innerBox.xmin, innerBox.ymin, innerBox.xmax, innerBox.ymax]; context.DrawStroke[path: path, width: minLineWidth[output], closed: TRUE]; inBoxW _ innerBox.xmax - innerBox.xmin; inBoxH _ innerBox.ymax - innerBox.ymin; nLabelsX _ MAX[1, MIN[10, Real.RoundI[inBoxW/hLabelWidth*5/8]]]; nLabelsY _ MAX[1, MIN[10, Real.RoundI[inBoxH/vLabelHeight*5/8]]]; [realBounds.xmin, realBounds.xmax, xStep, xScale] _ ScaleAxis[ bounds.xmin, bounds.xmax, inBoxW, nLabelsX]; [realBounds.ymin, realBounds.ymax, yStep, yScale] _ ScaleAxis[ bounds.ymin, bounds.ymax, inBoxH, nLabelsY]; HorizontalAxisLabels[context, innerBox, realBounds.xmin, realBounds.xmax, xScale, xStep]; VerticalAxisLabels[context, innerBox, realBounds.ymin, realBounds.ymax, yScale, yStep]; }; -- DrawAxes GetRopeSize: PROC [text: Rope.ROPE, fontType: FontType _ normal, output: OutputType _ screen] RETURNS [dx, dy: REAL] = { font: Graphics.FontRef_ textFont[fontType][output]; scale: REAL _ textScale[fontType][output]; xmin, ymin, xmax, ymax: REAL; [xmin, ymin, xmax, ymax] _ Graphics.RopeBox[font, text]; dx _ (xmax - xmin) * scale; dy _ (ymax - ymin) * scale; }; -- GetRopeSize ScaleAxis: PROC [minDataValue, maxDataValue, innerBoxSize: REAL, nLabels: CARDINAL] 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); }; -- ScaleAxis FindStepSize: PROC [range: REAL, nSteps: CARDINAL] 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: CARDINAL 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]; }; -- FindStepSize AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = { absE: REAL = ABS[e]; nSteps: 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 ae _ IF xend >= absE THEN xend ELSE step*(nSteps + 1) ELSE ae _ IF xend <= absE THEN xend ELSE step*(nSteps - 1); IF e < 0.0 THEN ae _ -ae; }; -- AlignEnd Almost: PROC [p, q: REAL] RETURNS [BOOL] = INLINE { RETURN[MAX[ABS[p], ABS[q]] = 0.0 OR ABS[p - q]/MAX[ABS[p], ABS[q]] < 0.00001]; }; MyDrawText: PROC[context: Graphics.Context, text: Rope.ROPE, x0, y0: REAL, xJustification: {left, center, right} _ left, yJustification: {top, center, bottom} _ bottom, fontType: FontType _ normal, output: OutputType _ screen, xScale, yScale: REAL _ 1.0] RETURNS [newX, newY: REAL] = { mark: Graphics.Mark _ context.Save[]; font: Graphics.FontRef = textFont[fontType][output]; scale: REAL = textScale[fontType][output]; realScaleX: REAL = xScale*scale; realScaleY: REAL = yScale*scale; xmin, xmax, ymin, ymax, sizeX, sizeY: REAL; IF text.IsEmpty[] THEN RETURN[x0, y0]; context.Translate[x0, y0]; [xmin, ymin, xmax, ymax] _ Graphics.RopeBox[font: font, rope: text]; sizeX _ xmax - xmin; sizeY _ ymax - ymin; xmin_ SELECT xJustification FROM right => xmin - sizeX, center => xmin - sizeX/2., ENDCASE => xmin; ymin_ SELECT yJustification FROM top => ymin - sizeY, center => ymin - sizeY/2., ENDCASE => ymin; context.Scale[realScaleX, realScaleY]; context.SetCP[xmin, ymin]; context.DrawRope[rope: text, font: font]; [newX, newY] _ context.GetCP[]; newX _ newX / realScaleX + x0; newY _ newY / realScaleY + y0; context.Restore[mark]; }; -- MyDrawText DrawCurves: PROC [context: Graphics.Context, box, bounds: Graphics.Box, curves: Curves, output: OutputType] = { IF curves = NIL THEN RETURN ELSE { mark: Graphics.Mark _ context.Save[]; nCurvesMax: CARDINAL = curves.first.size - 1; firstVector: BOOL _ TRUE; tFactor: REAL _ (box.xmax - box.xmin)/(bounds.xmax - bounds.xmin); vFactor: REAL _ (box.ymax - box.ymin)/(bounds.ymax - bounds.ymin); t, v: REAL; paths: REF PathSequence; IF nCurvesMax = 0 THEN RETURN; paths _ NEW[PathSequence[nCurvesMax]]; FOR i: CARDINAL IN [0..nCurvesMax) DO paths[i] _ Graphics.NewPath[2]; ENDLOOP; context.Translate[box.xmin, box.ymin]; FOR graph: Curves _ curves, graph.rest UNTIL graph = NIL DO t _ (graph.first[0] - bounds.xmin)*tFactor; FOR i: CARDINAL IN [1..nCurvesMax] DO ipath: CARDINAL = i - 1; v _ (graph.first[i] - bounds.ymin)*vFactor; IF NOT firstVector THEN { Graphics.LineTo[paths[ipath], t, v]; context.SetColor[color[ipath MOD 16]]; ColorMap.SetRGBColor[ index: 100+ ipath, r: rgb[ipath].r, g: rgb[ipath].g, b: rgb[ipath].b]; context.DrawStroke[paths[ipath], minLineWidth[output]]; }; Graphics.MoveTo[paths[ipath], t, v, TRUE]; ENDLOOP; IF firstVector THEN firstVector _ FALSE; ENDLOOP; paths _ NIL; context.Restore[mark]; }; }; -- DrawCurves DrawLegendFootnote: PROC [context: Graphics.Context, box: Graphics.Box, legendEntries: REF RopeSequence, footNote: Rope.ROPE, output: OutputType] = { mark: Graphics.Mark _ Graphics.Save[context]; nEntries: CARDINAL _ legendEntries.size; MaxEntriesPerColumn: CARDINAL = 5; xmin, xmax: REAL _ 0; yIncPerRow: REAL _ MAX[-fontHeight[normal][screen], (box.ymin - box.ymax) / MaxEntriesPerColumn]; -- note: they are negative. yScale: REAL = -yIncPerRow / fontHeight[normal][screen]; -- ! context.Translate[box.xmin, box.ymax]; IF yScale >= 0.99 THEN [] _ MyDrawText[context, footNote, 0, -(box.ymax-box.ymin), left, top, normal, output]; FOR i: CARDINAL IN [0..nEntries) DO row: CARDINAL _ i MOD MaxEntriesPerColumn; col: CARDINAL _ i / MaxEntriesPerColumn; index: CARDINAL _ i MOD 16; newX: REAL; IF row = 0 AND col > 0 THEN { xmin _ xmax + TextWidthFudge; IF xmin >= box.xmax THEN EXIT; }; context.SetColor[color[i MOD 16]]; ColorMap.SetRGBColor[index: 100+ i, r: rgb[i].r, g: rgb[i].g, b: rgb[i].b]; [newX,] _ MyDrawText[ context, legendEntries[i], xmin, row*yIncPerRow, left, top, normal, output, 1.0, yScale]; IF newX > xmax THEN xmax _ newX; ENDLOOP; context.Restore[mark]; }; -- DrawLegendFootnote MakeMenu: PROC [] RETURNS [menu: Menus.Menu] = { menu _ Menus.CreateMenu[]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["SwapBackground", SwapBackground]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Save", Save]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Press", Press]]; }; -- MakeMenus InitPlot: PUBLIC PROC [] = { ymax, ymin: REAL; color _ [ GraphicsColor.red, GraphicsColor.blue, GraphicsColor.magenta, GraphicsColor.cyan, GraphicsColor.green, GraphicsColor.yellow, GraphicsColor.RGBToColor[r: 0.410, g: 0.125, b: 0.875], -- purple GraphicsColor.RGBToColor[r: 0.725, g: 0.125, b: 0.875], -- reddish purple GraphicsColor.RGBToColor[r: 0.875, g: 0.125, b: 0.710], -- purple red GraphicsColor.RGBToColor[r: 0.875, g: 0.125, b: 0.125], -- (mid) red GraphicsColor.RGBToColor[r: 0.875, g: 0.575, b: 0.125], -- brown yellow GraphicsColor.RGBToColor[r: 0.875, g: 0.875, b: 0.125], -- (mid) yellow GraphicsColor.RGBToColor[r: 0.692, g: 0.875, b: 0.125], -- greenish yellow GraphicsColor.RGBToColor[r: 0.125, g: 0.875, b: 0.125], -- (mid) green GraphicsColor.RGBToColor[r: 0.125, g: 0.875, b: 0.845], -- green blue GraphicsColor.RGBToColor[r: 0.125, g: 0.125, b: 0.875] -- (mid) blue ]; FOR i: INTEGER IN [0..nColors) DO rgb[i].r _ color[i].r/255.0; rgb[i].g _ color[i].g/255.0; rgb[i].b _ color[i].g/255.0; ENDLOOP; FOR f: FontType IN FontType DO FOR o: OutputType IN OutputType DO [ , ymin, , ymax] _ Graphics.FontBox[textFont[f][o]]; fontHeight[f][o] _ ymax - ymin; textScale[f][o] _ IF o = screen THEN 1.0 ELSE fontHeight[f][screen] / fontHeight[f][o]; ENDLOOP; ENDLOOP; plotViewerClass _ NEW [ViewerClasses.ViewerClassRec _ [paint: Display, icon: document, cursor: textPointer]]; -- cursor when mouse is in viewer ViewerOps.RegisterViewerClass[$Plot, plotViewerClass]; }; -- Init }. CHANGE LOG. †PlotScreen.mesa Last Edited by: SChen, August 10, 1984 3:01:31 am PDT constants MicasPerPixel: REAL = 2540.0/72.0; (constant) variables public procedures If spec.nCurvesMax <= 0, no curves will be shown. Note: if original nCurvesMax # new one, then old data will be cleared. menu commands [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] other private procedures [self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL] there will be no checking of the viewer class in DrawMe. MyPaintViewer: PROC [viewer: ViewerClasses.Viewer, handle: Handle, whatChanged: REF ANY, output: OutputType] = { clients of this proc should have checked the viewer class context: Graphics.Context _ ViewerPaintImpl.AcquireContext[viewer, viewer.column=color]; DrawMe[context, handle, whatChanged, output]; }; -- MyPaintViewer press context has been rotated title / legend / footnote axes and curves WaitPreviousPaints[handle]; external vars referenced: output and path. external vars referenced: output and path. DrawLineSeg: PROC[context: Graphics.Context, x0, y0, x1, y1: REAL _ 0.0, lineStyle: {solid, dotted, dashed, dotDash} _ solid, lineState: REAL _ 0.0] = { IF Almost[x0, x1] AND Almost[y0, y1] THEN RETURN; IF lineStyle = solid THEN { context.SetCP[x0, y0]; context.DrawTo[x1, y1]} ELSE { max: REAL = SELECT lineStyle FROM dotted => 300/MicasPerPixel, dashed => 600/MicasPerPixel, dotDash => 900/MicasPerPixel, ENDCASE => 0.0; -- solid 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; }; }; -- DrawLineSeg Ê‹˜Jšœ™šœ5™5J™—šÏk ˜ Jšœ œœ ˜Jšœ œ˜šœ œ˜Jšœú˜ú—Jšœœ7˜JJšœœ"˜7Jšœœ ˜(Jšœœ=˜HJšœœ˜$JšœœF˜PJšœœ2˜?Jšœœ˜#Jšœœ˜Jšœœœ˜$JšœœK˜^šœ œ!˜0JšœY˜Y—J˜—šœ œ˜Jšœ@œEÏc˜¡Jšœ˜Jšœ˜—J˜Jšœ˜J˜Jšœœœœ˜ Jšœ œ˜'Jšœ œ˜!Jšœœœ ˜!Jšœ œœ œ˜*J˜Jšœ ™ J˜Jšœœ˜Jšœœ˜Jšœœ œœ ˜2Jšœ œ˜Jšœ"™"J™Jšœ™J˜Jšœ-œ˜1Jšœ:˜:Jšœ<˜Jšœ œœ˜Jšœ ˜ Jšœœ˜Jšœœ˜Jšœœ˜%J˜ Jšœ˜Jšœ˜Jšœ.˜.šœœ˜8Jšœ6˜8Jšœœœ˜1—šœ˜Jšœ$˜$Jš œœœœ œ˜1šœ˜Jšœ œœ œ˜*Jšœ œ œ˜Jšœœ˜J˜—J˜—J˜Jšœ œ'œœ ˜dJ˜—Jšœž ˜J˜—š Ÿ œœœœœ˜5Jšœœ˜(Jšœ%ž˜@Jšœ œ˜Jšœœ˜%Jšœœœ˜#JšœœG˜QJšœž ˜J˜—š Ÿœœœ!œœ˜VJšœF™Fšœœ˜Jšœœ˜%J˜ šœ1œ˜9Jšœœ#˜5Jšœ˜Jšœœ˜J˜—Jšœ˜J˜Jšœ&œœž˜HJ˜—Jšœž ˜ J˜—šŸœœœ#˜5šœœ˜Jšœœ˜%J˜ Jšœ˜Jšœœ˜Jšœ˜Jšœœ˜J˜Jšœ&œœ˜1J˜—Jšœž˜ —J˜šŸœœœ<˜Mšœœ˜Jšœœ˜%J˜ J˜#J˜Jšœ&œœ˜1J˜—Jšœž˜ J˜—š Ÿ œœœ œœ˜KJš œ œœœœ˜#Jšœ˜$Jšœž˜J˜—Jšœ ™ J˜šœ"˜"JšœN™NJšœœ ˜.Jšœœ˜%J˜šœœ$˜:Jšœœ˜)—J˜Jšœ&œœ˜0Jšœž˜—J˜šœ˜Jšœ˜Jšœœ ˜.Jšœœ˜%Jšœ ˜ JšœL˜LJšœ%˜%J˜Jšœœ ˜(J˜Jšœ˜Jšœž˜ —J˜šœ˜Jšœ œ˜Jšœœ ˜.Jšœœ˜%J˜ Jšœ4˜4J˜šœœœ˜Jšœ˜Jšœ/œ˜5J˜—Jšœž˜ J˜—Jšœ™J˜šœ$˜$JšœL™Lšœœœ œ˜0Jšœ8™8Jšœœ ˜#Jšœ ˜ Jšœ-˜-Jšœ˜J˜—Jšœž ˜ —J˜šŸ œœ=œœ™pJšœ9™9JšœX™XJ™-Jšœž™J˜—šŸœœ:œœ˜fJšœ™š Ÿ œœœœœ˜?Jšœœ˜4Jšœž˜—Jšœ0˜0Jšœœ˜#Jšœœ˜#J˜Jšœ œ˜Jšœ œ˜Jšœ œ˜"Jšœ œ˜#J˜Jšœœ˜!Jšœœ˜!Jšœœ˜!Jšœœ˜ Jšœ œ˜&Jšœ œ˜(J˜Jšœ œ˜%Jšœœ0˜DJšœœ ˜2Jš œ œœœœ˜HJš œ œœœœ˜BJ˜Jšœ5˜5Jšœ9˜9Jšœ9˜9J˜Jšœœœœ˜\Jšœœœœ˜dJ˜"Jšœœ˜&J˜š œœœœ,œ˜Pšœœž˜-J˜J˜Jšœ˜—J˜Jšœ™J˜šœœœ˜Jš œœœœœ˜uJšœ=˜=JšœX˜XJ˜—J˜Jšœ™šœ%œ˜-Jšœc˜cJšœP˜PJ˜—J˜—šœœœœ˜0Jšœœ˜1Jšœ œ˜)J˜Jšœœ˜J˜Jšœœœ˜.Jšœ™š˜J˜šœœ˜J˜J˜Jšœ˜J˜—J˜Jšœ˜ —šœž˜ Jšœ œ1˜AJšœ œ1˜BJšœœ˜$Jšœ œ.˜;Jšœœ˜$Jšœ œ/˜˜>Jšœ˜—J˜—J˜—Jšœž ˜ —J˜šŸ œœ<œ˜hJšœœ*˜4Jšœœ4˜>J˜Jšœœœ˜šœ#œ˜*JšœK˜K—šœœ$˜/JšœM˜M—Jšœž ˜J˜—šŸœœK˜YJšœ)˜0J˜šŸœœ/˜IJšœœ˜ Jšœ*™*Jšœ œ˜,Jšœ œ!˜4Jšœ œ ž2˜LJšœœ˜ J˜šœœœ˜$Jšœ˜šœ˜Jšœ œ˜&Jšœ˜Jšœ˜—šœœœ˜!Jšœ1œ˜7J˜;Jšœ/˜/Jšœ1œ˜7Jšœ;˜;Jšœ/˜/J˜—Jšœ˜Jšœž˜J˜——šŸœœ/˜GJšœœ˜ Jšœ*™*Jšœ œ˜,Jšœ œ!˜4Jšœ œ˜,Jšœœ˜ J˜šœœœ˜$Jšœ˜˜Jšœ œ˜&Jšœ˜Jšœ˜—šœœœ˜!Jšœ1œ˜7Jšœ;˜;Jšœ/˜/Jšœ1œ˜7Jšœ;˜;Jšœ/˜/J˜——Jšœ˜Jšœž˜J˜—Jšœœ˜ Jšœœ˜ J˜0Jšœœž˜&Jšœœ˜Jšœœ˜JšœQœ˜VJšœœ˜J˜šœ*œ œ˜OJšœ˜—šœ*œ œ˜OJšœ˜—Jšœœ˜,Jšœœ˜/J˜šœ*œ œ˜OJšœ˜—šœ*œ œ˜OJšœ˜—Jšœœ˜,Jšœœ˜/J˜šœ ˜ Jšœ.˜.Jšœ0˜0Jšœ˜Jšœ˜—Jšœœ˜?Jšœœ˜?J˜J˜4J˜UJšœDœ˜JJ˜Jšœ'˜'Jšœ'˜'J˜Jšœ œœ+˜@Jšœ œœ,˜AJ˜šœ>˜>Jšœ,˜,—šœ>˜>Jšœ,˜,—J˜JšœY˜YJšœW˜WJšœž ˜J˜—š Ÿ œœ œ<œ œ˜xJšœ3˜3Jšœœ˜*Jšœœ˜Jšœ8˜8J˜J˜Jšœž˜J˜—šŸ œœ,œ œ˜SJšœœ˜)J˜:Jšœ#œ˜*Jšœ#œ˜)Jšœœ&˜>J˜!šœž ˜J˜——š Ÿ œœ œ œœœ˜JJšœ œ˜Jšœœ˜Jšœœ˜Jšœœœœ#˜?J˜Jšœœ˜)J˜J˜$J˜%J˜%šœœ˜Jšœ$˜$J˜—J˜/šœœœ˜Jšœ˜Jšœœœ˜4Jšœ˜—š œœœœ˜SJšœœœœ˜>—Jšœœ ˜Jšœž˜J˜—š Ÿœœ œ œœœ˜GJšœœœ˜Jšœœ˜Jšœœ˜ J˜Jšœ œœ˜Jšœ œ˜#J˜'J˜Jšœœ ˜+šœœ˜Jšœœœœ˜:Jšœœœœ˜;—Jšœ œ ˜šœž ˜J˜——š Ÿœœœœœœ˜3Jšœœœœ œœœœœ˜Q—J˜šŸ œœ˜,Jšœ œ œ˜J˜-J˜/Jšœ˜Jšœ˜Jšœœ˜Jšœœ˜J˜Jšœ%˜%Jšœ4˜4Jšœœ˜*Jšœ œ˜ Jšœ œ˜ Jšœ&œ˜+J˜Jšœœœ ˜&Jšœ˜JšœD˜DJšœ˜Jšœ˜šœœ˜ Jšœ˜Jšœ˜Jšœ ˜—šœœ˜ Jšœ˜Jšœ˜Jšœ ˜—Jšœ&˜&Jšœ˜Jšœ)˜)Jšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœž ˜J˜—šŸ œœG˜WJšœ˜š œ œœœœ˜"Jšœ%˜%Jšœ œ˜-Jšœ œœ˜Jšœ œ5˜BJšœ œ5˜BJšœœ˜ Jšœœ˜J˜Jšœœœ˜Jšœœ˜&Jš œœœœ!œ˜NJšœ&˜&J˜šœ$œ œ˜;Jšœ+˜+šœœœ˜%Jšœœ ˜Jšœ+˜+šœœ œ˜Jšœ$˜$Jšœœ˜&šœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ˜—Jšœ7˜7J˜—Jšœ$œ˜*Jšœ˜—Jšœ œœ˜(Jšœ˜—Jšœœ˜ J˜J˜—Jšœž ˜J˜—šŸ œœ™,Jšœœ™Jšœ4™4Jšœ œ ™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šœœ™ Jšœœ$™-————Jšœœ™—J™ J™J™Jšœœ1™?J™J™J™Jšœœ™&—Jšœ™Jšœ™—šœž™J˜——šŸœœ/˜GJšœœœ˜MJ˜-Jšœ œ˜(Jšœœ˜"Jšœ œ˜Jšœ œœLž˜}Jšœœ-ž˜=Jšœ&˜&šœ˜JšœW˜W—šœœœ˜#Jšœœœ˜*Jšœœ˜(Jšœœœ˜Jšœœ˜ šœ œ œ˜Jšœ˜Jšœœœ˜J˜—Jšœœ˜"JšœK˜Kšœ˜Jšœ˜Jšœ˜Jšœ˜Jšœ(˜(—Jšœ œ ˜ Jšœ˜—Jšœ˜Jšœž˜J˜—šŸœœœ˜0Jšœ˜˜J˜<—šœ˜Jšœ)˜)—˜J˜+—Jšœž ˜—J˜šŸœœœ˜Jšœ œ˜J˜˜ J˜Jšœ˜Jšœ˜Jšœ˜J˜Jšœ˜Jšœ8ž ˜AJšœ8ž˜IJšœ8ž ˜EJšœ8ž ˜DJšœ8ž˜GJšœ8žœž˜GJšœ8ž˜JJšœ8ž˜FJšœ8ž ˜EJšœ7ž ˜DJ˜—šœœœ˜!J˜J˜J˜Jšœ˜—J˜šœ œ ˜Jšœœ ˜"Jšœ5˜5Jšœ˜Jšœœ œœ*˜WJšœ˜—Jšœ˜J˜˜šœ˜!šœ˜Jšœ˜Jšœž!˜8———Jšœ6˜6Jšœž˜ —J˜Jšœ˜J˜Jšœœ˜ —…—W}