PlotGraphImpl.mesa
Copyright Ó 1986, 1987 by Xerox Corporation. All rights reserved.
Christian Le Cocq May 13, 1987 10:11:27 am PDT
Barth, March 31, 1987 3:58:43 pm PST
PlotGraph is a package which claimed to be of general interest, but was tailored to fit the needs of the time simulators from one hand, and the users with oscilloscope habits on the other hand. A lot of handy feature were sacrificed in the name of speed of display.
DIRECTORY
Imager
Draw2d,
Imager USING [Context, SetColor, ColorOperator, MaskRectangle, SetFont, SetStrokeEnd, SetStrokeJoint, SetStrokeWidth, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move, black, white],
Imager USING [Context, SetColor, MaskRectangle, SetFont, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move],
ImagerBackdoor USING [GetBounds, invert],
ImagerColor USING [NewColorOperatorRGB, ColorFromRGB],
ImagerDitherContext USING [MakeSpecialColor],
ImagerFont USING [Extents, Scale, Find, RopeEscapement, RopeBoundingBox, FontBoundingBox],
ImagerInterpress USING [Ref, Create, DeclareColorOperator, DeclareFont, DoPage, Close],
Viewers
Menus USING [Menu, CreateMenu, AppendMenuEntry, ReplaceMenuEntry, CreateEntry, MouseButton, MenuEntry],
ViewerClasses USING [Viewer, ViewerRec, ViewerClass, ViewerClassRec, NotifyProc, PaintProc, ModifyProc, DestroyProc, ScrollProc, HScrollProc, AdjustProc, GetProc],
ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, DestroyViewer],
TIPUser USING [InstantiateNewTIPTable ,TIPScreenCoords],
Icons USING [NewIconFromFile],
InputFocus USING [GetInputFocus, SetInputFocus],
others
CedarProcess USING [Fork, ForkableProc],
IO USING [PutFR, real],
Convert USING [RopeFromInt],
Vector2 USING [VEC],
Rope USING [ROPE, Cat, Length, InlineFetch],
Real USING [InlineFixI, InlineFix, Floor],
TerminalIO USING [PutRopes],
PlotGraph;
PlotGraphImpl: CEDAR MONITOR
IMPORTS
Convert,
Draw2d,
Icons,
Imager,
ImagerBackdoor,
ImagerColor,
ImagerDitherContext,
ImagerFont,
ImagerInterpress,
InputFocus,
IO,
Menus,
Real,
Rope,
TerminalIO,
TIPUser,
ViewerOps
EXPORTS PlotGraph
~ BEGIN OPEN PlotGraph;
Type Definitions
VEC: TYPE ~ Vector2.VEC;
PlotList: TYPE ~ LIST OF Plot;
AxisList: TYPE ~ LIST OF Axis;
GraphList: TYPE ~ LIST OF Graph;
Viewer: TYPE ~ ViewerClasses.Viewer;
Color: TYPE ~ Imager.Color;
ViewerData: TYPE ~ REF ViewerDataRec;
ViewerDataRec: TYPE ~ RECORD [
plot: Plot,  -- circular references...
liasonEntry: ARRAY BOOLEAN OF Menus.MenuEntry,
magEntry: ARRAY BOOLEAN OF Menus.MenuEntry,
xScale: REAL ← 0.125, --mag plot width / plot width
xOr: REAL ← 0.0, --mag origin ← plot origin+ plot width*xOr
frozen: BOOLEANFALSE,--the display is no longer refreshed by outside requests
grid: BOOLEANFALSE, --an oscilloscope grid is painted on the viewer (if axis are coherent)
magOn: BOOLFALSE, --state of the magnifier
bw: BOOLFALSE, --black and white substitution of colors
upToDate: BOOLFALSE--the data is valid for painting
];
PaintData: TYPE ~ REF PaintDataRec;
PaintDataRec: TYPE ~ RECORD [
window: Rectangle,
plotOr: VEC,  -- physical coordinates
or: VEC  -- client coordinates
];
debugPlot: Plot ← NIL;
debugAxis: Axis ← NIL;
debugGraph: Graph ← NIL;
Plot management
CreatePlot: PUBLIC PROC [name: ROPENIL] RETURNS [plot: Plot] ~ {
Builds the graphic viewer, and establish the data structures.
plot ← NEW[PlotRec];
plot.name ← NEW[ROPE ← name];
plot.private ← NEW[PlotGraphPrivateRec];
CreatePlotViewer[plot];
};
CreatePlotViewer: PROC [plot: Plot] ~ {
creates a graphic viewer and the menu buttons
menu: Menus.Menu;
resetEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "Reset",
proc: SetDefaults,
clientData: NIL,
documentation: "Reset graphViewer"
];
magOffEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "MagOff",
proc: MagOnOff,
clientData: false,
documentation: "Magnifier switch"
];
magOnEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "MagOn",
proc: MagOnOff,
clientData: true,
documentation: "Magnifier switch"
];
gratEntry: Menus.MenuEntry ← Menus.CreateEntry[ -- which correspond to the "grid" things
name: "Grat",
proc: GridOnOff,
clientData: NIL,
documentation: "Draw time graticule"
];
shotEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "I.P.",
proc: IPBut,
clientData: NIL,
documentation: "Creates Interpress master"
];
activeEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "Active",
proc: Freeze,
clientData: true,
documentation: "Stops interactive refresh"
];
frozenEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "Frozen",
proc: Freeze,
clientData: false,
documentation: "Stops interactive refresh"
];
sizeXEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "sizeX",
proc: ScaleX,
clientData: NIL,
documentation: "Zoom X"
];
magSizeEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "MagSizeX",
proc: MagScaleX,
clientData: NIL,
documentation: "Zoom X mag"
];
sizeYEntry: Menus.MenuEntry ← Menus.CreateEntry[
name: "sizeY",
proc: ScaleY,
clientData: NIL,
documentation: "Zoom Y"
];
viewerData: ViewerData ← NEW[ViewerDataRec];
viewerData.magEntry ← [magOffEntry, magOnEntry];
viewerData.liasonEntry ← [frozenEntry, activeEntry];
plot.private ← NEW[PlotGraphPrivateRec];
viewerData.plot ← plot;
menu ← Menus.CreateMenu[];
Menus.AppendMenuEntry[menu: menu, entry: resetEntry];
Menus.AppendMenuEntry[menu: menu, entry: magOffEntry];
Menus.AppendMenuEntry[menu: menu, entry: gratEntry];
Menus.AppendMenuEntry[menu: menu, entry: shotEntry];
Menus.AppendMenuEntry[menu: menu, entry: activeEntry];
Menus.AppendMenuEntry[menu: menu, entry: sizeXEntry];
Menus.AppendMenuEntry[menu: menu, entry: magSizeEntry];
Menus.AppendMenuEntry[menu: menu, entry: sizeYEntry];
plot.private.viewer ← ViewerOps.CreateViewer[
flavor: $PlotGViewer,
info: [
name: plot.name^,
menu: menu,
iconic: TRUE,
column: left,
scrollable: TRUE,
hscrollable: TRUE,
border: TRUE,
guardDestroy: TRUE, -- window menu guards destroy
data: viewerData
]
];
};
CreateChild: PUBLIC PROC [mom: Plot] RETURNS [baby: Plot] ~ {
copy the main data structure in a twin pointing at the same axisList
tempAxis: AxisList;
baby ← NEW[PlotRec ← mom^];
baby.name ← NEW[ROPE ← Rope.Cat[mom.name^, "+"]];
baby.axis ← LIST[NIL];
tempAxis ← baby.axis;
FOR iAxisList: AxisList ← mom.axis, iAxisList.rest UNTIL iAxisList=NIL DO
tempAxis.rest ← LIST[NEW[AxisRec ← iAxisList.first^]];
tempAxis ← tempAxis.rest;
ENDLOOP;
baby.axis ← baby.axis.rest;
};
AwakeOthers: ENTRY PROC [plot: Plot] ~ {
ENABLE UNWIND => NULL;
BROADCAST plot.private.unlocked;
};
LockPlot: PUBLIC ENTRY PROC [plot: Plot] ~{
ENABLE UNWIND => NULL;
IF plot=NIL THEN RETURN;
IF plot.private=NIL THEN RETURN;
WHILE plot.private.locked DO WAIT plot.private.unlocked; ENDLOOP;
IF plot.private=NIL THEN RETURN; -- If somebody destroyed the plot meanwhile
plot.private.locked ← TRUE;
};
UnlockPlot: PUBLIC ENTRY PROC [plot: Plot]~{
ENABLE UNWIND => NULL;
IF plot=NIL THEN RETURN;
IF plot.private=NIL THEN RETURN;
plot.private.locked ← FALSE;
BROADCAST plot.private.unlocked;
};
RefreshPlot: PUBLIC PROC [plot: Plot, axis: AxisList ← NIL, graphs: GraphList ← NIL, within: Rectangle ← WorldRectangle, eraseFirst: BOOLFALSE] ~ {
viewerData: ViewerData;
IF plot.private=NIL THEN CreatePlotViewer[plot];
IF plot.private.viewer.iconic THEN RETURN;
viewerData ← NARROW[plot.private.viewer.data];
IF viewerData.frozen THEN RETURN;
viewerData.upToDate ← FALSE;
RefreshScreen[plot, axis, graphs, within, eraseFirst];
};
PaintGrid: PROC [context: Imager.Context, plot: Plot, page: Rectangle, d: REAL, green, blue: Imager.Color] ~ {
x, y: REAL;
unit: REAL ← NormalizeScaleAndOrigin[plot];
Draw a mid screen horizontal green (or black) vector
Imager.SetColor[context, green];
x ← page.x;
y ← page.h+gridFactor*page.y;
Imager.MaskVector[context, [x, y], [x+page.w, y]];
y ← y-3.0;
and mark 20 units by drawing well... let's see... 19 ticks I guess.
FOR i: INT IN [1..19] DO
x ← page.x+i*page.w/20.0;
Imager.MaskVector[context, [x, y], [x, y+6.0]];
ENDLOOP;
write the unit value and the origin in fixed or floating notation depending on the size of it, over the first tick.
ShowRope[page.x+page.w/20.0, y+8.0, IO.PutFR["u:%g", IO.real[unit]], context, blue];
ShowRope[page.x+4.0, y-d, IO.PutFR["%g", IO.real[plot.axis.first.bounds.x]], context, blue]
};
PaintTextInContext: PROC [context: Imager.Context, plot: Plot, page: Rectangle, font: Imager.Font] ~ {
FOR iTextList: LIST OF PlotText ← plot.texts, iTextList.rest UNTIL iTextList=NIL DO
text: PlotText ~ iTextList.first;
IF text.wrt=NIL THEN {
RotateAndShow: PROC ~ {
called through Imager.DoSaveAll to avoid having to restore the current origin and rotation (actually absence of rotation).
Imager.SetXY[context, [page.x+xScaled, page.y+yScaled]];
Imager.Move[context];
Imager.RotateT[context, text.rotation];
Imager.SetXY[context, [x, y]];
Imager.ShowRope[context, text.contents];
};
x, y: REAL;
make the appropriate x translation according to x justification
xScaled: REAL ← text.bounds.x*page.w;
yScaled: REAL ← text.bounds.y*page.h;
wScaled: REAL ← text.bounds.w*page.w;
hScaled: REAL ← text.bounds.h*page.h;
ropeSize: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, text.contents];
x ← SELECT iTextList.first.justifyX FROM
left => 0.0,
center => MAX[0.0, (wScaled-ropeSize.leftExtent)*0.5],
right => MAX[0.0, wScaled-ropeSize.leftExtent],
ENDCASE => 0.0;
make the appropriate y translation according to y justification
y ← SELECT iTextList.first.justifyY FROM
bottom => 0.0,
center => MAX[0.0, (hScaled-ropeSize.descent)*0.5],
top => MAX[0.0, hScaled-ropeSize.descent],
ENDCASE => 0.0;
then do it and forget these temporary transformations.
Imager.DoSaveAll[context, RotateAndShow];
};
ENDLOOP;
};
PaintContext: PROC [context: Imager.Context, plot: Plot, axis: AxisList, graphs: GraphList, within, page: Rectangle, font: Imager.Font, eraseFirst, color, grid: BOOLFALSE] ~ {
WritePtV: PROC [x, y: REAL, rope: ROPENIL] RETURNS [quit: BOOLFALSE] ~ {
write "rope" if any, or the hexa value of y, at location approx [x, 0]
when rope is NIL y is written only if it is # than the previous y.
the value is written top down.
x0, y0: REAL;
n: INT;
r: ROPE;
n ← Real.InlineFix[y];
IF (Real.InlineFix[prevPt.y] = n) AND rope=NIL THEN RETURN;
x0 ← (x+or.x)*mult.x+window.x;
IF x0<window.x THEN RETURN;
IF x0>window.x+window.w THEN RETURN;
y0 ← window.y;
Draw2d.Line[context, [x0, y0], [x0, y0+tinyTick], solid, zip];
r ← IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE];
ShowRopeV[context, r, [x0 - 2.0, y0+spaceOverTick], font];
prevPt ← [x0, y];
};
WritePtH: PROC [x, y: REAL, rope: ROPENIL] RETURNS [quit: BOOLFALSE] ~ {
write "rope" if any, or the hexa value of y, at location approx [x, 0]
the value is written as we are used to in Indo-European languages.
x0, y0: REAL;
n: INT;
r: ROPE;
n ← Real.InlineFix[y];
IF (Real.InlineFix[prevPt.y] = n) AND rope=NIL THEN RETURN;
x0 ← (x+or.x)*mult.x+window.x;
IF x0<window.x THEN RETURN;
IF x0>window.x+window.w THEN RETURN;
y0 ← window.y;
Draw2d.Line[context, [x0, y0], [x0, y0+tinyTick], solid, zip];
Imager.SetXY[context, [x0 - 2.0, y0+spaceOverTick]];
r ← IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE];
Imager.ShowRope[context, r];
prevPt ← [x0, y];
};
DrawAndMarkPt: PROC [x, y: REAL, rope: ROPENIL] RETURNS [quit: BOOLFALSE] ~ {
draw the vector from the previous pt to the current on with clipping in window, except of course the first point.
IF DrawPt[x, y, rope] THEN RETURN[TRUE];
IF previousVisible THEN Draw2d.Mark[context, prevPt, dot];
};
DrawPt: PROC [x, y: REAL, rope: ROPENIL] RETURNS [quit: BOOLFALSE] ~ {
draw the vector from the previous pt to the current on with clipping in window, except of course the first point.
pt: VEC;
visible: BOOLEAN;
IF firstPoint THEN {
prevPt ← [(x+or.x)*mult.x+window.x, (y+or.y)*mult.y+window.y];
firstPoint ← FALSE;
previousVisible ← IsPtInArea[prevPt, window];
}
ELSE {
pt ← [(x+or.x)*mult.x+window.x, (y+or.y)*mult.y+window.y];
visible ← IsPtInArea[pt, window];
SELECT TRUE FROM
visible AND previousVisible => Draw2d.Line[context, prevPt, pt, solid, zip];
visible AND ~previousVisible => Draw2d.Line[context, ClipVect[pt, prevPt, window], pt, solid, zip];
~visible AND previousVisible => Draw2d.Line[context, prevPt, ClipVect[prevPt, pt, window], solid, zip];
~visible AND ~previousVisible => {
pt1, pt2: VEC;
IF pt.y~=prevPt.y THEN {
pt1 ← ClipVect[prevPt, pt, window];
pt2 ← ClipVect[pt, prevPt, window];
IF pt1.x~=pt2.x OR pt1.y~=pt2.y THEN Draw2d.Line[context, pt1, pt2, solid, zip];
}
};
ENDCASE;
prevPt ← pt;
previousVisible ← visible;
}
};
axisFound, graphFound: BOOL;
quit: BOOLFALSE;
wwhite, rred, ggreen, bblue, ppuce: Imager.Color;
charHeight: REAL ← ImagerFont.FontBoundingBox[font].ascent+1.0;
axisChoice: BOOLEAN = ~(axis=NIL); --test only once the presence of an axis specification
graphsChoice: BOOLEAN = ~(graphs=NIL); -- the same for graphs
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
selectedAxis: Axis ← GetSelectedAxis[plot];
window: Rectangle ← [page.x, page.y, page.w, 1.0];
or: VEC ← [0.0, 0.0];
prevPt: VEC ← [0.0, 0.0];
mult: VEC ← [1.0, 1.0];
firstPoint: BOOLEAN ← TRUE;
previousVisible: BOOLEAN ← TRUE;
zip: Draw2d.Zip;
If the viewer is on the B&W display we will draw black vectors only, due to the poor readability of the patterns of bits simulating color on the B&W screen.
IF color THEN {
wwhite ← grey;
rred ← red;
ggreen ← green;
bblue ← blue;
ppuce ← puce;
}
ELSE {
wwhite ← white;
rred ← black;
ggreen ← black;
bblue ← black;
ppuce ← black;
};
IF eraseFirst THEN {
Show the global texts on the full screen.
Imager.SetColor[context, bblue];
PaintTextInContext[context, plot, page, font];
};
Draw the curves
IF plot.axis=NIL THEN RETURN;
Verify the coherence of the axis width along the plot to enable the grid and count the number and size of the various axis flavours.
hAxis ← SetHeights[plot, charHeight, IF grid THEN page.h-page.y ELSE page.h];
Draw the grid, after adjusting the x scale to a "round decimal" value if needed.
IF grid AND eraseFirst THEN PaintGrid[context, plot, page, charHeight, ggreen, bblue];
Draw the various axis after the other stuff so that the significant lines will overwrite anything else.
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
thisAxis: Axis ~ iAxisList.first;
xoffset, inter: REAL ← 0.0;
colorList: LIST OF Color;
if a specific axis list was given let's check it, else (which is common) lets draw.
IF axisChoice THEN {
axisFound ← FALSE;
FOR jAxisList: AxisList ← axis, jAxisList.rest UNTIL jAxisList=NIL DO
IF jAxisList.first=thisAxis THEN {
axisFound ← TRUE;
EXIT;
};
ENDLOOP;
}
ELSE axisFound ← TRUE;
draw the axis and its furniture
IF axisFound THEN {
xoffset and colorList for multi graphs per axis
xoffset ← 2.0;
colorList ← graphColorList;
If this axis contains vertical numbers, then scale it according to the max number of chars
inter ← AxisHeight[thisAxis, hAxis];
prepair the COMMON (COMMON is a trade mark of FORTRAN and sons) for the drawPt procs.
or.x ← -thisAxis.bounds.x;
or.y ← -thisAxis.bounds.y;
mult.x ← page.w/thisAxis.bounds.w;
mult.y ← (inter-charHeight-3.0)/thisAxis.bounds.h;
window.h ← inter;
IF eraseFirst THEN {
Draw both local axis, if needed.
IF thisAxis.axisData[X].visible THEN {
Imager.SetColor[context, ggreen];
Imager.MaskVector[context, [window.x, window.y], [window.x+page.w, window.y]];
};
IF thisAxis.axisData[Y].visible THEN {
Imager.SetColor[context, ggreen];
Imager.MaskVector[context, [window.x, window.y], [window.x, window.y+inter]];
};
then display the name of the axis under its baseline
IF thisAxis=selectedAxis THEN ShowRopeInv[window.x+xAxisName, window.y+yAxisName-charHeight, thisAxis.name, context, wwhite, bblue, font]
ELSE ShowRopeInv[window.x+xAxisName, window.y+yAxisName-charHeight, thisAxis.name, context, bblue, wwhite, font];
};
we are not so far from what we really wanted to do : display [x, y] pairs
FOR iGraphList: GraphList ← thisAxis.graphs, iGraphList.rest UNTIL iGraphList=NIL DO
thisGraph: Graph ~ iGraphList.first;
check if a graph list was specified for the matching of this one
IF graphsChoice THEN {
graphFound ← FALSE;
FOR jGraphList: GraphList ← graphs, jGraphList.rest UNTIL jGraphList=NIL DO
IF jGraphList.first=thisGraph THEN {
graphFound ← TRUE;
EXIT;
};
ENDLOOP;
IF ~graphFound THEN LOOP;
};
here we are ! set the appropriate color and ask the client for data, sending him the draw proc appropriate for its religion.
firstPoint ← TRUE;
Imager.SetColor[context, colorList.first];
zip ← Draw2d.GetZip[context];
quit ← thisGraph.class.enumerate[plot, thisGraph, within, SELECT thisAxis.style FROM
hexaH => WritePtH,
hexaV => WritePtV,
mark => DrawAndMarkPt,
ENDCASE => DrawPt];
Draw2d.ReleaseZip[zip];
IF quit THEN RETURN;
name the graph in the same color.
IF eraseFirst THEN ShowRope[window.x+xoffset, window.y+inter*0.5, thisGraph.name, context, colorList.first];
prepair the new color and the new name offset for the next graph
xoffset ← xoffset+ImagerFont.RopeEscapement[font, thisGraph.name].x+5.0;
IF colorList.rest#NIL THEN colorList ← colorList.rest ELSE colorList ← graphColorList;
ENDLOOP;
};
even if this axis was not shown, it is worth update the y origin
window.y ← window.y+inter;
ENDLOOP;
};
RefreshScreen: PROC [plot: Plot, axis: AxisList ← NIL, graphs: GraphList ← NIL, within: Rectangle ← WorldRectangle, eraseFirst: BOOLFALSE] ~ {
DoRefreshScreen: PROC [viewer: Viewer, context: Imager.Context] ~ {
Sceen specific initializations
grid: BOOL ← viewerData.grid;
color: BOOL ← viewer.column=color;
page: Rectangle ← [xSpace, ySpace, viewer.cw-2*xSpace, viewer.ch-2*ySpace];
Imager.SetColor[context, IF color THEN grey ELSE white];
Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; -- fill screen
Imager.SetFont[context, currentFont];
PaintContext[context, plot, axis, graphs, within, page, currentFont, eraseFirst, color, grid];
If the magnifier is not on, just show the limits (2 vertical vectors) of it.
IF ~viewerData.magOn AND eraseFirst THEN DrawMag[viewer, context];
};
viewerData: ViewerData ← NARROW[plot.private.viewer.data];
IF plot#viewerData.plot THEN ERROR;
some data structure went corrupted somehow...
DrawInViewer[plot.private.viewer, DoRefreshScreen, FALSE];
};
ProduceIPMaster: PUBLIC PROC [plot: Plot] ~ {
Creates a file named "///Temp/PlotGraph/nameOfThisPlot.interpress" which contains an InterPress master reproducing more or less the screen when it was buttonned.
DoPrint: PROC [context: Imager.Context] ~ {
essentially the same structure as for the screen except for the selective tests, for a lot of "suitably adjusted" constants to fit a page, and of course the fonts and the width of the stroke.
grid: BOOL ← viewerData.grid;
color: BOOL ← ipInColor;
Interpress specific stuff: adjusting the shape of the lines
Imager.SetStrokeWidth[context, fineStroke];
Imager.SetStrokeEnd[context, round];
Imager.SetStrokeJoint[context, round];
Imager.SetFont[context, pressFont];
PaintContext[context, plot, NIL, NIL, within, pageSize, pressFont, TRUE, color, grid];
};
viewerData: ViewerData ← NARROW[plot.private.viewer.data];
fileName: Rope.ROPE ← Rope.Cat["///Temp/PlotGraph/", plot.name^, ".interpress"];
within: Rectangle ← [plot.lowerBounds.x, plot.lowerBounds.y, plot.upperBounds.x-plot.lowerBounds.x, plot.upperBounds.y - plot.lowerBounds.y];
ip: ImagerInterpress.Ref ← ImagerInterpress.Create[fileName];
rgbLinear: Imager.ColorOperator ~ ImagerColor.NewColorOperatorRGB[255];
ImagerInterpress.DeclareColorOperator[ip, rgbLinear];
ImagerInterpress.DeclareFont[ip, pressFont];
ImagerInterpress.DoPage[ip, DoPrint, pressScale];
ImagerInterpress.Close[ip];
TerminalIO.PutRopes[fileName, " created\n"];
};
DeletePlot: PUBLIC PROC [plot: Plot] ~ {
the client way to kill the viewer, and then empty the plot. see the DestroyProc
viewer: Viewer ← plot.private.viewer;
ViewerOps.DestroyViewer[viewer];
};
Menu Actions
SetDefaults: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ {
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ← viewerData.plot;
IF viewerData.magOn THEN MagOnOff[viewer, true, yellow, FALSE, FALSE];
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← plot.lowerBounds.x;
iAxisList.first.bounds.y ← plot.lowerBounds.y;
iAxisList.first.bounds.w ← plot.upperBounds.x - plot.lowerBounds.x;
iAxisList.first.bounds.h ← plot.upperBounds.y - plot.lowerBounds.y;
ENDLOOP;
UnlockPlot[plot];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE];
};
GridOnOff: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { 
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
viewerData.grid ← ~viewerData.grid;
RefreshScreen[plot: viewerData.plot, within: WorldRectangle, eraseFirst: TRUE]
};
IPBut: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { 
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
ProduceIPMaster[viewerData.plot];
};
Freeze: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { 
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ~ viewerData.plot;
liasonState: BOOLEANNARROW[clientData, REF BOOLEAN]^;
Menus.ReplaceMenuEntry[viewer.menu, viewerData.liasonEntry[liasonState], viewerData.liasonEntry[~liasonState]];
viewerData.frozen ← ~viewerData.frozen;
ViewerOps.PaintViewer[viewer, menu];
};
MagScaleX: PROC [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ {
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ~ viewerData.plot;
alpha: REAL;
IF mouseButton=yellow THEN RETURN;
alpha ← IF mouseButton=red THEN 0.5 ELSE 2.0;
IF shift THEN alpha ← alpha*alpha;
IF viewerData.xScale*alpha>1.0 THEN RETURN;
IF viewerData.magOn THEN {
viewerData.xScale ← viewerData.xScale*alpha;
PlotScaleX[plot, alpha];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
}
ELSE {
MagExpand: PROC [viewer: Viewer, context: Imager.Context] ~ {
zip: Draw2d.Zip;
viewerData: ViewerData ~ NARROW[viewer.data];
x: REAL ← viewer.cw*(viewerData.xOr+viewerData.xScale);
Imager.SetColor[context, tempColor];
zip ← Draw2d.GetZip[context];
Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip];
viewerData.xScale ← viewerData.xScale*alpha;
x ← viewer.cw*(viewerData.xOr+viewerData.xScale);
Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip];
Draw2d.ReleaseZip[zip];
};
DrawInViewer[viewer, MagExpand, FALSE];
};
};
ScaleX: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ {
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ~ viewerData.plot;
alpha: REAL ← 2.0;
IF mouseButton=yellow THEN RETURN;
IF mouseButton=red THEN alpha ← 0.5;
IF shift THEN alpha ← alpha*alpha;
viewerData.xScale ← MIN[1.0, viewerData.xScale/alpha];
IF ~viewerData.magOn THEN {
PlotScaleX[plot, alpha];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
};
ScaleY: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ {
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ~ viewerData.plot;
alpha: REAL ← 2.0;
IF mouseButton=yellow THEN RETURN;
IF mouseButton=red THEN alpha ← 0.5;
IF shift THEN alpha ← alpha*alpha;
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.h ← alpha*iAxisList.first.bounds.h;
ENDLOOP;
UnlockPlot[plot];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
MagOnOff: PROCEDURE [parent: REF ANY, clientData: REF ANY, mouseButton: Menus.MouseButton, shift, control: BOOL] ~ { 
viewer: Viewer ~ NARROW[parent];
viewerData: ViewerData ~ NARROW[viewer.data];
plot: Plot ~ viewerData.plot;
magState: BOOLEANNARROW[clientData, REF BOOLEAN]^;
IF magState#viewerData.magOn THEN ERROR; -- ?????
Menus.ReplaceMenuEntry[viewer.menu, viewerData.magEntry[magState], viewerData.magEntry[~magState]];
ViewerOps.PaintViewer[viewer, menu];
IF viewerData.magOn THEN {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.w ← iAxisList.first.bounds.w/viewerData.xScale;
iAxisList.first.bounds.x ← iAxisList.first.bounds.x-iAxisList.first.bounds.w*viewerData.xOr;
ENDLOOP;
UnlockPlot[plot];
}
ELSE {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← iAxisList.first.bounds.x+iAxisList.first.bounds.w*viewerData.xOr;
iAxisList.first.bounds.w ← viewerData.xScale*iAxisList.first.bounds.w;
ENDLOOP;
UnlockPlot[plot];
};
viewerData.magOn ← ~viewerData.magOn;
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE];
};
ViewerClasses Definitions
PaintProc: ViewerClasses.PaintProc ~ { -- repaint screen for updates
PROC [self: Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL]
SELECT whatChanged FROM
NIL  => {   -- window resized, redraw
viewerData: ViewerData ~ NARROW[self.data];
RefreshScreen[plot: viewerData.plot, within: WorldRectangle, eraseFirst: TRUE]
};
ENDCASE  => {  -- call path for DrawInViewer
NARROW[whatChanged, REF PROC[Viewer, Imager.Context]]^[self, context];
};
}; 
NotifyProc: ViewerClasses.NotifyProc ~ {
NotifyProc ~ PROC[self: Viewer, input: LIST OF REF ANY]
x, y, t, v: REAL;
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ~ viewerData.plot;
IF InputFocus.GetInputFocus[].owner#self THEN InputFocus.SetInputFocus[self, plot];
IF ISTYPE[input.first, TIPUser.TIPScreenCoords] -- If input is coords from mouse
THEN {
mousePos: TIPUser.TIPScreenCoords ← NARROW[input.first];
x ← mousePos.mouseX;
y ← mousePos.mouseY;
IF ISTYPE[input.rest.first, ATOM] THEN {
actionName: ATOMNARROW[input.rest.first];
thisAxis: Axis;
[thisAxis, t, v] ← GetAxis[self, x, y];
SELECT actionName FROM
$Refresh => {
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$WriteCoord => IF thisAxis#NIL THEN{
IF thisAxis.style=analog THEN DrawRope[[x, y], IO.PutFR["t:%g,v:%g", IO.real[t], IO.real[v]], self, IF self.column= color THEN red ELSE black, currentFont]
ELSE DrawRope[[x, y], IO.PutFR["t:%d", IO.real[t]], self, IF self.column= color THEN red ELSE black, currentFont];
RETURN;
};
$MoveMag => {
MoveMag: PROC [viewer: Viewer, context: Imager.Context] ~ {
zip: Draw2d.Zip;
viewerData: ViewerData ~ NARROW[viewer.data];
xx: REAL ← viewer.cw*viewerData.xOr;
Imager.SetColor[context, tempColor];
zip ← Draw2d.GetZip[context];
Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip];
xx ← xx+viewer.cw*viewerData.xScale;
Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip];
viewerData.xOr ← x/viewer.cw;
Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip];
xx ← x+viewer.cw*viewerData.xScale;
Draw2d.Line[context, [xx, 0.0], [xx, viewer.ch], solid, zip];
Draw2d.ReleaseZip[zip];
};
IF ~viewerData.magOn THEN DrawInViewer[self, MoveMag, FALSE];
ask the viewer procs to call you back
};
$SelectAxis => {
SelectAxis[plot, thisAxis];
};
$MoveAxis => {
selectedAxis: Axis ← GetSelectedAxis[plot];
[] ← MoveAxis[plot, selectedAxis, thisAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$CopyAxis => {
selectedAxis: Axis ← GetSelectedAxis[plot];
[] ← InsertAxis[plot, CopyAxis[thisAxis], selectedAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$RevMoveAxis => {
selectedAxis: Axis ← GetSelectedAxis[plot];
[] ← MoveAxis[plot, thisAxis, selectedAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$DelClickedAxis => {
[] ← DeleteAxis[plot, thisAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$XChange => {
selectedAxis: Axis ← GetSelectedAxis[plot];
[] ← XChangeAxis[plot, thisAxis, selectedAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$DelSelection => {
DeleteSelection[plot];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$ChgStyle => {
LockPlot[plot];
SELECT thisAxis.style FROM
analog  => thisAxis.style ← mark;
mark  => thisAxis.style ← analog;
hexaH  => thisAxis.style ← hexaV;
hexaV  => thisAxis.style ← hexaH;
ENDCASE => ERROR;
UnlockPlot[plot];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$Fork => {
ForkAxis[plot, thisAxis];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
$Join => {
selectedAxis: Axis ← GetSelectedAxis[plot];
thisGraph: Graph ← thisAxis.graphs.first;
IF thisGraph.name=NIL THEN thisGraph.name ← thisAxis.name;
[] ← MoveGraphInAxis[plot, selectedAxis, thisGraph];
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
ENDCASE => {
IF plot.eventProc#NIL THEN plot.eventProc[plot, thisAxis, [t, v], actionName];
};
};
};
};
ModifyProc: ViewerClasses.ModifyProc ~ {
= PROC [self: Viewer, change: ModifyAction], ModifyAction: TYPE = {set, push, pop, kill}
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ~ viewerData.plot;
SELECT change FROM
set => NULL;
push => NULL;
pop => NULL;
kill => SelectAxis[plot, NIL];
ENDCASE;
};
DestroyProc: ViewerClasses.DestroyProc ~ {
~ PROC [self: Viewer] This will be called when the viewer is destroyed
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
LockPlot[plot];
plot.axis ← NIL;
AwakeOthers[plot];
plot.private ← NIL;
};
GetProc: ViewerClasses.GetProc ~ {
PROC [self: Viewer, op: ATOMNIL] RETURNS [data: REF ANY];
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
WITH plot.selection.first SELECT FROM
axis: Axis => RETURN[axis.name];
graph: Graph => RETURN[graph.name];
text: PlotText => RETURN[text.contents];
ENDCASE => NULL;
};
VScrollProc: ViewerClasses.ScrollProc ~ {    
Acts on scrollbar mouse hits
ScrollProc ~ PROC[self: Viewer, op: ScrollOp, amount: INTEGER] RETURNS[top, bottom: INTEGER ← LAST[INTEGER]];
ScrollOp: TYPE = {query, up, down, thumb}
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
IF plot.axis=NIL THEN RETURN;
SELECT op FROM
up => {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.y ← iAxisList.first.bounds.y - amount*iAxisList.first.bounds.h/self.ch;
ENDLOOP;
UnlockPlot[plot];
};
down => {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.y ← iAxisList.first.bounds.y + amount*iAxisList.first.bounds.h/self.ch;
ENDLOOP;
UnlockPlot[plot];
};
thumb => {
newY: REAL ← amount*0.01*(plot.upperBounds.y-plot.lowerBounds.y)+plot.lowerBounds.y;
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.y ← newY;
ENDLOOP;
UnlockPlot[plot];
};
query => {
top ← 0;
bottom ← 100;
IF plot.upperBounds.y=plot.lowerBounds.y THEN RETURN;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
top ← MAX[top, Real.InlineFixI[100.0*(plot.upperBounds.y-iAxisList.first.bounds.y-iAxisList.first.bounds.h) /(plot.upperBounds.y-plot.lowerBounds.y)]];
bottom ← MIN[bottom, Real.InlineFixI[100.0*(plot.upperBounds.y-iAxisList.first.bounds.y) /(plot.upperBounds.y-plot.lowerBounds.y)]];
ENDLOOP;
RETURN;
};
ENDCASE;
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
HScrollProc: ViewerClasses.HScrollProc ~ {    
Acts on hscrollbar mouse hits
HScrollProc ~ PROC[self: Viewer, op: HScrollOp, amount: INTEGER] RETURNS[left, right: INTEGER ← LAST[INTEGER]];
HScrollOp: TYPE = {query, left, right, thumb}
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
IF plot.axis=NIL THEN RETURN;
SELECT op FROM
left => {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← iAxisList.first.bounds.x + amount*iAxisList.first.bounds.w/self.cw;
ENDLOOP;
UnlockPlot[plot];
};
right => {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← iAxisList.first.bounds.x - amount*iAxisList.first.bounds.w/self.cw;
ENDLOOP;
UnlockPlot[plot];
};
thumb => {
newX: REAL ← amount*0.01*(plot.upperBounds.x-plot.lowerBounds.x)+plot.lowerBounds.x;
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← newX;
ENDLOOP;
UnlockPlot[plot];
};
query => {
left ← 0;
right ← 100;
IF plot.upperBounds.x=plot.lowerBounds.x THEN RETURN;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
left ← MAX[left, Real.InlineFixI[100.0*(plot.upperBounds.x-iAxisList.first.bounds.x-iAxisList.first.bounds.w) /(plot.upperBounds.x-plot.lowerBounds.x)]];
right ← MIN[right, Real.InlineFixI[100.0*(plot.upperBounds.x-iAxisList.first.bounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]];
ENDLOOP;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
left ← MAX[left, Real.InlineFixI[100.0*(iAxisList.first.bounds.x-plot.lowerBounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]];
right ← MIN[right, Real.InlineFixI[100.0*(iAxisList.first.bounds.x+iAxisList.first.bounds.w-plot.lowerBounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]];
ENDLOOP;
RETURN;
};
ENDCASE;
RefreshScreen[plot: plot, within: WorldRectangle, eraseFirst: TRUE]
};
AdjustProc: ViewerClasses.AdjustProc ~ {
= PROC [self: Viewer] RETURNS [adjusted: BOOLFALSE]
ViewerBLT will assume that a full repaint is required if adjusted=TRUE.
adjusted ← TRUE;
};
Data Structure Modifications
DeleteSelection : PUBLIC PROC [plot: Plot] ~ {
axisList: AxisList ← plot.axis;
IF plot.selection=NIL THEN RETURN;
UNTIL plot.selection=NIL DO
WITH plot.selection.first SELECT FROM
axis: Axis => DeleteAxis[plot, axis];
graph: Graph => NULL;
point: GraphPoint => NULL;
text: PlotText => DeleteText[plot, text];
ENDCASE => NULL;
plot.selection ← plot.selection.rest;
ENDLOOP;
};
InsertAxis: PUBLIC PROC [plot: Plot, axis, after: Axis] ~ {
axisList: AxisList ← plot.axis;
IF axis=NIL THEN RETURN;
LockPlot[plot];
IF after=NIL THEN plot.axis ← CONS[axis, plot.axis]
ELSE {
UNTIL axisList.first=after OR axisList=NIL DO axisList ← axisList.rest ENDLOOP;
IF axisList=NIL THEN {UnlockPlot[plot]; RETURN};
axisList.rest ← CONS[axis, axisList.rest];
};
UnlockPlot[plot];
};
DeleteAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ {
axisList: AxisList ← plot.axis;
IF axisList=NIL THEN RETURN;
LockPlot[plot];
IF axisList.first=axis THEN {plot.axis ← plot.axis.rest; UnlockPlot[plot]; RETURN};
UNTIL axisList.rest.first=axis DO
axisList ← axisList.rest;
IF axisList.rest=NIL THEN {UnlockPlot[plot]; RETURN};
ENDLOOP;
axisList.rest ← axisList.rest.rest;
UnlockPlot[plot];
};
MoveAxis: PUBLIC PROC [plot: Plot, axis, after: Axis] ~ {
DeleteAxis[plot, axis];
InsertAxis[plot, axis, after];
};
XChangeAxis: PUBLIC PROC [plot: Plot, axis1, axis2: Axis] ~ {
axisList: AxisList ← plot.axis;
IF axisList=NIL THEN RETURN;
LockPlot[plot];
UNTIL axisList=NIL DO
SELECT axisList.first FROM
axis1 => axisList.first ← axis2;
axis2 => axisList.first ← axis1;
ENDCASE;
axisList ← axisList.rest;
ENDLOOP;
UnlockPlot[plot];
};
CopyAxis: PUBLIC PROC [axis: Axis] RETURNS [duplicate: Axis] ~ {
duplicate ← NEW[AxisRec ← [
graphs: axis.graphs,
bounds: axis.bounds,
name: axis.name,
style: axis.style,
maxChars: axis.maxChars,
axisData: axis.axisData
]];
};
ForkAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ {
axisList: AxisList ← plot.axis;
newAxisList, firstItem: AxisList;
IF axisList=NIL THEN RETURN;
IF axis.graphs.rest=NIL THEN RETURN;
FOR iGraphList: GraphList ← axis.graphs, iGraphList.rest UNTIL iGraphList=NIL DO
thisGraph: Graph ← iGraphList.first;
newAxisList ← CONS[NEW[PlotGraph.AxisRec ← [
graphs: LIST[thisGraph],
bounds: axis.bounds,
name: thisGraph.name,
style: axis.style,
axisData: axis.axisData
]], newAxisList];
thisGraph.name ← NIL;
IF firstItem=NIL THEN firstItem ← newAxisList;
ENDLOOP;
LockPlot[plot];
IF axisList.first=axis THEN {
firstItem.rest ← plot.axis.rest;
plot.axis ← newAxisList;
}
ELSE {
UNTIL axisList.rest.first=axis DO
axisList ← axisList.rest;
IF axisList=NIL THEN {UnlockPlot[plot]; RETURN};
ENDLOOP;
IF axisList.rest#NIL THEN firstItem.rest ← axisList.rest.rest;
axisList.rest ← newAxisList;
};
UnlockPlot[plot];
};
MoveGraphInAxis: PUBLIC PROC [plot: Plot, axis: Axis, graph: Graph] ~ {
axisList: AxisList ← plot.axis;
IF axis=NIL THEN RETURN;
IF graph=NIL THEN RETURN;
LockPlot[plot];
IF axis.graphs.first.name=NIL THEN {
axis.graphs.first.name ← axis.name;
axis.name ← "#";
};
axis.graphs ← CONS[graph, axis.graphs];
UnlockPlot[plot];
};
NormalizeScaleAndOrigin: PUBLIC PROC [plot: Plot] RETURNS[unit: REAL]~ {
unit is 1, 2 or 5 times a power of ten in client x coordinates, which are supposed to be coherent all over the graphs.
test, excess: REAL;
unit ← Normalize[plot.axis.first.bounds.w/20.0];
excess ← plot.axis.first.bounds.x-Real.Floor[plot.axis.first.bounds.x/unit]*20.0;
test ← plot.axis.first.bounds.w-Real.Floor[plot.axis.first.bounds.w/unit]*20.0;
IF test=0.0 AND excess=0.0 THEN RETURN;
LockPlot[plot];
scale adequately the axis.
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← iAxisList.first.bounds.x - excess;
iAxisList.first.bounds.w ← 20.0*unit;
ENDLOOP;
UnlockPlot[plot];
};
Utilities
DrawInViewer: PROC [viewer: Viewer, proc: PROC [Viewer, Imager.Context], clear: BOOL ← FALSE] ~ {
Pass procedure to PaintProc
drawProc: REF PROC[Viewer, Imager.Context] ← NIL;
TRUSTED { drawProc ← NEW[PROC[Viewer, Imager.Context] ← proc]; };
ViewerOps.PaintViewer[
viewer: viewer,-- pass record to viewer painter
hint: client,
whatChanged: drawProc,
clearClient: clear
];
};
SetHeights: PROC [plot: Plot, charHeight, yActualSize: REAL] RETURNS [hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0]] ~ {
nAxis, cAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
inter: REAL;
IF plot.axis=NIL THEN RETURN;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
nAxis[iAxisList.first.style] ← nAxis[iAxisList.first.style] + 1.0;
cAxis[iAxisList.first.style] ← cAxis[iAxisList.first.style] + iAxisList.first.maxChars+1.0;
ENDLOOP;
compute the unitary heigth of each kind for this viewer
inter ← yActualSize /(nAxis[analog]+nAxis[mark]+nAxis[hexaH]+cAxis[hexaV]);
hAxis[hexaH] ← MIN[inter, 2*charHeight];
hAxis[hexaV] ← MIN[inter, charHeight];
IF (nAxis[analog]+nAxis[mark])#0 THEN hAxis[analog] ← (yActualSize-hAxis[hexaH]*nAxis[hexaH]-hAxis[hexaV]*cAxis[hexaV]) /(nAxis[analog]+nAxis[mark])
ELSE {
IF nAxis[hexaH]#0.0 THEN hAxis[hexaH] ← (yActualSize-hAxis[hexaV]*cAxis[hexaV]) /nAxis[hexaH]
ELSE hAxis[hexaV] ← inter;
};
hAxis[mark] ← hAxis[analog];
};
PlotScaleX: PROC [plot: Plot, alpha: REAL] ~ {
LockPlot[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.w ← alpha*iAxisList.first.bounds.w;
ENDLOOP;
UnlockPlot[plot];
};
DrawMag: PROC [viewer: Viewer, context: Imager.Context] ~ {
zip: Draw2d.Zip;
viewerData: ViewerData ~ NARROW[viewer.data];
x: REAL ← viewer.cw*viewerData.xOr;
Imager.SetColor[context, tempColor];
zip ← Draw2d.GetZip[context];
Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip];
x ← x+viewer.cw*viewerData.xScale;
Draw2d.Line[context, [x, 0.0], [x, viewer.ch], solid, zip];
Draw2d.ReleaseZip[zip];
};
ClipVect: PROC [fix, mov: VEC, area: Rectangle] RETURNS [newPt: VEC] ~ {
fix is inside area, mov outside. newPt is the intersection of the vector [fix, mov] and the area boundaries.
slope: REAL;
newPt ← mov;
IF fix.x~=mov.x THEN {
slope ← (fix.y-mov.y)/(fix.x-mov.x);
IF newPt.x<area.x THEN {newPt.x ← area.x; newPt.y ← slope*(newPt.x-fix.x) + fix.y};
IF newPt.x>area.x+area.w THEN {newPt.x ← area.x+area.w; newPt.y ← slope*(newPt.x-fix.x) + fix.y};
};
IF fix.y~=mov.y THEN {
slope ← (fix.x-mov.x)/(fix.y-mov.y);
IF newPt.y<area.y THEN {newPt.y ← area.y; newPt.x ← (newPt.y-fix.y)*slope + fix.x};
IF newPt.y>area.y+area.h THEN {newPt.y ← area.y+area.h; newPt.x ← (newPt.y-fix.y)*slope + fix.x};
};
};
IsPtInArea: PROC [pt: VEC, area: Rectangle] RETURNS [inside: BOOLEAN ← FALSE] ~ {
IF pt.x<area.x THEN RETURN;
IF pt.y<area.y THEN RETURN;
IF pt.x>area.x+area.w THEN RETURN;
IF pt.y>area.y+area.h THEN RETURN;
inside ← TRUE;
};
ShowRopeInv: PROC [x, y: REAL, rope: Rope.ROPE, context: Imager.Context, color, background: Imager.Color, font: Imager.Font] ~ {
ropeSize: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[font, rope];
Imager.SetColor[context, background];
Imager.MaskRectangle[context, [x-ropeSize.leftExtent, y-ropeSize.descent, ropeSize.leftExtent+ropeSize.rightExtent, ropeSize.descent+ropeSize.ascent]];
Imager.SetXY[context, [x, y]];
Imager.SetColor[context, color];
Imager.ShowRope[context, rope];
};
ShowRopeV: PROC[context: Imager.Context, rope: Rope.ROPE, pos: VEC, font: Imager.Font] ~ {
charHeight: REAL ← ImagerFont.FontBoundingBox[font].ascent+1.0;
IF rope=NIL THEN RETURN;
FOR i: INT ← Rope.Length[rope]-1, i-1 UNTIL i=-1 DO
Imager.SetXY[context, pos];
Imager.ShowChar[context, Rope.InlineFetch[rope, i]];
pos.y ← pos.y+ charHeight;
ENDLOOP;
};
ShowRope: PROC[x, y: REAL, rope: Rope.ROPE, context: Imager.Context, color: Imager.Color] ~ {
IF rope=NIL THEN RETURN;
Imager.SetColor[context, color];
Imager.SetXY[context, [x, y]];
Imager.ShowRope[context, rope];
};
DrawRopeV: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color: Imager.Color, font: Imager.Font] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetFont[context, font];
Imager.SetColor[context, color];
ShowRopeV[context, rope, pos, font];
};
DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back
};
DrawRope: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color: Imager.Color, font: Imager.Font] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetFont[context, font];
ShowRope[pos.x, pos.y, rope, context, color];
};
DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back
};
DrawRopeInv: PUBLIC PROC[pos: VEC, rope: Rope.ROPE, viewer: Viewer, color, background: Imager.Color, font: Imager.Font] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetFont[context, font];
ShowRopeInv[pos.x, pos.y, rope, context, color, background, font];
};
DrawInViewer[viewer, DoDrawRope, FALSE];-- ask the viewer procs to call you back
};
Normalize: PROC [r: REAL] RETURNS [dec: REAL] ~ {
rounds r to the closest "round decimal number"
neg: BOOL ← r<0.0;
unsigned: REALIF neg THEN -r ELSE r;
base: REALIF unsigned<1.0 THEN 10.0 ELSE 0.1;
dec ← 1.0;
IF r#0.0 THEN UNTIL unsigned>=1.0 AND unsigned<10.0 DO
dec ← dec/base;
unsigned ← unsigned*base;
ENDLOOP;
dec ← SELECT TRUE FROM
unsigned<1.5 => dec,
unsigned<3.5 => 2.0*dec,
unsigned<7.5 => 5.0*dec,
ENDCASE => 10.0*dec;
IF neg THEN dec ← -dec;
};
GetSelectedAxis: PUBLIC PROC[plot: Plot] RETURNS [axis: Axis] ~ {
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, Axis] THEN {
axis ← NARROW[iList.first];
EXIT;
};
ENDLOOP;
};
GetSelectedAxisList: PUBLIC PROC[plot: Plot] RETURNS [axisList: LIST OF Axis] ~ {
axisList ← NIL;
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, Axis] THEN axisList ← CONS[NARROW[iList.first, Axis], axisList];
ENDLOOP;
};
SelectAxis: PUBLIC PROC [plot: Plot, axis: Axis] ~ {
selectedAxis: Axis ← GetSelectedAxis[plot];
self : Viewer ← plot.private.viewer;
charHeight: REAL ← ImagerFont.FontBoundingBox[currentFont].ascent+1.0;
viewerData: ViewerData ~ NARROW[self.data];
xor, yor: REAL;
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
bblue: Imager.Color ← IF self.column=color THEN blue ELSE black;
wwhite: Imager.Color ← IF self.column=color THEN grey ELSE white;
IF axis=selectedAxis THEN RETURN;
plot.selection ← IF axis=NIL THEN NIL ELSE LIST[axis];
xor ← xSpace;
yor ← ySpace;
hAxis ← SetHeights[plot, charHeight, IF viewerData.grid THEN self.ch-3*ySpace ELSE self.ch-2*ySpace];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
thisAxis: Axis = iAxisList.first;
inter: REAL ← AxisHeight[thisAxis, hAxis];
IF thisAxis=selectedAxis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName-charHeight], thisAxis.name, self, bblue, wwhite, currentFont];
IF thisAxis=axis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName-charHeight], thisAxis.name, self, wwhite, bblue, currentFont];
yor ← yor+inter;
ENDLOOP;
};
AxisHeight: PROC [axis: Axis, hAxis: ARRAY DrawingStyle OF REAL] RETURNS [h: REAL] ~ {
h ← IF axis.style=hexaV THEN hAxis[hexaV]*(axis.maxChars+1.0) ELSE hAxis[axis.style]
};
GetAxis: PROC [self: Viewer, x, y: REAL] RETURNS [axis: Axis, localx, localy: REAL] ~ {
viewerData: ViewerData ~ NARROW[self.data];
charHeight: REAL ← ImagerFont.FontBoundingBox[currentFont].ascent+1.0;
plot: Plot ← viewerData.plot;
xor, yor, inter, t, v: REAL;
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
xor ← xSpace;
yor ← ySpace;
IF plot.axis=NIL THEN RETURN[NIL, 0.0, 0.0];
hAxis ← SetHeights[plot, charHeight, IF viewerData.grid THEN self.ch-3*ySpace ELSE self.ch-2*ySpace];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
thisAxis: Axis = iAxisList.first;
inter ← AxisHeight[thisAxis, hAxis];
IF y>=yor AND y<yor+inter THEN {
t ← thisAxis.bounds.x+thisAxis.bounds.w*(x-xor)/self.cw;
v ← IF inter#charHeight THEN thisAxis.bounds.y+thisAxis.bounds.h*(y-yor)/(inter-charHeight) ELSE 0.0;
RETURN[thisAxis, t, v];
};
yor ← yor+inter;
ENDLOOP;
};
Graphs
SelectGraph: PUBLIC PROC [plot: Plot, graph: Graph] ~ {
plot.selection ← LIST[graph];
};
AddGraph: PUBLIC PROC [plot: Plot, graph: Graph] ~ {
plot.selection ← CONS[graph, plot.selection];
};
GetSelectedGraph: PUBLIC PROC [plot: Plot] RETURNS [graph: Graph] ~ {
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, Graph] THEN {
graph ← NARROW[iList.first];
EXIT;
};
ENDLOOP;
};
GetSelectedGraphList: PUBLIC PROC [plot: Plot] RETURNS [graphList: LIST OF Graph] ~ {
graphList ← NIL;
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, Graph] THEN graphList ← CONS[NARROW[iList.first, Graph], graphList];
ENDLOOP;
};
Graph Points
SelectPoint: PUBLIC PROC [plot: Plot, point: GraphPoint] ~ {
plot.selection ← LIST[point];
};
AddPoint: PUBLIC PROC [plot: Plot, point: GraphPoint] ~ {
plot.selection ← CONS[point, plot.selection];
};
GetSelectedPoint: PUBLIC PROC [plot: Plot] RETURNS [point: GraphPoint] ~ {
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, GraphPoint] THEN {
point ← NARROW[iList.first];
EXIT;
};
ENDLOOP;
};
GetSelectedPointList: PUBLIC PROC [plot: Plot] RETURNS [pointList: LIST OF GraphPoint] ~ {
pointList ← NIL;
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, GraphPoint] THEN pointList ← CONS[NARROW[iList.first, GraphPoint], pointList];
ENDLOOP;
};
InsertPoint: PUBLIC PROC [graph: Graph, x, y: REAL] RETURNS [ok: BOOLTRUE] ~ {
ok ← graph.class.insert#NIL;
IF ok THEN graph.class.insert[graph, x, y];
};
DeletePoint: PUBLIC PROC [graph: Graph, x, y: REAL] RETURNS [ok: BOOLTRUE] ~ {
ok ← graph.class.delete#NIL;
IF ok THEN graph.class.delete[graph, x, y];
};
Texts
SelectText: PUBLIC PROC [plot: Plot, text: PlotText] ~ {
plot.selection ← LIST[text];
};
AddText: PUBLIC PROC [plot: Plot, text: PlotText] ~ {
plot.selection ← CONS[text, plot.selection];
};
GetSelectedText: PUBLIC PROC [plot: Plot] RETURNS [text: PlotText] ~ {
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, PlotText] THEN {
text ← NARROW[iList.first];
EXIT;
};
ENDLOOP;
};
GetSelectedTextList: PUBLIC PROC [plot: Plot] RETURNS [textList: LIST OF PlotText] ~ {
textList ← NIL;
FOR iList: LIST OF REF ANY ← plot.selection, iList.rest UNTIL iList=NIL DO
IF ISTYPE[iList.first, PlotText] THEN textList ← CONS[NARROW[iList.first, PlotText], textList];
ENDLOOP;
};
MoveText: PUBLIC PROC [plot: Plot, text: PlotText, position: VEC] ~ {
text.bounds.x ← position.x;
text.bounds.y ← position.y;
};
RotateText: PUBLIC PROC [plot: Plot, text: PlotText, alpha: REAL] ~ {
text.rotation ← text.rotation+alpha;
};
DeleteText: PUBLIC PROC [plot: Plot, text: PlotText] ~ {
IF plot.texts=NIL THEN RETURN;
IF text=NIL THEN RETURN;
IF plot.texts.first=text THEN {
LockPlot[plot];
plot.texts ← plot.texts.rest;
UnlockPlot[plot];
RETURN;
};
FOR iTextList: LIST OF PlotText ← plot.texts, iTextList.rest UNTIL iTextList.rest=NIL DO
IF iTextList.rest.first=text THEN {
LockPlot[plot];
iTextList.rest ← iTextList.rest.rest;
UnlockPlot[plot];
RETURN;
};
ENDLOOP;
};
Initialization
black: PUBLIC Imager.Color ← Imager.black;
white: PUBLIC Imager.Color ← Imager.white;
grey: PUBLIC Imager.Color ← ImagerDitherContext.MakeSpecialColor[ImagerColor.ColorFromRGB[ [ R: 0.5, G: 0.5, B: 0.5 ] ], [190, null]];
grey: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 0.5, G: 0.5, B: 0.5 ] ];
red: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 1.0, G: 0.0, B: 0.0 ] ];
green: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 0.0, G: 1.0, B: 0.0 ] ];
blue: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 0.0, G: 0.0, B: 1.0 ] ];
puce: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 0.5, G: 0.2, B: 0.4 ] ];
tempColor: Imager.Color ← ImagerBackdoor.invert;
defaultScalex: REAL ← 0.01;
ipInColor: BOOLEANFALSE;
currentFont: Imager.Font ← ImagerFont.Find["Xerox/TiogaFonts/Gacha8"];
pressFont: Imager.Font ← ImagerFont.Scale[ImagerFont.Find["Xerox/PressFonts/Helvetica-brr"], 18.0];
pressScale: REAL ← 0.0002;
pageSize: Rectangle ← [100.0, 100.0, 900.0, 1250.0];
fineStroke: REAL ← 1.0;
coarseStroke: REAL ← 3.0;
tinyTick: REAL ← 3.0;
spaceOverTick: REAL ← 4.0;
xSpace: REAL ← 2.0;
ySpace: REAL ← 15.0;
gridFactor: REAL ← 1.0;
plotGPictureClass: ViewerClasses.ViewerClass ← NEW [
ViewerClasses.ViewerClassRec ← [ 
notify: NotifyProc, -- procedure to respond to input events (from TIP table)
paint: PaintProc, -- procedure called when viewer contents must be repainted
modify: ModifyProc, -- reports InputFocus changes
destroy: DestroyProc, -- procedure to clean up when done
get: GetProc, -- get the viewer contents i.e. the name of the selection
scroll: VScrollProc, -- procedure to respond to vertical scroll bar hits
hscroll: HScrollProc, -- procedure to respond to horizontal scroll bar hits
adjust: AdjustProc, -- called when viewer size is changed
-- Tip table (translates mouse events to commands)
tipTable: TIPUser.InstantiateNewTIPTable["PlotGraph.TIP"],
icon: Icons.NewIconFromFile["PlotGraph.icons", 3],
cursor: crossHairsCircle -- cursor when mouse is in viewer
]
];
true: REF BOOLEANNEW[BOOLEANTRUE];
false: REF BOOLEANNEW[BOOLEANFALSE];
graphColorList: LIST OF Color ← LIST[black, red, green, blue, puce];
selection: LIST OF REF ANY NIL;
xAxisName: REAL ← 2.0;
yAxisName: REAL ← -1.0;
ViewerOps.RegisterViewerClass[$PlotGViewer, plotGPictureClass];-- Register with viewers
END.