<> <> <<>> DIRECTORY BasicTime USING [GMT, nullGMT], ColorMap USING [SetRGBColor, StandardMap], Convert USING [Error, RealFromRope], Graphics USING [Box, ClipBox, 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], GraphicsColor USING [ColorToIntensity, RGBToColor], GraphicsToPress USING [Close, NewContext, SetPageSize], Icons USING [IconFlavor, NewIconFromFile], IO USING [int, PutFR, real, rope, time], Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc], MessageWindow USING [Append, Blink], Plot USING [Curves, PlotSpec, PlotSpecRec, ReadPlotFile, RopeSequence, SavePlot, Vector], PlotOps USING [BackgroundType, Handle, HandleData, Lock, PathSequence, Unlock], Real USING [FixI, RoundI, RoundLI], RealFns USING [Log, Power], Rope USING [Concat, Equal, Find, IsEmpty, ROPE, Substr], Terminal USING [ColorCursorPresentation, ColorMode, Current, GetColorCursorPresentation, GetColorMode, SetColorCursorPresentation, Virtual], UserProfile USING [ListOfTokens, Token], ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [ChangeColumn, ComputeColumn, CreateViewer, EnumerateViewers, EnumProc, OpenIcon, PaintViewer, RegisterViewerClass, SwapIconAndViewer], ViewerTools USING [GetSelectionContents], WindowManager USING [colorDisplayOn, RestoreCursor]; PlotViewer: CEDAR PROGRAM IMPORTS BasicTime, ColorMap, Convert, Graphics, GraphicsColor, GraphicsToPress, Icons, IO, Menus, MessageWindow, Plot, PlotOps, Real, RealFns, Rope, Terminal, UserProfile, ViewerOps, ViewerTools, WindowManager EXPORTS Plot = { OPEN Plot, PlotOps; <> OutputType: TYPE = {screen, pressFile}; FontType: TYPE = {title, normal}; VectorPair: TYPE = RECORD[start, end: Vector]; NColors: TYPE = [0..nColors); <> TextHeightFudge: REAL = 6.0; TextWidthFudge: REAL = 8.0; minLineWidth: ARRAY OutputType OF REAL = [0, 2.0]; nColors: INTEGER = 16; <<>> <<(constant) variables>> plotIcon: Icons.IconFlavor _ Icons.NewIconFromFile["Plot.icons", 0]; plotViewerClass: ViewerClasses.ViewerClass _ NEW [ViewerClasses.ViewerClassRec _ [paint: Display, icon: plotIcon, cursor: textPointer]]; 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]]; color: ARRAY NColors OF Graphics.Color _ ALL[[r: 0, g: 0, b: 0]]; fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL; textScale: ARRAY FontType OF ARRAY OutputType OF REAL; virtual: Terminal.Virtual _ Terminal.Current[]; <> CreateViewer: PUBLIC PROC [spec: PlotSpec _ NIL, iconic, inhibitDestroy: BOOL _ FALSE] RETURNS [viewer: ViewerClasses.Viewer _ NIL] = { <> handle: Handle _ NEW[HandleData _ [ plotSpec: spec, background: GetBackground[], paths: NEW[PathSequence[spec.nCurvesMax]] ] ]; <<>> <> columnRope: Rope.ROPE = UserProfile.Token[key: "Plot.Column", default: "color"]; column: ViewerClasses.Column _ SELECT TRUE FROM columnRope.Equal["color", FALSE] => color, columnRope.Equal["left", FALSE] => left, columnRope.Equal["right", FALSE] => right, ENDCASE => static; IF column = static THEN { MessageWindow.Append[ IO.PutFR["Warning: Illegal entry in user profile, Plot.Column: %g.", IO.rope[columnRope]], TRUE]; column _ color; }; IF column = color THEN IF iconic OR NOT virtual.hasColorDisplay THEN column _ left; FOR i: CARDINAL IN [0..spec.nCurvesMax) DO handle.paths[i] _ Graphics.NewPath[2]; ENDLOOP; iconic _ iconic OR UserProfile.Token[key: "Plot.CreateOption", default: "CloseOtherViewers"].Equal["Iconic", FALSE]; viewer _ ViewerOps.CreateViewer[ flavor: $Plot, info: [ name: spec.file, menu: MakeMenu[], icon: plotIcon, column: IF column = color THEN left ELSE column, iconic: TRUE, inhibitDestroy: inhibitDestroy, data: handle], paint: FALSE]; IF iconic THEN ViewerOps.PaintViewer[viewer, all, TRUE, NIL] ELSE { vOpen: ViewerClasses.Viewer _ NIL; createOption: Rope.ROPE = UserProfile.Token[key: "Plot.CreateOption", default: "CloseOtherViewers"]; closeOthers: BOOL _ createOption.Equal["CloseOtherViewers", FALSE]; swapOld: BOOL _ createOption.Equal["SwapBottomViewer", FALSE]; IF NOT (closeOthers OR swapOld) THEN closeOthers _ TRUE; IF swapOld THEN { -- get the viewer at the bottom of the column (vOpen) MyEnum: ViewerOps.EnumProc = { IF v.column = column AND NOT v.iconic THEN {vOpen _ v; RETURN[FALSE]}; }; -- MyEnum ViewerOps.EnumerateViewers[MyEnum]; }; IF column = color THEN ViewerOps.ChangeColumn[viewer, color]; IF vOpen = NIL THEN ViewerOps.OpenIcon[ icon: viewer, closeOthers: closeOthers, bottom: TRUE, paint: FALSE] ELSE ViewerOps.SwapIconAndViewer[ icon: viewer, openViewer: vOpen, paint: FALSE]; ViewerOps.ComputeColumn[static]; IF NOT iconic THEN ViewerOps.ComputeColumn[column]; }; }; -- CreateViewer AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer _ NIL, vector: Vector _ NIL] = { IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE { shouldPaint: BOOL _ FALSE; expectedSize: CARDINAL; handle: Handle _ NARROW[viewer.data]; IF handle # NIL THEN { Lock[handle]; 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 { IF handle.curves # NIL THEN IF handle.curves.first # NIL THEN shouldPaint _ TRUE; handle.curves _ CONS[vector, handle.curves] }; Unlock[handle]; }; IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, handle.curves]; }; }; -- 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]; IF handle = NIL THEN RETURN; Lock[handle]; IF handle.plotSpec.nCurvesMax # newSpec.nCurvesMax THEN { handle.paths _ NEW[PathSequence[newSpec.nCurvesMax]]; FOR i: CARDINAL IN [0..newSpec.nCurvesMax) DO handle.paths[i] _ Graphics.NewPath[2]; ENDLOOP; }; handle.curves _ NIL; handle.plotSpec _ newSpec; viewer.name _ Rope.Concat["Plot of ", handle.plotSpec.file]; Unlock[handle]; ViewerOps.PaintViewer[viewer, caption, FALSE, NIL]; 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.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 <> SwapCursor: Menus.MenuProc = { [] _ Terminal.SetColorCursorPresentation[virtual, IF Terminal.GetColorCursorPresentation[virtual] = onesAreBlack THEN onesAreWhite ELSE onesAreBlack ]; }; -- SwapCursor Background: Menus.MenuProc = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; IF handle # NIL THEN { bkGndColor: Graphics.Color; cursorType: Terminal.ColorCursorPresentation; Lock[handle]; handle.background _ SELECT mouseButton FROM red => SELECT handle.background FROM black => white, ENDCASE => SUCC[handle.background], blue => SELECT handle.background FROM white => black, gray => white, darkGray => gray, ENDCASE => darkGray, ENDCASE => GetBackground[]; -- yellow button bkGndColor _ SELECT handle.background FROM gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0]; cursorType _ IF GraphicsColor.ColorToIntensity[bkGndColor] < 0.5 THEN onesAreWhite ELSE onesAreBlack; IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN [] _ Terminal.SetColorCursorPresentation[virtual, cursorType]; Unlock[handle]; ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; }; }; -- Background CedarColors: Menus.MenuProc = { IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN { SetCedarColors[]; PaintAllColorViewers[]; }; }; -- CedarColors MyColors: Menus.MenuProc = {SetMyColors[]}; Get: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; name: Rope.ROPE _ ViewerTools.GetSelectionContents[]; msg: Rope.ROPE _ IF name.IsEmpty[] THEN "Please select a plot file name." ELSE IF handle = NIL THEN "Not a good plot viewer." ELSE ReadPlotFile[name: name, viewer: viewer, iconic: FALSE]; IF msg # NIL THEN MessageWindow.Append[msg, TRUE]; }; -- Get Store: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; name: Rope.ROPE _ ViewerTools.GetSelectionContents[]; msg: Rope.ROPE _ SavePlot[viewer, name]; -- viewer type checked there IF msg # NIL THEN { MessageWindow.Blink[]; MessageWindow.Append[message: msg, clearFirst: TRUE]; }; }; -- Store Press: Menus.MenuProc = { viewer: ViewerClasses.Viewer _ NARROW[parent]; handle: Handle _ NARROW[viewer.data]; Lock[handle]; { excl: INT _ handle.plotSpec.file.Find["!"]; context: Graphics.Context _ GraphicsToPress.NewContext[ Rope.Concat[ SELECT excl FROM < 0 => handle.plotSpec.file, > 0 => handle.plotSpec.file.Substr[0, excl], ENDCASE => "Plot", -- unlikely though ".press"]]; GraphicsToPress.SetPageSize[context]; context.Rotate[90]; DrawMe[context, handle, NIL, pressFile]; GraphicsToPress.Close[context]; }; Unlock[handle]; }; -- Press SetCedarColors: PROC [] = { ColorMap.StandardMap[]; WindowManager.RestoreCursor[]; IF Terminal.GetColorCursorPresentation[virtual] # onesAreBlack THEN [] _ Terminal.SetColorCursorPresentation[virtual, onesAreBlack]; }; -- SetCedarColors PaintAllColorViewers: PROC [] = { PaintEachColorViewer: ViewerOps.EnumProc = { IF v.column = color AND NOT v.iconic THEN ViewerOps.PaintViewer[v, all, TRUE, NIL]; }; ViewerOps.EnumerateViewers[PaintEachColorViewer]; }; -- PaintAllColorViewers SetMyColors: PROC [] = { RGB: TYPE = RECORD[r, g, b: REAL]; defaultColor: ARRAY NColors OF RGB _ [ <> [r: 1.0, g: 1.0, b: 1.0], <> [r: 1.0, g: 0.0, b: 0.8], -- purple red [r: 0.0, g: 0.0, b: 1.0], -- blue [r: 0.0, g: 0.56, b: 1.0], -- greenish blue [r: 0.0, g: 1.0, b: 1.0], -- green blue [r: 0.0, g: 1.0, b: 0.0], -- green [r: 0.52, g: 1.0, b: 0.0], -- yellow green [r: 1.0, g: 1.0, b: 0.0], -- yellow [r: 1.0, g: 0.60, b: 0.0], -- orange yellow [r: 1.0, g: 0.42, b: 0.0], -- yellowish orange [r: 1.0, g: 0.25, b: 0.0], -- orange [r: 1.0, g: 0.0, b: 0.0], -- red [r: 0.8, g: 0.0, b: 1.0], -- reddish purple <> [r: 0.5, g: 0.5, b: 0.5], -- gray [r: 0.17, g: 0.17, b: 0.17], -- dark gray <> [r: 0.0, g: 0.0, b: 0.0] -- black ]; GetColor: PROC [num: NColors] RETURNS [Graphics.Color] = { <> ColorSpec: TYPE = LIST OF Rope.ROPE; list: ColorSpec _ UserProfile.ListOfTokens[ key: IO.PutFR["Plot.Color%g", IO.int[num]], default: NIL ]; c: ARRAY[1..3] OF REAL _ [defaultColor[num].r, defaultColor[num].g, defaultColor[num].b]; i: CARDINAL _ 1; FOR lr: ColorSpec _ list, list.rest UNTIL list = NIL OR i > 3 DO c[i] _ Convert.RealFromRope[list.first ! Convert.Error => EXIT]; c[i] _ MAX[0, MIN[1.0, c[i]]]; i _ i + 1; ENDLOOP; RETURN[GraphicsColor.RGBToColor[r: c[1], g: c[2], b: c[3]]]; }; -- GetColor IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN { m: Terminal.ColorMode = Terminal.GetColorMode[virtual]; IF NOT m.full THEN { offset: INTEGER _ 0; IF m.bitsPerPixelChannelA >= 8 THEN offset _ 100; SetCedarColors[]; FOR i: NColors IN NColors DO c: Graphics.Color _ color[i] _ GetColor[i]; ColorMap.SetRGBColor[index: i+offset, r: c.r/255.0, g: c.g/255.0, b: c.b/255.0]; ENDLOOP; PaintAllColorViewers[]; }; }; }; -- SetMyColors <> Display: ViewerClasses.PaintProc = { <<[self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]>> IF IsPlotViewer[self] AND NOT self.iconic THEN { <> handle: Handle _ NARROW[self.data]; IF handle # NIL THEN { Lock[handle]; DrawMe[context, handle, whatChanged, screen]; Unlock[handle]; }; }; }; -- Display <> <> <> <> <<}; -- MyPaintViewer>> GetBackground: PROC [] RETURNS [BackgroundType] = { bkGndColorRope: Rope.ROPE = UserProfile.Token[key: "Plot.Background", default: "White"]; SELECT TRUE FROM bkGndColorRope.Equal["Black", FALSE] => RETURN[black]; bkGndColorRope.Equal["DarkGray", FALSE] => RETURN[darkGray]; bkGndColorRope.Equal["Gray", FALSE] => RETURN[gray]; ENDCASE => { -- background is white IF NOT bkGndColorRope.Equal["White", FALSE] THEN MessageWindow.Append[ IO.PutFR["Warning: Illegal entry in user profile, Plot.Background: %g.", IO.rope[bkGndColorRope]], TRUE]; RETURN[white]; }; }; -- GetBackground 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]; [] _ context.SetPaintMode[opaque]; [] _ Graphics.SetFat[context, FALSE]; IF virtual.hasColorDisplay THEN { m: Terminal.ColorMode = Terminal.GetColorMode[virtual]; IF m.full OR m.bitsPerPixelChannelA < 4 OR m.bitsPerPixelChannelA > 8 THEN { IF whatChanged = NIL THEN { MessageWindow.Blink[]; MessageWindow.Append[IO.PutFR["%g bpp are not supported.", IO.int[IF m.full THEN 24 ELSE m.bitsPerPixelChannelA]], TRUE]; }; RETURN; }; }; IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN { foreground: Graphics.Color _ IF output = pressFile THEN color[15] ELSE IF handle.background = black OR handle.background = darkGray THEN color[0] ELSE color[15]; background: Graphics.Color _ IF output = pressFile THEN color[0] ELSE SELECT handle.background FROM gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0]; 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]; IF output = screen THEN { -- clear the screen context.SetColor[background]; context.DrawBox[box]; }; <> context.SetColor[foreground]; IF NOT windowTooLow THEN { excl: INT _ handle.plotSpec.file.Find["!"]; pattern: Rope.ROPE = IF excl < 0 THEN handle.plotSpec.file ELSE handle.plotSpec.file.Substr[0, excl]; footNote: Rope.ROPE _ IO.PutFR["File: %g, created on %g.", IO.rope[pattern], IO.time[handle.plotSpec.time]]; DrawTitle[context, titleBox, handle.plotSpec.title, output]; DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries, footNote, output]; }; <<axes and curves>> IF BoundsValid[handle.plotSpec.bounds] THEN { c: Curves _ NIL; [handle.curvesBox, handle.realBounds] _ DrawAxes[context, axesBox, handle.plotSpec.bounds, output]; context.ClipBox[handle.curvesBox]; FOR l: Curves _ handle.curves, l.rest UNTIL l = NIL DO c _ CONS[l.first, c]; ENDLOOP; DrawCurves[context, handle.curvesBox, handle.realBounds, c, output]; }; } ELSE IF ISTYPE[whatChanged, Curves] THEN { OPEN handle; cv: Curves _ NARROW[whatChanged]; IF cv # NIL AND BoundsValid[handle.realBounds] THEN { m0, m1: Vector; m1 _ cv.first; IF m1 = NIL OR cv.rest = NIL THEN RETURN; m0 _ cv.rest.first; IF m0 = NIL THEN RETURN; IF m0.size = m1.size THEN { tMin: REAL = realBounds.xmin; tFactor: REAL = (curvesBox.xmax - curvesBox.xmin + 1)/(realBounds.xmax - tMin); vMin: REAL = realBounds.ymin; vFactor: REAL = (curvesBox.ymax - curvesBox.ymin + 1)/(realBounds.ymax - vMin); t0: REAL = (m0[0] - tMin)*tFactor; t1: REAL = (m1[0] - tMin)*tFactor; context.ClipBox[curvesBox]; context.Translate[curvesBox.xmin, curvesBox.ymin]; FOR index: CARDINAL IN [1..m0.size) DO ipath: CARDINAL _ index - 1; Graphics.MoveTo[paths[ipath], t0, (m0[index]-vMin)*vFactor, TRUE]; Graphics.LineTo[paths[ipath], t1, (m1[index]-vMin)*vFactor]; context.SetColor[color[(ipath MOD 12) + 1]]; context.DrawStroke[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] = { <<external vars referenced: output and path.>> 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] = { <<external vars referenced: output and path.>> 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]; context.Restore[mark]; [newX, newY] _ context.GetCP[]; }; -- 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 12)+1]]; 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 <<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>> 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; newX: REAL; IF row = 0 AND col > 0 THEN { xmin _ xmax + TextWidthFudge; IF xmin >= box.xmax THEN EXIT; }; context.SetColor[color[(i MOD 12)+1]]; [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["SwapCursor", SwapCursor]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Background", Background]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["CedarColors", CedarColors]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["MyColors", MyColors]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry[name: "Get", proc: Get, guarded: TRUE]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Store", Store]]; Menus.AppendMenuEntry [menu, Menus.CreateEntry["Press", Press]]; }; -- MakeMenus Init: PROC [] = { ymax, ymin: REAL; bkGndColor: Graphics.Color; cursorType: Terminal.ColorCursorPresentation; 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; IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN SetMyColors[]; bkGndColor _ SELECT GetBackground[] FROM gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0]; cursorType _ IF GraphicsColor.ColorToIntensity[bkGndColor] < 0.5 THEN onesAreWhite ELSE onesAreBlack; IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN [] _ Terminal.SetColorCursorPresentation[virtual, cursorType]; ViewerOps.RegisterViewerClass[$Plot, plotViewerClass]; }; -- Init Init[]; }. CHANGE LOG. SChen, May 14, 1985 5:39:02 pm PDT, custom icon.