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: 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 ← 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: BOOLTRUE] 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: 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,
reverse, discard: BOOLTRUE
] 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: 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
] 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: BOOLTRUE] = {
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.