<> <> <<>> 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; <> <<>> <<(constant) variables>> 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 = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> 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 = { <<[self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]>> IF IsPlotViewer[self] AND NOT self.iconic THEN { <> handle: Handle _ NARROW[self.data]; Lock[handle]; DrawMe[context, handle, whatChanged, screen]; Unlock[handle]; }; }; -- Display <> <> <> <> <<}; -- MyPaintViewer>> 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]; }; <<axes and curves>> 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}; <<WaitPreviousPaints[handle];>> 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] = { <<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]; [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 <<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; 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.