PlotScreen.mesa
Last Edited by: SChen, August 10, 1984 3:01:31 am PDT
DIRECTORY
BasicTime USING [GMT, nullGMT],
ColorMap USING [SetRGBColor],
Graphics USING
[black, Box, Color, Context, DrawBox, DrawRope, DrawStroke, FontBox, FontRef, GetBounds, GetCP, LineTo, MakeFont, Mark, MoveTo, NewPath, Path, Rectangle, Restore, RopeBox, Rotate, Save, Scale, SetColor, SetCP, SetFat, SetPaintMode, Translate, white],
GraphicsColor USING [blue, cyan, green, magenta, red, RGBToColor, yellow],
GraphicsToPress USING [Close, NewContext, SetPageSize],
IO USING [int, PutFR, real, rope, time],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append, Blink],
Plot USING [Curves, Vector, PlotSpec, PlotSpecRec, RopeSequence, WritePlotFile],
PlotOps USING [Handle, HandleData, Lock, PathSequence, Unlock],
Real USING [FixI, RoundI, RoundLI],
RealFns USING [Log, Power],
Rope USING [Concat, IsEmpty, ROPE],
ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec],
ViewerOps USING [--ComputeColumn, ChangeColumn,
CloseViewer, CreateViewer, EnumerateViewers, EnumProc, PaintViewer, RegisterViewerClass];
PlotScreen: CEDAR PROGRAM
IMPORTS BasicTime, ColorMap, Graphics, GraphicsColor, GraphicsToPress, IO, Menus, MessageWindow, Plot, PlotOps, Real, RealFns, Rope, ViewerOps--, ViewerPaintImpl
EXPORTS Plot, PlotOps
SHARES ViewerOps = {
OPEN Plot, PlotOps;
RGB: TYPE = RECORD[r,g,b: REAL];
OutputType: TYPE = {screen, pressFile};
FontType: TYPE = {title, normal};
RefNumVector: TYPE = REF NVector;
NVector: TYPE = RECORD[nVector: CARDINAL];
constants
TextHeightFudge: REAL = 6.0;
TextWidthFudge: REAL = 8.0;
minLineWidth: ARRAY OutputType OF REAL = [0, 2.0];
nColors: INTEGER = 16;
MicasPerPixel: REAL = 2540.0/72.0;
(constant) variables
plotViewerClass: ViewerClasses.ViewerClass ← NIL;
t12: Graphics.FontRef = Graphics.MakeFont["TimesRoman12"];
tD36: Graphics.FontRef = Graphics.MakeFont["TimesRomanD36"];
h8: Graphics.FontRef = Graphics.MakeFont["Helvetica8"];
hD24: Graphics.FontRef = Graphics.MakeFont["HelveticaD24"];
textFont: ARRAY FontType OF ARRAY OutputType OF Graphics.FontRef = [[t12, tD36], [h8, hD24]];
rgb: ARRAY[0..nColors) OF RGB;
color: ARRAY [0..nColors) OF Graphics.Color;
fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL;
textScale: ARRAY FontType OF ARRAY OutputType OF REAL;
public procedures
CreateViewer: PUBLIC PROC [spec: PlotSpec ← NIL, column: ViewerClasses.Column ← left, iconic: BOOLTRUE] RETURNS [viewer: ViewerClasses.Viewer ← NIL] = {
If spec.nCurvesMax <= 0, no curves will be shown.
handle: Handle ← NEW[HandleData ← [
plotSpec: spec,
paths: NEW[PathSequence[spec.nCurvesMax]]
] ];
FOR i: CARDINAL IN [0..spec.nCurvesMax) DO
handle.paths[i] ← Graphics.NewPath[2]; ENDLOOP;
IF column = color AND NOT iconic THEN {
MyEnum: ViewerOps.EnumProc = {
IF v.column = color AND NOT v.iconic THEN {
ViewerOps.CloseViewer[v, FALSE];
ViewerOps.PaintViewer[v, all, TRUE, NIL];
};
};
ViewerOps.EnumerateViewers[MyEnum];
};
viewer ← ViewerOps.CreateViewer[
flavor: $Plot,
info: [
name: Rope.Concat["Plot of ", spec.file],
menu: MakeMenu[],
column: column,
iconic: iconic,
data: handle],
paint: FALSE];
ViewerOps.PaintViewer[viewer, all, TRUE, NIL];
}; -- CreateViewer
AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer ← NIL, vector: Vector ← NIL] = {
IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE {
shouldPaint: BOOLFALSE;
z: Curves;
expectedSize: CARDINAL;
oldNumVector: CARDINAL;
handle: Handle ← NARROW[viewer.data];
Lock[handle];
oldNumVector ← handle.nVector;
z ← handle.curves;
expectedSize ← handle.plotSpec.nCurvesMax + 1;
IF vector.size # expectedSize THEN MessageWindow.Append[
IO.PutFR["Only %g elements in vector when %g expected.",
IO.int[vector.size], IO.int[expectedSize]], TRUE]
ELSE {
handle.nVector ← handle.nVector + 1;
IF z = NIL THEN handle.curves ← CONS[vector, NIL]
ELSE {
UNTIL z.rest = NIL DO z ← z.rest ENDLOOP;
z.rest ← CONS[vector, NIL];
shouldPaint ← TRUE;
};
};
Unlock[handle];
IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, NEW[NVector ← [oldNumVector + 1]]];
};
}; -- AddVector
CreateSpec: PUBLIC PROC[file, title: Rope.ROPENIL,
time: BasicTime.GMT ← BasicTime.nullGMT,
bounds: Graphics.Box ← [0, 0, 0, 0], -- [xmin, xmax, ymin, ymax]
nCurvesMax: CARDINAL ← 0,
legendEntries: REF RopeSequence ← NIL
] RETURNS[spec: PlotSpec ← NIL] = {
spec ← NEW[PlotSpecRec ← [file, title, time, bounds, nCurvesMax, legendEntries]];
}; -- CreateSpec
SetSpec: PUBLIC PROC [viewer: ViewerClasses.Viewer ← NIL, newSpec: PlotSpec ← NIL] = {
Note: if original nCurvesMax # new one, then old data will be cleared.
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
Lock[handle];
IF handle.plotSpec.nCurvesMax # newSpec.nCurvesMax THEN {
handle.paths ← NEW[PathSequence[newSpec.nCurvesMax]];
handle.nVector ← 0;
handle.curves ← NIL;
};
handle.plotSpec ← newSpec;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL]; -- update the display.
};
}; -- SetSpec
Clear: PUBLIC PROC [viewer: ViewerClasses.Viewer] = {
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
Lock[handle];
handle.plotSpec^ ← [];
handle.paths ← NIL;
handle.nVector ← 0;
handle.curves ← NIL;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- Clear
Zoom: PUBLIC PROC [viewer: ViewerClasses.Viewer, newBounds: Graphics.Box] = {
IF IsPlotViewer[viewer] THEN {
handle: Handle ← NARROW[viewer.data];
Lock[handle];
handle.plotSpec.bounds ← newBounds;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- Zoom
IsPlotViewer: PUBLIC PROC [viewer: ViewerClasses.Viewer] RETURNS [BOOL] = {
IF viewer = NIL THEN RETURN[FALSE];
RETURN[viewer.class.flavor = $Plot];
}; -- IsPlotViewer
menu commands
SwapBackground: Menus.MenuProc = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
Lock[handle];
handle.foreground ← IF handle.foreground = Graphics.white
THEN Graphics.black ELSE Graphics.white;
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL]
}; -- SwapBackground
Press: Menus.MenuProc = {
context: Graphics.Context;
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
Lock[handle];
context ← GraphicsToPress.NewContext[handle.plotSpec.file.Concat[".press"]];
GraphicsToPress.SetPageSize[context];
context.Rotate[90];
DrawMe[context, handle, NIL, pressFile];
Unlock[handle];
GraphicsToPress.Close[context];
}; -- Press
Save: Menus.MenuProc = {
msg: Rope.ROPE;
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
Lock[handle];
msg ← WritePlotFile[handle.plotSpec, handle.curves];
Unlock[handle];
IF msg # NIL THEN {
MessageWindow.Blink[];
MessageWindow.Append[message: msg, clearFirst: TRUE];
};
}; -- Save
other private procedures
Display: ViewerClasses.PaintProc = {
[self: Viewer, context: Graphics.Context, whatChanged: REF ANY, clear: BOOL]
IF IsPlotViewer[self] AND NOT self.iconic THEN {
there will be no checking of the viewer class in DrawMe.
handle: Handle ← NARROW[self.data];
Lock[handle];
DrawMe[context, handle, whatChanged, screen];
Unlock[handle];
};
}; -- Display
MyPaintViewer: PROC [viewer: ViewerClasses.Viewer, handle: Handle, whatChanged: REF ANY, output: OutputType] = {
clients of this proc should have checked the viewer class
context: Graphics.Context ← ViewerPaintImpl.AcquireContext[viewer, viewer.column=color];
DrawMe[context, handle, whatChanged, output];
}; -- MyPaintViewer
DrawMe: PROC [context: Graphics.Context, handle: Handle, whatChanged: REF ANY, output: OutputType] = {
press context has been rotated
BoundsValid: PROC [box: Graphics.Box] RETURNS [BOOL] = INLINE {
RETURN[box.xmax > box.xmin AND box.ymax > box.ymin];
}; -- BoundsValid
box: Graphics.Box ← Graphics.GetBounds[context];
width: REAL ← box.xmax - box.xmin;
height: REAL ← box.ymax - box.ymin;
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][screen] + TextHeightFudge;
windowTooLow: BOOL ← tenthHeight < textLineHeight;
legendTop: REALIF windowTooLow THEN minY ELSE minY + tenthHeight*1.5;
axesTop: REALIF windowTooLow THEN maxY ELSE maxY - tenthHeight;
titleBox: Graphics.Box ← [minX, axesTop, maxX, maxY];
axesBox: Graphics.Box ← [minX, legendTop, maxX, axesTop];
legendBox: Graphics.Box ← [minX, minY, maxX, legendTop];
foreground: Graphics.Color ← IF output = screen THEN handle.foreground ELSE Graphics.black;
background: Graphics.Color ← IF foreground = Graphics.white THEN Graphics.black ELSE Graphics.white;
[] ← context.SetPaintMode[opaque];
[] ← Graphics.SetFat[context, FALSE];
IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN {
IF output = screen THEN { -- clear the screen
context.SetColor[background];
context.DrawBox[box];
};
title / legend / footnote
context.SetColor[foreground];
IF NOT windowTooLow THEN {
footNote: Rope.ROPEIO.PutFR["File: %g; Time: %g.", IO.rope[handle.plotSpec.file], IO.time[handle.plotSpec.time]];
DrawTitle[context, titleBox, handle.plotSpec.title, output];
DrawLegendFootnote[context, legendBox, handle.plotSpec.legendEntries, footNote, output];
};
axes and curves
IF BoundsValid[handle.plotSpec.bounds] THEN {
[handle.curvesBox, handle.realBounds] ← DrawAxes[context, axesBox, handle.plotSpec.bounds, output];
DrawCurves[context, handle.curvesBox, handle.realBounds, handle.curves, output];
};
}
ELSE IF ISTYPE[whatChanged, RefNumVector] THEN {
newNumVector: RefNumVector ← NARROW[whatChanged];
nVector: CARDINAL ← newNumVector.nVector;
curves: Curves ← handle.curves;
index: CARDINAL ← 1;
m0, m1: Vector;
IF nVector <= 1 THEN {Unlock[handle]; RETURN};
WaitPreviousPaints[handle];
DO
index ← index + 1;
IF index = nVector THEN {
m0 ← curves.first;
m1 ← curves.rest.first;
EXIT;
};
curves ← curves.rest;
ENDLOOP;
{ -- begin to plot the last step
curveWidth: REAL = handle.curvesBox.xmax - handle.curvesBox.xmin;
curveHeight: REAL = handle.curvesBox.ymax - handle.curvesBox.ymin;
tMin: REAL = handle.realBounds.xmin;
tFactor: REAL = curveWidth/(handle.realBounds.xmax - tMin);
vMin: REAL = handle.realBounds.ymin;
vFactor: REAL = curveHeight/(handle.realBounds.ymax - vMin);
t0: REAL = (m0[0] - tMin)*tFactor;
t1: REAL = (m1[0] - tMin)*tFactor;
size: CARDINAL = m0.size; -- should be equal to m1.size
context.Translate[handle.curvesBox.xmin, handle.curvesBox.ymin];
FOR index IN [1..size) DO
ipath: CARDINAL ← index - 1;
Graphics.MoveTo[handle.paths[ipath], t0, (m0[index]-vMin)*vFactor, TRUE];
Graphics.LineTo[handle.paths[ipath], t1, (m1[index]-vMin)*vFactor];
context.SetColor[color[ipath MOD 16]];
ColorMap.SetRGBColor[
index: 100+ ipath,
r: rgb[ipath].r,
g: rgb[ipath].g,
b: rgb[ipath].b];
context.DrawStroke[handle.paths[ipath], minLineWidth[output]];
ENDLOOP;
};
};
}; -- DrawMe
DrawTitle: PROC [context: Graphics.Context, box: Graphics.Box, title: Rope.ROPE, output: OutputType] = {
xPos: REAL ← box.xmin + (box.xmax - box.xmin) / 2.0;
boxH: REAL ← box.ymax - box.ymin - fontHeight[normal][screen];
IF title.IsEmpty[] THEN RETURN;
IF boxH >= fontHeight[title][screen] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, title, output]
ELSE IF boxH >= fontHeight[normal][screen] THEN
[] ← MyDrawText[context, title, xPos, box.ymax, center, top, normal, output];
}; -- DrawTitle
DrawAxes: PROC [context: Graphics.Context, box, bounds: Graphics.Box, output: OutputType]
RETURNS [innerBox, realBounds: Graphics.Box] = {
HorizontalAxisLabels: PROC[context: Graphics.Context, box: Graphics.Box,
min, max, scale, step: REAL] = {
external vars referenced: output and path.
tickLen: REAL ← (box.ymax - box.ymin)/50.0;
tickCount: CARDINAL = Real.RoundI[(max - min)/step];
textTop: REAL ← box.ymin; -- In practice, no need to deduct 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 {
Graphics.MoveTo[path, box.xmin + tick, box.ymin, TRUE];
Graphics.LineTo[path, box.xmin + tick, box.ymin + tickLen];
context.DrawStroke[path, minLineWidth[output]];
Graphics.MoveTo[path, box.xmin + tick, box.ymax, TRUE];
Graphics.LineTo[path, box.xmin + tick, box.ymax - tickLen];
context.DrawStroke[path, minLineWidth[output]];
};
ENDLOOP;
}; -- HorizontalAxisLabels
VerticalAxisLabels: PROC[context: Graphics.Context, box: Graphics.Box,
min, max, scale, step: REAL] = {
external vars referenced: output and path.
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, bottom, normal, output];
IF i # 0 AND i # tickCount THEN {
Graphics.MoveTo[path, box.xmin, box.ymin + tick, TRUE];
Graphics.LineTo[path, box.xmin + tickLen, box.ymin + tick];
context.DrawStroke[path, minLineWidth[output]];
Graphics.MoveTo[path, box.xmax, box.ymin + tick, TRUE];
Graphics.LineTo[path, box.xmax - tickLen, box.ymin + tick];
context.DrawStroke[path, minLineWidth[output]];
};
ENDLOOP;
}; -- VerticalAxisLabels
xSize: REAL = box.xmax-box.xmin;
ySize: REAL = box.ymax-box.ymin;
path: Graphics.Path = Graphics.NewPath[size: 5];
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,
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;
Graphics.MoveTo[path, innerBox.xmin, innerBox.ymin];
Graphics.Rectangle[path, innerBox.xmin, innerBox.ymin, innerBox.xmax, innerBox.ymax];
context.DrawStroke[path: path, width: minLineWidth[output], closed: TRUE];
inBoxW ← innerBox.xmax - innerBox.xmin;
inBoxH ← innerBox.ymax - innerBox.ymin;
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];
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: Graphics.FontRef← textFont[fontType][output];
scale: REAL ← textScale[fontType][output];
xmin, ymin, xmax, ymax: REAL;
[xmin, ymin, xmax, ymax] ← Graphics.RopeBox[font, text];
dx ← (xmax - xmin) * scale;
dy ← (ymax - ymin) * scale;
}; -- 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 [BOOL] = INLINE {
RETURN[MAX[ABS[p], ABS[q]] = 0.0 OR ABS[p - q]/MAX[ABS[p], ABS[q]] < 0.00001]; };
MyDrawText: PROC[context: Graphics.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 [newX, newY: REAL] = {
mark: Graphics.Mark ← context.Save[];
font: Graphics.FontRef = textFont[fontType][output];
scale: REAL = textScale[fontType][output];
realScaleX: REAL = xScale*scale;
realScaleY: REAL = yScale*scale;
xmin, xmax, ymin, ymax, sizeX, sizeY: REAL;
IF text.IsEmpty[] THEN RETURN[x0, y0];
context.Translate[x0, y0];
[xmin, ymin, xmax, ymax] ← Graphics.RopeBox[font: font, rope: text];
sizeX ← xmax - xmin;
sizeY ← ymax - ymin;
xmin← SELECT xJustification FROM
right => xmin - sizeX,
center => xmin - sizeX/2.,
ENDCASE => xmin;
ymin← SELECT yJustification FROM
top => ymin - sizeY,
center => ymin - sizeY/2.,
ENDCASE => ymin;
context.Scale[realScaleX, realScaleY];
context.SetCP[xmin, ymin];
context.DrawRope[rope: text, font: font];
[newX, newY] ← context.GetCP[];
newX ← newX / realScaleX + x0;
newY ← newY / realScaleY + y0;
context.Restore[mark];
}; -- MyDrawText
DrawCurves: PROC [context: Graphics.Context, box, bounds: Graphics.Box, curves: Curves,
output: OutputType] = {
IF curves = NIL THEN RETURN ELSE {
mark: Graphics.Mark ← context.Save[];
nCurvesMax: CARDINAL = curves.first.size - 1;
firstVector: BOOLTRUE;
tFactor: REAL ← (box.xmax - box.xmin)/(bounds.xmax - bounds.xmin);
vFactor: REAL ← (box.ymax - box.ymin)/(bounds.ymax - bounds.ymin);
t, v: REAL;
paths: REF PathSequence;
IF nCurvesMax = 0 THEN RETURN;
paths ← NEW[PathSequence[nCurvesMax]];
FOR i: CARDINAL IN [0..nCurvesMax) DO paths[i] ← Graphics.NewPath[2]; ENDLOOP;
context.Translate[box.xmin, box.ymin];
FOR graph: Curves ← curves, graph.rest UNTIL graph = NIL DO
t ← (graph.first[0] - bounds.xmin)*tFactor;
FOR i: CARDINAL IN [1..nCurvesMax] DO
ipath: CARDINAL = i - 1;
v ← (graph.first[i] - bounds.ymin)*vFactor;
IF NOT firstVector THEN {
Graphics.LineTo[paths[ipath], t, v];
context.SetColor[color[ipath MOD 16]];
ColorMap.SetRGBColor[
index: 100+ ipath,
r: rgb[ipath].r,
g: rgb[ipath].g,
b: rgb[ipath].b];
context.DrawStroke[paths[ipath], minLineWidth[output]];
};
Graphics.MoveTo[paths[ipath], t, v, TRUE];
ENDLOOP;
IF firstVector THEN firstVector ← FALSE;
ENDLOOP;
paths ← NIL;
context.Restore[mark];
};
}; -- DrawCurves
DrawLineSeg: PROC[context: Graphics.Context,
x0, y0, x1, y1: REAL ← 0.0,
lineStyle: {solid, dotted, dashed, dotDash} ← solid,
lineState: REAL ← 0.0] = {
IF Almost[x0, x1] AND Almost[y0, y1] THEN RETURN;
IF lineStyle = solid THEN {
context.SetCP[x0, y0]; context.DrawTo[x1, y1]}
ELSE {
max: REAL = SELECT lineStyle FROM
dotted => 300/MicasPerPixel,
dashed => 600/MicasPerPixel,
dotDash => 900/MicasPerPixel,
ENDCASE => 0.0; -- solid
dx: REAL = x1 - x0;
dy: REAL = y1 - y0;
s: REAL ← RealFns.SqRt[dx*dx + dy*dy];
lineState: REAL ← 0.0;
dxds: REAL ← dx/s;
dyds: REAL ← dy/s;
UNTIL Almost[s, 0] DO
space: BOOLEAN;
inc: REAL;
SELECT lineStyle FROM
dotted =>
IF lineState < 100/MicasPerPixel THEN {
space ← FALSE;
inc ← MIN[s, 100/MicasPerPixel - lineState]}
ELSE {
space ← TRUE;
inc ← MIN[s, 300/MicasPerPixel - lineState]};
dashed =>
IF lineState < 400/MicasPerPixel THEN {
space ← FALSE;
inc ← MIN[s, 400/MicasPerPixel - lineState]}
ELSE {
space ← TRUE;
inc ← MIN[s, 600/MicasPerPixel - lineState]};
dotDash =>
IF lineState < 400/MicasPerPixel THEN {
space ← FALSE;
inc ← MIN[s, 400/MicasPerPixel - lineState]}
ELSE
IF lineState < 600/MicasPerPixel THEN {
space ← TRUE;
inc ← MIN[s, 600/MicasPerPixel - lineState]}
ELSE
IF lineState < 700/MicasPerPixel THEN {
space ← FALSE;
inc ← MIN[s, 700/MicasPerPixel - lineState]}
ELSE {
space ← TRUE;
inc ← MIN[s, 900/MicasPerPixel - lineState]};
ENDCASE => ERROR;
s ← s - inc;
x1 ← x0 + dxds*inc;
y1 ← y0 + dyds*inc;
IF ~space THEN {context.SetCP[x0, y0]; context.DrawTo[x1, y1]};
x0 ← x1;
y0 ← y1;
lineState ← lineState + inc;
IF lineState >= max THEN lineState ← 0
ENDLOOP;
};
}; -- DrawLineSeg
DrawLegendFootnote: PROC [context: Graphics.Context, box: Graphics.Box,
legendEntries: REF RopeSequence, footNote: Rope.ROPE, output: OutputType] = {
mark: Graphics.Mark ← Graphics.Save[context];
nEntries: CARDINAL ← legendEntries.size;
MaxEntriesPerColumn: CARDINAL = 5;
xmin, xmax: REAL ← 0;
yIncPerRow: REALMAX[-fontHeight[normal][screen], (box.ymin - box.ymax) / MaxEntriesPerColumn]; -- note: they are negative.
yScale: REAL = -yIncPerRow / fontHeight[normal][screen]; -- !
context.Translate[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 16;
newX: REAL;
IF row = 0 AND col > 0 THEN {
xmin ← xmax + TextWidthFudge;
IF xmin >= box.xmax THEN EXIT;
};
context.SetColor[color[i MOD 16]];
ColorMap.SetRGBColor[index: 100+ i, r: rgb[i].r, g: rgb[i].g, b: rgb[i].b];
[newX,] ← MyDrawText[
context,
legendEntries[i],
xmin, row*yIncPerRow,
left, top, normal, output, 1.0, yScale];
IF newX > xmax THEN xmax ← newX;
ENDLOOP;
context.Restore[mark];
}; -- DrawLegendFootnote
MakeMenu: PROC [] RETURNS [menu: Menus.Menu] = {
menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["SwapBackground", SwapBackground]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Save", Save]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Press", Press]];
}; -- MakeMenus
InitPlot: PUBLIC PROC [] = {
ymax, ymin: REAL;
color ← [
GraphicsColor.red,
GraphicsColor.blue,
GraphicsColor.magenta,
GraphicsColor.cyan,
GraphicsColor.green,
GraphicsColor.yellow,
GraphicsColor.RGBToColor[r: 0.410, g: 0.125, b: 0.875], -- purple
GraphicsColor.RGBToColor[r: 0.725, g: 0.125, b: 0.875], -- reddish purple
GraphicsColor.RGBToColor[r: 0.875, g: 0.125, b: 0.710], -- purple red
GraphicsColor.RGBToColor[r: 0.875, g: 0.125, b: 0.125], -- (mid) red
GraphicsColor.RGBToColor[r: 0.875, g: 0.575, b: 0.125], -- brown yellow
GraphicsColor.RGBToColor[r: 0.875, g: 0.875, b: 0.125], -- (mid) yellow
GraphicsColor.RGBToColor[r: 0.692, g: 0.875, b: 0.125], -- greenish yellow
GraphicsColor.RGBToColor[r: 0.125, g: 0.875, b: 0.125], -- (mid) green
GraphicsColor.RGBToColor[r: 0.125, g: 0.875, b: 0.845], -- green blue
GraphicsColor.RGBToColor[r: 0.125, g: 0.125, b: 0.875] -- (mid) blue
];
FOR i: INTEGER IN [0..nColors) DO
rgb[i].r ← color[i].r/255.0;
rgb[i].g ← color[i].g/255.0;
rgb[i].b ← color[i].g/255.0;
ENDLOOP;
FOR f: FontType IN FontType DO
FOR o: OutputType IN OutputType DO
[ , ymin, , ymax] ← Graphics.FontBox[textFont[f][o]];
fontHeight[f][o] ← ymax - ymin;
textScale[f][o] ← IF o = screen THEN 1.0 ELSE fontHeight[f][screen] / fontHeight[f][o];
ENDLOOP;
ENDLOOP;
plotViewerClass ←
NEW [ViewerClasses.ViewerClassRec
← [paint: Display,
icon: document,
cursor: textPointer]]; -- cursor when mouse is in viewer
ViewerOps.RegisterViewerClass[$Plot, plotViewerClass];
}; -- Init
}.
CHANGE LOG.