PlotGraphImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Christian LeCocq September 30, 1986 8:54:08 am PDT
Barth, August 25, 1986 12:32:13 pm PDT
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
Imager USING [Context, SetColor, ColorOperator, MaskRectangle, SetFont, SetStrokeWidth, SetStrokeEnd, SetStrokeJoint, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move],
ImagerBackdoor USING [GetBounds],
ImagerColor USING [ColorFromRGB],
ImagerColorOperator USING [RGBLinearColorModel],
ImagerDitheredDevice USING [ColorFromSpecialPixel],
ImagerFont USING [Scale, Find, RopeWidth, FontBoundingBox],
ImagerInterpress USING [Ref, Create, DeclareColorOperator, DeclareFont, DoPage, Close],
Draw2d,
Viewers
Menus USING [Menu, CreateMenu, AppendMenuEntry, ReplaceMenuEntry, CreateEntry, MouseButton, MenuEntry],
ViewerClasses USING [Viewer, ViewerRec, ViewerClass, ViewerClassRec, NotifyProc, PaintProc, ModifyProc, DestroyProc, ScrollProc, HScrollProc, AdjustProc],
ViewerOps USING [CreateViewer, PaintViewer, RegisterViewerClass, DestroyViewer],
TIPUser USING [InstantiateNewTIPTable ,TIPScreenCoords],
Icons USING [NewIconFromFile],
InputFocus USING [GetInputFocus, SetInputFocus],
others
IO USING [PutFR, real],
Convert USING [RopeFromInt],
Vector2 USING [VEC],
Rope USING [ROPE, Cat, Length, InlineFetch],
Real USING [FixI, Fix],
MessageWindow USING [Append],
PlotGraph;
PlotGraphImpl: CEDAR MONITOR
IMPORTS
Imager,
ImagerColor,
ImagerColorOperator,
ImagerDitheredDevice,
ImagerInterpress,
ImagerFont,
ImagerBackdoor,
Draw2d,
IO,
Convert,
Menus,
MessageWindow,
ViewerOps,
TIPUser,
InputFocus,
Rope,
Real,
Icons
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;
ViewerData: TYPE ~ REF ViewerDataRec;
Color: TYPE ~ Imager.Color;
ViewerDataRec: TYPE ~ RECORD [
plot: Plot,  -- circular references...
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
xScale: REAL ← 0.125, --mag plot width / plot width
xOr: REAL ← 0.0 --mag origin ← plot origin+ plot width*xOr
];
DrawPtData: TYPE ~ REF DrawPtDataRec;
DrawPtDataRec: TYPE ~ RECORD [
window: Rectangle,
plotOr: VEC,  -- physical coordinates
or: VEC,  -- client coordinates
prevPt: VEC,
mult: VEC,
context: Imager.Context,
visible: BOOLEANTRUE,
firstPoint: BOOLEANTRUE
];
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;
viewerData: ViewerData ← NEW[ViewerDataRec];
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;
};
WritePtV: PROC [x, y: REAL, data: REF ANY ← NIL, 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;
ptData: DrawPtData ~ NARROW[data];
n ← Real.Fix[y];
IF (Real.Fix[ptData.prevPt.y] = n) AND rope=NIL THEN RETURN;
x0 ← (x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x;
IF x0<ptData.window.x THEN RETURN;
IF x0>ptData.window.x+ptData.window.w THEN RETURN;
y0 ← ptData.plotOr.y;
Draw2d.Line[ptData.context, [x0, y0], [x0, y0+tinyTick]];
r ← IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE];
ShowRopeV[ptData.context, r, [x0 - 2.0, y0+spaceOverTick]];
ptData.prevPt ← [x0, y];
};
WritePtH: PROC [x, y: REAL, data: REF ANY ← NIL, 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;
ptData: DrawPtData ~ NARROW[data];
n ← Real.Fix[y];
IF (Real.Fix[ptData.prevPt.y] = n) AND rope=NIL THEN RETURN;
x0 ← (x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x;
IF x0<ptData.window.x THEN RETURN;
IF x0>ptData.window.x+ptData.window.w THEN RETURN;
y0 ← ptData.plotOr.y;
Draw2d.Line[ptData.context, [x0, y0], [x0, y0+tinyTick]];
Imager.SetXY[ptData.context, [x0 - 2.0, y0+spaceOverTick]];
r ← IF rope~=NIL THEN rope ELSE Convert.RopeFromInt[n, 16, FALSE];
Imager.ShowRope[ptData.context, r];
ptData.prevPt ← [x0, y];
};
DrawPt: PROC [x, y: REAL, data: REF ANY ← NIL, rope: ROPENIL] RETURNS [quit: BOOLFALSE] ~ {
draw the vector from the previous pt to the current on with clipping in ptData.window, except of course the first point.
pt: VEC;
visible: BOOLEAN;
ptData: DrawPtData ~ NARROW[data];
IF ptData.firstPoint THEN {
ptData.prevPt ← [(x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x, (y+ptData.or.y)*ptData.mult.y+ptData.plotOr.y];
ptData.firstPoint ← FALSE;
ptData.visible ← IsPtInArea[ptData.prevPt, ptData.window];
}
ELSE {
pt ← [(x+ptData.or.x)*ptData.mult.x+ptData.plotOr.x, (y+ptData.or.y)*ptData.mult.y+ptData.plotOr.y];
visible ← IsPtInArea[pt, ptData.window];
SELECT TRUE FROM
visible AND ptData.visible => Draw2d.Line[ptData.context, ptData.prevPt, pt];
visible AND ~ptData.visible => Draw2d.Line[ptData.context, ClipVect[pt, ptData.prevPt, ptData.window], pt];
~visible AND ptData.visible => Draw2d.Line[ptData.context, ptData.prevPt, ClipVect[ptData.prevPt, pt, ptData.window]];
~visible AND ~ptData.visible => {
pt1, pt2: VEC;
IF pt.y~=ptData.prevPt.y THEN {
pt1 ← ClipVect[ptData.prevPt, pt, ptData.window];
pt2 ← ClipVect[pt, ptData.prevPt, ptData.window];
IF pt1.x~=pt2.x OR pt1.y~=pt2.y THEN Draw2d.Line[ptData.context, pt1, pt2];
}
};
ENDCASE;
ptData.prevPt ← pt;
ptData.visible ← visible;
}
};
RefreshPlot: PUBLIC PROC [plot: Plot, axis: AxisList ← NIL, graphs: GraphList ← NIL, within: Rectangle ← WorldRectangle, eraseFirst: BOOLFALSE] ~ {
IF plot.private=NIL THEN CreatePlotViewer[plot];
IF plot.private.viewer.iconic THEN RETURN;
IF NARROW[plot.private.viewer.data, ViewerData].frozen THEN RETURN;
RefreshScreen[plot, axis, graphs, within, eraseFirst];
};
RefreshScreen: PROC [plot: Plot, axis: AxisList ← NIL, graphs: GraphList ← NIL, within: Rectangle ← WorldRectangle, eraseFirst: BOOLFALSE] ~ {
DoRefreshScreen: PROC [viewer: Viewer, context: Imager.Context] ~ {
axisFound, graphFound, gridOK: BOOL;
wwhite, rred, ggreen, bblue, ppuce: Imager.Color;
axisChoice: BOOLEAN = ~(axis=NIL); --test only once the presence of an axis specification
graphsChoice: BOOLEAN = ~(graphs=NIL); -- the same for graphs
viewerData: ViewerData ~ NARROW[viewer.data];
xoffset, inter: REAL ← 0.0;
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
colorList: LIST OF Color;
selectedAxis: Axis ← GetSelectedAxis[plot];
ptData: DrawPtData ← NEW[DrawPtDataRec ← [
window: WorldRectangle,
plotOr: [xSpace, ySpace],
or: [0.0, 0.0],
prevPt: [0.0, 0.0],
mult: [1.0, 1.0],
context: context,
firstPoint: TRUE
]];
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 viewer.column= color THEN {
wwhite ← grey;
rred ← red;
ggreen ← green;
bblue ← blue;
ppuce ← puce;
}
ELSE {
wwhite ← white;
rred ← black;
ggreen ← black;
bblue ← black;
ppuce ← black;
};
Imager.SetFont[context, currentFont];
IF eraseFirst THEN {
Imager.SetColor[context, wwhite];
Imager.MaskRectangle[context, ImagerBackdoor.GetBounds[context]]; -- fill screen
If the magnifier is not on, just show the limits (2 vertical vectors) of it.
Imager.SetColor[context, rred];
IF ~viewerData.magOn THEN DrawMag[viewer, context];
Show the global texts on the full screen.
FOR iTextList: LIST OF PlotText ← plot.texts, iTextList.rest UNTIL iTextList=NIL DO
IF iTextList.first.wrt=NIL THEN {
RotateAndShow: PROC ~ {
called through Imager.DoSaveAll to avoid having to restore the current origin and rotation (actually absence of rotation).
Imager.SetColor[context, bblue];
Imager.SetXY[context, [ptData.plotOr.x+iTextList.first.bounds.x*viewer.cw, ptData.plotOr.y+iTextList.first.bounds.y*viewer.ch]];
Imager.Move[context];
Imager.RotateT[context, iTextList.first.rotation];
Imager.SetXY[context, [x, y]];
Imager.ShowRope[context, iTextList.first.contents];
};
x, y: REAL;
make the appropriate x translation according to x justification
x ← SELECT iTextList.first.justifyX FROM
left => ptData.plotOr.x+iTextList.first.bounds.x*viewer.cw,
center => ptData.plotOr.x+(iTextList.first.bounds.x+MAX[0.0, (iTextList.first.bounds.w-ImagerFont.RopeWidth[currentFont, iTextList.first.contents].x)*0.5])*viewer.cw,
right => ptData.plotOr.x+(iTextList.first.bounds.x+MAX[0.0, iTextList.first.bounds.w-ImagerFont.RopeWidth[currentFont, iTextList.first.contents].x])*viewer.cw,
ENDCASE => 0.0;
make the appropriate y translation according to y justification
y ← SELECT iTextList.first.justifyY FROM
bottom => ptData.plotOr.y+iTextList.first.bounds.y*viewer.ch,
center => ptData.plotOr.y+(iTextList.first.bounds.y+ MAX[0.0, (iTextList.first.bounds.h-ImagerFont.RopeWidth[currentFont, iTextList.first.contents].y)*0.5])*viewer.ch,
top => ptData.plotOr.y+(iTextList.first.bounds.y+MAX[0.0, iTextList.first.bounds.h-ImagerFont.RopeWidth[currentFont, iTextList.first.contents].y])*viewer.ch,
ENDCASE => 0.0;
then do it and forget these temporary transformations.
Imager.DoSaveAll[context, RotateAndShow];
};
ENDLOOP;
};
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.
gridOK ← viewerData.grid;
hAxis ← SetHeights[plot];
Draw the grid, after adjusting the x scale to a "round decimal" value if needed.
IF gridOK AND eraseFirst THEN {
x, y: REAL;
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.
unit ← Normalize[plot.axis.first.bounds.w/20.0];
scale adequately the axis.
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.w ← 20.0*unit;
ENDLOOP;
Draw a mid screen horizontal green (or black) vector
Imager.SetColor[context, ggreen];
x ← ptData.plotOr.x;
y ← ptData.plotOr.y+viewer.ch/2.0;
Draw2d.Line[context, [x, y], [x+viewer.cw, y]];
y ← y-5.0;
and mark 20 units by drawing well... let's see... 19 ticks I guess.
FOR i: INT IN [1..19] DO
x ← ptData.plotOr.x+i*viewer.cw/20.0;
Draw2d.Line[context, [x, y], [x, y+10.0]];
ENDLOOP;
write the unit value in fixed or floating notation depending on the size of it, over the first tick.
ShowRope[ptData.plotOr.x+viewer.cw/20.0, y+15.0, IO.PutFR["%g", IO.real[unit]], context, 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
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=iAxisList.first 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 ← IF iAxisList.first.style=hexaV THEN hAxis[hexaV]*(iAxisList.first.maxChars+1.0) ELSE hAxis[iAxisList.first.style];
prepair the COMMON (COMMON is a trade mark of FORTRAN and sons) for the drawPt procs.
ptData.or.x ← -iAxisList.first.bounds.x;
ptData.or.y ← -iAxisList.first.bounds.y;
ptData.mult.x ← viewer.cw/iAxisList.first.bounds.w;
ptData.mult.y ← (inter-charHeight)/iAxisList.first.bounds.h;
ptData.window ← [ptData.plotOr.x, ptData.plotOr.y, viewer.cw, inter];
IF eraseFirst THEN {
Draw both local axis, if needed.
IF iAxisList.first.axisData[X].visible THEN {
Imager.SetColor[context, ggreen];
Draw2d.Line[context, ptData.plotOr, [ptData.plotOr.x+viewer.cw, ptData.plotOr.y]];
};
IF iAxisList.first.axisData[Y].visible THEN {
Imager.SetColor[context, ggreen];
Draw2d.Line[context, ptData.plotOr, [ptData.plotOr.x, ptData.plotOr.y+inter]];
};
then display the name of the axis under its baseline
IF iAxisList.first=selectedAxis THEN ShowRopeInv[ptData.plotOr.x+xAxisName, ptData.plotOr.y+yAxisName, iAxisList.first.name, context, wwhite, bblue]
ELSE ShowRopeInv[ptData.plotOr.x+xAxisName, ptData.plotOr.y+yAxisName, iAxisList.first.name, context, bblue, wwhite];
};
we are not so far from what we really wanted to do : display [x, y] pairs
FOR iGraphList: GraphList ← iAxisList.first.graphs, iGraphList.rest UNTIL iGraphList=NIL DO
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=iGraphList.first 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.
ptData.firstPoint ← TRUE;
Imager.SetColor[context, colorList.first];
SELECT iAxisList.first.style FROM
analog => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, DrawPt, ptData];
hexaH => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtH, ptData];
hexaV => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtV, ptData];
ENDCASE;
name the graph in the same color.
IF eraseFirst THEN ShowRope[ptData.plotOr.x+xoffset, ptData.plotOr.y+inter*0.5, iGraphList.first.name, context, colorList.first];
prepair the new color and the new name offset for the next graph
xoffset ← xoffset+ImagerFont.RopeWidth[currentFont, iGraphList.first.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
ptData.plotOr.y ← ptData.plotOr.y+inter;
ENDLOOP;
};
IF plot#NARROW[plot.private.viewer.data, 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.
xoffset, inter: REAL;
nAxis, hAxis, cAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
colorList: LIST OF Color;
ptData: DrawPtData ← NEW[DrawPtDataRec ← [
window: WorldRectangle,
plotOr: [50.0, 100.0],
or: [0.0, 0.0],
prevPt: [0.0, 0.0],
mult: [1.0, 1.0],
context: context,
firstPoint: TRUE
]];
Interpress specific stuff: adjusting the shape of the lines
Imager.SetStrokeEnd[context, round];
Imager.SetStrokeJoint[context, round];
Imager.SetFont[context, pressFont];
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;
ENDLOOP;
inter ← pageSize.y/(nAxis[analog]+nAxis[hexaH]+cAxis[hexaV]);
hAxis[hexaH] ← MIN[inter, charHeight+10.0];
hAxis[hexaV] ← MIN[inter, charHeight*1.15];
hAxis[analog] ← (pageSize.y -hAxis[hexaH]*nAxis[hexaH] -hAxis[hexaV]*cAxis[hexaV]) /nAxis[analog];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
xoffset ← 0.0;
colorList ← graphColorList;
inter ← IF iAxisList.first.style=hexaV THEN hAxis[hexaV]*iAxisList.first.maxChars ELSE hAxis[iAxisList.first.style];
ptData.or.x ← -iAxisList.first.bounds.x;
ptData.or.y ← -iAxisList.first.bounds.y;
ptData.mult.x ← pageSize.x/iAxisList.first.bounds.w;
ptData.mult.y ← (inter-10.0)/iAxisList.first.bounds.h;
ptData.window ← [ptData.plotOr.x, ptData.plotOr.y, pageSize.x, inter];
IF iAxisList.first.axisData[X].visible THEN {
Imager.SetStrokeWidth[context, fineStroke];
Imager.SetColor[context, green];
Imager.MaskVector[context, ptData.plotOr, [ptData.plotOr.x+1000.0, ptData.plotOr.y]];
};
IF iAxisList.first.axisData[Y].visible THEN {
Imager.SetStrokeWidth[context, fineStroke];
Imager.SetColor[context, green];
Imager.MaskVector[context, ptData.plotOr, [ptData.plotOr.x, ptData.plotOr.y+inter]];
};
ShowRope[ptData.plotOr.x, ptData.plotOr.y+inter*0.1, iAxisList.first.name, context, blue];
FOR iGraphList: GraphList ← iAxisList.first.graphs, iGraphList.rest UNTIL iGraphList=NIL DO
ptData.firstPoint ← TRUE;
Imager.SetStrokeWidth[context, coarseStroke];
Imager.SetColor[context, colorList.first];
SELECT iAxisList.first.style FROM
analog => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, DrawPt, ptData];
hexaH => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtH, ptData];
hexaV => [] ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtV, ptData];
ENDCASE;
ShowRope[ptData.plotOr.x+xoffset, ptData.plotOr.y+inter*0.5, iGraphList.first.name, context, colorList.first];
xoffset ← xoffset+ImagerFont.RopeWidth[currentFont, iGraphList.first.name].x+5.0;
IF colorList.rest#NIL THEN colorList ← colorList.rest ELSE colorList ← graphColorList;
ENDLOOP;
ptData.plotOr.y ← ptData.plotOr.y+inter;
ENDLOOP;
};
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 ~ ImagerColorOperator.RGBLinearColorModel[255];
ImagerInterpress.DeclareColorOperator[ip, rgbLinear];
ImagerInterpress.DeclareFont[ip, pressFont];
ImagerInterpress.DoPage[ip, DoPrint, pressScale];
ImagerInterpress.Close[ip];
MessageWindow.Append[Rope.Cat[fileName, " created"],TRUE];
};
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];
liasonState: BOOLEANNARROW[clientData, REF BOOLEAN]^;
Menus.ReplaceMenuEntry[viewer.menu, liasonEntry[liasonState], 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] ~ {
Imager.SetColor[context, IF viewer.column=color THEN grey ELSE white];
DrawMag[viewer, context];
viewerData.xScale ← viewerData.xScale*alpha;
Imager.SetColor[context, IF viewer.column=color THEN red ELSE black];
DrawMag[viewer, context];
};
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, magEntry[magState], 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.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]
ELSE DrawRope[[x, y], IO.PutFR["t:%d", IO.real[t]], self, IF self.column= color THEN red ELSE black];
RETURN;
};
$MoveMag => {
MoveMag: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetColor[context, IF viewer.column=color THEN grey ELSE white];
DrawMag[viewer, context];
viewerData.xOr ← x/viewer.cw;
Imager.SetColor[context, IF viewer.column=color THEN red ELSE black];
DrawMag[viewer, context];
};
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]
};
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;
};
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;
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 => {
RETURN;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.y ← amount*0.01*(plot.clientBounds.h - plot.clientBounds.y) + plot.clientBounds.y;
ENDLOOP;
};
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.FixI[100.0*(plot.upperBounds.y-iAxisList.first.bounds.y-iAxisList.first.bounds.h) /(plot.upperBounds.y-plot.lowerBounds.y)]];
bottom ← MIN[bottom, Real.FixI[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;
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 => {
RETURN;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
iAxisList.first.bounds.x ← amount*0.01*(plot.clientBounds.w - plot.clientBounds.x) + plot.clientBounds.x;
ENDLOOP;
};
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.FixI[100.0*(plot.upperBounds.x-iAxisList.first.bounds.x-iAxisList.first.bounds.w) /(plot.upperBounds.x-plot.lowerBounds.x)]];
right ← MIN[right, Real.FixI[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.FixI[100.0*(iAxisList.first.bounds.x-plot.lowerBounds.x) /(plot.upperBounds.x-plot.lowerBounds.x)]];
right ← MIN[right, Real.FixI[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 => NULL;
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
]];
};
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] RETURNS [hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0]] ~ {
viewer: Viewer ~ plot.private.viewer;
nAxis, cAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
inter: REAL;
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
IF iAxisList.first.bounds.w~=plot.axis.first.bounds.w THEN gridOK ← FALSE;
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 ← (viewer.ch-2*ySpace) /(nAxis[analog]+nAxis[hexaH]+cAxis[hexaV]);
hAxis[hexaH] ← MIN[inter, charHeight+10.0];
hAxis[hexaV] ← MIN[inter, charHeight];
IF nAxis[analog]#0 THEN hAxis[analog] ← (viewer.ch-2*ySpace-hAxis[hexaH]*nAxis[hexaH] -hAxis[hexaV]*cAxis[hexaV]) /nAxis[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] ~ {
viewerData: ViewerData ~ NARROW[viewer.data];
x: REAL ← viewer.cw*viewerData.xOr;
Draw2d.Line[context, [x, 0.0], [x, viewer.ch]];
x ← x+viewer.cw*viewerData.xScale;
Draw2d.Line[context, [x, 0.0], [x, viewer.ch]];
};
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] ~ {
Imager.SetColor[context, background];
Imager.MaskRectangle[context, [x, y-1.0, ImagerFont.RopeWidth[currentFont, rope].x, charHeight+2.0]];
Imager.SetXY[context, [x, y]];
Imager.SetColor[context, color];
Imager.ShowRope[context, rope];
};
ShowRopeV: PROC[context: Imager.Context, rope: Rope.ROPE, pos: VEC] ~ {
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] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetColor[context, color];
ShowRopeV[context, rope, pos];
};
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] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetFont[context, currentFont];
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] ~ {
DoDrawRope: PROC [viewer: Viewer, context: Imager.Context] ~ {
Imager.SetFont[context, currentFont];
ShowRopeInv[pos.x, pos.y, rope, context, color, background];
};
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;
xor, yor, inter: 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;
plot.selection ← LIST[axis];
xor ← xSpace;
yor ← ySpace;
hAxis ← SetHeights[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
inter ← IF iAxisList.first.style=hexaV THEN hAxis[hexaV]*(iAxisList.first.maxChars+1.0) ELSE hAxis[iAxisList.first.style];
IF iAxisList.first=selectedAxis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName], iAxisList.first.name, self, bblue, wwhite];
IF iAxisList.first=axis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName], iAxisList.first.name, self, wwhite, bblue];
yor ← yor+inter;
ENDLOOP;
};
GetAxis: PROC [self: Viewer, x, y: REAL] RETURNS [axis: Axis, localx, localy: REAL] ~ {
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
xor, yor, inter, t, v: REAL;
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
xor ← xSpace;
yor ← ySpace;
hAxis ← SetHeights[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
inter ← IF iAxisList.first.style=hexaV THEN hAxis[hexaV]*iAxisList.first.maxChars ELSE hAxis[iAxisList.first.style];
IF y>=yor AND y<yor+inter THEN {
t ← iAxisList.first.bounds.x+iAxisList.first.bounds.w*(x-xor)/self.cw;
v ← IF inter#charHeight THEN iAxisList.first.bounds.y+iAxisList.first.bounds.h*(y-yor)/(inter-charHeight) ELSE 0.0;
RETURN[iAxisList.first, t, v];
};
yor ← yor+inter;
ENDLOOP;
};
GetAxisBox: PROC [self: Viewer, axis: Axis] RETURNS [box: Rectangle] ~ {
viewerData: ViewerData ~ NARROW[self.data];
plot: Plot ← viewerData.plot;
xor, yor, inter: REAL;
hAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
xor ← xSpace;
yor ← ySpace;
hAxis ← SetHeights[plot];
FOR iAxisList: AxisList ← plot.axis, iAxisList.rest UNTIL iAxisList=NIL DO
inter ← IF iAxisList.first.style=hexaV THEN hAxis[hexaV]*iAxisList.first.maxChars ELSE hAxis[iAxisList.first.style];
IF iAxisList.first=axis THEN {
RETURN[[xor, yor, self.cw, inter]];
};
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;
};
Initialization
black: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 0.0, G: 0.0, B: 0.0 ] ];
white: PUBLIC Imager.Color ← ImagerColor.ColorFromRGB[ [ R: 1.0, G: 1.0, B: 1.0 ] ];
grey: PUBLIC Imager.Color ← ImagerDitheredDevice.ColorFromSpecialPixel[[190, null]];
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 ] ];
defaultScalex: REAL ← 0.01;
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: VEC ← [1000.0, 1250.0];
fineStroke: REAL ← 1.0;
coarseStroke: REAL ← 3.0;
charHeight: REAL ← ImagerFont.FontBoundingBox[currentFont].ascent+1.0;
tinyTick: REAL ← 3.0;
spaceOverTick: REAL ← 4.0;
xSpace: REAL ← 2.0;
ySpace: REAL ← 12.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
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];
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"
];
magEntry: ARRAY BOOLEAN OF Menus.MenuEntry ← [magOffEntry, magOnEntry];
liasonEntry: ARRAY BOOLEAN OF Menus.MenuEntry ← [frozenEntry, activeEntry];
graphColorList: LIST OF Color ← LIST[black, red, green, blue, puce];
selection: LIST OF REF ANY NIL;
xAxisName: REAL ← 2.0;
yAxisName: REAL ← -charHeight-2.0;
ViewerOps.RegisterViewerClass[$PlotGViewer, plotGPictureClass];-- Register with viewers
END.