GraphDraw.mesa, Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, April 23, 1986 2:29:46 pm PST
DIRECTORY
Convert USING [RopeFromReal],
FS USING [Error],
Graph USING [BackgroundIndex, BWLineState, CaretIndex, CaretSpec, ColorMode, Entity, EntityColor, EntityList, FontIndex, Fonts, GraphHandle, JustifX, JustifY, LastEntityColor, LineStateRec, Mark, MaxStep, NullVec, OutputType, PaintAction, PaintInfo, PaintInfoRec, ROPE, SegmentData, SegmentDataList, StepCount, Text, Texts, UnitLineWidth, ValueList, XY],
GraphCarets USING [Resume, Suspend],
GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope],
GraphOps USING [LabelFont, LegendFont, Math8],
GraphPrivate USING [systemColor],
GraphUtil USING [Almost, BackgroundColor, BlinkMsg, TextPosToChartPos, DataBounds, FontHeight, ForegroundColor, FullName, HandleNotNil, NotANan, NotANumber, RealToScreen, RealToScreenI, RealVecToScreenVec, RopeSize],
Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, Error, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, ScaleT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC],
ImagerBackdoor USING [GetBounds],
ImagerFont USING [Extents, Find, Font, RopeBoundingBox, Scale],
ImagerInterpress USING [Close, Create, DoPage, Ref],
Real USING [FixI, RoundI, RoundLI],
RealFns USING [Log, Power, SqRt],
Rope USING [Cat, Concat, IsEmpty];
GraphDraw: CEDAR PROGRAM
IMPORTS Convert, Imager, ImagerBackdoor, ImagerFont, ImagerInterpress, FS, GraphCarets, GraphConvert, GraphPrivate, GraphUtil, Real, RealFns, Rope
EXPORTS GraphPrivate = {
OPEN Graph, GraphPrivate, GraphUtil;
constants
TextHeightFudge: REAL = 8.0;
TextWidthFudge: REAL = 8.0;
shortLegendW: REAL = 200;
global vars
bwLineState: BWLineState ← NEW[LineStateRec ← []];
procs
Draw: PUBLIC PROC [handle: GraphHandle, context: Imager.Context, info: PaintInfo] = {
IF context # NIL AND handle # NIL AND info # NIL THEN { OPEN handle;
autoRepaint: BOOL ← paintInfo.item = all AND (NOT paintInfo.clear) AND chart.dirty;
IF NOT autoRepaint THEN GraphCarets.Suspend[];
GraphCarets.Suspend[handle];
WITH info SELECT FROM
a: REF all PaintInfoRec => DrawGraph[handle, context, a.action, a.output, a.clear, a.updateDivBds];
a: REF allCurves PaintInfoRec => DrawMajorPart[handle, context, a.action, a.output, a.clear];
r: REF rectangle PaintInfoRec => NULL; -- for now. DrawGraph[handle, context, r.action, r.output, r.clear, r.updateDivBds];
t: REF graphText PaintInfoRec => DrawText[handle, context, t.action, t.output, t.text];
e: REF graphEntity PaintInfoRec => DrawEntity[handle, context, e.action, e.output, e.entity];
l: REF legend PaintInfoRec => DrawLegend[handle, context, l.action, l.output, l.entity, l.onlyOne];
t: REF tails PaintInfoRec => DrawTails[handle, context, t.action, t.output, t.x1, t.x2, t.v1, t.v2, t.entities];
t: REF target PaintInfoRec => DrawTarget[handle, context, t.action, t.xy];
g: REF grid PaintInfoRec => DrawTicks[handle, context, g.action, g.output, g.xy, g.long, FALSE]; -- false meaning no label.
ENDCASE;
x: PaintXsegInfo => DrawCrossSegments[context, handle, c];
x: PaintPlaceInfo => DrawXhairPlaces[context, handle, x];
IF NOT autoRepaint THEN GraphCarets.Resume[];
GraphCarets.Resume[handle];
info^ ← [];
info ← NIL;
};
}; -- Draw
All drawing procs below are called by Draw above, which gurantees context, handle, and handle.paintInfo not nil.
DrawGraph: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear, updateDivBds: BOOL] = { OPEN handle;
IF output = screen, the following vars on handle are updated: axesRec, realRec, scale, step.
useful values:
rect: Imager.Rectangle ← IF output = interpress THEN [0.0, -518.16, 670.56, 518.16]
ELSE ImagerBackdoor.GetBounds[context];
vars to save some output device dependent values before making interpress master.
axesRectSave: Imager.Rectangle;
scaleSave: ARRAY XY OF REAL;
dlSave: BOOL;
IF output = interpress THEN {axesRectSave ← axesRect; scaleSave ← scale; dlSave ← normalLegend;}
ELSE IF clear THEN { -- happens in screen painting only.
clearProc: PROC = {
context.SetColor[BackgroundColor[backgroundIndex, screen]];
context.MaskRectangle[rect];
};
context.DoSaveAll[clearProc];
FOR i: CaretIndex IN CaretIndex DO
chart.caretState[i].visible ← FALSE;
ENDLOOP; -- already supended.
};
normalLegend ← (output = interpress) OR rect.h >= 450;
{ -- setting axesRect
margin1: REAL = MIN[rect.h * 0.05, 10.0];
margin2: REAL = margin1*2.0; -- MIN[rect.w * 0.05, 20.0];
xLabelH: REAL = FontHeight[imagerFonts[output][GraphOps.LabelFont]];
legendH: REAL = FontHeight[imagerFonts[output][GraphOps.LegendFont]]; -- for legend, footnote
topTextsH: REALMIN[rect.h * 0.17, 50.0]; -- don't calculate anything else to save time.
leftTextsW: REAL = MIN[rect.h * 0.25, 70.0];
rightTextsW: REAL = MIN[rect.w * 0.35, IF normalLegend THEN leftTextsW ELSE shortLegendW];
bottomTextsH: REALMIN[rect.h*0.35, xLabelH + TextHeightFudge + (IF normalLegend THEN legendH*7.0 + TextHeightFudge + TextHeightFudge ELSE 0.0)];
IF output = interpress THEN topTextsH ← topTextsH + 20; -- experimental, for now.
axesRect ← [
x: rect.x + margin1 + leftTextsW,
y: rect.y + margin1 + bottomTextsH,
w: rect.w - margin2 - leftTextsW - rightTextsW,
h: rect.h - margin2 - topTextsH - bottomTextsH
];
}; -- setting axesRect
IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN {
wholePicture: PROC = {
ScaleAxes[handle, output, updateDivBds];
DrawAxes[handle, context, output]; -- action must be paint.
DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE];
DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE];
DrawTexts[handle, context, output]; -- action must be paint.
IF graph.entityList # NIL THEN
DrawLegend[handle, context, action, output, graph.entityList.first, FALSE];
IF output = interpress THEN PrintCaretsInfo[handle, context]; -- action must be paint.
IF graph.target[x].on THEN DrawTarget[handle, context, action, x];
IF graph.target[y].on THEN DrawTarget[handle, context, action, y];
DrawEntities[handle, context, output]; -- action must be paint.
};
context.DoSaveAll[wholePicture];
};
IF output = interpress THEN {
resume altered values after making interpress master.
axesRect ← axesRectSave; scale ← scaleSave; normalLegend ← dlSave};
update carets places on graph viewer.
[chart.caretState[text].x, chart.caretState[text].y] ←
TextPosToChartPos[axesRect, graph.caret[text].place];
FOR i: CaretIndex IN [primary..secondary] DO
chart.caretState[i].x ← RealToScreenI[handle, graph.caret[i].place.x, x];
chart.caretState[i].y ← RealToScreenI[handle, graph.caret[i].place.y, y];
ENDLOOP;
}; -- DrawGraph
DrawMajorPart: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, clear: BOOL] = { OPEN handle;
IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN {
majorPart: PROC = {
IF clear THEN {
clearProc: PROC = {
context.SetColor[BackgroundColor[backgroundIndex, screen]];
context.MaskRectangle[axesRect];
};
context.DoSaveAll[clearProc];
FOR i: CaretIndex IN CaretIndex DO
chart.caretState[i].visible ← FALSE;
ENDLOOP;
};
context.ClipRectangle[axesRect];
DrawAxes[handle, context, output]; -- action must be paint.
DrawTicks[handle, context, action, output, x, graph.grids[x], TRUE];
DrawTicks[handle, context, action, output, y, graph.grids[y], TRUE];
DrawTexts[handle, context, output]; -- action must be paint.
-- no legend, no carets info, cf. DrawGraph.
IF graph.target[x].on THEN DrawTarget[handle, context, action, x];
IF graph.target[y].on THEN DrawTarget[handle, context, action, y];
DrawEntities[handle, context, output]; -- action must be paint.
};
context.DoSaveAll[majorPart];
};
}; -- DrawMajorPart
DrawTarget: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, xy: XY] = { OPEN handle;
vars on handle referenced: backgroundIndex, graph.target[xy], axesRect, realRect.
the same treatment for either output type.
targetProc: PROC = {
rect: Imager.Rectangle;
tcolor: Imager.Color ← IF action = erase THEN BackgroundColor[backgroundIndex, screen]
ELSE systemColor[graph.target[xy].colorIndex];
erasing action can happen only in screen painting
IF xy = x THEN { OPEN graph.target[x];
IF value < realRect.x OR value > realRect.x + realRect.w THEN RETURN;
rect ← [x: RealToScreen[handle, value, x] - UnitLineWidth*width*0.5, y: axesRect.y+1.0,
w: UnitLineWidth*width, h: axesRect.h-2.0];
}
ELSE { OPEN graph.target[y];
IF value < realRect.y OR value > realRect.y + realRect.h THEN RETURN;
rect ← [x: axesRect.x+1.0, y: RealToScreen[handle, value, y] - UnitLineWidth*width*0.5,
w: axesRect.w-2.0, h: UnitLineWidth*width];
};
context.SetColor[tcolor];
context.MaskRectangle[rect];
};
context.DoSaveAll[targetProc];
}; -- DrawTarget
DrawRope: PROC[context: Imager.Context, text: ROPE, x0, y0: REAL, justifX: JustifX, justifY: JustifY, color: Imager.Color, font: ImagerFont.Font, rotation: REAL] = {
font and color should be set before calling this proc. x0 and y0 are in screen coordinates.
IF text.IsEmpty[] THEN RETURN[[x0, y0]];
IF ~text.IsEmpty[] THEN {
showTextProc: PROC = {
context.TranslateT[[x0, y0]];
context.RotateT[rotation];
context.SetXY[[xmin, ymin]];
context.SetColor[color];
context.SetFont[font];
context.ShowRope[text];
}; -- showTextProc
extents: ImagerFont.Extents;
xmin, ymin, width, height: REAL;
extents ← ImagerFont.RopeBoundingBox[font: font, rope: text];
width ← extents.rightExtent - extents.leftExtent;
height ← extents.descent + extents.ascent;
xmin← SELECT justifX FROM
right => - width - extents.leftExtent,
center => - width/2.0 - extents.leftExtent,
ENDCASE => - extents.leftExtent;
ymin← SELECT justifY FROM
top => - height + extents.descent,
center => - height/2.0 + extents.descent,
ENDCASE => extents.descent;
context.DoSaveAll[showTextProc];
RETURN[IF output = interpress THEN [x0 + width, y0 + height] ELSE ImagerBackdoor.GetCP[context]];
};
}; -- DrawRope
DrawTexts: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = {
paint action must be paint, not erase.
vars on handle referenced: graph.texts, axesRect.
OPEN handle, handle.axesRect;
FOR tl: Texts ← graph.texts, tl.rest UNTIL tl = NIL DO
t: Text ← tl.first;
DrawRope[context, t.text, t.place.x * w + x, t.place.y * h + y, t.justifX, t.justifY,
systemColor[t.colorIndex], imagerFonts[output][t.fontIndex], t.rotation];
ENDLOOP;
}; -- DrawTexts
DrawText: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, gt: Text] = {
vars on handle referenced: axesRect.
OPEN handle, handle.axesRect;
DrawRope[context, gt.text,
gt.place.x * w + x, gt.place.y * h + y, gt.justifX, gt.justifY,
IF action = erase THEN BackgroundColor[backgroundIndex, output]
ELSE systemColor[gt.colorIndex],
imagerFonts[output][gt.fontIndex],
gt.rotation];
}; -- DrawText
ScaleAxes: PROC [handle: GraphHandle, output: OutputType, updateDivBds: BOOL] = { OPEN handle;
vars on handle referenced: graph.bounds, axesRect.w, axesRect.h, graph.division, graph.auto;
updates vars on handle: realRect, scale, step.
useOldDivBds: BOOLFALSE;
IF NOT updateDivBds THEN {
oldOk: BOOL ← (scale[x] # 0.0 AND scale[y] # 0.0) AND (step[x] # 0.0 AND step[y] # 0.0) AND (realRect.h > 0.0 AND realRect.w > 0.0);
IF oldOk THEN useOldDivBds ← TRUE;
};
IF useOldDivBds THEN scale ← [axesRect.w/realRect.w, axesRect.h/realRect.h]
ELSE {
ScaleAxis: PROC [minDataValue, maxDataValue, axisSize: REAL, nDiv: INT] RETURNS [min, range, step, factor: REAL] = {
max: REAL ← maxDataValue;
min ← minDataValue;
range ← max - min;
step ← range/nDiv;
IF graph.auto[bounds] THEN {
step ← FindStepSize[range, nDiv];
min ← AlignEnd[minDataValue, step, FALSE];
max ← AlignEnd[maxDataValue, step, TRUE];
IF Almost[min, max] THEN {max ← max + 50.0; min ← min - 50.0}; -- won't happen, see below.
range ← max - min;
IF NOT graph.auto[divisions] THEN step ← range/nDiv;
};
factor ← axisSize/range;
}; -- ScaleAxis
nDivX, nDivY: INT;
divBad: BOOL ← graph.division[x] < 1 OR graph.division[y] < 1;
boundsBad: BOOL ← (graph.bounds.xmin >= graph.bounds.xmax) OR
(graph.bounds.ymin >= graph.bounds.ymax);
bounds checking
IF boundsBad OR graph.auto[bounds] THEN {
graph.bounds ← DataBounds[graph.entityList, graph.bounds];
IF mergingBounds THEN {
graph.bounds ← [
xmin: MIN[boundsToMerge.xmin, graph.bounds.xmin],
ymin: MIN[boundsToMerge.ymin, graph.bounds.ymin],
xmax: MAX[boundsToMerge.xmax, graph.bounds.xmax],
ymax: MAX[boundsToMerge.ymax, graph.bounds.ymax]
];
};
IF NOT graph.auto[bounds] THEN BlinkMsg["Bounds invalid. (xmin >= xmax or ymin >= ymax.)"];
};
divisions
IF graph.auto[divisions] OR divBad THEN {
labelFont: ImagerFont.Font ← imagerFonts[output][GraphOps.LabelFont];
longest: ROPE ← "0.4444444";
width, height: REAL;
[width, height] ← RopeSize[longest, labelFont];
nDivX ← MAX[1, MIN[10, Real.RoundLI[axesRect.w/width*0.9]]]; -- 0.9 is arbitrary
nDivY ← MAX[1, MIN[10, Real.RoundLI[axesRect.h/height*0.625]]]; -- 0.625 is arbitrary
IF NOT graph.auto[divisions] THEN BlinkMsg["Number of divisions must be >= 1"];
}
ELSE {
nDivX ← graph.division[x];
nDivY ← graph.division[y];
};
scaling
[realRect.x, realRect.w, step[x], scale[x]] ← ScaleAxis[
graph.bounds.xmin, graph.bounds.xmax, axesRect.w, nDivX];
[realRect.y, realRect.h, step[y], scale[y]] ← ScaleAxis[
graph.bounds.ymin, graph.bounds.ymax, axesRect.h, nDivY];
};
graph.bounds ← [realRect.x, realRect.y, realRect.x + realRect.w, realRect.y + realRect.h];
graph.division ← [Real.RoundLI[realRect.w/step[x]], Real.RoundLI[realRect.h/step[y]]];
}; -- ScaleAxes
FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS [step: REAL] = {
logRange: REAL;
logMext, minStep: REAL;
exp: INTEGER;
steps: ARRAY [0..6) OF REAL = [0.2, 0.5, 1.0, 2.0, 5.0, 10.0];
IF Almost[range, 0.0] THEN range ← 100.0;
logRange ← RealFns.Log[10.0, range];
exp ← Real.FixI[logRange];
logMext ← logRange - exp;
IF logRange < 0.0 THEN {
exp ← exp - 1;
logMext ← logMext + 1.0};
minStep ← RealFns.Power[10.0, logMext]/nSteps;
FOR i: CARDINAL IN [0..5) DO
step ← steps[i];
IF step > minStep OR Almost[step, minStep] THEN EXIT
ENDLOOP;
IF exp >= 0 THEN THROUGH [1..exp] DO step ← step*10.0 ENDLOOP
ELSE THROUGH [1..-exp] DO step ← step/10.0 ENDLOOP;
step ← MAX[1.0, step];
}; -- FindStepSize
AlignEnd: PROC [e, step: REAL, roundUp: BOOL] RETURNS [ae: REAL] = {
absE: REAL = ABS[e];
nSteps: INTEGER;
xend: REAL;
IF e = 0.0 THEN RETURN[0.0];
IF e < 0.0 THEN roundUp ← ~roundUp;
nSteps ← Real.RoundLI[absE/step];
xend ← step*nSteps;
IF Almost[nSteps, absE/step] THEN ae ← xend
ELSE IF roundUp
THEN {ae ← IF xend >= absE THEN xend ELSE step*(nSteps + 1)}
ELSE {ae ← IF xend <= absE THEN xend ELSE step*(nSteps - 1)};
IF e < 0.0 THEN ae ← -ae;
}; -- AlignEnd
DrawAxes: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = {
vars on handle referenced: backgroundIndex, axesRect.
axesProc: PROC = { OPEN handle;
color: Imager.Color ← ForegroundColor[backgroundIndex, output];
must be painting instead of erasing.
rect: Imager.Rectangle ← axesRect;
border: Imager.PathProc = { OPEN rect;
xmax: REAL = x + w;
ymax: REAL = y + h;
moveTo[[x, y]];
lineTo[[x, ymax]];
lineTo[[xmax, ymax]];
lineTo[[xmax, y]];
};
context.SetColor[color];
context.SetStrokeEnd[square];
context.SetStrokeJoint[round];
context.SetStrokeWidth[UnitLineWidth*2.0];
context.MaskStroke[path: border, closed: TRUE];
};
context.DoSaveAll[axesProc];
}; -- DrawAxes
DrawTicks: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, xyChoice: XY ← x, longTicks, drawLabel: BOOLFALSE] = { OPEN handle;
global consts refrenced: UnitLineWidth, TextHeightFudge.
vars on handle referenced: backgroundIndex, imagerFont[output][0], axesRect, realRect, scale, step.
ticksProc: PROC = {
tickCount: CARDINAL; -- max tick count, starting from 0.
tickLen, textPlace, tick: REAL;
stdColor: Imager.Color ← GetStandardColor[handle, action, output];
labelFont: ImagerFont.Font ← imagerFonts[output][GraphOps.LabelFont];
context.SetColor[stdColor];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth];
IF xyChoice = x THEN { -- x ticks (on horizontal axis)
tickLen ← IF longTicks THEN axesRect.h ELSE axesRect.h/50.0;
tickCount ← Real.RoundI[realRect.w/step[x]];
textPlace ← axesRect.y - TextHeightFudge;
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step[x]*scale[x]*i;
IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[x]*i + realRect.x],
axesRect.x + tick, textPlace, center, top, stdColor, labelFont, 0.0];
IF i # 0 AND i # tickCount THEN {
xPos: REAL = axesRect.x + tick;
yStart: REAL ← axesRect.y;
yEnd: REAL ← axesRect.y + tickLen;
Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]};
context.MaskStroke[path: Tick, closed: FALSE];
IF NOT longTicks THEN {
yStart ← axesRect.y + axesRect.h; yEnd ← yStart - tickLen;
context.MaskStroke[path: Tick, closed: FALSE];
};
};
ENDLOOP;
}
ELSE {
tickLen ← IF longTicks THEN axesRect.w ELSE axesRect.w/50.0;
tickCount ← Real.RoundI[realRect.h/step[y]];
textPlace ← axesRect.x - TextWidthFudge;
vertical axis
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step[y]*scale[y]*i;
IF drawLabel THEN DrawRope[context, Convert.RopeFromReal[step[y]*i + realRect.y],
textPlace, axesRect.y + tick, right, center, stdColor, labelFont, 0.0];
IF i # 0 AND i # tickCount THEN {
yPos: REAL = axesRect.y + tick;
xStart: REAL ← axesRect.x;
xEnd: REAL ← axesRect.x + tickLen;
Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]};
context.MaskStroke[path: Tick, closed: FALSE];
IF NOT longTicks THEN {
xStart ← axesRect.x + axesRect.w; xEnd ← xStart - tickLen;
context.MaskStroke[path: Tick, closed: FALSE];
};
};
ENDLOOP;
};
};
context.DoSaveAll[ticksProc];
}; -- DrawTicks
DrawLegend: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, onlyOne: BOOL] = { OPEN handle;
vars on handle referenced: graph.entityList, normalLegend, (some more by DrawLegendEntry).
vars on handle updated (possibly): lastEntityColor
MaxEntries: INT = 12;
MaxEntriesPerColumn: INT = MaxEntries/2;
notYet: BOOL ← entity # NIL;
irow, icol: INT ← 0;
AdvanceRC: PROC [] RETURNS [ok: BOOLTRUE]= {
If normalLegend {icol = 1..2, irow = 1..6} otherwise {icol = 3, irow = 1..12}.
IF irow = MaxEntries THEN ok ← FALSE
ELSE IF icol = 2 AND irow = MaxEntriesPerColumn THEN ok ← FALSE
ELSE IF icol = 0 THEN {irow ← 1; icol ← IF normalLegend THEN 1 ELSE 3}
ELSE {
IF normalLegend AND (irow = MaxEntriesPerColumn) THEN {icol ← icol + 1; irow ← 1}
ELSE irow ← irow + 1;
};
}; -- AdvanceRC
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
IF AdvanceRC[] THEN {
IF notYet AND entity # NIL THEN
IF el.first = entity THEN notYet ← FALSE;
IF NOT notYet THEN {
IF el.first.colorIndex = 0 THEN -- auto color selection for white curves.
lastEntityColor ← el.first.colorIndex ← IF lastEntityColor = LastEntityColor THEN 1
ELSE lastEntityColor + 1;
DrawLegendEntry[handle, context, action, output, el.first, icol, irow];
IF onlyOne THEN EXIT;
};
}
ELSE EXIT;
ENDLOOP;
IF AdvanceRC[] AND NOT onlyOne THEN
DrawLegendEntry[handle, context, action, output, NIL, icol, irow]; -- blank
}; -- DrawLegend
DrawLegendEntry: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity, icol, irow: INT] = { OPEN handle;
consts referenced: shortLegendW, TextHeightFudge, TextWidthFudge, UnitLineWidth
vars on handle referenced: imagerFonts, normalLegend, axesRect, backgroundIndex
legendProc: PROC = {
legendFont: ImagerFont.Font ← imagerFonts[output][GraphOps.LegendFont];
legendH: REAL ← FontHeight[legendFont];
labelH: REAL ← FontHeight[imagerFonts[output][GraphOps.LabelFont]];
legendW: REALIF normalLegend THEN axesRect.w*0.5 ELSE shortLegendW;
lx1: REALSELECT icol FROM
1 => axesRect.x, 2 => axesRect.x + legendW,
ENDCASE => axesRect.x + axesRect.w + 2;
ty: REAL ← axesRect.y - legendH*irow + (SELECT icol FROM
1, 2 => - (labelH + TextHeightFudge + TextHeightFudge),
ENDCASE => axesRect.h);
IF output = screen THEN { -- clear
context.SetColor[BackgroundColor[backgroundIndex, output]];
context.MaskRectangle[[lx1, ty, legendW, legendH]];
};
context.ClipRectangle[[lx1, ty, legendW, legendH]];
IF action = paint AND entity # NIL THEN {
lColor: Imager.Color ← GetEntityColor[handle, action, output, entity];
lineLength: REALIF entity.colorIndex IN [8..9] THEN 31.0 ELSE 34.0;
lx2: REALIF normalLegend THEN lx1 + 34.0 ELSE lx1;
tx: REAL ← lx2 + TextWidthFudge;
ly: REAL ← ty + legendH*0.5;
IF normalLegend THEN {
context.SetColor[lColor];
IF NOT Almost[1.0 - entity.width, 1.0] THEN {
context.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth*entity.width];
IF colorMode = color THEN context.MaskVector[[lx1, ly], [lx2, ly]]
ELSE {
bwLineState^ ← [];
MaskLineSeg[context, [lx1, ly], [lx1+lineLength, ly], entity.colorIndex, bwLineState]
};
};
IF entity.mark # none THEN {
lFont: ImagerFont.Font ← imagerFonts[output][
IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8];
DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark],
lx1, ly, left, center, lColor, lFont, 0.0];
};
};
DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, legendFont, 0.0];
DrawRope[context, entity.name, tx, ty, left, bottom, lColor, legendFont];
};
}; -- proc
context.DoSaveAll[legendProc];
}; -- DrawLegendEntry
DrawEntities: PROC [handle: GraphHandle, context: Imager.Context, output: OutputType] = { OPEN handle;
color is the foreground color for use when colormode = black-and-white.
this proc is called by DrawGraph only, when paint action = paint instead of erase.
IF graph.entityList # NIL THEN {
entitiesProc: PROC = {
context.ClipRectangle[axesRect];
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
entity: Entity ← el.first;
IF entity = NIL THEN LOOP; -- error ?!
IF entity.colorIndex = 0 THEN -- auto color selection for white curves.
lastEntityColor ← entity.colorIndex ← IF lastEntityColor = LastEntityColor THEN 1
ELSE lastEntityColor + 1;
IF handle.colorMode = bw THEN DrawBWEntity[handle, context, paint, output, entity]
ELSE DrawColorEntity[handle, context, paint, output, entity];
ENDLOOP;
}; -- entitiesProc
context.DoSaveAll[entitiesProc];
};
}; -- DrawEntities
DrawBWEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle;
Remember to clip any rectangles before calling this proc.
oneCurve: PROC = {
first, prevOk, currOk: BOOLTRUE;
lastVec, newVec: Imager.VEC; -- places on screen.
xseg: SegmentDataList ← entity.group.x.segments;
eColor: Imager.Color ← GetEntityColor[handle, action, output, entity];
lineVisible: BOOLNOT Almost[1.0 - entity.width, 1.0];
IF lineVisible THEN { -- not too thin.
bwLineState^ ← [];
context.SetStrokeJoint[mitered];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth*entity.width];
};
context.SetColor[eColor];
context.TranslateT[[axesRect.x, axesRect.y]];
FOR yseg: SegmentDataList ← entity.segments, yseg.rest UNTIL yseg = NIL DO
IF xseg = NIL THEN EXIT;
currOk ← NotANan[xseg.first.end] AND NotANan[yseg.first.end];
IF currOk THEN newVec ←
[(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]];
IF first THEN first ← FALSE
ELSE IF currOk AND prevOk AND lineVisible THEN MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState];
IF (entity.mark # none) AND currOk THEN {
eFont: ImagerFont.Font ← imagerFonts[output][
IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8];
DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark],
newVec.x, newVec.y, center, center, eColor, eFont, 0.0];
};
lastVec ← newVec;
prevOk ← currOk;
xseg ← xseg.rest;
ENDLOOP;
};
context.DoSaveAll[oneCurve];
}; -- DrawBWEntity
DrawColorEntity: PROC[handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = { OPEN handle;
Remember to clip any rectangles before calling this proc.
oneCurve: PROC = {
xseg: SegmentDataList;
eColor: Imager.Color ← GetEntityColor[handle, action, output, entity];
context.SetColor[eColor];
context.TranslateT[[axesRect.x, axesRect.y]];
context.SetXY[[axesRect.x, axesRect.y]];
IF NOT Almost[1.0 - entity.width, 1.0] THEN {
If width is almost 0.0 then don't plot the lines.
curve: Imager.PathProc = {
first, currOk: BOOLTRUE;
prevOk: BOOLFALSE;
xseg ← entity.group.x.segments;
FOR yseg: SegmentDataList ← entity.segments, yseg.rest UNTIL yseg = NIL DO
vec: Imager.VEC;
IF xseg = NIL THEN EXIT;
currOk ← NotANan[xseg.first.end] AND NotANan[yseg.first.end];
IF currOk THEN {
vec ← [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]];
IF prevOk THEN lineTo[vec] ELSE moveTo[vec];
};
prevOk ← currOk;
xseg ← xseg.rest;
ENDLOOP;
};
context.SetStrokeJoint[mitered];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[entity.width];
context.MaskStroke[path: curve, closed: FALSE];
};
IF entity.mark # none THEN {
xseg ← entity.group.x.segments;
FOR yseg: SegmentDataList ← entity.segments, yseg.rest UNTIL yseg = NIL DO
vec: Imager.VEC;
IF NotANan[xseg.first.end] AND NotANan[yseg.first.end] THEN {
eFont: ImagerFont.Font ← imagerFonts[output][
IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8];
vec ← [(xseg.first.end-realRect.x)*scale[x], (yseg.first.end-realRect.y)*scale[y]];
DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark],
vec.x, vec.y, center, center, eColor, eFont, 0.0];
};
xseg ← xseg.rest;
ENDLOOP;
};
};
context.DoSaveAll[oneCurve];
}; -- DrawColorEntity
LineShape: TYPE = {solid, dot, dash, longDash};
StepSize: ARRAY LineShape[dot..longDash] OF REAL = [3.0, 6.0, 9.0];
LineLimit: ARRAY LineShape[dot..longDash] OF REAL = [1.0, 4.0, 7.0]; -- beyond which is space.
shape: ARRAY EntityColor OF ARRAY StepCount OF LineShape = [
[solid, solid, solid, solid, solid, solid], -- solid, useless
[dot, dot, dot, dot, dot, dot], -- dot
[dash, dash, dash, dash, dash, dash], -- dash
[longDash, longDash, longDash, longDash, longDash, longDash], -- longDash
[dot, dash, dot, dash, dot, dash], -- dot-dash
[dot, longDash, dot, longDash, dot, longDash], -- dot-longDash
[dot, dot, dash, dot, dot, dash], -- dot-dot-dash
[dot, dot, longDash, dot, dot, longDash], -- dot-dot-longDash
[dot, dash, dash, dot, dash, dash], -- dot-dash-dash
[dot, longDash, longDash, dot, longDash, longDash], -- dot-longDash-longDash
[dash, longDash, dash, longDash, dash, longDash], -- dash-longDash
[dot, dash, longDash, dot, dash, longDash] -- dot-dash-longDash
];
MaskLineSeg: PROC[context: Imager.Context,
v0, v1: Imager.VEC ← [0.0, 0.0], style: EntityColor ← 1, state: BWLineState ← NIL] = {
Remember to clip any rectangles, set the color and width before calling this proc.
IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN;
IF state = NIL THEN {bwLineState^ ← []; state ← bwLineState};
IF style = 1 THEN context.MaskVector[v0, v1]
ELSE { -- Note that normally state.step and state.progress will be updated.
dx: REAL = v1.x - v0.x;
dy: REAL = v1.y - v0.y;
lengthToGo: REAL ← RealFns.SqRt[dx*dx + dy*dy];
cosine: REAL ← dx/lengthToGo;
sine: REAL ← dy/lengthToGo;
newVec, oldVec: Imager.VEC ← v0;
stepSize, lineLimit, increment: REAL;
drawIt: BOOL;
UNTIL Almost[lengthToGo, 0.0] DO
stepSize← StepSize[shape[style][state.step]];
lineLimit← LineLimit[shape[style][state.step]];
IF state.progress < lineLimit THEN {
drawIt ← TRUE;
increment ← MIN[lengthToGo, lineLimit - state.progress];
}
ELSE {
drawIt ← FALSE;
increment ← MIN[lengthToGo, stepSize - state.progress];
};
lengthToGo ← lengthToGo - increment;
state.progress ← state.progress + increment;
newVec ← [oldVec.x + cosine*increment, oldVec.y + sine*increment];
IF drawIt THEN context.MaskVector[oldVec, newVec]
ELSE IF Almost[state.progress, stepSize] THEN {
state.step ← (state.step + 1) MOD MaxStep;
state.progress ← 0.0;
};
oldVec ← newVec;
ENDLOOP;
};
}; -- MaskLineSeg
DrawEntity: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, entity: Entity] = {
OPEN handle;
IF entity # NIL THEN {
entityProc: PROC = {
IF entity.colorIndex = 0 THEN -- auto color selection for white curves.
lastEntityColor ← entity.colorIndex ← IF lastEntityColor = LastEntityColor THEN 1
ELSE lastEntityColor + 1;
DrawLegend[context, handle, entity];
context.ClipRectangle[axesRect];
IF colorMode = bw THEN DrawBWEntity[handle, context, action, output, entity]
ELSE DrawColorEntity[handle, context, action, output, entity];
};
context.DoSaveAll[entityProc];
};
}; -- DrawEntity
DrawCrossSegments: PROC[context: Imager.Context, handle: GraphHandle ← NIL, info: PaintXsegInfo ← NIL] = { OPEN handle;
FOR el: EntityList ← graph.entityList, el.rest UNTIL el = NIL DO
entity: Entity ← el.first;
vx: ValueList ← entity.group.x;
vy: ValueList ← entity.newValues;
v1, v2: Imager.VEC;
[v1, v2] ← CrossSegment[vx, vy,
ENDLOOP;
}; -- DrawCrossSegments
DrawTails: PROC [handle: GraphHandle, context: Imager.Context, action: PaintAction, output: OutputType, x1, x2: REAL, v1, v2: ValueList, entities: EntityList] = {
OPEN handle;
drawTails: PROC = {
entity: Entity;
vec1, vec2: Imager.VEC;
oneSegment: PROC = {
eColor: Imager.Color ← GetEntityColor[handle, action, output, entity];
context.SetColor[eColor];
IF NOT Almost[1.0 - entity.width, 1.0] THEN {
context.SetStrokeWidth[UnitLineWidth*entity.width];
IF colorMode = bw THEN {
bwLineState^ ← [];
MaskLineSeg[context, vec1, vec2, entity.colorIndex, bwLineState];
}
ELSE context.MaskVector[vec1, vec2];
};
IF entity.mark # none THEN {
eFont: ImagerFont.Font ← imagerFonts[output][
IF entity.mark > cross THEN GraphOps.LabelFont ELSE GraphOps.Math8];
DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark],
vec1.x, vec1.y, center, center, eColor, eFont, 0.0];
DrawRope[context, GraphConvert.CharRopeFromMark[entity.mark],
vec2.x, vec2.y, center, center, eColor, eFont, 0.0];
};
}; -- oneSegment
context.SetStrokeEnd[square];
context.ClipRectangle[axesRect];
context.TranslateT[[axesRect.x, axesRect.y]];
IF NotANumber[x1] THEN RETURN ELSE vec1.x ← (x1 - realRect.x)*scale[x];
IF NotANumber[x2] THEN RETURN ELSE vec2.x ← (x2 - realRect.x)*scale[x];
FOR el: EntityList ← entities, el.rest UNTIL el = NIL DO
ysOk: BOOLTRUE;
entity ← el.first;
IF NotANumber[v1.first] THEN ysOk ← FALSE
ELSE vec1.y ← (v1.first - realRect.y)*scale[y];
IF ysOk THEN
IF NotANumber[v2.first] THEN ysOk ← FALSE
ELSE vec2.y ← (v2.first - realRect.y)*scale[y];
IF entity.colorIndex = 0 THEN -- auto color selection for white curves.
lastEntityColor ← entity.colorIndex ← IF lastEntityColor = LastEntityColor THEN 1
ELSE lastEntityColor + 1;
IF ysOk THEN context.DoSaveAll[oneSegment];
IF (v1 ← v1.rest) = NIL OR (v2 ← v2.rest) = NIL THEN EXIT;
ENDLOOP;
}; -- drawTails
context.DoSaveAll[drawTails];
}; -- DrawTails
GetEntityColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType, entity: Entity] RETURNS [Imager.Color] = {
choose the right color for painting entity and legend.
OPEN handle;
RETURN[
IF action = erase THEN BackgroundColor[backgroundIndex, output]
ELSE IF colorMode = bw THEN ForegroundColor[backgroundIndex, output]
ELSE systemColor[entity.colorIndex]
];
}; -- GetEntityColor
GetStandardColor: PROC [handle: GraphHandle, action: PaintAction, output: OutputType] RETURNS [Imager.Color] = {
choose the drawing color for axes, ticks, grids, targets, and labels on axes, whether painting or erasing.
OPEN handle;
RETURN[
IF action = paint THEN ForegroundColor[handle.backgroundIndex, output]
ELSE BackgroundColor[handle.backgroundIndex, output]
];
}; -- GetStandardColor
Print: PUBLIC PROC[handle: GraphHandle, file: ROPE] RETURNS [msg: ROPENIL] = {
IF HandleNotNil[handle] THEN IF handle.graph # NIL THEN { OPEN handle;
InterpressDrawGraph: PROC[context: Imager.Context] ~ {
coordRatio: REAL = 0.25/600.0; -- 0.25 m ~ 600 pixels
context.RotateT[90];
context.ScaleT[coordRatio];
Draw[handle, context, info];
};
interpress: ImagerInterpress.Ref ← NIL;
info: PaintInfo ← NEW[all PaintInfoRec ← [
action: paint, output: interpress, data: all[clear: FALSE]]];
InitPFonts[handle];
interpress ← ImagerInterpress.Create[file
! FS.Error => {msg ← error.explanation; CONTINUE}];
IF msg = NIL THEN {
ImagerInterpress.DoPage[interpress, InterpressDrawGraph];
ImagerInterpress.Close[interpress];
};
};
}; -- Print
InitPFonts: PROC [h: GraphHandle] = {
EnsurePFont: PROC [i: FontIndex] RETURNS [f: ImagerFont.Font] = {
f ← h.imagerFonts[interpress][i];
IF f = NIL THEN {
pFontName: ROPE ← Rope.Cat[
"Xerox/PressFonts/",
h.graph.font[i].family,
Rope.Concat[
IF h.graph.font[i].bold THEN "-b" ELSE "-m",
IF h.graph.font[i].italic THEN "ir" ELSE "rr"]
];
f ← ImagerFont.Find[pFontName
! Imager.Error => {
f ← ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"];
CONTINUE}];
f ← ImagerFont.Scale[f, h.graph.font[i].pFontScale
! Imager.Error => {f ← ImagerFont.Scale[f, 9.0]; CONTINUE}];
};
}; -- InitPFont
-- fonts for legend, label, and mark
h.imagerFonts[interpress][GraphOps.LabelFont] ← EnsurePFont[GraphOps.LabelFont];
h.imagerFonts[interpress][GraphOps.LegendFont] ← EnsurePFont[GraphOps.LegendFont];
h.imagerFonts[interpress][GraphOps.Math8] ← EnsurePFont[GraphOps.Math8];
-- fonts for texts
FOR ts: Texts ← h.graph.texts, ts.rest UNTIL ts = NIL DO
i: FontIndex ← ts.first.fontIndex;
h.imagerFonts[interpress][i] ← EnsurePFont[i];
ENDLOOP;
}; -- InitPFonts
PrintCaretsInfo: PROC [handle: GraphHandle, context: Imager.Context] = { OPEN handle;
caretsProc: PROC = {
xh1: CaretSpec ← graph.caret[primary];
xh2: CaretSpec ← graph.caret[secondary];
xh3: CaretSpec ← graph.caret[text];
cvec: Imager.VEC;
cColor: Imager.Color ← ForegroundColor[backgroundIndex, interpress];
context.SetColor[cColor];
context.SetStrokeWidth[UnitLineWidth];
context.SetStrokeEnd[butt];
IF xh1.on THEN {
cvec ← RealVecToScreenVec[handle, xh1.place];
context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]];
context.MaskVector[[cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0]];
};
IF xh2.on THEN {
cvec ← RealVecToScreenVec[handle, xh2.place];
bwLineState^ ← [];
MaskLineSeg[context, [cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y], 2, bwLineState];
bwLineState^ ← [];
MaskLineSeg[context, [cvec.x, cvec.y - 8.0], [cvec.x, cvec.y + 8.0], 2, bwLineState];
};
IF xh3.on THEN {
cvec ← RealVecToScreenVec[handle, xh3.place];
context.MaskVector[[cvec.x - 8.0, cvec.y], [cvec.x + 8.0, cvec.y]];
context.MaskVector[[cvec.x - 5.65, cvec.y + 5.65], [cvec.x + 5.65, cvec.y - 5.65]];
context.MaskVector[[cvec.x - 5.65, cvec.y - 5.65], [cvec.x + 5.65, cvec.y + 5.65]];
};
IF xh1.on OR xh2.on THEN {
bothOn: BOOL ← xh1.on AND xh2.on;
rope: ROPE ← Rope.Concat["Crosshair", IF bothOn THEN "s: " ELSE ": "];
IF xh1.on THEN rope ← rope.Concat[GraphConvert.RopeOfPlace[xh1.place]];
IF bothOn THEN rope ← rope.Concat[", "];
IF xh2.on THEN rope ← rope.Concat[GraphConvert.RopeOfPlace[xh2.place]];
IF bothOn THEN rope ← rope.Cat["; slope: ",
GraphConvert.RopeOfSlope[xh1.place, xh2.place]];
DrawRope[context, rope,
axesRect.x + axesRect.w, axesRect.y + axesRect.h + TextHeightFudge,
right, bottom,
cColor, imagerFonts[interpress][GraphOps.LegendFont], 0.0];
};
};
context.DoSaveAll[caretsProc];
}; -- PrintCaretsInfo
}.
CHANGE LOG.
SChen, created at October 9, 1985 6:13:12 pm PDT.