GraphOpsImpl.mesa
Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited:
Sweetsun Chen, November 17, 1985 2:48:56 am 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, AddTextButton, defaultColors, defaultFonts, PaintAll, PaintTails, PaintEntity, PaintText, ShowChart],
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];
GraphOpsImpl:
CEDAR
PROGRAM
IMPORTS Convert, GraphCleanUp, GraphOps, GraphPrivate, GraphUtil, IO, Rope
EXPORTS GraphOps = { OPEN Graph, GraphOps, GraphPrivate, GraphUtil;
NewGraph:
PUBLIC PROC [
all arguments can be defaulted.
fileName, -- suggested file name to save the graph.
If fileName is empty, default name will depend on whether oldGraph is nil. If oldGraph is nil, "Graph.graph" will be used, otherwise the original fileName on handle retains.
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[] ELSE newGraph ← oldGraph;
Lock[newGraph];
IF replace AND NOT create 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.
]];
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]];
graph.fileName ←
IF oldGraph = NIL THEN IF fileName.IsEmpty[] THEN "Graph.graph" ELSE fileName
ELSE graph.fileName;
graph.auto ← [autoDivisions, autoBounds];
graph.bounds ← bounds;
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] < 2 OR divisions[y] < 2 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]
ELSE PaintAll[newGraph];
};
Unlock[newGraph];
}; -- NewGraph
Two methods to set x and y data:
1. SetXValues then AddCurve; or
2. call EnlistEntity for all curves, then AddCrossSection.
To ensure data integrity, it's better to lock/unlock the handle before/after calling the following procs.
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[] OR handle = NIL THEN BlinkMsg["Rope is empty or handle is nil."]
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 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 handle = NIL AND values = NIL THEN BlinkMsg["handle or x values are nil."]
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["handle is nil."]
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["handle is nil."]
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;
curve ←
NEW[EntityRec ← [
name: name, comment: comment, colorIndex: colorIndex, mark: mark, width: width,
oldValues: NIL,
group: group,
parent: NIL,
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]];
IF group.ys = NIL THEN group.ys ← NEW[NestedEntitiesRec ← []];
group.ys.entityList ← AppendEntityList[group.ys.entityList, CONS[curve, NIL]];
graph.entityList ← AppendEntityList[graph.entityList, CONS[curve, NIL]];
IF handle.controller # 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 handle = NIL OR yvalues = NIL THEN BlinkMsg["handle = NIL or yvalues = NIL."]
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
}.
LOG.
SChen, October 26, 1985 9:26:57 pm PDT, created.