DIRECTORY
Imager
Imager USING [Context, SetColor, ColorOperator, MaskRectangle, SetFont, SetStrokeWidth, SetStrokeEnd, SetStrokeJoint, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move],
Imager USING [Context, SetColor, MaskRectangle, SetFont, MaskVector, Color, Font, SetXY, ShowChar, ShowRope, DoSaveAll, RotateT, Move],
ImagerBackdoor USING [GetBounds, invert],
ImagerColor USING [ColorFromRGB],
ImagerColorOperator USING [RGBLinearColorModel],
ImagerDitherContext USING [MakeSpecialColor],
ImagerFont USING [Scale, Find, RopeEscapement, 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, 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],
MessageWindow USING [Append],
Type Definitions
VEC: TYPE ~ Vector2.VEC;
PlotList: TYPE ~ LIST OF Plot;
AxisList: TYPE ~ LIST OF Axis;
GraphList: TYPE ~ LIST OF Graph;
Viewer: TYPE ~ ViewerClasses.Viewer;
Color: TYPE ~ Imager.Color;
ViewerData: TYPE ~ REF ViewerDataRec;
ViewerDataRec:
TYPE ~
RECORD [
plot: Plot, -- circular references...
liasonEntry: ARRAY BOOLEAN OF Menus.MenuEntry,
magEntry: ARRAY BOOLEAN OF Menus.MenuEntry,
xScale: REAL ← 0.125, --mag plot width / plot width
xOr: REAL ← 0.0, --mag origin ← plot origin+ plot width*xOr
frozen: BOOLEAN ← FALSE,--the display is no longer refreshed by outside requests
grid: BOOLEAN ← FALSE, --an oscilloscope grid is painted on the viewer (if axis are coherent)
magOn: BOOL ← FALSE, --state of the magnifier
upToDate: BOOL ← FALSE --the data is valid for painting
];
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: BOOLEAN ← TRUE,
firstPoint: BOOLEAN ← TRUE,
viewerData: ViewerData
];
debugPlot: Plot ← NIL;
debugAxis: Axis ← NIL;
debugGraph: Graph ← NIL;
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;
};
WritePtV:
PROC [x, y:
REAL, data:
REF ANY ← NIL, 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;
ptData: DrawPtData ~ NARROW[data];
IF ~ptData.viewerData.upToDate THEN RETURN[TRUE];
n ← Real.InlineFix[y];
IF (Real.InlineFix[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;
Imager.MaskVector[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:
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;
ptData: DrawPtData ~ NARROW[data];
IF ~ptData.viewerData.upToDate THEN RETURN[TRUE];
n ← Real.InlineFix[y];
IF (Real.InlineFix[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;
Imager.MaskVector[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:
ROPE ←
NIL]
RETURNS [quit:
BOOL ←
FALSE] ~ {
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.viewerData.upToDate THEN RETURN[TRUE];
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 => Imager.MaskVector[ptData.context, ptData.prevPt, pt];
visible AND ~ptData.visible => Imager.MaskVector[ptData.context, ClipVect[pt, ptData.prevPt, ptData.window], pt];
~visible AND ptData.visible => Imager.MaskVector[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 Imager.MaskVector[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:
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];
};
RefreshScreen:
PROC [plot: Plot, axis: AxisList ←
NIL, graphs: GraphList ←
NIL, within: Rectangle ← WorldRectangle, eraseFirst:
BOOL ←
FALSE] ~ {
DoRefreshScreen:
PROC [viewer: Viewer, context: Imager.Context] ~ {
axisFound, graphFound, gridOK: BOOL;
quit: BOOL ← FALSE;
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,
viewerData: viewerData
]];
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.
viewerData.upToDate ← TRUE;
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
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+xScaled, ptData.plotOr.y+yScaled]];
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
xScaled: REAL ← iTextList.first.bounds.x*viewer.cw;
yScaled: REAL ← iTextList.first.bounds.y*viewer.ch;
x ←
SELECT iTextList.first.justifyX
FROM
left => 0.0,
center => MAX[0.0, (xScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].x)*0.5],
right => MAX[0.0, xScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].x],
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, (yScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].y)*0.5],
top => MAX[0.0, yScaled-ImagerFont.RopeEscapement[currentFont, iTextList.first.contents].y],
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 ← NormalizeScaleAndOrigin[plot];
Draw a mid screen horizontal green (or black) vector
Imager.SetColor[context, ggreen];
x ← ptData.plotOr.x;
y ← viewer.ch-2*ySpace;
Imager.MaskVector[context, [x, y], [x+viewer.cw, 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 ← ptData.plotOr.x+i*viewer.cw/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[ptData.plotOr.x+viewer.cw/20.0, y+12.0, IO.PutFR["%g", IO.real[unit]], context, bblue];
ShowRope[ptData.plotOr.x+4.0, y-12.0, IO.PutFR["%g", IO.real[plot.axis.first.bounds.x]], 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 ← AxisHeight[iAxisList.first, hAxis];
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];
Imager.MaskVector[context, ptData.plotOr, [ptData.plotOr.x+viewer.cw, ptData.plotOr.y]];
};
IF iAxisList.first.axisData[Y].visible
THEN {
Imager.SetColor[context, ggreen];
Imager.MaskVector[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 => quit ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, DrawPt, ptData];
hexaH => quit ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtH, ptData];
hexaV => quit ← iGraphList.first.class.enumerate[plot, iGraphList.first, within, WritePtV, ptData];
ENDCASE;
IF quit THEN RETURN;
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.RopeEscapement[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 the magnifier is not on, just show the limits (2 vertical vectors) of it.
Imager.SetColor[context, rred];
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...
viewerData.upToDate ← FALSE;
DrawInViewer[plot.private.viewer, DoRefreshScreen, FALSE];
};
ProduceIPMaster:
PUBLIC
PROC [plot: Plot] ~ {
};
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.RopeEscapement[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];
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+viewer.cw*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;
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]
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] ~ {
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]
};
$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]*unit;
test ← plot.axis.first.bounds.w-Real.Floor[plot.axis.first.bounds.w/unit]*unit;
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]
RETURNS [hAxis:
ARRAY DrawingStyle
OF
REAL
← ALL[0.0]] ~ {
viewer: Viewer ~ plot.private.viewer;
viewerData: ViewerData ~ NARROW[viewer.data];
nAxis, cAxis: ARRAY DrawingStyle OF REAL ← ALL[0.0];
yActualSize: REAL ← IF viewerData.grid THEN viewer.ch-4*ySpace ELSE viewer.ch-2*ySpace;
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[hexaH]+cAxis[hexaV]);
hAxis[hexaH] ← MIN[inter, 2*charHeight];
hAxis[hexaV] ← MIN[inter, charHeight];
IF nAxis[analog]#0 THEN hAxis[analog] ← (yActualSize-hAxis[hexaH]*nAxis[hexaH]-hAxis[hexaV]*cAxis[hexaV]) /nAxis[analog]
ELSE {
IF nAxis[hexaH]#0.0 THEN hAxis[hexaH] ← (yActualSize-hAxis[hexaV]*cAxis[hexaV]) /nAxis[hexaH]
ELSE hAxis[hexaV] ← inter;
};
};
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] ~ {
Imager.SetColor[context, background];
Imager.MaskRectangle[context, [x, y-1.0, ImagerFont.RopeEscapement[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: 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;
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];
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], thisAxis.name, self, bblue, wwhite];
IF thisAxis=axis THEN DrawRopeInv[[xor+xAxisName, yor+yAxisName], thisAxis.name, self, wwhite, bblue];
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];
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];
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;
};
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
thisAxis: Axis = iAxisList.first;
inter ← AxisHeight[thisAxis, hAxis];
IF thisAxis=axis THEN RETURN[[xor, yor, self.cw, inter]];
yor ← yor+inter;
ENDLOOP;
};
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 ← 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;
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
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 ← -charHeight-2.0;
ViewerOps.RegisterViewerClass[$PlotGViewer, plotGPictureClass];-- Register with viewers