GraphOpsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited:
Sweetsun Chen, November 21, 1985 10:47:37 pm PST
DIRECTORY
Convert USING [RopeFromInt],
Graph USING [AutoType, CaretIndex, CaretSpec, ColorIndex, Entity, EntityRec, EntityGroup, EntityGroupList, EntityGroupRec, EntityHashSize, EntityList, FontIndex, GraphColors, GraphFonts, GraphHandle, HashIndex, JustifX, JustifY, Mark, NestedEntities, NestedEntitiesRec, 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, LengthOfEL, 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 ← NIL, groupId: INT ← 0] = {
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]; -- 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 EnlistEntity 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, paint, text];
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, values: ValueList, groupId:
INT ← 0, reverse, discard:
BOOL ←
TRUE]
RETURNS [xEntity: Entity ←
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];
xEntity ← group.x;
[xEntity.oldValues, xEntity.lastValue] ← IF reverse THEN ReverseValueList[values, discard]
ELSE CopyValueList[values, discard];
InitSegEnd[xEntity];
};
};
}; -- SetXValues
AddCurve:
PUBLIC PROC [handle: GraphHandle, values: ValueList, groupId:
INT ← 0,
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,
reverse, discard: BOOL ← TRUE
] RETURNS [curve: Entity ← NIL]= {
If handle = nil, or if number of values # number of x values, then return nil and no curve is created.
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 ← NewEntity[handle, group, name, comment, colorIndex, mark, width];
[curve.oldValues, curve.lastValue] ←
IF reverse THEN ReverseValueList[values, discard]
ELSE CopyValueList[values, discard];
InitSegAll[curve];
IF handle.chart.viewer # NIL THEN PaintEntity[handle, paint, curve];
};
};
}; -- AddCurve
EnlistEntity:
PUBLIC
PROC [handle: GraphHandle,
groupId: INT ← 0,
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
] RETURNS [curve: Entity ← NIL] = {
same as AddCurve except that value list is nil.
IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE {
group: EntityGroup ← GroupFromId[handle, groupId];
IF group =
NIL
THEN BlinkMsg[
Rope.Concat["Can't find a group with id = ", Convert.RopeFromInt[groupId]]]
ELSE curve ← NewEntity[handle, group, name, comment, colorIndex, mark, width];
};
}; -- EnlistEntity
NewEntity:
PROC [handle: GraphHandle, group: EntityGroup,
-- handle and group must not be nil.
name, comment: ROPE,
colorIndex: ColorIndex,
mark: Mark,
width: REAL
] RETURNS [curve: Entity ← 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];
}; -- NewEntity
AddCrossSection:
PUBLIC
PROC [
handle: GraphHandle, x: REAL, yvalues: ValueList,
groupId: INT ← 0, reverse, discard: BOOL ← TRUE] = {
If handle = nil or yvalues = nil then noop.
IF yvalues = NIL THEN BlinkMsg["no y values."]
ELSE IF handle = NIL THEN BlinkMsg[handleIsNil]
ELSE {
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[yvalues] # LengthOfEL[handle.graph.entityList, group]
THEN
BlinkMsg[
IO.PutFR["Number of values (%g) # number of curves (%g).",
IO.int[LengthOfVL[yvalues]],
IO.int[LengthOfEL[handle.graph.entityList, group]],
]]
ELSE {
OPEN handle;
xEntity: Entity ← group.x;
IF xEntity = NIL THEN BlinkMsg["x and/or y entities not on the list yet."]
ELSE {
x1: REAL;
v1, v2, v2temp: ValueList ← NIL;
first: BOOL ← xEntity.lastValue = NIL;
IF
NOT first
THEN
IF xEntity.lastValue.first >= x
THEN {
BlinkMsg["Sorry, cross sections must be added with their x values in strictly increasing order."];
RETURN;
};
[firstx, x1] ← AppendX[xEntity, x];
x1 ← AppendX[xEntity, x];
[v2, ] ←
IF reverse
THEN ReverseValueList[yvalues, discard]
ELSE CopyValueList[yvalues, discard];
v2temp ← v2;
FOR el: EntityList ← graph.entityList, el.rest
UNTIL el =
NIL
DO
oldy:
REAL ← AppendY[el.first, v2temp.first, x1, x];
[firsty, oldy] ← AppendY[el.first, tempVL2.first, x1, x];
IF NOT first THEN v1 ← CONS[oldy, v1];
v2temp ← v2temp.rest;
ENDLOOP;
group.length ← group.length + 1;
[v1, ] ← ReverseValueList[v1, TRUE];
IF
NOT first
THEN PaintTails[handle, paint, v1, v2, x1, x,
FALSE];
v1, v2 are in the correct order.
};
};
};
}; -- AddCrossSection
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.