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],
Plot management
CreatePlot:
PUBLIC
PROC [name:
ROPE ←
NIL]
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:
BOOL ←
FALSE] ~ {
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:
BOOL ←
FALSE] ~ {
WritePtV:
PROC [x, y:
REAL, rope:
ROPE ←
NIL]
RETURNS [quit:
BOOL ←
FALSE] ~ {
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:
ROPE ←
NIL]
RETURNS [quit:
BOOL ←
FALSE] ~ {
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:
ROPE ←
NIL]
RETURNS [quit:
BOOL ←
FALSE] ~ {
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:
ROPE ←
NIL]
RETURNS [quit:
BOOL ←
FALSE] ~ {
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: BOOL ← FALSE;
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:
BOOL ←
FALSE] ~ {
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: BOOLEAN ← NARROW[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: BOOLEAN ← NARROW[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: ATOM ← NARROW[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: ATOM ← NIL] 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: BOOL ← FALSE]
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: REAL ← IF neg THEN -r ELSE r;
base: REAL ← IF 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;
};
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: BOOLEAN ← FALSE;
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 BOOLEAN ← NEW[BOOLEAN ← TRUE];
false: REF BOOLEAN ← NEW[BOOLEAN ← FALSE];
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