DIRECTORY Buttons USING [Button, SetDisplayStyle], Convert USING [BoolFromRope, Error, IntFromRope, RealFromRope, RopeFromInt, RopeFromReal], FS USING [Error, ComponentPositions, ExpandName], Graph USING [BackgroundIndex, CaretIndex, CaretSpecRec, CaretStateRec, Chart, ChartRec, ColorIndex, Controller, Entity, EntityGroup, EntityGroupList, EntityHash, EntityHashArray, EntityHashSize, EntityList, FontArray, FontIndex, GRAPH, GraphColorsArray, GraphFont, GraphFontsArray, GraphHandle, GraphHandleRec, GraphRec, NestedEntities, NullRect, NullVec, OutputType, PaintInfoRec, ROPE, SegmentDataList, SegmentData, SegmentDataRec, TargetSpecRec, Text, Texts, ValueList, Viewer, XY], GraphCleanUp USING [CleanUpEL, CleanUpSDL, CleanUpTexts, CleanUpVL], GraphPrivate USING [defaultColors, defaultFonts, Draw, IsController, IsGraphViewer, systemColor], GraphUtil, ImagerInterpress USING [Close, Create, DoPage, Ref], Imager USING [Box, ConstantColor, Context, Error, Rectangle, RotateT, ScaleT, VEC], ImagerColor USING [GrayFromColor, RGB], ImagerColorDefs USING [ConstantColor], ImagerColorMap USING [LoadEntries, MapEntry, SetStandardColorMap], ImagerDitheredDevice USING [ColorFromSpecialRGB], ImagerFont USING [Extents, Font, Find, FontBoundingBox, RopeBoundingBox, Scale], IO USING [int, PutFR], MessageWindow USING [Append, Blink], Process USING [MsecToTicks, SetTimeout], Real USING [Float, LargestNumber, RealException, RoundC, RoundI], RealFns USING [SqRt], Rope USING [Cat, Concat, IsEmpty, Substr], Terminal USING [ColorCursorPresentation, Current, GetColorCursorPresentation, SetColorCursorPresentation, Virtual], UserProfile USING [ListOfTokens], VFonts USING [EstablishFont], ViewerOps USING [FetchProp], ViewerTools USING [GetContents, GetSelectionContents, SetContents], WindowManager USING [RestoreCursor]; GraphUtilImpl: CEDAR PROGRAM IMPORTS Buttons, Convert, FS, GraphCleanUp, GraphPrivate, GraphUtil, Imager, ImagerColor, ImagerColorMap, ImagerDitheredDevice, ImagerFont, ImagerInterpress, IO, MessageWindow, Process, Real, RealFns, Rope, Terminal, UserProfile, VFonts, ViewerOps, ViewerTools, WindowManager EXPORTS Graph, GraphUtil = { OPEN Graph, GraphPrivate, GraphUtil; Error: PUBLIC SIGNAL[atom: ATOM, info: ROPE _ NIL]= CODE; RaiseError: PUBLIC PROC[atom: ATOM, msg: ROPE _ NIL] = { info: ROPE _ NilMessage[atom, msg]; IF info = NIL THEN info _ SELECT atom FROM $NullBox => "Box = [0, 0, 0, 0]", $BadBox => "xmin = xmax or ymin = ymax", $UnknownInfo => "Unknown PaintInfo", $UnknownAtom => "Unknown ATOM", $Other => "Error", ENDCASE => "Unknown error"; Error[atom, info]; }; -- RaiseError BlinkMsg: PUBLIC PROC[msg: ROPE _ NIL] = { MessageWindow.Blink[]; MessageWindow.Append[msg, TRUE]; }; -- BlinkMsg NilMessage: PROC [atom: ATOM _ $Ref, msg: ROPE _ NIL] RETURNS [ROPE] = { header: ROPE _ SELECT atom FROM $NilViewer => "Viewer", $NilHandle => "GraphHandle", $NilChart => "Chart", $NilController => "Controller", $Ref => "REF", ENDCASE => NIL; IF header # NIL THEN header _ header.Concat[" is NIL"]; RETURN[IF msg = NIL THEN header ELSE Rope.Cat[header, " ", msg]]; }; -- NilMessage CheckNil: PROC[ref: REF ANY _ NIL, atom: ATOM _ $Other, msg: ROPE _ NIL, degug: BOOL _ FALSE] RETURNS [ok: BOOL _ TRUE] = { IF ref = NIL THEN { ok _ FALSE; IF degug THEN RaiseError[atom, msg] ELSE BlinkMsg[NilMessage[atom, msg]]; }; }; -- CheckNil HandleNotNil: PUBLIC PROC[handle: GraphHandle _ NIL, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ TRUE] = {ok _ CheckNil[handle, $NilHandle, msg, debug]}; ChartNotNil: PUBLIC PROC[chart: Chart _ NIL, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ TRUE] = {ok _ CheckNil[chart, $NilChart, msg, debug]}; ViewerNotNil: PUBLIC PROC[viewer: Viewer _ NIL, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ TRUE] = {ok _ CheckNil[viewer, $NilViewer, msg, debug]}; GraphViewerExits: PUBLIC PROC [handle: GraphHandle, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ FALSE] = { IF HandleNotNil[handle, msg, debug] THEN IF ChartNotNil[handle.chart, msg, debug] THEN IF ViewerNotNil[handle.chart.viewer, msg, debug] THEN ok _ TRUE; }; -- GraphViewerExits ControllerNotNil: PUBLIC PROC[c: Controller _ NIL, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ TRUE] = {ok _ CheckNil[c, $NilController, msg, debug]}; ControllerViewerExits: PUBLIC PROC [handle: GraphHandle _ NIL, msg: ROPE _ NIL, debug: BOOL _ FALSE] RETURNS [ok: BOOL _ FALSE] = { IF HandleNotNil[handle, msg, debug] THEN IF ControllerNotNil[handle.controller, msg, debug] THEN IF ViewerNotNil[handle.controller.viewer, msg, debug] THEN ok _ TRUE; }; -- GraphViewerExits BoundsValid: PUBLIC PROC[box: Imager.Box] RETURNS [BOOL] = { RETURN[box.xmax > box.xmin AND box.ymax > box.ymin]}; DivisionsValid: PUBLIC PROC[divX, divY: INT] RETURNS [BOOL] = { RETURN[(divX IN [2..50]) AND (divY IN [2..50])]}; RectangleValid: PUBLIC PROC[rect: Imager.Rectangle _ NullRect] RETURNS [BOOL] = { RETURN[rect.w > 0 AND rect.h > 0.0]}; ColorValueOk: PUBLIC PROC[value: REAL _ 0.0, blinkMsg: BOOL _ TRUE] RETURNS [ok: BOOL _ TRUE] = { IF value > 1.0 OR value < 0.0 THEN { IF blinkMsg THEN BlinkMsg["Value must be in [0..1]."]; RETURN[FALSE]; }; }; -- ColorValueOk ForegroundColor: PUBLIC PROC[background: BackgroundIndex _ white, output: OutputType _ screen] RETURNS [Imager.ConstantColor] ={ RETURN[IF output = interpress THEN systemColor[15] ELSE IF background = black OR background = darkGray THEN systemColor[0] ELSE systemColor[15]]; }; -- ForegroundColor BackgroundColor: PUBLIC PROC[background: BackgroundIndex _ white, output: OutputType _ screen] RETURNS [Imager.ConstantColor] ={ RETURN[IF output = interpress THEN systemColor[0] ELSE SELECT background FROM gray => systemColor[13], darkGray => systemColor[14], black => systemColor[15], ENDCASE => systemColor[0]]; }; -- ForegroundColor UseMyColors: PUBLIC PROC[handle: GraphHandle _ NIL] = { IF HandleNotNil[handle] THEN FOR index: ColorIndex IN ColorIndex DO systemColor[index] _ SetColor[index, handle.graph.color[index]]; ENDLOOP; }; -- UseMyColors SetColor: PUBLIC PROC[index: ColorIndex _ 0, rgb: ImagerColor.RGB _ [0, 0, 0]] RETURNS [ImagerColorDefs.ConstantColor] = { entry: ImagerColorMap.MapEntry _ [ mapIndex: index+100, red: Real.RoundC[rgb.R*255], green: Real.RoundC[rgb.G*255], blue: Real.RoundC[rgb.B*255] ]; ImagerColorMap.LoadEntries[ vt: Terminal.Current[], mapEntries: LIST[entry], shared: FALSE]; RETURN[ImagerDitheredDevice.ColorFromSpecialRGB[specialPixel: [index+100, null], rgb: rgb]]; }; -- SetColor UseDefaultColors: PUBLIC PROC[handle: GraphHandle _ NIL] = { GetColor: PUBLIC PROC [num: ColorIndex] RETURNS [ImagerColor.RGB] = { ColorSpec: TYPE = LIST OF ROPE; list: ColorSpec _ UserProfile.ListOfTokens[ key: IO.PutFR["Graph.Color%g", IO.int[num]], default: NIL ]; c: ARRAY[1..3] OF REAL _ [ defaultColors[num].R, defaultColors[num].G, defaultColors[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[[R: c[1], G: c[2], B: c[3]]]; }; -- GetColor FOR i: ColorIndex IN ColorIndex DO c: ImagerColor.RGB _ GetColor[i]; systemColor[i] _ SetColor[i, c]; IF handle # NIL THEN IF handle.graph # NIL THEN handle.graph.color[i] _ c; ENDLOOP; }; -- UseDefaultColors UseCedarColors: PUBLIC PROC[] = { virtual: Terminal.Virtual = Terminal.Current[]; ImagerColorMap.SetStandardColorMap[virtual]; WindowManager.RestoreCursor[]; IF Terminal.GetColorCursorPresentation[virtual] # onesAreBlack THEN [] _ Terminal.SetColorCursorPresentation[virtual, onesAreBlack]; }; -- UseCedarColors SetCursorForBackgroundIndex: PUBLIC PROC [bkgnd: BackgroundIndex _ white] = { bkGndColor: Imager.ConstantColor _ SELECT bkgnd FROM gray => systemColor[13], darkGray => systemColor[14], black => systemColor[15], ENDCASE => systemColor[0]; cursorType: Terminal.ColorCursorPresentation _ IF ImagerColor.GrayFromColor[bkGndColor] > 0.5 THEN onesAreWhite ELSE onesAreBlack; virtual: Terminal.Virtual _ Terminal.Current[]; IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN [] _ Terminal.SetColorCursorPresentation[virtual, cursorType]; }; -- SetCursorForBackgroundIndex UseDefaultFonts: PUBLIC PROC [handle: GraphHandle] = { GetFont: PUBLIC PROC [num: FontIndex] RETURNS [gf: GraphFont] = { FontSpec: TYPE = LIST OF ROPE; keyRope: ROPE _ Rope.Concat["Graph.Font", Convert.RopeFromInt[num]]; list: FontSpec _ UserProfile.ListOfTokens[key: keyRope, default: NIL]; i: CARDINAL _ 1; gf _ defaultFonts[num]; FOR lr: FontSpec _ list, list.rest UNTIL list = NIL OR i > 3 DO SELECT i FROM 1 => gf.family _ list.first; 2 => gf.bold _ Convert.BoolFromRope[list.first ! Convert.Error => {gf.bold _ FALSE; CONTINUE}]; 3 => gf.italic _ Convert.BoolFromRope[list.first ! Convert.Error => {gf.italic _ FALSE; CONTINUE}]; 4 => gf.vFontSize _ Convert.IntFromRope[list.first ! Convert.Error => {gf.vFontSize _ 8; CONTINUE}]; 5 => gf.pFontScale _ Convert.RealFromRope[list.first ! Convert.Error => {gf.pFontScale _ 9.0; CONTINUE}]; ENDCASE => BlinkMsg[Rope.Cat[ "more than five values found in profile after '", keyRope, "'."]]; i _ i + 1; ENDLOOP; }; -- GetFont FOR i: FontIndex IN FontIndex DO handle.graph.font[i] _ GetFont[i]; ENDLOOP; UseMyFonts[handle]; }; -- UseDefaultFonts UseMyFonts: PUBLIC PROC [handle: GraphHandle] = { OPEN handle; FOR i: FontIndex IN FontIndex DO OPEN handle; imagerFonts[screen][i] _ VFonts.EstablishFont[graph.font[i].family, graph.font[i].vFontSize, graph.font[i].bold, graph.font[i].italic]; ENDLOOP; }; -- UseMyFonts InViewer: PUBLIC PROC [viewer: Viewer _ NIL, x, y: INTEGER _ 0] RETURNS [BOOL] ~ { IF viewer = NIL THEN RETURN[FALSE]; RETURN[x IN[0..viewer.cw) AND y IN[0..viewer.ch)]}; CrossSegment: PUBLIC PROC[xseg, yseg: SegmentDataList _ NIL, at: REAL _ 0.0] RETURNS [ok: BOOL _ FALSE, v1, v2: Imager.VEC _ NullVec] = { IF xseg # NIL THEN IF xseg.rest # NIL THEN { IF GraphUtil.NotANan[xseg.first.end] THEN IF xseg.first.end >= at THEN GOTO normal; FOR xseg _ xseg, xseg.rest UNTIL xseg = NIL DO IF xseg.rest = NIL THEN GOTO right; v1 _ [xseg.first.end, yseg.first.end]; IF GraphUtil.NotANan[xseg.rest.first.end] THEN IF xseg.rest.first.end > at THEN GOTO normal; yseg _ yseg.rest; ENDLOOP; EXITS right => { IF GraphUtil.NotANan[v1.x] AND GraphUtil.NotANan[v1.y] AND GraphUtil.NotANan[xseg.first.end] AND GraphUtil.NotANan[yseg.first.end] THEN { ok _ TRUE; v2 _ [xseg.first.end, yseg.first.end]; -- Note: that v1.x may still be = v2.x. }; }; normal => { IF GraphUtil.NotANan[xseg.first.end] AND GraphUtil.NotANan[yseg.first.end] AND GraphUtil.NotANan[xseg.rest.first.end] AND GraphUtil.NotANan[yseg.rest.first.end] THEN { ok _ TRUE; v1 _ [xseg.first.end, yseg.first.end]; v2 _ [xseg.rest.first.end, yseg.rest.first.end]; }; }; }; }; -- CrossSegment InterOrExtraPolate: PUBLIC PROC[v1, v2: Imager.VEC _ NullVec, x: REAL _ 0.0] RETURNS [REAL] = { IF v1.x = v2.x THEN RaiseError[$Other, "x1 = x2 in InterOrExtraPolate"]; -- for now. RETURN[v1.y + (v2.y - v1.y)/(v2.x - v1.x)*(x - v1.x)] }; -- InterOrExtraPolate Crosssection: PUBLIC PROC[xseg, yseg: SegmentDataList _ NIL, at: REAL _ 0.0] RETURNS [ok: BOOL _ FALSE, value: REAL] = { v1, v2: Imager.VEC; [ok, v1, v2] _ CrossSegment[xseg, yseg, at]; IF ok THEN { -- v1 and v2 don't have NtNan as component. IF v1.x = v2.x THEN {value _ v1.y; ok _ FALSE} ELSE value _ InterOrExtraPolate[v1, v2, at]; }; }; -- Crosssection RealToScreen: PUBLIC PROC [handle: GraphHandle _ NIL, r: REAL _ 0.0, xy: XY _ x] RETURNS [s: REAL _ 0] = { IF HandleNotNil[handle] THEN { OPEN handle; RETURN[IF xy = x THEN axesRect.x + (r - realRect.x)*scale[x] ELSE axesRect.y + (r - realRect.y)*scale[y]]}; }; -- RealToScreen RealToScreenI: PUBLIC PROC [handle: GraphHandle _ NIL, r: REAL _ 0.0, xy: XY _ x] RETURNS [s: INTEGER] = { rs: REAL _ RealToScreen[handle, r, xy]; IF rs > LAST[INTEGER] OR rs < FIRST[INTEGER] THEN { s _ IF rs > 0 THEN LAST[INTEGER] ELSE FIRST[INTEGER]; BlinkMsg["The place is too far off the screen."]; } ELSE s _ Real.RoundI[rs]; }; -- RealToScreenI RealVecToScreenVec: PUBLIC PROC [handle: GraphHandle _ NIL, vec: Imager.VEC _ NullVec] RETURNS [svec: Imager.VEC _ NullVec] = { IF HandleNotNil[handle] THEN RETURN[ [RealToScreen[handle, vec.x, x], RealToScreen[handle, vec.y, y]]]; }; -- RealVecToScreenVec RealToScreenRel: PUBLIC PROC [handle: GraphHandle _ NIL, r: REAL _ 0.0, xy: XY _ x] RETURNS [s: REAL _ 0.0] = { -- relative to origin of the axes. IF HandleNotNil[handle] THEN { OPEN handle; RETURN[IF xy = x THEN (r - realRect.x)*scale[x] ELSE (r - realRect.y)*scale[y]]; }; }; -- RealToScreen ScreenToReal: PUBLIC PROC [handle: GraphHandle _ NIL, s: REAL _ 0.0, xy: XY _ x] RETURNS [r: REAL _ 0.0] = { IF HandleNotNil[handle] THEN { OPEN handle; RETURN[IF xy = x THEN realRect.x + (s - axesRect.x)/scale[x] ELSE realRect.y + (s - axesRect.y)/scale[y]]; }; }; -- ScreenToReal ScreenIToReal: PUBLIC PROC [handle: GraphHandle _ NIL, s: INTEGER _ 0, xy: XY _ x] RETURNS [REAL] = {RETURN[ScreenToReal[handle, Real.Float[s], xy]]}; -- ScreenIToReal ScreenIToRealVec: PUBLIC PROC [handle: GraphHandle _ NIL, sx, sy: INTEGER _ 0] RETURNS [Imager.VEC _ NullVec] = { RETURN[[ScreenToReal[handle, Real.Float[sx], x], ScreenToReal[handle, Real.Float[sy], y]]]}; -- ScreenIToRealVec TextPosToChartPos: PUBLIC PROC[axesRect: Imager.Rectangle _ NullRect, textPos: Imager.VEC _ NullVec] RETURNS [chartPos: Imager.VEC _ NullVec] = { IF RectangleValid[axesRect] THEN { chartPos.x _ axesRect.x + textPos.x*axesRect.w; chartPos.y _ axesRect.y + textPos.y*axesRect.h; }; }; -- TextPosToChartPos ChartPosToTextPos: PUBLIC PROC[axesRect: Imager.Rectangle _ NullRect, chartPos: Imager.VEC _ NullVec] RETURNS [textPos: Imager.VEC _ NullVec] = { IF RectangleValid[axesRect] THEN { textPos.x _ (chartPos.x - axesRect.x)/axesRect.w; textPos.y _ (chartPos.y - axesRect.y)/axesRect.h; }; }; -- ChartPosToTextPos TextRect: PUBLIC PROC[text: Text _ NIL, font: ImagerFont.Font _ NIL, axesRect: Imager.Rectangle _ NullRect] RETURNS [rect: Imager.Rectangle _ NullRect, hotPoint: Imager.VEC _ NullVec] = { IF text # NIL AND font # NIL THEN { extents: ImagerFont.Extents _ ImagerFont.RopeBoundingBox[font, text.text]; rect.w _ extents.rightExtent - extents.leftExtent; rect.h _ extents.descent + extents.ascent; hotPoint _ TextPosToChartPos[axesRect, text.place]; rect.x _ hotPoint.x - extents.leftExtent - (SELECT text.justifX FROM right => rect.w, center => rect.w*0.5, ENDCASE => 0.0); rect.y _ hotPoint.y + extents.descent - (SELECT text.justifY FROM top => rect.h, center => rect.h*0.5, ENDCASE => 0.0); }; }; -- TextRect SetToggleColor: PUBLIC PROC[switch: Buttons.Button _ NIL, on: BOOL _ TRUE, paint: BOOL _ TRUE] RETURNS [BOOL] = { IF ViewerNotNil[switch] THEN Buttons.SetDisplayStyle[switch, IF on THEN $WhiteOnBlack ELSE $BlackOnWhite, paint]; RETURN[on]; }; -- SetToggleColor SetIntField: PUBLIC PROC[field: Viewer _ NIL, value: INT _ 0] = { IF ViewerNotNil[field] THEN ViewerTools.SetContents[field, Convert.RopeFromInt[value]]; }; -- SetIntField GetIntField: PUBLIC PROC[field: Viewer _ NIL, showMsg: BOOL _ TRUE] RETURNS [msg: ROPE _ NIL, int: INT _ 0] = { IF ViewerNotNil[field] THEN int _ Convert.IntFromRope[ViewerTools.GetContents[field] ! Convert.Error => { msg _ SELECT reason FROM empty => "field empty", syntax => Rope.Cat["syntax error at location [", Convert.RopeFromInt[index], "]"], overflow => "input string cannot be expressed as an INT", ENDCASE => "error in convertion"; IF showMsg THEN BlinkMsg[msg.Cat[" in the ", NARROW[ViewerOps.FetchProp[field, $Label]], " field."]]; int _ 0; CONTINUE; } ]; }; -- GetIntField SetRealField: PUBLIC PROC[field: Viewer _ NIL, value: REAL _ 0.0] = { IF ViewerNotNil[field] THEN ViewerTools.SetContents[field, Convert.RopeFromReal[value]]; }; -- SetRealField GetRealField: PUBLIC PROC[field: Viewer _ NIL, showMsg: BOOL _ TRUE] RETURNS [msg: ROPE _ NIL, real: REAL _ 0.0] = { IF ViewerNotNil[field] THEN { real _ Convert.RealFromRope[ViewerTools.GetContents[field] ! Convert.Error => { msg _ SELECT reason FROM empty => "field empty", syntax => Rope.Cat["syntax error at location [", Convert.RopeFromInt[index], "]"], ENDCASE => "convertion error"; IF showMsg THEN BlinkMsg[msg.Cat["in the ", NARROW[ViewerOps.FetchProp[field, $Label]], " field."]]; real _ 0.0; CONTINUE; } ]; }; }; -- GetRealField FontHeight: PUBLIC PROC [font: ImagerFont.Font _ NIL] RETURNS [height: REAL _ 0] = { IF font # NIL THEN { extents: ImagerFont.Extents _ ImagerFont.FontBoundingBox[font]; height _ extents.descent+extents.ascent; }; }; -- FontHeight RopeSize: PUBLIC PROC [rope: ROPE _ NIL, font: ImagerFont.Font] RETURNS [width, height: REAL _ 0] = { IF NOT rope.IsEmpty[] THEN { extents: ImagerFont.Extents _ ImagerFont.RopeBoundingBox[font: font, rope: rope]; width _ extents.rightExtent - extents.leftExtent; height _ extents.descent + extents.ascent; }; }; -- RopeSize FullName: PUBLIC PROC [entity: Entity _ NIL] RETURNS [fullName: ROPE _ NIL] = { IF entity # NIL THEN { fullName _ entity.name; FOR gen: NestedEntities _ entity.parent, gen.parent UNTIL gen = NIL DO fullName _ IF gen.parent = NIL AND gen.name = NIL THEN fullName ELSE IF gen.name = NIL THEN Rope.Concat["/", fullName] ELSE gen.name.Cat["/", fullName]; ENDLOOP; }; }; -- FullName TextFromId: PUBLIC PROC[texts: Texts _ NIL, id: INT _ 0] RETURNS [text: Text _ NIL] = { FOR ts: Texts _ texts, ts.rest UNTIL ts = NIL DO IF ts.first.id = id THEN text _ ts.first; ENDLOOP; }; -- TextFromId EntityFromId: PUBLIC PROC[entityList: EntityList, id: INT] RETURNS [entity: Entity _ NIL] = { -- ref may be EntityList or EntityHash. FOR el: EntityList _ entityList, el.rest UNTIL el = NIL DO IF el.first.id = id THEN entity _ el.first; ENDLOOP; }; -- EntityFromId SpecIndexedText: PUBLIC PROC [controller: Controller _ NIL, texts: Texts _ NIL] RETURNS [text: Text _ NIL] = { IF ControllerNotNil[controller] THEN { msg: ROPE; i: INT; [msg, i] _ GetIntField[controller.textId]; IF msg = NIL THEN { text _ TextFromId[texts, i]; IF text = NIL THEN BlinkMsg[Rope.Concat[ "There is no text with id = ", Convert.RopeFromInt[i]]]; }; }; }; -- SpecIndexedText SpecIndexedEntity: PUBLIC PROC [controller: Controller _ NIL, ref: REF ANY _ NIL] RETURNS [entity: Entity _ NIL] = { -- ref may be an entityList or an entityHash. IF ControllerNotNil[controller] THEN { msg: ROPE; i: INT; [msg, i] _ GetIntField[controller.entityId]; IF msg = NIL THEN { entityList: EntityList; entityList _ WITH ref SELECT FROM h: EntityHash => h[i MOD EntityHashSize], e: EntityList => e, ENDCASE => NIL; entity _ EntityFromId[entityList, i]; IF entity = NIL THEN BlinkMsg[Rope.Concat[ "There is no curve with id = ", Convert.RopeFromInt[i]]]; }; }; }; -- SpecIndexedEntity ReverseTexts: PUBLIC PROC [old: Texts, killOld: BOOL _ TRUE] RETURNS [new: Texts] = { -- killOld only "partially" kills the old list. new _ NIL; FOR ts: Texts _ old, ts.rest UNTIL ts = NIL DO new _ CONS[ts.first, new]; ENDLOOP; IF killOld THEN old _ GraphCleanUp.CleanUpTexts[old, FALSE]; }; -- ReverseTexts ReverseEntityList: PUBLIC PROC [old: EntityList, killOld: BOOL _ TRUE] RETURNS [new: EntityList] = { -- killOld only "partially" kills the old list. new _ NIL; FOR el: EntityList _ old, el.rest UNTIL el = NIL DO new _ CONS[el.first, new]; ENDLOOP; IF killOld THEN old _ GraphCleanUp.CleanUpEL[old, FALSE]; }; -- ReverseEntityList ReverseValueList: PUBLIC PROC [old: ValueList _ NIL, killOld: BOOL _ TRUE] RETURNS [new, last: ValueList _ NIL] = { FOR vl: ValueList _ old, vl.rest UNTIL vl = NIL DO new _ CONS[vl.first, new]; IF vl = old THEN last _ new; ENDLOOP; IF killOld THEN old _ GraphCleanUp.CleanUpVL[old]; }; -- ReverseValueList CopyValueList: PUBLIC PROC [old: ValueList _ NIL, killOld: BOOL _ TRUE] RETURNS [new, last: ValueList] = { [new, ] _ ReverseValueList[old, killOld]; [new, last] _ ReverseValueList[new, TRUE]; }; -- CopyValueList ReverseSDL: PUBLIC PROC [old: SegmentDataList _ NIL, killOld: BOOL _ TRUE] RETURNS [new, last: SegmentDataList _ NIL] = { FOR sdl: SegmentDataList _ old, sdl.rest UNTIL sdl = NIL DO new _ CONS[sdl.first, new]; IF sdl = old THEN last _ new; ENDLOOP; IF killOld THEN old _ GraphCleanUp.CleanUpSDL[old]; }; -- ReverseSDL InitSegEnd: PUBLIC PROC [entity: Entity _ NIL] = { sdl: SegmentDataList _ NIL; entity.segments _ NIL; FOR vl: ValueList _ entity.oldValues, vl.rest UNTIL vl = NIL DO sdl _ CONS[NEW[SegmentDataRec _ [end: vl.first]], sdl]; ENDLOOP; [entity.segments, entity.lastSegment] _ ReverseSDL[sdl]; }; -- InitSegEnd SetSegment: PROC [seg: SegmentData, x1, x2, y2: REAL] = { IF seg # NIL THEN { ENABLE Real.RealException => {seg^ _ [ok: FALSE, end: seg.end]; CONTINUE}; a, b, factor: REAL; IF GraphUtil.NotANan[x1] AND GraphUtil.NotANan[x2] AND GraphUtil.NotANan[seg.end] AND GraphUtil.NotANan[y2] THEN { a _ y2 - seg.end; b _ x1 - x2; IF (factor _ RealFns.SqRt[a*a+b*b]) > 0.0 THEN { factor _ 1.0/factor; seg^ _ [ok: TRUE, end: seg.end, nx: a*factor, ny: b*factor, d0: (x1*y2 - x2*seg.end)*factor]; }; }; }; }; -- SetSegment SetSegments: PUBLIC PROC [entity: Entity] = { xseg: SegmentDataList _ entity.group.x.segments; FOR yseg: SegmentDataList _ entity.segments, yseg.rest UNTIL yseg.rest = NIL DO SetSegment[yseg.first, xseg.first.end, xseg.rest.first.end, yseg.rest.first.end]; xseg _ xseg.rest; ENDLOOP; }; -- SetSegments InitSegAll: PUBLIC PROC [entity: Entity _ NIL] = { InitSegEnd[entity]; SetSegments[entity]; }; -- InitSegAll UpdateSegEnd: PUBLIC PROC [entity: Entity, values: ValueList] = { oldSDL: SegmentDataList _ entity.segments; vl: ValueList _ values; entity.segments _ NIL; IF values # NIL THEN FOR sdl: SegmentDataList _ oldSDL, sdl.rest UNTIL sdl = NIL DO sdl.first.end _ vl.first; vl _ vl.rest; ENDLOOP; entity.segments _ oldSDL; }; -- UpdateSegEnd UpdateSegAll: PUBLIC PROC [entity: Entity, values: ValueList] = { IF values # NIL THEN { UpdateSegEnd[entity, values]; SetSegments[entity]; }; }; -- UpdateSegAll LengthOfVL: PUBLIC PROC [valueList: ValueList _ NIL] RETURNS [INT] = { length: INT _ 0; FOR vl: ValueList _ valueList, vl.rest UNTIL vl = NIL DO length _ length + 1; ENDLOOP; RETURN[length]; }; -- LengthOfVL LengthOfEL: PUBLIC PROC [entityList: EntityList _ NIL, group: EntityGroup _ NIL] RETURNS [INT] = { length: INT _ 0; FOR el: EntityList _ entityList, el.rest UNTIL el = NIL DO IF group = NIL THEN length _ length + 1 ELSE IF el.first.group.id = group.id THEN length _ length + 1; ENDLOOP; RETURN[length]; }; -- LengthOfEL LengthOfSDL: PUBLIC PROC [segmentDataList: SegmentDataList _ NIL] RETURNS [INT] = { length: INT _ 0; FOR sdl: SegmentDataList _ segmentDataList, sdl.rest UNTIL sdl = NIL DO length _ length + 1; ENDLOOP; RETURN[length]; }; -- LengthOfSDL AppendX: PUBLIC PROC [xEntity: Entity, newx: REAL] RETURNS [oldx: REAL _ 0.0] = { vl: Graph.ValueList _ CONS[newx, NIL]; sdl: Graph.SegmentDataList _ CONS[NEW[Graph.SegmentDataRec _ [end: newx]], NIL]; IF xEntity.lastValue = NIL THEN { xEntity.oldValues _ xEntity.lastValue _ vl; xEntity.segments _ xEntity.lastSegment _ sdl; } ELSE { xEntity.lastValue.rest _ vl; xEntity.lastValue _ vl; oldx _ xEntity.lastSegment.first.end; xEntity.lastSegment.rest _ sdl; xEntity.lastSegment _ sdl; }; }; -- AppendX AppendY: PUBLIC PROC [entity: Entity, y, x1, x2: REAL _ 0.0] RETURNS [oldy: REAL _ 0.0] = { vl: Graph.ValueList _ CONS[y, NIL]; sdl: Graph.SegmentDataList _ CONS[NEW[Graph.SegmentDataRec _ [end: y]], NIL]; IF entity.lastValue = NIL THEN { entity.oldValues _ entity.lastValue _ vl; entity.segments _ entity.lastSegment _ sdl; } ELSE { entity.lastValue.rest _ vl; entity.lastValue _ vl; IF entity.segments # NIL THEN { oldy _ entity.lastSegment.first.end; SetSegment[entity.lastSegment.first, x1, x2, y]; entity.lastSegment.rest _ sdl; entity.lastSegment _ sdl; }; }; }; -- AppendY AppendTexts: PUBLIC PROC [first, second: Texts _ NIL] RETURNS [new: Texts] = { IF first = NIL THEN new _ second ELSE { FOR txs: Texts _ first, txs.rest UNTIL txs = NIL DO IF txs.rest = NIL THEN {txs.rest _ second; EXIT}; ENDLOOP; new _ first; }; }; -- AppendTexts AppendEntityList: PUBLIC PROC [first, second: EntityList _ NIL] RETURNS [new: EntityList] = { IF first = NIL THEN new _ second ELSE { FOR txs: EntityList _ first, txs.rest UNTIL txs = NIL DO IF txs.rest = NIL THEN {txs.rest _ second; EXIT}; ENDLOOP; new _ first; }; }; -- AppendEntityList AppendEGL: PUBLIC PROC [first, second: EntityGroupList _ NIL] RETURNS [new: EntityGroupList] = { IF first = NIL THEN new _ second ELSE { FOR txs: EntityGroupList _ first, txs.rest UNTIL txs = NIL DO IF txs.rest = NIL THEN {txs.rest _ second; EXIT}; ENDLOOP; new _ first; }; }; -- AppendEntityList NewTextId: PUBLIC PROC [handle: GraphHandle, tryId: INT] RETURNS [newId: INT _ -1] = { IF HandleNotNil[handle] THEN { OPEN handle; IF tryId <= lastTextId THEN { IF TextFromId[allTexts, tryId] = NIL THEN RETURN[tryId] }; RETURN[lastTextId _ lastTextId + 1]; }; }; -- NewTextId NewEntityId: PUBLIC PROC [handle: GraphHandle, tryId: INT] RETURNS [newId: INT _ -1] = { IF HandleNotNil[handle] THEN { OPEN handle; IF tryId < 0 THEN tryId _ 0; IF tryId <= lastEntityId THEN { IF EntityFromId[entityHash[tryId MOD EntityHashSize], tryId] = NIL THEN RETURN[tryId] }; RETURN[lastEntityId _ lastEntityId + 1]; }; }; -- NewEntityId NewGroupId: PUBLIC PROC [handle: GraphHandle, tryId: INT] RETURNS [newId: INT _ -1] = { IF HandleNotNil[handle] THEN { OPEN handle; lastId: INT _ -1; used: BOOL _ FALSE; FOR egl: EntityGroupList _ entityGroupList, egl.rest UNTIL egl = NIL DO IF tryId = egl.first.id THEN used _ FALSE; lastId _ MAX[lastId, egl.first.id]; ENDLOOP; RETURN[IF used THEN lastId + 1 ELSE tryId]; }; }; -- NewGroupId VanilaGraph: PUBLIC PROC[] RETURNS [graph: GRAPH] = { graph _ NEW[GraphRec _ []]; FOR i: CaretIndex IN CaretIndex DO graph.caret[i] _ NEW[CaretSpecRec _ []]; ENDLOOP; FOR xy: XY IN XY DO graph.target[xy] _ NEW[TargetSpecRec _ []]; ENDLOOP; }; -- VanilaGraph FileFromSelection: PUBLIC PROC[wDir: ROPE _ NIL] RETURNS [file, msg: ROPE _ NIL] = { file _ ViewerTools.GetSelectionContents[]; IF file.IsEmpty[] THEN msg _ "Please select a file name." ELSE [file, ] _ FS.ExpandName[file, wDir! FS.Error => {msg _ error.explanation; CONTINUE}]; }; -- FileFromSelection ReplaceFileExt: PUBLIC PROC[file: ROPE, extension: ROPE _ NIL] RETURNS [new: ROPE _ NIL] = { cp: FS.ComponentPositions; fullName, msg: ROPE; ok: BOOL _ TRUE; IF file.IsEmpty[] THEN {msg _ "File name is empty. "; file _ "Graph"}; IF extension.IsEmpty[] THEN { msg _ msg.Concat["Extension is empty. "]; extension _ "graph"}; IF msg = NIL THEN [fullName, cp, ] _ FS.ExpandName[file ! FS.Error => {msg _ error.explanation; CONTINUE}]; IF msg = NIL THEN new _ Rope.Cat[fullName.Substr[0, cp.base.start + cp.base.length], ".", extension] ELSE { new _ file.Cat[".", extension]; BlinkMsg[msg.Cat[new, " ok?"]]; }; }; -- ReplaceFileExt WDirOfViewer: PUBLIC PROC [viewer: Viewer] RETURNS [wDir: ROPE] = { wDir _ "[]<>"; IF viewer # NIL THEN { cp: FS.ComponentPositions; viewerName: ROPE _ NIL; [viewerName, cp, ] _ FS.ExpandName[viewer.name ! FS.Error => {viewerName _ NIL}]; IF NOT viewerName.IsEmpty[] THEN wDir _ viewerName.Substr[0, cp.base.start]; }; }; -- WDirOfChartViewer waitProgramMsg: PUBLIC ROPE = "Graph is currently controlled by a program. Please wait till it finishes to do this."; HandleFromViewer: PUBLIC PROC[viewer: Viewer _ NIL] RETURNS [GraphHandle] = { RETURN[ IF IsGraphViewer[viewer] THEN NARROW[viewer.data] ELSE IF IsController[viewer] THEN NARROW[ ViewerOps.FetchProp[viewer, $GraphController]] ELSE NIL ]; }; -- HandleFromViewer VanillaHandle: PUBLIC PROC [] RETURNS [handle: GraphHandle _ NIL] = { handle _ NEW[GraphHandleRec _ []]; { OPEN handle; chart _ VanillaChart[]; graph _ VanillaGraph[]; entityHash _ NEW[EntityHashArray _ ALL[NIL]]; imagerFonts _ [NEW[FontArray _ ALL[NIL]], NEW[FontArray _ ALL[NIL]]]; paintInfo _ NEW[PaintInfoRec _ []]; TRUSTED {Process.SetTimeout[@unlocked, Process.MsecToTicks[500]]}; }; }; -- VanillaHandle VanillaChart: PROC [] RETURNS [chart: Chart _ NIL] = { chart _ NEW[ChartRec _ []]; FOR i: CaretIndex IN CaretIndex DO chart.caretState[i] _ NEW[CaretStateRec _ []]; ENDLOOP; }; -- VanillaChart VanillaGraph: PROC [] RETURNS [graph: GRAPH _ NIL] = { graph _ NEW[GraphRec _ []]; FOR i: CaretIndex IN CaretIndex DO graph.caret[i] _ NEW[CaretSpecRec _ []]; ENDLOOP; FOR i: XY IN XY DO graph.target[i] _ NEW[TargetSpecRec _ []]; ENDLOOP; graph.color _ NEW[GraphColorsArray _ defaultColors^]; graph.font _ NEW[GraphFontsArray _ defaultFonts^]; }; -- VanillaGraph Almost: PUBLIC PROC [p, q: REAL] RETURNS [a: BOOL _ TRUE] = { IF GraphUtil.NotANumber[p] THEN RETURN[GraphUtil.NotANumber[q]] ELSE IF GraphUtil.NotANumber[q] THEN RETURN[FALSE] ELSE { max: REAL _ MAX[ABS[p], ABS[q]]; IF max # 0.0 THEN a _ (ABS[p - q]/max) < 0.00001; }; }; -- Almost DataBounds: PUBLIC PROC[entityList: EntityList, old: Imager.Box] RETURNS [Imager.Box] = { ValidateMax: PROC [min, max: REAL] RETURNS [REAL] = { RETURN[IF min = 0.0 AND max = 0.0 THEN 1.0 ELSE IF min >= max THEN min + (ABS[min] + ABS[max])/2.0 ELSE max]; }; xmin, ymin, xmax, ymax: REAL; IF entityList = NIL THEN { IF BoundsValid[old] THEN RETURN[old] ELSE [xmin, ymin, xmax, ymax] _ old; } ELSE { -- entityList # nil prevX: Entity _ NIL; xmin _ ymin _ Real.LargestNumber; xmax _ ymax _ -xmin; FOR el: EntityList _ entityList, el.rest UNTIL el = NIL DO currX: Entity _ el.first.group.x; IF currX # prevX THEN { FOR sdl: SegmentDataList _ currX.segments, sdl.rest UNTIL sdl = NIL DO rx: REAL _ sdl.first.end; IF GraphUtil.NotANan[rx] THEN {xmin _ MIN[rx, xmin]; xmax _ MAX[rx, xmax]}; ENDLOOP; prevX _ currX; }; FOR sdl: SegmentDataList _ el.first.segments, sdl.rest UNTIL sdl = NIL DO ry: REAL _ sdl.first.end; IF GraphUtil.NotANan[ry] THEN {ymin _ MIN[ry, ymin]; ymax _ MAX[ry, ymax]}; ENDLOOP; ENDLOOP; }; RETURN[[xmin, ymin, ValidateMax[xmin, xmax], ValidateMax[ymin, ymax]]]; }; -- DataBounds Print: PUBLIC PROC[handle: GraphHandle, file: ROPE] RETURNS [msg: ROPE _ NIL] = { IF HandleNotNil[handle] THEN IF handle.graph # NIL THEN { OPEN handle; InterpressDrawGraph: PROC[context: Imager.Context] ~ { coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels context.RotateT[90]; context.ScaleT[coordRatio]; Draw[context, handle]; }; interpress: ImagerInterpress.Ref _ NIL; paintInfo.item _ all; paintInfo.action _ paint; paintInfo.output _ interpress; paintInfo.clear _ FALSE; InitPFonts[handle]; interpress _ ImagerInterpress.Create[file ! FS.Error => {msg _ error.explanation; CONTINUE}]; IF msg = NIL THEN { ImagerInterpress.DoPage[interpress, InterpressDrawGraph]; ImagerInterpress.Close[interpress]; }; }; }; -- Print InitPFonts: PROC [h: GraphHandle] = { EnsurePFont: PROC [i: FontIndex] RETURNS [f: ImagerFont.Font] = { f _ h.imagerFonts[interpress][i]; IF f = NIL THEN { pFontName: ROPE _ Rope.Cat[ "Xerox/PressFonts/", h.graph.font[i].family, Rope.Concat[ IF h.graph.font[i].bold THEN "-b" ELSE "-m", IF h.graph.font[i].italic THEN "ir" ELSE "rr"] ]; f _ ImagerFont.Find[pFontName ! Imager.Error => { f _ ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"]; CONTINUE}]; f _ ImagerFont.Scale[f, h.graph.font[i].pFontScale ! Imager.Error => {f _ ImagerFont.Scale[f, 9.0]; CONTINUE}]; }; }; -- InitPFont h.imagerFonts[interpress][0] _ EnsurePFont[0]; h.imagerFonts[interpress][1] _ EnsurePFont[1]; h.imagerFonts[interpress][LAST[FontIndex]] _ EnsurePFont[LAST[FontIndex]]; FOR ts: Texts _ h.graph.texts, ts.rest UNTIL ts = NIL DO i: FontIndex _ ts.first.fontIndex; h.imagerFonts[interpress][i] _ EnsurePFont[i]; ENDLOOP; }; -- InitPFonts }. LOG. SChen, October 9, 1985 8:59:32 pm PDT, created. PGraphUtilImpl.mesa, Copyright c 1985 by Xerox Corporation. All rights reserved. Last Edited by: Sweetsun Chen, November 21, 1985 10:03:40 pm PST error checking IF ref = nil and degug then raise error. colors & fonts get color spec from user profile, or return default handle must not be nil !! get font spec from user profile, or return default handle can not be nil !! for vfonts only, but display is not altered. press fonts are initialized when user wants to print the plot or when font is updated through control-panel. coordinates and coordinates transformation find the segment that crosses the vertical line, x = at. Client should make sure that x1 # x2, and their components are not NtNan. returns the rectangle and hotpoint of text on graph viewer. panel fields text and entity Returns the full name of the entity. get it from the id specified on spec. assume entity.segments.end's and entity.group.x.segments have been set. should check that values and entity.segments have the same length before calling this proc. AppendX: PUBLIC PROC [entity: Entity, newx: REAL] RETURNS [first: BOOL _ FALSE, oldx: REAL] = { -- so far called by GraphOps.AddCrossSection only. Append new value on x entity. IF entity.lastValue # NIL THEN { entity.lastValue.rest _ CONS[newx, NIL]; entity.lastValue _ entity.lastValue.rest; } ELSE { IF entity.oldValues = NIL THEN { first _ TRUE; entity.lastValue _ entity.oldValues _ CONS[newx, NIL]; } ELSE { entity.lastValue _ entity.oldValues; UNTIL entity.lastValue.rest = NIL DO entity.lastValue _ entity.lastValue.rest; ENDLOOP; entity.lastValue.rest _ CONS[newx, NIL]; entity.lastValue _ entity.lastValue.rest; }; }; IF entity.lastSegment = NIL AND entity.segments = NIL THEN { InitSegEnd[entity]; entity.lastSegment _ entity.segments; UNTIL entity.lastSegment.rest = NIL DO IF entity.lastSegment.rest.rest = NIL THEN oldx _ entity.lastSegment.first.end; entity.lastSegment _ entity.lastSegment.rest; ENDLOOP; } ELSE { IF entity.lastSegment = NIL THEN { entity.lastSegment _ entity.segments; UNTIL entity.lastSegment.rest = NIL DO entity.lastSegment _ entity.lastSegment.rest; ENDLOOP; }; oldx _ entity.lastSegment.first.end; entity.lastSegment.rest _ CONS[NEW[SegmentDataRec _ [end: newx]], NIL]; entity.lastSegment _ entity.lastSegment.rest; }; }; -- AppendX AppendY: PUBLIC PROC [entity: Entity, x1, x2, y: REAL] RETURNS [first: BOOL _ FALSE, oldy: REAL] = { -- so far called by GraphOps.AddCrossSection only. AppendX must have been called. IF entity.lastValue # NIL THEN { oldy _ entity.lastValue.first; entity.lastValue.rest _ CONS[y, NIL]; entity.lastValue _ entity.lastValue.rest; } ELSE { IF entity.oldValues = NIL THEN { entity.lastValue _ entity.oldValues _ CONS[y, NIL]; first _ TRUE; } ELSE { entity.lastValue _ entity.oldValues; UNTIL entity.lastValue.rest = NIL DO entity.lastValue _ entity.lastValue.rest; ENDLOOP; oldy _ entity.lastValue.first; entity.lastValue.rest _ CONS[y, NIL]; entity.lastValue _ entity.lastValue.rest; }; }; IF entity.lastSegment = NIL AND entity.segments = NIL THEN { InitSegAll[entity]; entity.lastSegment _ entity.segments; UNTIL entity.lastSegment.rest = NIL DO entity.lastSegment _ entity.lastSegment.rest; ENDLOOP; } ELSE { either lastSegment # nil or segments # nil. but if segments = nil then lastSegment must be nil. so segments # nil for sure. So we only have to make sure lastSegment # nil here. IF entity.lastSegment = NIL THEN { entity.lastSegment _ entity.segments; UNTIL entity.lastSegment.rest = NIL DO entity.lastSegment _ entity.lastSegment.rest; ENDLOOP; }; If y is the first value on entity, then it must have been taken care of above. (segments = lastsegment = nil.) IF first THEN RaiseError[$Other, "unexpected condition in GraphOpsImpl.AppendValueAndSegment."] ELSE { a: REAL _ y - oldy; b: REAL _ x1 - x2; factor: REAL _ 1.0/RealFns.SqRt[a*a+b*b]; entity.lastSegment.first^ _ [end: oldy, nx: a*factor, ny: b*factor, d0: (x1*y - x2*oldy)*factor]; entity.lastSegment.rest _ CONS[NEW[SegmentDataRec _ [end: y]], NIL]; entity.lastSegment _ entity.lastSegment.rest; }; }; }; -- AppendY order: old texts followed by new texts. order: old texts followed by new texts. order: old texts followed by new texts. ELSE BlinkMsg[IO.PutFR["Warning: Text id %g already used.", IO.int[tryId]]]; ELSE BlinkMsg[IO.PutFR["Warning: Entity id %g already used.", IO.int[tryId]]]; IF used THEN BlinkMsg[IO.PutFR["Warning: Group id %g already used.", IO.int[tryId]]]; graphs and entity groups CopyEntityGroupList: PUBLIC PROC[entityGroupList: EntityGroupList _ NIL, max: INT _ 0] RETURNS [new: EntityGroupList _ NIL, incomplete: BOOL _ FALSE] = { copy at most max elements on a to b and preserve the order. if there are more than max elements on a then set incomplete to TRUE. n: INT _ 0; FOR eg: EntityGroup _ entityGroupList.first, eg.rest UNTIL eg = NIL OR n >= max DO eg: EntityGroup; m: INT; [eg, m, incomplete] _ CopyEntityGroup[eg, max - n]; new _ CONS[t, new]; n _ n + m; ENDLOOP; }; -- CopyEntityGroupList CopyEntityGroup: PUBLIC PROC[entityGroup: EntityGroup _ NIL, max: INT _ 0] RETURNS [ new: EntityGroup _ NIL, m: INT _ 0, incomplete: BOOL _ FALSE] = { n: INT _ 0; FOR ne: NestedEntities _ entityGroup.y, ne.child UNTIL ne = NIL OR n >= max DO el: EntityList; m: INT; [el, m, incomplete] _ CopyEntities[ne, max - n]; new _ CONS[t, new]; n _ n + m; ENDLOOP; }; -- CopyEntityGroup MergeGraph: PUBLIC PROC[handle: GraphHandle _ NIL, graph: GRAPH _ NIL, paint: BOOL _ TRUE] = { }; -- MergeGraph ReplaceGraph: PUBLIC PROC[handle: GraphHandle _ NIL, graph: GRAPH _ NIL, paint: BOOL _ TRUE] = { }; -- ReplaceGraph strings viewer is assumed to be a graph viewer, but it may also work for some other viewers. viewer, handle, properties AddProp: PUBLIC PROC [object: REF ANY, prop: ATOM, val: REF ANY] = { WITH object SELECT FROM c: Entity => ATOM.PutPropOnList[c.props, prop, val]; cs: EntityList => ATOM.PutPropOnList[cs.props, prop, val]; t: Text => ATOM.PutPropOnList[t.props, prop, val]; ENDCASE => RaiseError[$UnknowObject, "in AddProp"]; }; -- AddProp FetchProp: PUBLIC PROC [object: REF ANY, prop: ATOM] RETURNS [val: REF ANY] = { WITH object SELECT FROM c: Entity => val _ ATOM.GetPropFromList[c.props, prop]; cs: EntityList => val _ ATOM.GetPropFromList[cs.props, prop]; t: Text => val _ ATOM.GetPropFromList[t.props, prop]; ENDCASE => RaiseError[$UnknowObject, "in FetchProp"]; }; -- FetchProp misc x bounds y bounds -- fonts for legend, label, and mark -- fonts for texts Κ/2˜JšœΟmœ1™Pšœ™Jšœ0™0—šΟk ˜ Jšœžœ˜(JšœžœM˜ZJšžœžœ)˜1Jš œžœΨžœ”žœ_žœ˜εJšœ žœ2˜DJšœ žœO˜aJ˜ Jšœžœ˜4JšœžœBžœ˜SJšœ žœžœ˜'Jšœžœ˜&Jšœžœ.˜BJšœžœ˜1Jšœ žœ@˜PJšžœžœ˜Jšœžœ˜$Jšœžœ˜(Jšœžœ7˜AJšœžœ˜Jšœžœ ˜*Jšœ žœe˜sJšœ žœ˜!Jšœžœ˜Jšœ žœ ˜Jšœ žœ2˜CJšœžœ˜$—J˜šœžœž˜Jšžœžœ‚žœs˜“Jšžœžœ ˜AJ˜—™š œžœžœžœžœžœžœ˜9J˜—š Οn œžœžœžœžœžœ˜8Jšœžœ˜#š žœžœžœžœž˜*J˜!J˜(J˜$J˜J˜Jšžœ˜—Jšœ˜JšœΟc ˜J˜—š Ÿœžœžœžœžœ˜*Jšœ˜Jšœžœ˜ Jšœ  ˜—˜šŸ œžœžœžœžœžœžœ˜Hšœžœžœž˜J˜Jšœ˜Jšœ˜Jšœ˜J˜Jšžœžœ˜—Jšžœ žœžœ#˜7Jš žœžœžœžœžœ˜AJšœ  ˜—J˜šŸœžœžœžœžœžœžœžœ žœžœžœžœžœ˜{Jšœ(™(šžœžœžœ˜Jšœžœ˜ Jšžœžœžœ!˜IJšœ˜—Jšœ  ˜—J˜—šŸ œžœžœžœžœžœ žœžœžœžœžœ4˜§J˜—šŸ œžœžœžœžœžœ žœžœžœžœžœ2˜J˜—šŸ œžœžœžœžœžœ žœžœžœžœžœ4˜’J˜—šŸœžœžœžœžœ žœžœžœžœžœ˜xšžœ"ž˜(šžœ'ž˜-Jšžœ/žœžœ˜@——Jšœ ˜J˜—šŸœžœžœžœžœžœ žœžœžœžœžœ3˜€J˜—šŸœžœžœžœžœžœ žœžœžœžœžœ˜ƒšžœ"ž˜(šžœ1ž˜7Jšžœ4žœžœ˜E——Jšœ ˜J˜—š Ÿ œžœžœžœžœ˜˜>—Jšœ ˜!J˜—šŸœž œ˜6Jšœ™šŸœžœžœžœ˜AJšœ2™2Jš œ žœžœžœžœ˜Jšœ žœ7˜DJšœAžœ˜FJšœžœ˜Jšœ˜š žœ žœžœžœž˜?šžœž˜ Jšœ˜JšœMžœžœ˜_JšœQžœžœ˜cJšœYžœ˜dJšœ^žœ˜išžœ˜J˜B——J˜ Jšžœ˜—Jšœ  ˜ J˜—šžœžœ ž˜ Jšœ"˜"Jšžœ˜—Jšœ˜Jšœ ˜J˜—šŸ œžœžœžœ˜>JšœE™EJ™lšžœžœ žœžœ˜-Jšœ‡˜‡Jšžœ˜—Jšœ  ˜—J˜—™*šŸœžœžœžœžœžœžœ˜RJš žœ žœžœžœžœ˜#Jšžœžœžœžœ˜3J˜—šŸ œžœžœžœžœžœžœžœžœ˜‰J™8š žœžœžœžœ žœžœ˜,Jš žœ#žœžœžœžœ˜Sšžœžœžœž˜.Jšžœ žœžœžœ˜#Jšœ&˜&šžœ(ž˜.Jšžœžœžœ˜-—J˜Jšžœ˜—šž˜šœ ˜ šžœžœžœ˜;Jšœ"žœ#žœ˜NJšœžœ˜ Jšœ' '˜NJ˜—J˜—šœ ˜ šžœ#žœ#ž˜NJšœ'žœ(žœ˜XJšœžœ˜ Jšœ&˜&Jšœ0˜0J˜—Jšœ˜——J˜—Jšœ ˜J˜—šŸœžœžœžœžœžœžœ˜_J™IJšžœ žœ6  ˜TJšžœ/˜5Jšœ ˜J˜—šŸ œžœžœžœžœžœžœžœ žœ˜xJšœžœ˜Jšœ,˜,šžœžœ +˜8Jšžœ žœžœ˜.Jšžœ(˜,J˜—Jšœ ˜J˜—šŸ œžœžœžœžœ žœžœžœ ˜jšžœžœžœ˜+Jšžœžœžœ'žœ*˜k—Jšœ ˜J˜—šŸ œžœžœžœžœ žœžœžœ˜jJšœžœ˜'šžœžœžœžœžœžœžœ˜3Jšœžœžœžœžœžœžœžœ˜5J˜1J˜—Jšžœ˜Jšœ ˜J˜—šŸœžœžœžœžœ žœžœ˜šžœžœžœ˜$JšœB˜B—Jšœ ˜J˜—šŸœžœžœžœžœ žœžœžœ  "˜’šžœžœžœ˜+Jšžœžœžœžœ˜PJ˜—Jšœ ˜J˜—šŸ œžœžœžœžœ žœžœžœ ˜lšžœžœžœ˜+Jšžœžœžœ(žœ)˜jJ˜—Jšœ ˜J˜—šŸ œžœžœžœžœ žœžœžœžœ, ˜§J˜—šŸœžœžœžœ žœžœ žœ˜qJšžœW ˜pJ˜—š Ÿœžœžœ8žœ žœžœ˜‘šžœžœ˜"Jšœ/˜/Jšœ/˜/Jšœ˜—Jšœ ˜J˜—š Ÿœžœžœ9žœ žœžœ˜‘šžœžœ˜"Jšœ1˜1Jšœ1˜1Jšœ˜—Jšœ ˜J˜—šŸœžœžœžœžœ)žœ6žœ˜»J™;š žœžœžœžœžœ˜#JšœJ˜JJšœ2˜2Jšœ*˜*Jšœ3˜3šœ,žœž˜DJšœ&žœ ˜7—šœ)žœž˜AJšœ$žœ ˜5—J˜—Jšœ  ˜—J˜—™ šŸœžœžœžœžœžœ žœžœžœžœ˜qšžœžœ˜Jšœ žœžœžœ˜T—Jšžœ˜ Jšœ ˜J˜—š Ÿ œžœžœžœ žœ ˜Ašžœžœ˜Jšœ;˜;—Jšœ ˜J˜—šŸ œžœžœžœ žœžœžœžœžœžœ ˜ošžœžœ˜šœ:˜:˜šœžœž˜Jšœ˜JšœR˜RJšœ9˜9Jšžœ˜!—šžœ žœ˜,Jšžœ2˜8—Jšœ˜Jšžœ˜ Jšœ˜—Jšœ˜——Jšœ ˜J˜—š Ÿ œžœžœžœ žœ ˜Ešžœžœ˜Jšœ<˜<—Jšœ ˜—J˜šŸ œžœžœžœ žœžœžœžœžœžœ ˜tšžœžœ˜šœ<˜<˜šœžœž˜Jšœ˜JšœR˜RJšžœ˜—šžœ žœ˜+Jšžœ2˜8—Jšœ ˜ Jšžœ˜ Jšœ˜—Jšœ˜—J˜—Jšœ ˜—J˜—™š Ÿ œžœžœžœžœ žœ ˜Tšžœžœžœ˜Jšœ?˜?Jšœ(˜(J˜—Jšœ  ˜J˜—šŸœžœžœžœžœžœžœ ˜ešžœžœžœ˜JšœQ˜QJšœ1˜1Jšœ*˜*J˜—Jšœ  ˜J˜—šŸœžœžœžœžœ žœžœ˜OJ™$šžœ žœžœ˜Jšœ˜šžœ1žœžœž˜Fš œ žœžœžœ žœžœ ˜?Jšžœžœ žœžœ˜6Jšžœ˜!—Jšžœ˜—J˜—Jšœ  ˜J˜—šŸ œžœžœžœžœžœžœ˜Wšžœžœžœž˜0Jšžœžœ˜)Jšžœ˜—Jšœ  ˜J˜—š Ÿ œžœžœžœžœžœ '˜…šžœ&žœžœž˜:Jšžœžœ˜+Jšžœ˜—Jšœ ˜J˜—šŸœžœžœžœžœžœžœ˜nJ™%šžœžœ˜&Jšœžœžœ˜Jšœ*˜*šžœžœžœ˜Jšœ˜šžœžœžœ˜(Jšœ8˜8—J˜—J˜—Jšœ ˜J˜—šŸœžœžœžœžœžœžœžœžœ -˜’šžœžœ˜&Jšœžœžœ˜Jšœ,˜,šžœžœžœ˜Jšœ˜šœ žœžœž˜!Jšœžœ˜)Jšœ˜Jšžœžœ˜—Jšœ%˜%šžœ žœžœ˜*Jšœ9˜9—J˜—J˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœžœ /˜…Jšœžœ˜ šžœžœžœž˜.Jšœžœ˜Jšžœ˜—Jšžœ žœ&žœ˜Jšœ˜Jšœ ˜ Jšžœ˜—Jšœ˜Jšœ ˜J˜—šŸ œž œ(˜Ašžœ žœžœ˜Jšœ˜Jšœ˜J˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœžœ˜FJšœžœ˜šžœ$žœžœž˜8J˜Jšžœ˜—Jšžœ ˜Jšœ  ˜J˜—šŸ œžœžœžœžœžœžœ˜bJšœžœ˜šžœ&žœžœž˜:Jšžœ žœžœ˜'Jšžœžœžœ˜>Jšžœ˜—Jšžœ ˜Jšœ  ˜J˜—š Ÿ œžœžœ%žœžœžœ˜SJšœžœ˜šžœ2žœžœž˜GJ˜Jšžœ˜—Jšžœ ˜Jšœ ˜J˜—š Ÿœžœžœžœžœžœ ˜QJšœžœžœ˜&Jšœžœžœ&žœ˜Pšžœžœžœ˜!Jšœ+˜+Jšœ-˜-J˜—šžœ˜Jšœ˜Jšœ˜Jšœ%˜%Jšœ˜Jšœ˜J˜—Jšœ  ˜ J˜—š Ÿœžœžœžœžœžœ ˜[Jšœžœžœ˜#Jšœžœžœ#žœ˜Mšžœžœžœ˜ Jšœ)˜)Jšœ+˜+J˜—šžœ˜Jšœ˜Jšœ˜šžœžœžœ˜Jšœ$˜$Jšœ0˜0Jšœ˜Jšœ˜J˜—J˜—Jšœ  ˜ J˜—šŸœžœžœžœžœ žœžœžœ 2™’J™šžœžœžœ™ Jšœžœžœ™(Jšœ)™)J™—šžœ™šžœžœžœ™ Jšœžœ™ Jšœ&žœžœ™6J™—šžœ™J™$šžœžœž™$Jšœ)™)Jšžœ™—Jšœžœžœ™(Jšœ)™)J™—J™—š žœžœžœžœžœ™™>—šžœ™Jšœžœ ™Jšœžœ ™Jšœžœ™)Jšœa™aJšœžœžœžœ™DJšœ-™-J™—J™—Jšœ  ™ J™—š Ÿ œžœžœžœžœ˜NJ™'Jšžœ žœžœ ˜ šžœ˜šžœžœžœž˜3Jšžœ žœžœžœ˜1Jšžœ˜—J˜ J˜—Jšœ ˜J™—š Ÿœžœžœžœžœ˜]J™'Jšžœ žœžœ ˜ šžœ˜šžœ#žœžœž˜8Jšžœ žœžœžœ˜1Jšžœ˜—J˜ J˜—Jšœ ˜J˜—š Ÿ œžœžœ#žœžœ˜`J™'Jšžœ žœžœ ˜ šžœ˜šžœ(žœžœž˜=Jšžœ žœžœžœ˜1Jšžœ˜—J˜ J˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœ žœ ˜Všžœžœžœ˜+šžœžœ˜Jšžœžœž œ˜7Jšžœ žœ,žœ™LJ˜—Jšžœ˜$J˜—Jšœ  ˜J˜—š Ÿ œžœžœžœžœ žœ ˜Xšžœžœžœ˜+Jšžœ žœ ˜šžœžœ˜Jš žœžœžœžœžœ˜UJšžœ žœ-žœ™NJ˜—Jšžœ"˜(J˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœ žœ ˜Wšžœžœžœ˜+Jšœžœ˜Jšœžœžœ˜šžœ2žœžœž˜GJšžœžœžœ˜*Jšœ žœ˜#Jšžœ˜—Jšžœžœ žœ-žœ™UJšžœžœžœ žœ˜+J˜—Jšœ  ˜J™——™š Ÿœžœžœ$žœžœ™VJšžœžœžœžœ™BJ™;JšœE™EJšœžœ™ š žœ2žœžœžœ ž™RJšœžœ™Jšœ3™3Jšœžœ ™J™ Jšžœ™—Jšœ ™J™š Ÿœžœžœžœžœžœ™TJš œžœžœžœžœ™AJšœžœ™ š žœ.žœžœžœ ž™NJšœžœ™Jšœ0™0Jšœžœ ™J™ Jšžœ™—Jšœ ™—J™—šŸ œžœžœžœ žœžœ žœžœ™^Jšœ  ™J™—šŸ œžœžœžœ žœžœ žœžœ™`Jšœ ™J™—š Ÿ œžœžœžœ žœ˜5Jšœžœ˜šžœžœ ž˜"Jšœžœ˜(Jšžœ˜—š žœžœžœžœž˜Jšœžœ˜+Jšžœ˜—Jšœ ˜—J˜—™šŸœžœžœžœžœžœ žœžœ˜TJšœ*˜*Jšžœžœ#˜9šžœ žœ˜)Jšžœ$žœ˜1—Jšœ ˜J˜—šŸœžœžœžœ žœžœžœžœžœ˜\Jšœžœ˜Jšœžœ˜Jšœžœžœ˜Jšžœžœ0˜Fšžœžœ˜Jšœ?˜?—šžœžœžœžœ˜9Jšžœ#ž œ˜1—JšžœžœžœS˜dšžœ˜J˜J˜J˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœ˜CJšœT™TJšœ˜šžœ žœžœ˜Jšœžœ˜Jšœ žœžœ˜šœžœ˜.Jšœžœžœ˜"—Jšžœžœžœ,˜LJ˜—Jšœ ˜J˜—JšœžœžœZ˜uJ˜—™š Ÿœžœžœžœžœ˜Mšžœ˜Jšžœž œ ˜1šžœžœžœžœ˜)Jšœ.˜.—Jšžœž˜Jšœ˜—Jšœ ˜J˜—š Ÿ œžœžœžœžœ˜EJšœ žœ˜"šœžœ˜Jšœ˜J˜Jšœ žœžœžœ˜-Jš œžœ žœžœžœ žœžœ˜EJšœ žœ˜#Jšžœ;˜BJ˜—Jšœ ˜J˜šŸ œžœžœžœ˜6Jšœžœ˜šžœžœ ž˜"Jšœžœžœ˜7—Jšœ ˜—J˜š Ÿ œžœžœ žœžœ˜6Jšœžœ˜šžœžœ ž˜"Jšœžœžœ˜1—š žœžœžœžœž˜Jšœžœžœ˜3—Jšœžœ$˜5Jšœ žœ"˜2Jšœ ˜—J˜—šŸœžœžœ žœžœžœžœžœ™Dšžœžœž™Jšœ žœ#™4Jšœžœ$™:Jšœ žœ#™2Jšžœ,™3—Icodešœ  ™ L™—šŸ œžœžœ žœžœžœžœžœžœ™Ošžœžœž™Jšœžœ ™7Jšœžœ!™=Jšœžœ ™5Jšžœ.™5—Lšœ  ™—J™—™šŸœžœžœžœžœžœžœ˜=Jšžœž œ˜?Jš žœžœžœžœžœ˜2šžœ˜Jš œžœžœžœžœ˜ Jšžœ žœžœ˜1J˜—Jšœ  ˜ J˜—šŸ œžœžœ*žœ˜YJ˜š Ÿ œžœ žœžœžœ˜5šžœžœ žœ žœ˜*Jš žœžœ žœžœžœ ˜7Jšžœ˜ —J˜—J˜Jšœžœ˜šžœžœžœ˜Jšžœžœžœ˜$Jšžœ ˜$J˜—šžœ ˜Jšœžœ˜J˜!šœ˜J˜—šžœ&žœžœž˜:Jšœ!˜!J™šžœžœ˜šžœ1žœžœž˜FJšœžœ˜Jšžœžœ žœžœ ˜KJšžœ˜—Jšœ˜J˜—J™šžœ4žœžœž˜IJšœžœ˜Jšžœžœ žœžœ ˜KJšžœ˜—Jšžœ˜—J˜—JšžœA˜GJšœ  ˜J˜—šŸœžœžœžœžœžœžœ˜Qš žœžœžœžœžœžœ˜FšŸœžœ˜6Jšœ žœ ˜5J˜Jšœ˜Jšœ˜Jšœ˜—J˜Jšœ#žœ˜'J˜J˜J˜Jšœžœ˜J˜J˜šœ)˜)Jšœžœ$žœ˜3—šžœžœžœ˜Jšœ9˜9Lšœ#˜#L˜—J˜—Jšœ ˜ J˜šŸ œžœ˜%šŸ œžœžœ˜AJšœ!˜!šžœžœžœ˜šœ žœ ˜Jšœ˜Jšœ˜šœ ˜ Jšžœžœžœ˜,Jšžœžœžœ˜.—J˜—šœ˜šœ˜Jšœ7˜7Jšžœ˜ ——šœ2˜2Jšœ0ž œ˜<—J˜—Jšœ  ˜—J™$Jšœ.˜.Jšœ.˜.Jšœžœžœ ˜JJ™šžœ$žœžœž˜8Jšœ"˜"Jšœ.˜.Jšžœ˜—Jšœ  ˜———J˜J˜šžœ˜Jšœ/˜/——…—{βΔd