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; 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 = { 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 = { 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 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]; }; 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] = { 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]; 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 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. œPlotViewer.mesa Last Edited by: SChen, May 21, 1985 6:37:40 pm PDT types constants (constant) variables public procedures handle column Note: if original nCurvesMax # new one, then old data will be cleared. handle.paths _ NIL; menu commands [parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL] white forgeround colors, all vivid gray black get color spec from user profile, or return default 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 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šœ™šœ2™2J™—šÏk ˜ Jšœ œœ ˜Jšœ œ˜*Jšœœ˜$Jšœ œö˜„Jšœœ ˜3Jšœœ"˜7Jšœœ˜*Jšœœ ˜(Jšœœ=˜HJšœœ˜$JšœœO˜YJšœœB˜OJšœœ˜#Jšœœ˜Jšœœ œ ˜9Jšœ œ~˜ŒJšœ œ˜(JšœœK˜^Jšœ œˆ˜—Jšœ œ˜)Jšœœ!˜4J˜—šœ œ˜Jšœœ1œx˜ÑJšœ ˜—J˜Jšœ˜J˜J™J˜Jšœ œ˜'Jšœ œ˜!Jšœ œœ˜.Jšœ œ˜J˜Jšœ ™ J˜Jšœœ˜Jšœœ˜Jšœœ œœ ˜2Jšœ œ˜J™Jšœ™J˜JšœD˜Dšœ-œ ˜PJšœ7˜7—Jšœ:˜:Jšœ<˜J˜Jš œœœ œœ˜8šœ œÏc5˜G˜Jš œœœ œ œœ˜FJšœŸ ˜ —J˜#J˜—Jšœœ'˜=šœ œœ˜'Jšœ ˜ Jšœ˜Jšœœ˜ Jšœœ˜ —šœ˜!Jšœ ˜ Jšœ˜Jšœœ˜—Jšœ ˜ Jšœœœ!˜3J˜—JšœŸ˜—J˜š ž œœœ!œœ˜Ušœ œœœœœœ˜>Jšœ œœ˜Jšœœ˜Jšœœ˜%šœ œœ˜J˜ Jšœ.˜.šœœ˜8Jšœ6˜8Jšœœœ˜1—šœ˜šœœ˜Jšœœœœ˜5—Jšœœ˜+J˜—J˜J˜—Jšœ œ'œ˜PJ˜—JšœŸ ˜J˜—š ž œœœœœ˜5Jšœœ˜(Jšœ%Ÿ˜@Jšœ œ˜Jšœœ˜%Jšœœœ˜#JšœœG˜QJšœŸ ˜J˜—š žœœœ!œœ˜VJšœF™Fšœœ˜Jšœœ˜%Jšœ œœœ˜J˜ šœ1œ˜9Jšœœ#˜5šœœœ˜-Jšœ'œ˜/—J˜J˜—Jšœœ˜Jšœ˜Jšœ<˜œ ˜R—J˜Jšœ œ2œœ˜ešœ;˜AJšœ>˜>—J˜J˜Jšœ&œœ˜1J˜—JšœŸ ˜—J˜šœ˜šœœœ˜BJšœ˜Jšœ˜J˜—JšœŸ˜J˜—šœ+˜+J˜—šœ˜Jšœœ ˜.Jšœœ˜%Jšœ œ&˜5šœ œ˜Jšœœ"˜8Jšœœ œœ˜3Jšœ2œ˜=—Jšœœœœ˜2JšœŸ˜ J˜—šœ˜Jšœœ ˜.Jšœ œ&˜5Jšœ œŸ˜Ešœœœ˜Jšœ˜Jšœ/œ˜5J˜—JšœŸ˜ J˜—šœ˜Jšœœ ˜.Jšœœ˜%šœ˜Jšœœ"˜+šœ7˜7šœ ˜ šœ˜Jšœ˜Jšœ,˜,Jšœ Ÿ˜%—Jšœ ˜ ——Jšœ%˜%J˜Jšœœ ˜(Jšœ˜J˜—JšœŸ˜ —J˜šœœ˜Jšœ˜Jšœ˜šœ=˜CJšœ@˜@—JšœŸ˜J˜—šœœ˜!˜,Jš œœœ œœœ˜SJ˜—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šœ3™3Jš œ œœœœ˜$˜+Jšœœœ ˜+Jšœ ˜ J˜—JšœœœœC˜YJšœœ˜š œ!œœœ˜@Jšœ:œ˜@Jšœœœ ˜J˜ Jšœ˜—Jšœ6˜œ ˜R——J˜Jšœœ˜#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˜Jšœ˜—J˜Jšœ™J˜šœœœ˜Jšœœ"˜+Jš œœœ œœ&˜eJš œœœ#œœ˜lJšœ=˜=JšœX˜XJ˜—J˜Jšœ™šœ%œ˜-Jšœ œ˜Jšœc˜cJšœ"˜"šœ#œœ˜6Jšœœ ˜Jšœ˜—JšœD˜DJ˜—J˜—š œœœœœ˜7Jšœ œ˜!šœœ œ˜5J˜J˜Jš œœœ œœœ˜)J˜Jšœœœœ˜šœœ˜Jšœœ˜Jšœ œB˜OJšœœ˜Jšœ œB˜OJšœœ˜"Jšœœ˜"J˜Jšœ˜Jšœ2˜2šœœœ˜&Jšœœ ˜Jšœ<œ˜BJšœ<˜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˜—šž œœG˜WJšœ˜š œ œœœœ˜"Jšœ%˜%Jšœ œ˜-Jšœ œœ˜Jšœ œ5˜BJšœ œ5˜BJšœœ˜ Jšœœ˜J˜Jšœœœ˜Jšœœ˜&Jš œœœœ!œ˜NJšœ&˜&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šœ˜Jšœ˜Jšœ(˜(—Jšœ œ ˜ Jšœ˜—Jšœ˜JšœŸ˜J˜—šžœœœ˜0Jšœ˜˜Jšœ4˜4—˜Jšœ4˜4—˜Jšœ7˜7—˜J˜1—šœ˜Jšœ:œ˜B—šœ˜Jšœ+˜+—˜J˜+—JšœŸ ˜—J˜šžœœ˜Jšœ œ˜Jšœ˜Jšœ-˜-J˜šœ œ ˜Jšœœ ˜"Jšœ5˜5Jšœ˜Jšœœ œœ*˜WJšœ˜—Jšœ˜J˜Jšœœœ˜Ošœ œ˜)Jšœ>œ ˜R—Jšœ œ2œœ˜ešœ;˜AJšœ>˜>—Jšœ6˜6J˜JšœŸ˜ —J˜J˜Jšœ˜J˜šœœ˜ J˜0——…—oÒ