DIRECTORY
Buttons USING [Button, SetDisplayStyle],
Convert USING [BoolFromRope, Error, IntFromRope, RealFromRope, RopeFromInt, RopeFromReal],
FS USING [Error, ComponentPositions, ExpandName],
Graph USING [CaretIndex, CaretSpecRec, ColorIndex, Entity, EntityGroup, EntityList, FontIndex, GRAPH, GraphColorsArray, GraphFont, GraphFontsArray, GraphRec, NestedEntities, NullBox, NullRect, NullVec, ROPE, SegmentDataList, SegmentDataRec, TargetSpecRec, Text, Texts, ValueList, Viewer, XY],
GraphCleanUp USING [CleanUpEL, CleanUpSDL, CleanUpTexts, CleanUpVL],
GraphPrivate USING [BackgroundIndex, CaretStateRec, Chart, ChartRec, Controller, defaultColorsArray, defaultFontsArray, Draw, EntityHashArray, EntityGroupList, EntityHash, EntityHashSize, FontArray, GraphHandle, GraphHandleRec, IsController, IsGraphViewer, OutputType, PaintInfoRec, 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, 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, Imager, ImagerColor, ImagerColorMap, ImagerDitheredDevice, ImagerFont, ImagerInterpress, IO, MessageWindow, Process, Real, RealFns, Rope, Terminal, UserProfile, VFonts, ViewerOps, ViewerTools, WindowManager
EXPORTS Graph, GraphUtil = { OPEN Graph, GraphPrivate;
error checking
Warning, 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",
$NilEntityGroup => "EntityGroup",
$NilEntityList => "EntityList",
$NilEntity => "Entity",
$NilText => "Text",
$NilFont => "Font",
$NilValueList => "ValueList",
$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 and degug then raise error.
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]};
ViewerNotNil: PUBLIC PROC[viewer: Viewer ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[viewer, $NilViewer, msg, debug]};
ChartNotNil: PUBLIC PROC[chart: Chart ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[chart, $NilChart, msg, debug]};
ControllerNotNil: PUBLIC PROC[c: Controller ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[c, $NilController, msg, debug]};
EntityGroupNotNil: PUBLIC PROC[eg: EntityGroup ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[eg, $NilEntityGroup, msg, debug]};
EntityListNotNil: PUBLIC PROC[el: EntityList ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[el, $NilEntityList, msg, debug]};
EntityNotNil: PUBLIC PROC[entity: Entity ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[entity, $NilEntity, msg, debug]};
ValueListNotNil: PUBLIC PROC[vl: ValueList ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[vl, $NilValueList, msg, debug]};
TextNotNil: PUBLIC PROC [text: Text ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[text, $NilText, msg, debug]};
FontNotNil: PUBLIC PROC[font: REF ANY ← NIL, msg: ROPE ← NIL, debug: BOOL ← FALSE] RETURNS [ok: BOOL ← TRUE] = {ok ← CheckNil[font, $NilFont, msg, debug]};
BoundsValid:
PUBLIC
PROC[box: Imager.Box ← NullBox]
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]};
coordinates and coordinates transformation
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] = {
find the segment that crosses the vertical line, x = at.
IF xseg #
NIL
THEN
IF xseg.rest #
NIL
THEN {
ok ← TRUE;
IF xseg.first.end >= at THEN GOTO normal
ELSE
FOR xseg ← xseg, xseg.rest
UNTIL xseg =
NIL
DO
IF xseg.rest = NIL THEN GOTO right;
v1 ← [xseg.first.end, yseg.first.end];
IF xseg.rest.first.end > at THEN GOTO normal;
yseg ← yseg.rest;
ENDLOOP;
EXITS
right => v2 ← [xseg.first.end, yseg.first.end]; -- Note: that v1.x may still be = v2.x.
normal => {
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] = {
Client should make sure that x1 # x2.
IF v1.x = v2.x THEN RaiseError[$Other, "x1 = x2 in InterOrExtraPolate"];
RETURN[v1.y + (v2.y - v1.y)/(v2.x - v1.x)*(x - v1.x)]
}; -- InterOrExtraPolate
Crosssections:
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 {
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] = {
returns the rectangle and hotpoint of text on graph viewer.
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
text and entity
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
FullName:
PUBLIC
PROC [entity: Entity ←
NIL]
RETURNS [fullName:
ROPE ←
NIL] = {
Returns the full name of the entity.
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[ref:
REF
ANY ←
NIL, id:
INT ← 0]
RETURNS [entity: Entity ←
NIL] = {
-- ref may be EntityList or EntityHash.
entityList: EntityList ← NIL;
IF ISTYPE[ref, EntityList] THEN entityList ← NARROW[ref]
ELSE
IF
ISTYPE[ref, EntityHash]
THEN {
hash: EntityHash ← NARROW[ref];
entityList ← hash[id MOD EntityHashSize];
};
FOR el: EntityList ← entityList, el.rest
UNTIL el =
NIL
DO
IF el.first.id = id THEN entity ← el.first;
ENDLOOP;
}; -- EntityFromId
PanelIndexedText:
PUBLIC
PROC [controller: Controller ←
NIL, texts: Texts ←
NIL]
RETURNS [text: Text ←
NIL] = {
get it from the id specified on panel.
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]]];
};
};
}; -- PanelIndexedText
PanelIndexedEntity:
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 {
entity ← EntityFromId[ref, i];
IF entity =
NIL
THEN BlinkMsg[Rope.Concat[
"There is no curve with id = ", Convert.RopeFromInt[i]]];
};
};
}; -- PanelIndexedEntity
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: ValueList] = {
new ← NIL;
FOR vl: ValueList ← old, vl.rest
UNTIL vl =
NIL
DO
new ← CONS[vl.first, new];
ENDLOOP;
IF killOld THEN old ← GraphCleanUp.CleanUpVL[old];
}; -- ReverseValueList
CopyValueList:
PUBLIC
PROC [old: ValueList ←
NIL]
RETURNS [new: ValueList] = {
new ← ReverseValueList[old, FALSE];
new ← ReverseValueList[new, TRUE];
}; -- CopyValueList
ReverseSDL:
PUBLIC PROC [old: SegmentDataList ←
NIL, killOld:
BOOL ←
TRUE]
RETURNS [new: SegmentDataList] = {
new ← NIL;
FOR sdl: SegmentDataList ← old, sdl.rest
UNTIL sdl =
NIL
DO
new ← CONS[sdl.first, new];
ENDLOOP;
IF killOld THEN old ← GraphCleanUp.CleanUpSDL[old];
}; -- ReverseSDL
InitSegEnd:
PUBLIC
PROC [entity: Entity ←
NIL] = {
entity.segments ← NIL;
FOR vl: ValueList ← entity.oldValues, vl.rest
UNTIL vl =
NIL
DO
entity.segments ←
CONS[
NEW[SegmentDataRec ← [end: vl.first]],
entity.segments];
ENDLOOP;
entity.segments ← ReverseSDL[entity.segments];
}; -- InitSegEnd
InitSegAll:
PUBLIC
PROC [entity: Entity ←
NIL] = {
xseg: SegmentDataList ← entity.group.x.segments; -- should have been initialized.
InitSegEnd[entity];
IF entity.segments #
NIL
THEN
FOR yseg: SegmentDataList ← entity.segments, yseg.rest UNTIL yseg.rest = NIL DO
x1: REAL ← xseg.first.end;
x2: REAL ← xseg.rest.first.end;
y1: REAL ← yseg.first.end;
y2: REAL ← yseg.rest.first.end;
a: REAL ← y2 - y1;
b: REAL ← x1 - x2;
factor: REAL ← 1.0/RealFns.SqRt[a*a+b*b];
yseg.first^ ← [end: y1, nx: a*factor, ny: b*factor, d0: (x1*y2 - x2*y1)*factor];
xseg ← xseg.rest;
ENDLOOP;
}; -- InitSegAll
UpdateSegEnd:
PUBLIC PROC [entity: Entity, values: ValueList] = {
vl: ValueList ← values;
IF values = NIL THEN RETURN;
FOR sdl: SegmentDataList ← entity.segments, sdl.rest
UNTIL sdl =
NIL
DO
sdl.first.end ← vl.first;
vl ← vl.rest;
ENDLOOP;
}; -- UpdateSegEnd
UpdateSegAll:
PUBLIC PROC [entity: Entity, values: ValueList] = {
xseg: SegmentDataList ← entity.group.x.segments;
IF values = NIL THEN RETURN;
UpdateSegEnd[entity, values];
FOR yseg: SegmentDataList ← entity.segments, yseg.rest
UNTIL yseg.rest =
NIL
DO
x1: REAL ← xseg.first.end;
x2: REAL ← xseg.rest.first.end;
y1: REAL ← yseg.first.end;
y2: REAL ← yseg.rest.first.end;
a: REAL ← y2 - y1;
b: REAL ← x1 - x2;
factor: REAL ← 1.0/RealFns.SqRt[a*a+b*b];
yseg.first^ ← [end: y1, nx: a*factor, ny: b*factor, d0: (x1*y2 - x2*y1)*factor];
xseg ← xseg.rest;
ENDLOOP;
}; -- UpdateSegAll
}.