GraphDraw.mesa, Copyright © 1985 by Xerox Corporation. All rights reserved.
Last Edited by:
Sweetsun Chen, November 21, 1985 10:55:01 pm PST
DIRECTORY
Convert USING [RopeFromReal],
Graph USING [BackgroundIndex, BWLineState, CaretIndex, CaretSpec, ColorMode, Entity, EntityColor, EntityList, FontIndex, Fonts, GraphHandle, JustifX, JustifY, LastEntityColor, LineStateRec, Mark, MaxStep, NullVec, OutputType, PaintInfo, ROPE, SegmentData, SegmentDataList, StepCount, Text, Texts, UnitLineWidth, ValueList, XY],
GraphCarets USING [Resume, Suspend],
GraphConvert USING [CharRopeFromMark, RopeOfPlace, RopeOfSlope],
GraphPrivate USING [systemColor],
GraphUtil USING [Almost, BackgroundColor, BlinkMsg, DataBounds, FontHeight, ForegroundColor, FullName, NotANan, NotANumber, RealToScreen, RealToScreenI, RealVecToScreenVec, RopeSize],
Imager USING [Box, ClipRectangle, Color, Context, DoSaveAll, MaskRectangle, MaskStroke, MaskVector, PathProc, Rectangle, RotateT, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC],
ImagerBackdoor USING [GetBounds],
ImagerFont USING [Extents, Font, RopeBoundingBox],
Real USING [FixI, RoundI, RoundLI],
RealFns USING [Log, Power, SqRt],
Rope USING [Cat, Concat, IsEmpty];
GraphDraw: CEDAR PROGRAM
IMPORTS Convert, Imager, ImagerBackdoor, ImagerFont, 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 ← []];
type:
DrawProc: TYPE = PROC [context: Imager.Context ← NIL, handle: GraphHandle ← NIL];
procs
Draw: PUBLIC PROC [context: Imager.Context ← NIL, handle: GraphHandle ← NIL] = {
IF context # NIL AND handle # NIL THEN IF handle.paintInfo # NIL THEN { OPEN handle;
autoRepaint: BOOL ← paintInfo.item = all AND (NOT paintInfo.clear) AND chart.dirty;
IF NOT autoRepaint THEN GraphCarets.Suspend[];
GraphCarets.Suspend[];
SELECT handle.paintInfo.item FROM
all => DrawGraph[context, handle];
allCurves => DrawMajorPart[context, handle];
graphEntity => DrawEntity[context, handle];
graphText => DrawText[context, handle];
tails => DrawTails[context, handle];
rectangle => DrawGraph[context, handle];
target => DrawTargets[context, handle];
grid => DrawTicks[context, handle, handle.paintInfo.xy, FALSE]; -- false: no label.
legend => DrawLegend[context, handle];
x: PaintXsegInfo => DrawCrossSegments[context, handle, c];
x: PaintPlaceInfo => DrawXhairPlaces[context, handle, x];
ENDCASE;
IF NOT autoRepaint THEN GraphCarets.Resume[];
GraphCarets.Resume[];
};
}; -- Draw
All drawing procs below are called by Draw above, which gurantees context, handle, and handle.paintInfo not nil.
DrawGraph: DrawProc = { OPEN handle;
info: PaintInfo ← paintInfo;
IF info.output = screen, the following vars on handle are updated: axesRec, realRec, scale, step.
useful values
rect: Imager.Rectangle ← IF info.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 info.output = interpress THEN {axesRectSave ← axesRect; scaleSave ← scale; dlSave ← normalLegend;}
ELSE IF info.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;
};
normalLegend ← (info.output = interpress) OR rect.h >= 450;
{ -- setting axesRect
margin1: REAL = MIN[rect.h * 0.05, 10.0];
margin2: REAL = MIN[rect.w * 0.05, 20.0];
xLabelH: REAL = FontHeight[imagerFonts[paintInfo.output][0]];
legendH: REAL = FontHeight[imagerFonts[paintInfo.output][1]]; -- for legend, footnote
topTextsH: REAL = MIN[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)];
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];
DrawAxes[context, handle];
DrawTicks[context, handle, x, TRUE];
DrawTicks[context, handle, y, TRUE];
DrawTexts[context, handle];
IF graph.entityList # NIL THEN {
info.entity ← graph.entityList.first;
info.onlyOne ← FALSE;
DrawLegend[context, handle];
};
IF info.output = interpress THEN PrintCaretsInfo[context, handle];
IF graph.target[x].on THEN {info.xy ← x; DrawTargets[context, handle]};
IF graph.target[y].on THEN {info.xy ← y; DrawTargets[context, handle]};
DrawEntities[context, handle];
};
context.DoSaveAll[wholePicture];
};
resume altered values after making interpress master.
IF info.output = interpress THEN {axesRect ← axesRectSave; scale ← scaleSave; normalLegend ← dlSave};
update carets places on graph viewer.
FOR i: CaretIndex IN CaretIndex 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: DrawProc = { OPEN handle;
IF axesRect.w > 0.0 AND axesRect.h > 0.0 THEN {
majorPart: PROC = {
IF paintInfo.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[context, handle];
DrawTicks[context, handle, x, TRUE];
DrawTicks[context, handle, y, TRUE];
DrawTexts[context, handle];
IF graph.target[x].on THEN {paintInfo.xy ← x; DrawTargets[context, handle]};
IF graph.target[y].on THEN {paintInfo.xy ← y; DrawTargets[context, handle]};
DrawEntities[context, handle];
};
context.DoSaveAll[majorPart];
};
}; -- DrawMajorPart
DrawTargets: DrawProc = { OPEN handle;
draw x target or y target depending on info.xy.
targetProc: PROC = {
info: PaintInfo ← handle.paintInfo;
rect: Imager.Rectangle;
tcolor: Imager.Color ← IF info.action = erase THEN BackgroundColor[backgroundIndex, screen]
ELSE systemColor[graph.target[info.xy].colorIndex];
erasing action can happen only in screen painting
IF info.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];
}; -- DrawTargets
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: DrawProc = {
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[paintInfo.output][t.fontIndex], t.rotation];
ENDLOOP;
}; -- DrawTexts
DrawText: DrawProc = {
OPEN handle, handle.axesRect;
t: Text ← paintInfo.text;
DrawRope[context, t.text, t.place.x * w + x, t.place.y * h + y, t.justifX, t.justifY,
GetTextColor[handle, t], imagerFonts[paintInfo.output][t.fontIndex], t.rotation];
}; -- DrawText
ScaleAxes: PROC[handle: GraphHandle] = { OPEN handle;
vars on handle referenced: graph.bounds, axesRect.w, axesRect.h, graph.division, graph.auto;
updates vars on handle: realRect, scale, step.
nDivX, nDivY: INT;
invalidDiv: BOOL ← graph.division[x] < 1 OR graph.division[y] < 1;
invalidBounds: BOOL ← (graph.bounds.xmin >= graph.bounds.xmax) OR
(graph.bounds.ymin >= graph.bounds.ymax);
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
bounds checking
IF invalidBounds 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 invalidDiv THEN { -- 5/8 = 0.625
labelFont: ImagerFont.Font ← imagerFonts[paintInfo.output][0];
maxX: ROPE ← Convert.RopeFromReal[graph.bounds.xmax];
minX: ROPE ← Convert.RopeFromReal[graph.bounds.xmin];
wMaxX, wMinX, width, height: REAL;
[wMaxX, height] ← RopeSize[maxX, labelFont];
[wMinX, ] ← RopeSize[minX, labelFont];
width ← MAX[wMaxX, wMinX];
nDivX ← MAX[1, MIN[10, Real.RoundLI[axesRect.w/width*0.625]]];
nDivY ← MAX[1, MIN[10, Real.RoundLI[axesRect.h/height*0.625]]];
IF NOT graph.auto[divisions] THEN BlinkMsg["Divisions must be >= 2"];
}
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: DrawProc = {
vars on handle referenced: backgroundIndex, paintInfo.output, axesRect.
axesProc: PROC = { OPEN handle;
color: Imager.Color ← ForegroundColor[backgroundIndex, paintInfo.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[context: Imager.Context, handle: GraphHandle, xyChoice: XY ← x, drawLabel: BOOLFALSE] = { OPEN handle;
global const/vars refrenced: UnitLineWidth, TextHeightFudge.
vars on handle referenced: graph.grids[xyChoice], backgroundIndex, paintInfo.output, imagerFont[paintInfo.output][0], axesRect, realRect, scale, step, fgColor, stdFont.
ticksProc: PROC = {
tickCount: CARDINAL; -- max tick count, starting from 0.
tickLen, textPlace, tick: REAL;
gridsOn: BOOL ← graph.grids[xyChoice];
stdColor: Imager.Color ← GetStandardColor[handle];
stdFont: ImagerFont.Font ← imagerFonts[paintInfo.output][0];
context.SetColor[stdColor];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth];
IF xyChoice = x THEN { -- x ticks (on horizontal axis)
tickLen ← IF gridsOn 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, stdFont, 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 gridsOn THEN {
yStart ← axesRect.y + axesRect.h; yEnd ← yStart - tickLen;
context.MaskStroke[path: Tick, closed: FALSE];
};
};
ENDLOOP;
}
ELSE {
tickLen ← IF gridsOn 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, stdFont, 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 gridsOn THEN {
xStart ← axesRect.x + axesRect.w; xEnd ← xStart - tickLen;
context.MaskStroke[path: Tick, closed: FALSE];
};
};
ENDLOOP;
};
};
context.DoSaveAll[ticksProc];
}; -- DrawTicks
DrawLegend: DrawProc = { OPEN handle;
MaxEntries: INT = 12;
MaxEntriesPerColumn: INT = MaxEntries/2;
notYet: BOOL ← paintInfo.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 paintInfo.entity # NIL THEN
IF el.first = paintInfo.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[context, handle, el.first, icol, irow];
IF paintInfo.onlyOne THEN EXIT;
};
}
ELSE EXIT;
ENDLOOP;
IF AdvanceRC[] AND NOT paintInfo.onlyOne THEN
DrawLegendEntry[context, handle, NIL, icol, irow]; -- blank
}; -- DrawLegend
DrawLegendEntry: PROC [context: Imager.Context, handle: GraphHandle, entity: Entity, icol, irow: INT] = { OPEN handle;
legendProc: PROC = {
lFont: ImagerFont.Font ← imagerFonts[paintInfo.output][1];
legendH: REAL ← FontHeight[lFont];
labelH: REAL ← FontHeight[imagerFonts[paintInfo.output][0]];
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 paintInfo.output = screen THEN { -- clear
context.SetColor[BackgroundColor[backgroundIndex, paintInfo.output]];
context.MaskRectangle[[lx1, ty, legendW, legendH]];
};
context.ClipRectangle[[lx1, ty, legendW, legendH]];
IF paintInfo.action = paint AND entity # NIL THEN {
lColor: Imager.Color ← GetEntityColor[handle, 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.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth*entity.width];
context.SetColor[lColor];
IF colorMode = color THEN context.MaskVector[[lx1, ly], [lx2, ly]]
ELSE {
bwLineState^ ← [];
MaskLineSeg[context, [tx, ly], [lx1+lineLength, ly], entity.colorIndex, bwLineState]
};
};
DrawRope[context, FullName[entity], tx, ty, left, bottom, lColor, lFont, 0.0];
DrawRope[context, entity.name, tx, ty, left, bottom, lColor, lFont];
};
}; -- proc
context.DoSaveAll[legendProc];
}; -- DrawLegendEntry
DrawEntities: DrawProc = { 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[context, handle, entity]
ELSE DrawColorEntity[context, handle, entity];
ENDLOOP;
}; -- entitiesProc
context.DoSaveAll[entitiesProc];
};
}; -- DrawEntities
DrawBWEntity: PROC [context: Imager.Context, handle: GraphHandle, 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, entity];
bwLineState^ ← [];
context.SetColor[eColor];
context.TranslateT[[axesRect.x, axesRect.y]];
context.SetStrokeJoint[mitered];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[UnitLineWidth*entity.width];
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 THEN MaskLineSeg[context, lastVec, newVec, entity.colorIndex, bwLineState];
IF entity.mark # none AND currOk THEN {
eFont: ImagerFont.Font ← imagerFonts[paintInfo.output][IF entity.mark > cross THEN 0 ELSE LAST[FontIndex]];
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[context: Imager.Context, handle: GraphHandle, entity: Entity] = { OPEN handle;
Remember to clip any rectangles before calling this proc.
oneCurve: PROC = {
xseg: SegmentDataList;
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;
};
eColor: Imager.Color ← GetEntityColor[handle, entity];
context.SetColor[eColor];
context.SetStrokeJoint[mitered];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[entity.width];
context.TranslateT[[axesRect.x, axesRect.y]];
context.SetXY[[axesRect.x, axesRect.y]];
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[paintInfo.output][IF entity.mark > cross THEN 0 ELSE LAST[FontIndex]];
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: DrawProc = {
OPEN handle;
entity: Entity ← handle.paintInfo.entity;
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[context, handle, entity]
ELSE DrawColorEntity[context, handle, 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: DrawProc = {
OPEN handle;
info: PaintInfo ← handle.paintInfo;
caller must make sure that info.v1 and info.v2 are of the same length as graph.entityList.
drawTails: PROC = {
v1: ValueList ← info.v1;
v2: ValueList ← info.v2;
entity: Entity;
vec1, vec2: Imager.VEC;
oneSegment: PROC = {
context.SetStrokeWidth[UnitLineWidth*entity.width];
context.SetColor[GetEntityColor[handle, entity]];
IF colorMode = bw THEN {
bwLineState^ ← [];
MaskLineSeg[context, vec1, vec2, entity.colorIndex, bwLineState];
}
ELSE context.MaskVector[vec1, vec2];
}; -- oneSegment
context.SetStrokeEnd[square];
context.ClipRectangle[axesRect];
context.TranslateT[[axesRect.x, axesRect.y]];
IF NotANumber[info.x1] THEN RETURN ELSE vec1.x ← (info.x1 - realRect.x)*scale[x];
IF NotANumber[info.x2] THEN RETURN ELSE vec2.x ← (info.x2 - realRect.x)*scale[x];
FOR el: EntityList ← graph.entityList, 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, entity: Entity] RETURNS [Imager.Color] = {
choose the right color for painting entity and legend.
OPEN handle;
RETURN[
IF paintInfo.action = erase THEN BackgroundColor[backgroundIndex, paintInfo.output]
ELSE IF colorMode = bw THEN ForegroundColor[backgroundIndex, paintInfo.output]
ELSE systemColor[entity.colorIndex]
];
}; -- GetEntityColor
GetTextColor: PROC [handle: GraphHandle, text: Text] RETURNS [Imager.Color] = {
choose the right color for painting entity and legend.
OPEN handle;
RETURN[
IF paintInfo.action = erase THEN BackgroundColor[backgroundIndex, paintInfo.output]
ELSE systemColor[text.colorIndex]
];
}; -- GetTextColor
GetStandardColor: PROC [handle: GraphHandle] RETURNS [Imager.Color] = {
choose the drawing color for axes, ticks, grids, targets, and labels on axes, whether painting or erasing.
OPEN handle;
RETURN[
IF paintInfo.action = paint THEN ForegroundColor[handle.backgroundIndex, paintInfo.output]
ELSE BackgroundColor[handle.backgroundIndex, paintInfo.output]
];
}; -- GetStandardColor
PrintCaretsInfo: DrawProc = { 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 - 17.0, cvec.y], [cvec.x + 17.0, cvec.y]];
context.MaskVector[[cvec.x, cvec.y - 17.0], [cvec.x, cvec.y + 17.0]];
};
IF xh2.on THEN {
cvec ← RealVecToScreenVec[handle, xh2.place];
bwLineState^ ← [];
MaskLineSeg[context, [cvec.x - 17.0, cvec.y], [cvec.x + 17.0, cvec.y], 2, bwLineState];
bwLineState^ ← [];
MaskLineSeg[context, [cvec.x, cvec.y - 17.0], [cvec.x, cvec.y + 17.0], 2, bwLineState];
};
IF xh3.on THEN {
cvec ← RealVecToScreenVec[handle, xh3.place];
context.MaskVector[[cvec.x - 17.0, cvec.y], [cvec.x + 17.0, cvec.y]];
context.MaskVector[[cvec.x - 12.0, cvec.y + 12.0], [cvec.x + 12.0, cvec.y - 12.0]];
context.MaskVector[[cvec.x - 12.0, cvec.y - 12.0], [cvec.x + 12.0, cvec.y + 12.0]];
};
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.y + axesRect.h, left, bottom,
cColor, imagerFonts[paintInfo.output][0], 0.0];
};
};
context.DoSaveAll[caretsProc];
}; -- PrintCaretsInfo
}.
CHANGE LOG.
SChen, created at October 9, 1985 6:13:12 pm PDT.