GraphOpsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited:
Sweetsun Chen, November 26, 1985 12:43:25 pm PST
DIRECTORY
Convert USING [RopeFromInt],
Graph USING [AutoType, CaretIndex, CaretSpec, ColorIndex, Curve, EntityRec, EntityGroup, EntityGroupList, EntityGroupRec, EntityHashSize, CurveList, FontIndex, GraphColors, GraphFonts, GraphHandle, HashIndex, JustifX, JustifY, Mark, NestedEntities, NestedEntitiesList, NestedEntitiesRec, NtNan, NullBox, ROPE, SegmentDataRec, TargetSpec, Text, Texts, TextRec, UnitLineWidth, ValueList, XY],
GraphCleanUp USING [CleanUpHandle],
GraphOps USING [Lock, Unlock],
GraphPrivate USING [AddEntityButton, AddGroupButton, AddTextButton, defaultColors, defaultFonts, PaintAll, PaintTails, PaintEntity, PaintText, ShowChart, WriteGraphFile],
GraphUtil USING [AppendEGL, AppendEntityList, AppendTexts, AppendX, AppendY, BlinkMsg, CopyValueList, InitSegAll, InitSegEnd, LengthOfVL, NewTextId, NewEntityId, NewGroupId, ReverseValueList, VanillaHandle],
Imager USING [Box, VEC],
IO USING [int, PutFR],
Rope USING [Concat, IsEmpty],
ViewerOps USING [PaintViewer];
GraphOpsImpl: CEDAR PROGRAM
IMPORTS Convert, GraphCleanUp, GraphOps, GraphPrivate, GraphUtil, IO, Rope, ViewerOps
EXPORTS GraphOps = { OPEN Graph, GraphOps, GraphPrivate, GraphUtil;
handleIsNil: ROPE = "handle is nil.";
NewGraph: PUBLIC PROC [
all arguments can be defaulted.
fileName, -- suggested file name to save the graph.
If oldGraph is nil, this suggested name (or "Graph.graph", if it is empty) will be used;
otherwise this suggested name will be used, if and only if the original file name for oldGraph is empty.
groupName, -- name of the group of curves in this graph being created.
comment, -- will show up on graph table following the name, but not on graph viewer.
xName: ROPENIL,
autoBounds, autoDivisions: BOOLTRUE,
bounds: Imager.Box ← NullBox, -- [xmin, ymin, xmax, ymax]
divisions: ARRAY XY OF INTALL[5],
grids: ARRAY XY OF BOOLALL[FALSE], -- true: on.
targets: ARRAY XY OF TargetSpec ← ALL[NIL],
carets: ARRAY CaretIndex OF CaretSpec ← ALL[NIL],
showSlope: BOOLFALSE, -- slope between primary and secondary carets
colors: GraphColors ← NIL,
fonts: GraphFonts ← NIL,
oldGraph: GraphHandle ← NIL, -- Create new viewer iff nil.
replace: BOOLFALSE -- true: remove all existing curves and texts on old handle; false: merge new curves and texts with existing ones.
] RETURNS [newGraph: GraphHandle, groupId: INT] = {
create: BOOL ← oldGraph = NIL;
IF create THEN {newGraph ← VanillaHandle[]; Lock[newGraph]}
ELSE {
newGraph ← oldGraph;
IF replace THEN newGraph ← GraphCleanUp.CleanUpHandle[newGraph, FALSE];
};
{ OPEN newGraph;
group: EntityGroup ← NEW[EntityGroupRec ← [
x: NEW[EntityRec ← [
name: IF xName.IsEmpty[] THEN "X" ELSE xName,
id: NewEntityId[newGraph, 0]]],
ys: NEW[NestedEntitiesRec ← [name: groupName, comment: comment]],
id: (groupId ← NewGroupId[newGraph, 0]) -- length to be set later.
]];
hIndex: HashIndex ← group.x.id MOD EntityHashSize;
entityHash[hIndex] ← CONS[group.x, entityHash[hIndex]];
IF group.ys.name.IsEmpty[] AND group.ys.comment.IsEmpty[] THEN group.ys.comment ← IO.PutFR["Curves group %g", IO.int[group.id]];
group.x.group ← group;
entityGroupList ← GraphUtil.AppendEGL[entityGroupList, CONS[group, NIL]];
IF controller # NIL THEN IF controller.table # NIL THEN AddGroupButton[newGraph, group];
graph.fileName ←
IF graph.fileName.IsEmpty[] THEN
IF fileName.IsEmpty[] THEN "Graph.graph" ELSE fileName
ELSE graph.fileName;
graph.auto ← [autoDivisions, autoBounds];
boundsToMerge ← graph.bounds ← bounds;
IF graph.auto[bounds] THEN mergingBounds ← TRUE;
IF NOT autoBounds THEN IF bounds.xmax <= bounds.xmin OR bounds.ymax <= bounds.ymin THEN BlinkMsg["xmax <= xmin or ymax <= ymin."];
graph.division ← divisions;
IF NOT autoDivisions THEN IF divisions[x] < 1 OR divisions[y] < 1 THEN BlinkMsg["number of division < 2."];
FOR i: CaretIndex IN CaretIndex DO
IF carets[i] # NIL THEN graph.caret[i] ← carets[i];
ENDLOOP;
graph.showSlope ← showSlope;
FOR i: XY IN XY DO
IF targets[i] # NIL THEN graph.target[i] ← targets[i];
ENDLOOP;
graph.grids ← grids;
graph.color^ ← IF colors = NIL THEN defaultColors^ ELSE colors^;
graph.font^ ← IF fonts = NIL THEN defaultFonts^ ELSE fonts^;
IF chart.viewer = NIL THEN ShowChart[newGraph] -- will init colors and fonts
ELSE PaintAll[newGraph, TRUE, TRUE, TRUE]; -- use existing colors and fonts
};
IF create THEN Unlock[newGraph];
}; -- NewGraph
Two methods to set x and y data:
1. SetXValues then AddCurve; or
2. call EnlistCurve for all curves, then AddCrossSection.
AddText: PUBLIC PROC [handle: GraphHandle, rope: ROPE,
all following arguments may be defaulted.
place: Imager.VEC ← [0.0, 0.0], -- location with respect to origion and relative to size of axes box
fontIndex: FontIndex ← 0,
colorIndex: ColorIndex ← 15, -- black
rotation: REAL ← 0.0, -- angle of rotation, ccw, in degrees.
justifX: JustifX ← left,
justifY: JustifY ← bottom
] RETURNS [text: Text ← NIL]= {
IF rope.IsEmpty[] THEN BlinkMsg["Rope is nil."]
ELSE IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE { OPEN handle;
text ← NEW[TextRec ← [
text: rope, place: place, fontIndex: fontIndex, colorIndex: colorIndex, rotation: rotation, justifX: justifX, justifY: justifY,
id: NewTextId[handle, 0]
]];
allTexts ← AppendTexts[allTexts, CONS[text, NIL]];
graph.texts ← AppendTexts[graph.texts, CONS[text, NIL]];
IF chart.viewer # NIL THEN PaintText[handle, text, paint];
IF controller # NIL THEN IF controller.table # NIL THEN AddTextButton[handle, text];
};
}; -- AddText
GroupFromId: PROC [handle: GraphHandle, id: INT] RETURNS [eg: EntityGroup ← NIL] = {
FOR egl: EntityGroupList ← handle.entityGroupList, egl.rest UNTIL egl = NIL DO
IF egl.first.id = id THEN eg ← egl.first;
ENDLOOP;
}; -- GroupFromId
SetXValues: PUBLIC PROC [handle: GraphHandle ← NIL, groupId: INT, values: ValueList, reverse, discard: BOOLTRUE] RETURNS [x: Curve ← NIL]= {
If handle or values are nil then return nil without setting x values.
IF values = NIL THEN BlinkMsg["no x values."]
ELSE IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE { OPEN handle;
group: EntityGroup ← GroupFromId[handle, groupId];
IF group = NIL THEN BlinkMsg[
Rope.Concat["Can't find a group with id = ", Convert.RopeFromInt[groupId]]]
ELSE {
group.length ← LengthOfVL[values];
x ← group.x;
[x.oldValues, x.lastValue] ← IF reverse THEN ReverseValueList[values, discard]
ELSE CopyValueList[values, discard];
InitSegEnd[x];
};
};
}; -- SetXValues
AddCurve: PUBLIC PROC [handle: GraphHandle, groupId: INT,
name, comment: ROPENIL,
colorIndex: ColorIndex ← 0, -- If this argument is zero, a nonzero color index will be assigned for this curve automatically when it is plotted.
mark: Mark ← none,
width: REAL ← UnitLineWidth,
values: ValueList ← NIL, reverse, discard: BOOLTRUE
] RETURNS [curve: Curve ← NIL]= {
IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE { OPEN handle;
group: EntityGroup ← GroupFromId[handle, groupId];
IF group = NIL THEN BlinkMsg[
Rope.Concat["Can't find a group with id = ", Convert.RopeFromInt[groupId]]]
ELSE IF LengthOfVL[values] # group.length THEN
BlinkMsg["number of y values # number of x values."]
ELSE {
curve ← NewCurve[handle, group, name, comment, colorIndex, mark, width];
IF values # NIL THEN {
[curve.oldValues, curve.lastValue] ←
IF reverse THEN ReverseValueList[values, discard]
ELSE CopyValueList[values, discard];
InitSegAll[curve];
IF handle.chart.viewer # NIL THEN PaintEntity[handle, curve, TRUE, paint];
};
};
};
}; -- AddCurve
NewCurve: PROC [handle: GraphHandle, group: EntityGroup,
-- handle and group must not be nil.
name, comment: ROPE,
colorIndex: ColorIndex,
mark: Mark,
width: REAL
] RETURNS [curve: Curve ← NIL] = { OPEN handle;
hashIndex: HashIndex;
IF group.ys = NIL THEN {BlinkMsg["group.ys = nil."]; RETURN};
curve ← NEW[EntityRec ← [
name: name, comment: comment, colorIndex: colorIndex, mark: mark, width: width,
oldValues: NIL,
group: group,
parent: group.ys,
id: NewEntityId[handle, 1]
]];
IF name.IsEmpty[] AND comment.IsEmpty[] THEN curve.name ← IO.PutFR["Curve %g", IO.int[curve.id]];
hashIndex ← curve.id MOD EntityHashSize;
entityHash[hashIndex] ← CONS[curve, entityHash[hashIndex]];
group.ys.entityList ← AppendEntityList[group.ys.entityList, CONS[curve, NIL]];
graph.entityList ← AppendEntityList[graph.entityList, CONS[curve, NIL]];
IF handle.controller # NIL THEN IF handle.controller.table # NIL THEN AddEntityButton[handle, curve];
}; -- NewCurve
AppendValues: PUBLIC PROC [
handle: GraphHandle, groupId: INT, x: REAL, yvalues: ValueList,
reverse, discard: BOOLTRUE] = {
If handle = nil or yvalues = nil then noop.
Make sure that the number of y values agrees with the total number of curves in this group. And the order of values agrees with the order in which curves were added to the group by AddCurve, or its reverse if reverse is true.
IF yvalues = NIL THEN BlinkMsg["y's missing."]
ELSE IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE { OPEN handle;
group: EntityGroup ← GroupFromId[handle, groupId];
IF group = NIL THEN BlinkMsg[
Rope.Concat["Can't find a group with id = ", Convert.RopeFromInt[groupId]]]
ELSE IF group.x = NIL THEN BlinkMsg["can't find x on the group."]
ELSE {
AppendToNE: PROC [tne: NestedEntities] = {
FOR el: CurveList ← tne.entityList, el.rest UNTIL el = NIL DO
newy: REALIF v2temp = NIL THEN Graph.NtNan ELSE v2temp.first;
oldy: REAL ← AppendY[el.first, newy, x1, x];
IF NOT first AND el.first.segments # NIL THEN {
pv1 ← CONS[oldy, pv1];
pv2 ← CONS[newy, pv2];
pel ← CONS[el.first, pel];
};
v2temp ← v2temp.rest;
ENDLOOP;
FOR nel: NestedEntitiesList ← tne.children, nel.rest UNTIL nel = NIL DO
AppendToNE[nel.first];
ENDLOOP;
};
x1: REAL;
pv1, pv2, v2, v2temp: ValueList ← NIL;
pel: CurveList ← NIL;
first: BOOL ← group.x.lastValue = NIL;
IF NOT first THEN IF group.x.lastValue.first >= x THEN {
BlinkMsg["Can't append x <= previous x."];
RETURN;
};
x1 ← AppendX[group.x, x];
[v2, ] ← IF reverse THEN ReverseValueList[yvalues, discard]
ELSE CopyValueList[yvalues, discard];
v2temp ← v2;
AppendToNE[group.ys];
group.length ← group.length + 1;
IF NOT first THEN PaintTails[handle, x1, x, pv1, pv2, pel, paint, TRUE];
};
};
}; -- AppendValues
SaveGraph: PUBLIC PROC[handle: GraphHandle, plottedOnly: BOOLTRUE] RETURNS [msg: ROPENIL] = {
save currently plotted data (or all data on handle) to a file with handle.graph.fileName as name.
msg is not nil if there is any problem saving the graph.
IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE {
newName: ROPE;
[msg, newName] ← WriteGraphFile[handle, handle.graph.fileName, plottedOnly];
IF msg = NIL AND handle.chart.viewer # NIL THEN {
handle.chart.viewer.name ← newName;
ViewerOps.PaintViewer[handle.chart.viewer, caption, FALSE, NIL];
};
};
}; -- SaveGraph
}.
LOG.
SChen, October 26, 1985 9:26:57 pm PDT, created.