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: ROPE ← NIL,
autoBounds, autoDivisions: BOOL ← TRUE,
bounds: Imager.Box ← NullBox, -- [xmin, ymin, xmax, ymax]
divisions: ARRAY XY OF INT ← ALL[5],
grids: ARRAY XY OF BOOL ← ALL[FALSE], -- true: on.
targets: ARRAY XY OF TargetSpec ← ALL[NIL],
carets: ARRAY CaretIndex OF CaretSpec ← ALL[NIL],
showSlope: BOOL ← FALSE, -- slope between primary and secondary carets
colors: GraphColors ← NIL,
fonts: GraphFonts ← NIL,
oldGraph: GraphHandle ← NIL, -- Create new viewer iff nil.
replace: BOOL ← FALSE -- 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:
BOOL ←
TRUE]
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: ROPE ← NIL,
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: BOOL ← TRUE
] 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: BOOL ← TRUE] = {
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: REAL ← IF 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:
BOOL ←
TRUE]
RETURNS [msg:
ROPE ←
NIL] = {
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.