PlotViewerPaint.mesa
Last Edited by:
Sweetsun Chen, August 2, 1985 9:28:51 pm PDT
DIRECTORY
BasicTime USING [GMT],
Imager USING [Box, ClipRectangle, ConstantColor, Context, DoSave, MaskBox, MaskStroke, MaskVector, PathProc, Rectangle, Scale2T, SetColor, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, SetXY, ShowRope, TranslateT, VEC],
ImagerBackdoor USING [GetBounds, GetCP],
ImagerFont USING [Extents, Find, Font, FontBoundingBox, RopeBoundingBox, Scale],
IO USING [PutFR, real, rope, time],
Plot USING [Curves, PlotSpec, PlotSpecRec, RopeSequence, Vector],
PlotOps USING [BackgroundType, color, ColorMode, Handle, MaxStep, LineState, LineStateRec, LineStep, NColors, OutputType, StateSequence],
Real USING [FixI, RoundI, RoundLI],
RealFns USING [Log, Power, SqRt],
Rope USING [Find, IsEmpty, ROPE, Substr],
VFonts USING [EstablishFont],
ViewerClasses USING [PaintRectangle];
PlotViewerPaint: CEDAR PROGRAM
IMPORTS BasicTime, Imager, ImagerBackdoor, ImagerFont, IO, PlotOps, Real, RealFns, Rope, VFonts
EXPORTS PlotOps = {
OPEN Plot, PlotOps;
constants
TextHeightFudge: REAL = 4.0;
TextWidthFudge: REAL = 4.0;
minLineWidth: REAL = 1.0;
(constant) variables
t12b: ImagerFont.Font = VFonts.EstablishFont["TimesRoman", 12, TRUE]; -- bold
t12bp: ImagerFont.Font = ImagerFont.Scale[
ImagerFont.Find["Xerox/PressFonts/TimesRoman-mrr"], 13.0];
h8: ImagerFont.Font = VFonts.EstablishFont["Helvetica", 8];
h8p: ImagerFont.Font = ImagerFont.Scale[
ImagerFont.Find["Xerox/PressFonts/Helvetica-mrr"], 9.0];
textFont: PUBLIC ARRAY FontType OF ARRAY OutputType OF ImagerFont.Font = [[t12b, t12bp, t12bp], [h8, h8p, h8p]];
fontHeight: PUBLIC ARRAY FontType OF ARRAY OutputType OF REAL;
type
FontType: TYPE = {title, normal};
DrawMe: PUBLIC PROC [context: Imager.Context, handle: Handle, whatChanged: REF ANY, output: OutputType] = {
press context has been rotated
BoundsValid: PROC [box: Imager.Box] RETURNS [BOOL] = INLINE {
RETURN[box.xmax > box.xmin AND box.ymax > box.ymin];
}; -- BoundsValid
rect: Imager.Rectangle ← IF output = interpress THEN [0.0, -518.16, 670.56, 518.16]
ELSE ImagerBackdoor.GetBounds[context];
box: Imager.Box ← [rect.x, rect.y, rect.x + rect.w, rect.y + rect.h];
foreground: Imager.ConstantColor ← IF output # screen THEN color[15] ELSE
IF handle.background = black OR handle.background = darkGray THEN color[0]
ELSE color[15];
IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN {
background: Imager.ConstantColor ← IF output # screen THEN color[0] ELSE
SELECT handle.background FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
width: REAL ← rect.w;
height: REAL ← rect.h;
marginRatio: REAL = 0.05;
innerRatio: REAL = 0.9;
marginX: REAL ← width*marginRatio;
marginY: REAL ← height*marginRatio;
maxX: REAL ← box.xmax - marginX;
maxY: REAL ← box.ymax - marginY;
minX: REAL ← box.xmin + marginX;
minY: REAL ← box.ymin + marginY;
innerWidth: REAL ← width * innerRatio;
innerHeight: REAL ← height * innerRatio;
tenthHeight: REAL ← innerHeight/10.0;
textLineHeight: REAL ← fontHeight[normal][output] + TextHeightFudge;
windowTooLow: BOOL ← tenthHeight < textLineHeight;
legendTop: REALIF windowTooLow THEN minY ELSE minY + tenthHeight*1.8;
axesTop: REALIF windowTooLow THEN maxY ELSE maxY - tenthHeight;
titleBox: Imager.Box ← [minX, axesTop, maxX, maxY];
axesBox: Imager.Box ← [minX, legendTop, maxX, axesTop];
legendBox: Imager.Box ← [minX, minY, maxX, legendTop];
IF output = screen THEN { -- clear the screen
context.SetColor[background];
context.MaskBox[box];
};
title / legend / footnote
context.SetColor[foreground];
IF NOT windowTooLow THEN {
excl: INT ← handle.plotSpec.file.Find["!"];
pattern: Rope.ROPE = IF excl < 0 THEN handle.plotSpec.file ELSE handle.plotSpec.file.Substr[0, excl];
footNote: Rope.ROPEIO.PutFR["File: %g; time: %g.", IO.rope[pattern], IO.time[handle.plotSpec.time]];
DrawTitle[context, titleBox, handle.plotSpec.title, output];
DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries,
footNote, output, handle.colorMode];
};
axes and curves
IF BoundsValid[handle.plotSpec.bounds] THEN {
c: Curves ← NIL;
[handle.curvesBox, handle.realBounds] ← DrawAxes[context, axesBox, handle.plotSpec.bounds, output];
context.ClipRectangle[
[handle.curvesBox.xmin,
handle.curvesBox.ymin,
handle.curvesBox.xmax - handle.curvesBox.xmin,
handle.curvesBox.ymax - handle.curvesBox.ymin]];
FOR l: Curves ← handle.curves, l.rest UNTIL l = NIL DO
c ← CONS[l.first, c];
ENDLOOP;
DrawCurves[context, handle, c, output];
};
}
ELSE IF ISTYPE[whatChanged, Curves] THEN { OPEN handle;
cv: Curves ← NARROW[whatChanged];
IF cv # NIL AND BoundsValid[handle.realBounds] THEN {
m0, m1: Vector;
m1 ← cv.first;
IF m1 = NIL OR cv.rest = NIL THEN RETURN;
m0 ← cv.rest.first;
IF m0 = NIL THEN RETURN;
IF m0.size = m1.size THEN {
tMin: REAL = realBounds.xmin;
tFactor: REAL = (curvesBox.xmax - curvesBox.xmin + 1)/(realBounds.xmax - tMin);
vMin: REAL = realBounds.ymin;
vFactor: REAL = (curvesBox.ymax - curvesBox.ymin + 1)/(realBounds.ymax - vMin);
t0: REAL = (m0[0] - tMin)*tFactor;
t1: REAL = (m1[0] - tMin)*tFactor;
context.ClipRectangle[
[handle.curvesBox.xmin,
handle.curvesBox.ymin,
handle.curvesBox.xmax - handle.curvesBox.xmin,
handle.curvesBox.ymax - handle.curvesBox.ymin]];
context.TranslateT[[curvesBox.xmin, curvesBox.ymin]];
context.SetStrokeEnd[round];
context.SetStrokeWidth[minLineWidth];
IF handle.colorMode = bw THEN {
context.SetColor[foreground];
FOR index: CARDINAL IN [1..m0.size) DO
ipath: CARDINAL ← index - 1;
DrawLineSeg[context,
[t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor],
ipath MOD 12, handle.lineStates[ipath]];
ENDLOOP;
}
ELSE FOR index: CARDINAL IN [1..m0.size) DO
ipath: CARDINAL ← index - 1;
context.SetColor[color[(ipath MOD 12) + 1]];
context.MaskVector[
[t0, (m0[index]-vMin)*vFactor], [t1, (m1[index]-vMin)*vFactor]];
ENDLOOP;
};
};
};
}; -- DrawMe
DrawTitle: PROC [context: Imager.Context, box: Imager.Box, title: Rope.ROPE, output: OutputType] = {
xPos: REAL ← box.xmin + (box.xmax - box.xmin) / 2.0;
boxH: REAL ← box.ymax - box.ymin - fontHeight[normal][output];
IF title.IsEmpty[] THEN RETURN;
IF boxH >= fontHeight[title][output] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, title, output]
ELSE IF boxH >= fontHeight[normal][output] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, normal, output];
}; -- DrawTitle
DrawAxes: PROC [context: Imager.Context, box, bounds: Imager.Box, output: OutputType]
RETURNS [innerBox, realBounds: Imager.Box] = {
Border: PROC = {
context.SetStrokeEnd[square];
context.SetStrokeJoint[round];
context.SetStrokeWidth[minLineWidth*2.0];
IF output = press THEN {
-- Press doesn't support MaskStroke.
context.MaskVector[[innerBox.xmin, innerBox.ymin], [innerBox.xmin, innerBox.ymax]];
context.MaskVector[[innerBox.xmin, innerBox.ymax], [innerBox.xmax, innerBox.ymax]];
context.MaskVector[[innerBox.xmax, innerBox.ymax], [innerBox.xmax, innerBox.ymin]];
context.MaskVector[[innerBox.xmax, innerBox.ymin], [innerBox.xmin, innerBox.ymin]];
}
ELSE {
borderPath: Imager.PathProc = {
moveTo[[innerBox.xmin, innerBox.ymin]];
lineTo[[innerBox.xmin, innerBox.ymax]];
lineTo[[innerBox.xmax, innerBox.ymax]];
lineTo[[innerBox.xmax, innerBox.ymin]];
};
context.MaskStroke[path: borderPath, closed: TRUE];
};
}; -- Border
HorizontalAxisLabels: PROC[context: Imager.Context, box: Imager.Box,
min, max, scale, step: REAL] = {
external vars referenced: output.
tickLen: REAL ← (box.ymax - box.ymin)/50.0;
tickCount: CARDINAL = Real.RoundI[(max - min)/step];
textTop: REAL ← box.ymin - TextHeightFudge;
tick: REAL;
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step*scale*i;
[] ← MyDrawText[context,
IO.PutFR["%g", IO.real[step*i + min]],
box.xmin + tick, textTop,
center, top, normal, output];
IF i # 0 AND i # tickCount THEN {
xPos: REAL = box.xmin + tick;
yStart: REAL ← box.ymin;
yEnd: REAL ← box.ymin + tickLen;
Tick: Imager.PathProc = {moveTo[[xPos, yStart]]; lineTo[[xPos, yEnd]]};
proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]};
IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]]
ELSE context.DoSave[proc];
yStart ← box.ymax; yEnd ← box.ymax - tickLen;
IF output = press THEN context.MaskVector[[xPos, yStart], [xPos, yEnd]]
ELSE context.DoSave[proc];
};
ENDLOOP;
}; -- HorizontalAxisLabels
VerticalAxisLabels: PROC[context: Imager.Context, box: Imager.Box,
min, max, scale, step: REAL] = {
external vars referenced: output.
tickLen: REAL ← (box.ymax - box.ymin)/50.0;
tickCount: CARDINAL = Real.RoundI[(max - min)/step];
textRight: REAL ← box.xmin - TextWidthFudge;
tick: REAL;
FOR i: CARDINAL IN [0..tickCount] DO
tick ← step*scale*i;
[] ← MyDrawText[context,
IO.PutFR["%g", IO.real[step*i + min]],
textRight, box.ymin + tick,
right, center, normal, output];
IF i # 0 AND i # tickCount THEN {
yPos: REAL = box.ymin + tick;
xStart: REAL ← box.xmin;
xEnd: REAL ← box.xmin + tickLen;
Tick: Imager.PathProc = {moveTo[[xStart, yPos]]; lineTo[[xEnd, yPos]]};
proc: PROC = {context.MaskStroke[path: Tick, closed: FALSE]};
IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]]
ELSE context.DoSave[proc];
xStart ← box.xmax; xEnd ← box.xmax - tickLen;
IF output = press THEN context.MaskVector[[xStart, yPos], [xEnd, yPos]]
ELSE context.DoSave[proc];
};
ENDLOOP;
}; -- VerticalAxisLabels
xSize: REAL = box.xmax-box.xmin;
ySize: REAL = box.ymax-box.ymin;
xStep, yStep: REAL; -- between ticks
xScale, yScale: REAL;
nLabelsX, nLabelsY: CARDINAL;
hLabelWidth, hLabelHeight, vLabelWidth, vLabelHeight, tLabelWidth, tLabelHeight: REAL;
inBoxW, inBoxH: REAL;
[tLabelWidth, tLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmin]],
normal, output];
[hLabelWidth, hLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.xmax]],
normal, output];
hLabelWidth ← MAX[tLabelWidth, hLabelWidth];
hLabelHeight ← MAX[tLabelHeight, hLabelHeight];
[tLabelWidth, tLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymin]],
normal, output];
[vLabelWidth, vLabelHeight] ← GetRopeSize[IO.PutFR["%g", IO.real[bounds.ymax]],
normal, output];
vLabelWidth ← MAX[tLabelWidth, vLabelWidth];
vLabelHeight ← MAX[tLabelHeight, vLabelHeight];
innerBox ← [
xmin: box.xmin + vLabelWidth + TextWidthFudge,
ymin: box.ymin + hLabelHeight + TextHeightFudge*2,
xmax: box.xmax,
ymax: box.ymax];
IF innerBox.xmin > innerBox.xmax THEN innerBox.xmin ← box.xmin;
IF innerBox.ymin > innerBox.ymax THEN innerBox.ymin ← box.ymin;
inBoxW ← innerBox.xmax - innerBox.xmin;
inBoxH ← innerBox.ymax - innerBox.ymin;
context.DoSave[Border];
nLabelsX ← MAX[1, MIN[10, Real.RoundI[inBoxW/hLabelWidth*5/8]]];
nLabelsY ← MAX[1, MIN[10, Real.RoundI[inBoxH/vLabelHeight*5/8]]];
[realBounds.xmin, realBounds.xmax, xStep, xScale] ← ScaleAxis[
bounds.xmin, bounds.xmax, inBoxW, nLabelsX];
[realBounds.ymin, realBounds.ymax, yStep, yScale] ← ScaleAxis[
bounds.ymin, bounds.ymax, inBoxH, nLabelsY];
context.SetStrokeEnd[butt];
context.SetStrokeWidth[minLineWidth];
HorizontalAxisLabels[context, innerBox, realBounds.xmin, realBounds.xmax, xScale, xStep];
VerticalAxisLabels[context, innerBox, realBounds.ymin, realBounds.ymax, yScale, yStep];
}; -- DrawAxes
GetRopeSize: PROC [text: Rope.ROPE, fontType: FontType ← normal, output: OutputType ← screen] RETURNS [dx, dy: REAL] = {
font: ImagerFont.Font← textFont[fontType][output];
extents: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, text];
dx ← extents.rightExtent - extents.leftExtent;
dy ← extents.descent + extents.ascent;
}; -- GetRopeSize
ScaleAxis: PROC [minDataValue, maxDataValue, innerBoxSize: REAL, nLabels: CARDINAL]
RETURNS [min, max, step, scale: REAL] = {
step ← FindStepSize[maxDataValue - minDataValue, nLabels];
min ← AlignEnd[minDataValue, step, FALSE];
max ← AlignEnd[maxDataValue, step, TRUE];
IF Almost[min, max] THEN {max ← max + 50.0; min ← min - 50.0};
scale ← innerBoxSize/(max - min);
}; -- ScaleAxis
FindStepSize: PROC [range: REAL, nSteps: CARDINAL] RETURNS[step: REAL] = {
logRange: REAL;
mantissa, minStep: REAL;
characteristic: 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];
characteristic ← Real.FixI[logRange];
mantissa ← logRange - characteristic;
IF logRange < 0.0 THEN {
characteristic ← characteristic - 1;
mantissa ← mantissa + 1.0};
minStep ← RealFns.Power[10.0, mantissa]/nSteps;
FOR i: CARDINAL IN [0..5) DO
step ← steps[i];
IF step > minStep OR Almost[step, minStep] THEN EXIT
ENDLOOP;
IF characteristic >= 0 THEN THROUGH [1..characteristic] DO step ← step*10.0 ENDLOOP
ELSE THROUGH [1..-characteristic] 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 - 0.5];
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
Almost: PROC [p, q: REAL] RETURNS [a: BOOLTRUE] = {
max: REALMAX[ABS[p], ABS[q]];
IF max # 0.0 THEN a ← (ABS[p - q]/max) < 0.00001;
}; -- Almost
MyDrawText: PROC[context: Imager.Context,
text: Rope.ROPE, x0, y0: REAL,
xJustification: {left, center, right} ← left,
yJustification: {top, center, bottom} ← bottom,
fontType: FontType ← normal,
output: OutputType ← screen,
xScale, yScale: REAL ← 1.0]
RETURNS [Imager.VEC] = {
proc: PROC = {
context.TranslateT[[x0, y0]];
context.SetXY[[xmin, ymin]];
context.SetFont[font];
context.Scale2T[[xScale, yScale]];
context.ShowRope[text];
}; -- proc
extents: ImagerFont.Extents;
xmin, ymin, width, height: REAL;
font: ImagerFont.Font = textFont[fontType][output];
IF text.IsEmpty[] THEN RETURN[[x0, y0]];
extents ← ImagerFont.RopeBoundingBox[font: font, rope: text];
extents ← [extents.leftExtent*xScale, extents.rightExtent*xScale,
extents.descent*yScale, extents.ascent*yScale];
width ← extents.rightExtent - extents.leftExtent;
heightextents.descent + extents.ascent;
xmin← SELECT xJustification FROM
right => - width - extents.leftExtent,
center => - width/2.0 - extents.leftExtent,
ENDCASE => - extents.leftExtent;
ymin← SELECT yJustification FROM
top => - height + extents.descent,
center => - height/2.0 + extents.descent,
ENDCASE => extents.descent;
context.DoSave[proc];
RETURN[IF output = interpress THEN [x0 + width, y0 + height]
ELSE ImagerBackdoor.GetCP[context]];
}; -- MyDrawText
DrawCurves: PROC [context: Imager.Context, handle: Handle,
curves: Curves, output: OutputType] = {
box: Imager.Box ← handle.curvesBox;
bounds: Imager.Box ← handle.realBounds;
colorMode: ColorMode ← handle.colorMode;
IF curves # NIL THEN {
nCurvesMax: CARDINAL = curves.first.size - 1;
tFactor: REAL ← (box.xmax - box.xmin)/(bounds.xmax - bounds.xmin);
vFactor: REAL ← (box.ymax - box.ymin)/(bounds.ymax - bounds.ymin);
drawThem: PROC = {
context.TranslateT[[box.xmin, box.ymin]];
context.SetStrokeWidth[minLineWidth];
context.SetStrokeJoint[mitered];
IF colorMode = bw THEN {
context.SetStrokeEnd[butt];
FOR i: CARDINAL IN [1..nCurvesMax] DO
firstPoint: BOOLTRUE;
lastVec, newVec: Imager.VEC;
index: CARDINAL ← i - 1;
handle.lineStates[index]^ ← []; -- cf. AddVector.
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
newVec ← [
(graph.first[0] - bounds.xmin)*tFactor,
(graph.first[i] - bounds.ymin)*vFactor];
IF firstPoint THEN firstPoint ← FALSE
ELSE DrawLineSeg[context, lastVec, newVec,
index MOD 12, handle.lineStates[index]];
lastVec ← newVec;
ENDLOOP;
ENDLOOP;
}
ELSE {
context.SetStrokeEnd[round];
FOR i: CARDINAL IN [1..nCurvesMax] DO
context.SetColor[color[((i-1) MOD 12)+1]];
IF output = press THEN {
Press doesn't support MaskStroke.
normal: BOOLFALSE;
lastVec: Imager.VEC;
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
t: REAL ← (graph.first[0] - bounds.xmin)*tFactor;
v: REAL ← (graph.first[i] - bounds.ymin)*vFactor;
IF normal THEN context.MaskVector[lastVec, [t, v]] ELSE normal ← TRUE;
lastVec ← [t, v];
ENDLOOP;
}
ELSE {
oneCurve: Imager.PathProc = {
start: BOOLTRUE;
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
t: REAL ← (graph.first[0] - bounds.xmin)*tFactor;
v: REAL ← (graph.first[i] - bounds.ymin)*vFactor;
IF start THEN moveTo[[t, v]] ELSE lineTo[[t, v]];
start ← FALSE;
ENDLOOP;
}; -- oneCurve
context.MaskStroke[path: oneCurve, closed: FALSE];
};
ENDLOOP;
};
}; -- drawThem
IF nCurvesMax > 0 THEN context.DoSave[drawThem];
};
}; -- DrawCurves
DrawLegendFootnote: PROC [context: Imager.Context, box: Imager.Box,
legendEntries: REF RopeSequence, footNote: Rope.ROPE,
output: OutputType, colorMode: ColorMode] = {
nEntries: CARDINAL ← legendEntries.size;
MaxEntriesPerColumn: CARDINAL = 5;
xmin, xmax: REAL ← 0;
yIncPerRow: REALMAX[-fontHeight[normal][output], (box.ymin - box.ymax) / MaxEntriesPerColumn]; -- note: they are negative.
yScale: REAL = -yIncPerRow / fontHeight[normal][output];
colorSampleLineLength: REAL = 34.0;
sampleLineLength: ARRAY[0..12) OF REAL = [
34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 34.0, 31.0, 31.0, 34.0, 34.0];
state: LineState ← NEW[LineStateRec];
proc: PROC = {
context.TranslateT[[box.xmin, box.ymax]];
IF yScale >= 0.99 THEN
[] ← MyDrawText[context, footNote, 0, -(box.ymax-box.ymin), left, top, normal, output];
FOR i: CARDINAL IN [0..nEntries) DO
row: CARDINAL ← i MOD MaxEntriesPerColumn;
col: CARDINAL ← i / MaxEntriesPerColumn;
index: CARDINAL ← i MOD 12;
textStartX, textStartY, lineY, newX: REAL;
IF row = 0 AND col > 0 THEN {
xmin ← xmax + TextWidthFudge;
IF xmin >= box.xmax THEN EXIT;
};
textStartX ← xmin + 36.0;
textStartY ← row*yIncPerRow;
lineY ← textStartY + (yIncPerRow/2.0);
context.SetStrokeWidth[minLineWidth];
context.SetStrokeEnd[butt];
IF colorMode = bw THEN {
context.SetColor[foreground]; -- already done. ... bad!
state^ ← [];
DrawLineSeg[context, [xmin, lineY], [xmin+sampleLineLength[index], lineY],
index, state];
}
ELSE {
context.SetColor[color[index+1]];
context.MaskVector[[xmin, lineY], [xmin+colorSampleLineLength, lineY]];
};
[[newX, ]] ← MyDrawText [
context,
legendEntries[i],
textStartX, textStartY,
left, top, normal, output, 1.0, yScale];
IF newX > xmax THEN xmax ← newX;
ENDLOOP;
}; -- proc
context.DoSave[proc];
}; -- DrawLegendFootnote
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.
LineStyle: TYPE = NColors[0..12);
shape: ARRAY LineStyle OF ARRAY LineStep 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
];
DrawLineSeg: PROC[context: Imager.Context,
v0, v1: Imager.VEC ← [0.0, 0.0],
style: LineStyle ← 0,
state: LineState ← NIL] = {
IF Almost[v0.x, v1.x] AND Almost[v0.y, v1.y] THEN RETURN;
IF state = NIL THEN state ← NEW[LineStateRec ← []];
IF style = 0 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;
};
}; -- DrawLineSeg
Init: PROC [] = {
FOR f: FontType IN FontType DO
FOR o: OutputType IN OutputType DO
extents: ImagerFont.Extents ← ImagerFont.FontBoundingBox[textFont[f][o]];
fontHeight[f][o] ← extents.descent+extents.ascent;
ENDLOOP;
ENDLOOP;
}; -- Init
Init[];
}.
CHANGE LOG.
Chen, August 2, 1985 7:08:09 pm PDT, spin off from PlotViewer.