PlotViewer.mesa
Last Edited by: SChen, May 21, 1985 6:37:40 pm PDT
DIRECTORY
BasicTime USING [GMT, nullGMT],
ColorMap USING [SetRGBColor, StandardMap],
Convert USING [Error, RealFromRope],
Graphics USING [Box, ClipBox, 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],
GraphicsColor USING [ColorToIntensity, RGBToColor],
GraphicsToPress USING [Close, NewContext, SetPageSize],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [int, PutFR, real, rope, time],
Menus USING [AppendMenuEntry, CreateEntry, CreateMenu, Menu, MenuProc],
MessageWindow USING [Append, Blink],
Plot USING [Curves, PlotSpec, PlotSpecRec, ReadPlotFile, RopeSequence, SavePlot, Vector],
PlotOps USING [BackgroundType, Handle, HandleData, Lock, PathSequence, Unlock],
Real USING [FixI, RoundI, RoundLI],
RealFns USING [Log, Power],
Rope USING [Concat, Equal, Find, IsEmpty, ROPE, Substr],
Terminal USING [ColorCursorPresentation, ColorMode, Current, GetColorCursorPresentation, GetColorMode, SetColorCursorPresentation, Virtual],
UserProfile USING [ListOfTokens, Token],
ViewerClasses USING [Column, PaintProc, PaintRectangle, Viewer, ViewerClass, ViewerClassRec],
ViewerOps USING [ChangeColumn, ComputeColumn, CreateViewer, EnumerateViewers, EnumProc, OpenIcon, PaintViewer, RegisterViewerClass, SwapIconAndViewer],
ViewerTools USING [GetSelectionContents],
WindowManager USING [colorDisplayOn, RestoreCursor];
PlotViewer: CEDAR PROGRAM
IMPORTS BasicTime, ColorMap, Convert, Graphics, GraphicsColor, GraphicsToPress, Icons, IO, Menus, MessageWindow, Plot, PlotOps, Real, RealFns, Rope, Terminal, UserProfile, ViewerOps, ViewerTools, WindowManager
EXPORTS Plot = {
OPEN Plot, PlotOps;
types
OutputType: TYPE = {screen, pressFile};
FontType: TYPE = {title, normal};
VectorPair: TYPE = RECORD[start, end: Vector];
NColors: TYPE = [0..nColors);
constants
TextHeightFudge: REAL = 6.0;
TextWidthFudge: REAL = 8.0;
minLineWidth: ARRAY OutputType OF REAL = [0, 2.0];
nColors: INTEGER = 16;
(constant) variables
plotIcon: Icons.IconFlavor ← Icons.NewIconFromFile["Plot.icons", 0];
plotViewerClass: ViewerClasses.ViewerClass ← NEW [ViewerClasses.ViewerClassRec ←
[paint: Display, icon: plotIcon, cursor: textPointer]];
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]];
color: ARRAY NColors OF Graphics.Color ← ALL[[r: 0, g: 0, b: 0]];
fontHeight: ARRAY FontType OF ARRAY OutputType OF REAL;
textScale: ARRAY FontType OF ARRAY OutputType OF REAL;
virtual: Terminal.Virtual ← Terminal.Current[];
public procedures
CreateViewer: PUBLIC PROC [spec: PlotSpec ← NIL, iconic, inhibitDestroy: BOOLFALSE] RETURNS [viewer: ViewerClasses.Viewer ← NIL] = {
handle
handle: Handle ← NEW[HandleData ← [
plotSpec: spec,
background: GetBackground[],
paths: NEW[PathSequence[spec.nCurvesMax]]
] ];
column
columnRope: Rope.ROPE = UserProfile.Token[key: "Plot.Column", default: "color"];
column: ViewerClasses.Column ← SELECT TRUE FROM
columnRope.Equal["color", FALSE] => color,
columnRope.Equal["left", FALSE] => left,
columnRope.Equal["right", FALSE] => right,
ENDCASE => static;
IF column = static THEN {
MessageWindow.Append[
IO.PutFR["Warning: Illegal entry in user profile, Plot.Column: %g.",
IO.rope[columnRope]], TRUE];
column ← color;
};
IF column = color THEN IF iconic OR NOT virtual.hasColorDisplay THEN column ← left;
FOR i: CARDINAL IN [0..spec.nCurvesMax) DO
handle.paths[i] ← Graphics.NewPath[2]; ENDLOOP;
iconic ← iconic OR UserProfile.Token[key: "Plot.CreateOption", default: "CloseOtherViewers"].Equal["Iconic", FALSE];
viewer ← ViewerOps.CreateViewer[
flavor: $Plot,
info: [
name: spec.file,
menu: MakeMenu[],
icon: plotIcon,
column: IF column = color THEN left ELSE column,
iconic: TRUE,
inhibitDestroy: inhibitDestroy,
data: handle],
paint: FALSE];
IF iconic THEN ViewerOps.PaintViewer[viewer, all, TRUE, NIL]
ELSE {
vOpen: ViewerClasses.Viewer ← NIL;
createOption: Rope.ROPE = UserProfile.Token[key: "Plot.CreateOption", default: "CloseOtherViewers"];
closeOthers: BOOL ← createOption.Equal["CloseOtherViewers", FALSE];
swapOld: BOOL ← createOption.Equal["SwapBottomViewer", FALSE];
IF NOT (closeOthers OR swapOld) THEN closeOthers ← TRUE;
IF swapOld THEN { -- get the viewer at the bottom of the column (vOpen)
MyEnum: ViewerOps.EnumProc = {
IF v.column = column AND NOT v.iconic THEN {vOpen ← v; RETURN[FALSE]};
}; -- MyEnum
ViewerOps.EnumerateViewers[MyEnum];
};
IF column = color THEN ViewerOps.ChangeColumn[viewer, color];
IF vOpen = NIL THEN ViewerOps.OpenIcon[
icon: viewer,
closeOthers: closeOthers,
bottom: TRUE,
paint: FALSE]
ELSE ViewerOps.SwapIconAndViewer[
icon: viewer,
openViewer: vOpen,
paint: FALSE];
ViewerOps.ComputeColumn[static];
IF NOT iconic THEN ViewerOps.ComputeColumn[column];
};
}; -- CreateViewer
AddVector: PUBLIC PROC [viewer: ViewerClasses.Viewer ← NIL, vector: Vector ← NIL] = {
IF vector = NIL OR NOT IsPlotViewer[viewer] THEN RETURN ELSE {
shouldPaint: BOOLFALSE;
expectedSize: CARDINAL;
handle: Handle ← NARROW[viewer.data];
IF handle # NIL THEN {
Lock[handle];
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 {
IF handle.curves # NIL THEN
IF handle.curves.first # NIL THEN shouldPaint ← TRUE;
handle.curves ← CONS[vector, handle.curves]
};
Unlock[handle];
};
IF shouldPaint THEN ViewerOps.PaintViewer[viewer, client, FALSE, handle.curves];
};
}; -- 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];
IF handle = NIL THEN RETURN;
Lock[handle];
IF handle.plotSpec.nCurvesMax # newSpec.nCurvesMax THEN {
handle.paths ← NEW[PathSequence[newSpec.nCurvesMax]];
FOR i: CARDINAL IN [0..newSpec.nCurvesMax) DO
handle.paths[i] ← Graphics.NewPath[2]; ENDLOOP;
};
handle.curves ← NIL;
handle.plotSpec ← newSpec;
viewer.name ← Rope.Concat["Plot of ", handle.plotSpec.file];
Unlock[handle];
ViewerOps.PaintViewer[viewer, caption, FALSE, NIL];
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.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
SwapCursor: Menus.MenuProc = {
[] ← Terminal.SetColorCursorPresentation[virtual,
IF Terminal.GetColorCursorPresentation[virtual] = onesAreBlack THEN
onesAreWhite ELSE onesAreBlack
];
}; -- SwapCursor
Background: Menus.MenuProc = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
IF handle # NIL THEN {
bkGndColor: Graphics.Color;
cursorType: Terminal.ColorCursorPresentation;
Lock[handle];
handle.background ← SELECT mouseButton FROM
red => SELECT handle.background FROM
black => white, ENDCASE => SUCC[handle.background],
blue => SELECT handle.background FROM
white => black, gray => white, darkGray => gray, ENDCASE => darkGray,
ENDCASE => GetBackground[]; -- yellow button
bkGndColor ← SELECT handle.background FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
cursorType ← IF GraphicsColor.ColorToIntensity[bkGndColor] < 0.5 THEN onesAreWhite ELSE onesAreBlack;
IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN
[] ← Terminal.SetColorCursorPresentation[virtual, cursorType];
Unlock[handle];
ViewerOps.PaintViewer[viewer, client, TRUE, NIL];
};
}; -- Background
CedarColors: Menus.MenuProc = {
IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN {
SetCedarColors[];
PaintAllColorViewers[];
};
}; -- CedarColors
MyColors: Menus.MenuProc = {SetMyColors[]};
Get: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
msg: Rope.ROPE
IF name.IsEmpty[] THEN "Please select a plot file name."
ELSE IF handle = NIL THEN "Not a good plot viewer."
ELSE ReadPlotFile[name: name, viewer: viewer, iconic: FALSE];
IF msg # NIL THEN MessageWindow.Append[msg, TRUE];
}; -- Get
Store: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
name: Rope.ROPE ← ViewerTools.GetSelectionContents[];
msg: Rope.ROPE ← SavePlot[viewer, name]; -- viewer type checked there
IF msg # NIL THEN {
MessageWindow.Blink[];
MessageWindow.Append[message: msg, clearFirst: TRUE];
};
}; -- Store
Press: Menus.MenuProc = {
viewer: ViewerClasses.Viewer ← NARROW[parent];
handle: Handle ← NARROW[viewer.data];
Lock[handle]; {
excl: INT ← handle.plotSpec.file.Find["!"];
context: Graphics.Context ← GraphicsToPress.NewContext[
Rope.Concat[
SELECT excl FROM
< 0 => handle.plotSpec.file,
> 0 => handle.plotSpec.file.Substr[0, excl],
ENDCASE => "Plot", -- unlikely though
".press"]];
GraphicsToPress.SetPageSize[context];
context.Rotate[90];
DrawMe[context, handle, NIL, pressFile];
GraphicsToPress.Close[context];
}; Unlock[handle];
}; -- Press
SetCedarColors: PROC [] = {
ColorMap.StandardMap[];
WindowManager.RestoreCursor[];
IF Terminal.GetColorCursorPresentation[virtual] # onesAreBlack THEN
[] ← Terminal.SetColorCursorPresentation[virtual, onesAreBlack];
}; -- SetCedarColors
PaintAllColorViewers: PROC [] = {
PaintEachColorViewer: ViewerOps.EnumProc = {
IF v.column = color AND NOT v.iconic THEN ViewerOps.PaintViewer[v, all, TRUE, NIL];
};
ViewerOps.EnumerateViewers[PaintEachColorViewer];
}; -- PaintAllColorViewers
SetMyColors: PROC [] = {
RGB: TYPE = RECORD[r, g, b: REAL];
defaultColor: ARRAY NColors OF RGB ← [
white
[r: 1.0, g: 1.0, b: 1.0],
forgeround colors, all vivid
[r: 1.0, g: 0.0, b: 0.8], -- purple red
[r: 0.0, g: 0.0, b: 1.0], -- blue
[r: 0.0, g: 0.56, b: 1.0], -- greenish blue
[r: 0.0, g: 1.0, b: 1.0], -- green blue
[r: 0.0, g: 1.0, b: 0.0], -- green
[r: 0.52, g: 1.0, b: 0.0], -- yellow green
[r: 1.0, g: 1.0, b: 0.0], -- yellow
[r: 1.0, g: 0.60, b: 0.0], -- orange yellow
[r: 1.0, g: 0.42, b: 0.0], -- yellowish orange
[r: 1.0, g: 0.25, b: 0.0], -- orange
[r: 1.0, g: 0.0, b: 0.0], -- red
[r: 0.8, g: 0.0, b: 1.0], -- reddish purple
gray
[r: 0.5, g: 0.5, b: 0.5], -- gray
[r: 0.17, g: 0.17, b: 0.17], -- dark gray
black
[r: 0.0, g: 0.0, b: 0.0] -- black
];
GetColor: PROC [num: NColors] RETURNS [Graphics.Color] = {
get color spec from user profile, or return default
ColorSpec: TYPE = LIST OF Rope.ROPE;
list: ColorSpec ← UserProfile.ListOfTokens[
key: IO.PutFR["Plot.Color%g", IO.int[num]],
default: NIL
];
c: ARRAY[1..3] OF REAL ← [defaultColor[num].r, defaultColor[num].g, defaultColor[num].b];
i: CARDINAL ← 1;
FOR lr: ColorSpec ← list, list.rest UNTIL list = NIL OR i > 3 DO
c[i] ← Convert.RealFromRope[list.first ! Convert.Error => EXIT];
c[i] ← MAX[0, MIN[1.0, c[i]]];
i ← i + 1;
ENDLOOP;
RETURN[GraphicsColor.RGBToColor[r: c[1], g: c[2], b: c[3]]];
}; -- GetColor
IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN {
m: Terminal.ColorMode = Terminal.GetColorMode[virtual];
IF NOT m.full THEN {
offset: INTEGER ← 0;
IF m.bitsPerPixelChannelA >= 8 THEN offset ← 100;
SetCedarColors[];
FOR i: NColors IN NColors DO
c: Graphics.Color ← color[i] ← GetColor[i];
ColorMap.SetRGBColor[index: i+offset, r: c.r/255.0, g: c.g/255.0, b: c.b/255.0];
ENDLOOP;
PaintAllColorViewers[];
};
};
}; -- SetMyColors
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];
IF handle # NIL THEN {
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
GetBackground: PROC [] RETURNS [BackgroundType] = {
bkGndColorRope: Rope.ROPE = UserProfile.Token[key: "Plot.Background", default: "White"];
SELECT TRUE FROM
bkGndColorRope.Equal["Black", FALSE] => RETURN[black];
bkGndColorRope.Equal["DarkGray", FALSE] => RETURN[darkGray];
bkGndColorRope.Equal["Gray", FALSE] => RETURN[gray];
ENDCASE => { -- background is white
IF NOT bkGndColorRope.Equal["White", FALSE] THEN MessageWindow.Append[
IO.PutFR["Warning: Illegal entry in user profile, Plot.Background: %g.",
IO.rope[bkGndColorRope]], TRUE];
RETURN[white];
};
}; -- GetBackground
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];
[] ← context.SetPaintMode[opaque];
[] ← Graphics.SetFat[context, FALSE];
IF virtual.hasColorDisplay THEN {
m: Terminal.ColorMode = Terminal.GetColorMode[virtual];
IF m.full OR m.bitsPerPixelChannelA < 4 OR m.bitsPerPixelChannelA > 8 THEN {
IF whatChanged = NIL THEN {
MessageWindow.Blink[];
MessageWindow.Append[IO.PutFR["%g bpp are not supported.", IO.int[IF m.full THEN 24 ELSE m.bitsPerPixelChannelA]], TRUE];
};
RETURN;
};
};
IF whatChanged = NIL OR ISTYPE[whatChanged, ViewerClasses.PaintRectangle] THEN {
foreground: Graphics.Color ← IF output = pressFile THEN color[15] ELSE
IF handle.background = black OR handle.background = darkGray THEN color[0]
ELSE color[15];
background: Graphics.Color ← IF output = pressFile THEN color[0] ELSE
SELECT handle.background FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
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];
IF output = screen THEN { -- clear the screen
context.SetColor[background];
context.DrawBox[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, created on %g.", IO.rope[pattern], 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 {
c: Curves ← NIL;
[handle.curvesBox, handle.realBounds] ← DrawAxes[context, axesBox, handle.plotSpec.bounds, output];
context.ClipBox[handle.curvesBox];
FOR l: Curves ← handle.curves, l.rest UNTIL l = NIL DO
c ← CONS[l.first, c];
ENDLOOP;
DrawCurves[context, handle.curvesBox, handle.realBounds, 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.ClipBox[curvesBox];
context.Translate[curvesBox.xmin, curvesBox.ymin];
FOR index: CARDINAL IN [1..m0.size) DO
ipath: CARDINAL ← index - 1;
Graphics.MoveTo[paths[ipath], t0, (m0[index]-vMin)*vFactor, TRUE];
Graphics.LineTo[paths[ipath], t1, (m1[index]-vMin)*vFactor];
context.SetColor[color[(ipath MOD 12) + 1]];
context.DrawStroke[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];
context.Restore[mark];
[newX, newY] ← context.GetCP[];
}; -- 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 12)+1]];
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;
newX: REAL;
IF row = 0 AND col > 0 THEN {
xmin ← xmax + TextWidthFudge;
IF xmin >= box.xmax THEN EXIT;
};
context.SetColor[color[(i MOD 12)+1]];
[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["SwapCursor", SwapCursor]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Background", Background]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["CedarColors", CedarColors]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["MyColors", MyColors]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry[name: "Get", proc: Get, guarded: TRUE]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Store", Store]];
Menus.AppendMenuEntry
[menu, Menus.CreateEntry["Press", Press]];
}; -- MakeMenus
Init: PROC [] = {
ymax, ymin: REAL;
bkGndColor: Graphics.Color;
cursorType: Terminal.ColorCursorPresentation;
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;
IF virtual.hasColorDisplay AND WindowManager.colorDisplayOn THEN SetMyColors[];
bkGndColor ← SELECT GetBackground[] FROM
gray => color[13], darkGray => color[14], black => color[15], ENDCASE => color[0];
cursorType ← IF GraphicsColor.ColorToIntensity[bkGndColor] < 0.5 THEN onesAreWhite ELSE onesAreBlack;
IF cursorType # Terminal.GetColorCursorPresentation[virtual] THEN
[] ← Terminal.SetColorCursorPresentation[virtual, cursorType];
ViewerOps.RegisterViewerClass[$Plot, plotViewerClass];
}; -- Init
Init[];
}.
CHANGE LOG.
SChen, May 14, 1985 5:39:02 pm PDT, custom icon.