HistographImpl.mesa
Copyright © 1986 by Xerox Corporation. All rights reserved.
Russ Atkinson (RRA) May 23, 1986 2:57:25 pm PDT
DIRECTORY
Containers USING [ChildXBound, Create],
Convert USING [AppendF, AppendInt, AppendRope, RopeFromInt],
Histograph USING [],
Imager USING [black, Context, MaskRectangle, SetColor, SetFont, SetXYI, ShowRope, ShowText, TranslateT, white],
ImagerFont USING [Extents, Find, Font, Scale, RopeBoundingBox, RopeWidth, TextBoundingBox],
Real USING [Round, RoundI],
RealFns USING [Log],
RefText USING [ObtainScratch, ReleaseScratch],
Rope USING [Concat, ROPE],
ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec],
ViewerForkers USING [ForkPaint],
ViewerOps USING [CreateViewer, OpenIcon, PaintViewer, RegisterViewerClass, SetOpenHeight],
ViewerSpecs USING [captionHeight];
HistographImpl: CEDAR MONITOR
LOCKS hist USING hist: HistographData
IMPORTS Convert, Containers, Imager, ImagerFont, Real, RealFns, RefText, Rope, ViewerForkers, ViewerOps, ViewerSpecs
EXPORTS Histograph
= BEGIN
Types
Context: TYPE = Imager.Context;
Extents: TYPE = ImagerFont.Extents;
Font: TYPE = ImagerFont.Font;
ROPE: TYPE = Rope.ROPE;
Viewer: TYPE = ViewerClasses.Viewer;
HistographData: TYPE = REF HistographDataRep;
HistographDataRep: TYPE = MONITORED RECORD [
height: NAT ← 64, -- height of max entry (in viewer units)
width: NAT ← 120, -- display width of graph (<= sampleMax)
lastViewerWidth: NAT ← 0, -- last known viewer width (v.ww)
flexible: BOOLFALSE, -- TRUE iff inherits right edge from parent
historical: BOOLFALSE, -- TRUE iff inherits right edge from parent
nextPlotIndex: NAT ← 0, -- next index (in sampleData) to plot
nextDataIndex: NAT ← 0, -- next index (in sampleData) to plot
firstSampleX: NAT ← 64, -- the X position of the first sample
firstSampleY: NAT ← 8, -- the Y position of the first sample
tickX: NAT ← 60, -- frequency of ticks in the X direction
tickY: NAT ← 20, -- frequency of ticks in the Y direction
vertiLog: NAT ← 1, -- log base to use on vertical axis
scale: REAL ← 1.0, -- scaling factor to use in plotting samples
numberW: INTEGER ← 32, -- width to allow for numbers
numberH: INTEGER ← 10, -- height to allow for numbers
numberY: INTEGER ← -2,
lastSample: REAL ← 0, -- the most recent plotted sample
lastSampleAvg: REAL ← 0, -- the most recent plotted sample avg
averageFactor: REAL ← 0.9,
maxSample: REAL ← 100, -- largest permitted sample
numberFont: Font ← NIL,
smallFont: Font ← NIL,
largeFont: Font ← NIL,
title: ROPENIL,
subTitle: ROPENIL,
sampleData: SEQUENCE sampleMax: NAT OF REAL
];
numberYInit: INTEGER = -2; -- pixels of bottom offset for first line
numberPad: NAT = 4; -- pixels of top leading for numbers
subTitlePad: NAT = 2; -- pixels of top leading for sub-title
subTitleIndent: NAT = 6; -- pixels for sub-title indent
titlePad: NAT = 6; -- pixels of top leading for title
titleIndent: NAT = 2; -- pixels for title indent
clearAhead: NAT = 8; -- # of pixels to keep clear ahead of cursor
fontPrefix: ROPE ← "Xerox/TiogaFonts/Helvetica";
fontPostfix: NAT ← 8;
fontScale: NAT ← 0;
Public procedures
NewHistograph: PUBLIC PROC [
dataWidth: NAT ← 480, -- # of samples buffered for display
dataHeight: NAT ← 100, -- # of vertical units for samples
maxSample: INT ← 100, -- sample corresponding to the height
averageFactor: REAL ← 0.9, -- used to compute declining average
vertiLog: NAT ← 0, -- log base to use on Y-axis (0, 1 => linear)
title: ROPENIL, -- graph title
subTitle: ROPENIL, -- graph sub-title
firstSampleX: NAT ← 64, -- x position of first sample
numberW: INTEGER ← 32, -- # of units for displaying numbers
name: ROPENIL, -- name to use if top-level viewer
parent: Viewer ← NIL, -- parent viewer
wx: INTEGER ← 0, -- x position in parent
wy: INTEGER ← 0, -- y position in parent
historical: BOOLTRUE, -- => strip chart style, else random access
border: BOOLFALSE, -- => give returned viewer a border
childXbound: BOOLFALSE, -- => make right bound match parent
tickX: NAT ← 60, -- # of units to use between horizontal ticks
tickY: NAT ← 25, -- # of units to use between vertical ticks
numberFont: Font ← NIL, -- font for numbers (default: Helvetica8)
smallFont: Font ← NIL, -- font for subTitle (default: Helvetica8)
largeFont: Font ← NIL] -- font for title (default: Helvetica10)
RETURNS [Viewer] = {
viewer: Viewer ← NIL;
hist: HistographData ← NIL;
flexible: BOOLFALSE;
hPlus, hPlusCaption: NAT;
IF historical AND childXbound AND parent # NIL AND parent.class.flavor = $Container THEN
flexible ← TRUE;
IF numberFont = NIL THEN numberFont ← DefaultFont[0];
IF smallFont = NIL THEN smallFont ← DefaultFont[0];
IF largeFont = NIL THEN largeFont ← DefaultFont[2];
SELECT tickX FROM
< 8 => tickX ← 8;
> 1024 => tickX ← 1024;
ENDCASE;
SELECT tickY FROM
< 8 => tickY ← 8;
> 1024 => tickY ← 1024;
ENDCASE;
SELECT dataWidth FROM
< tickX => dataWidth ← tickX;
> 4096 => dataWidth ← 4096;
ENDCASE;
SELECT dataHeight FROM
< 8 => dataHeight ← 8;
> 1024 => dataWidth ← 1024;
ENDCASE;
SELECT numberW FROM
< 8 => numberW ← 8;
> 128 => numberW ← 128;
ENDCASE;
SELECT firstSampleX FROM
< numberW*2 => firstSampleX ← numberW*2;
> numberW*4 => firstSampleX ← numberW*4;
ENDCASE;
SELECT averageFactor FROM
< 0.0 => averageFactor ← 0.0;
> 1.0 => averageFactor ← 1.0;
ENDCASE;
hist ← NEW[HistographDataRep[dataWidth]];
hist.height ← dataHeight;
hist.width ← dataWidth;
hist.flexible ← flexible;
hist.historical ← historical;
hist.firstSampleX ← firstSampleX;
hist.tickX ← tickX;
hist.tickY ← tickY;
hist.vertiLog ← vertiLog;
hist.numberW ← numberW;
hist.maxSample ← IF maxSample <= 0.0 THEN 1.0 ELSE REAL[maxSample];
hist.scale ← dataHeight/ (IF vertiLog > 2 THEN RealFns.Log[vertiLog, maxSample] ELSE REAL[maxSample]);
hist.title ← title;
hist.subTitle ← subTitle;
hist.numberFont ← numberFont;
hist.smallFont ← smallFont;
hist.largeFont ← largeFont;
{
wh: INTEGER ← numberYInit;
ext: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[numberFont, "0123456789"];
rh: INTEGER ← hist.numberH ← Real.RoundI[ext.ascent];
curTop: INTEGER ← dataHeight+rh;
IF hist.subTitle # NIL THEN {
Allow room for the subtitle
ext ← ImagerFont.RopeBoundingBox[smallFont, hist.title];
wh ← wh + Real.RoundI[ext.ascent] + subTitlePad;
};
IF hist.title # NIL THEN {
Allow room for the title
ext ← ImagerFont.RopeBoundingBox[largeFont, hist.title];
wh ← wh + Real.RoundI[ext.ascent] + titlePad;
};
wh ← wh + (rh + 4)*2;
Always allow room for the numbers
IF wh < curTop THEN wh ← curTop;
hPlus ← wh+hist.firstSampleY;
hPlusCaption ← hPlus+ViewerSpecs.captionHeight;
};
viewer ← ViewerOps.CreateViewer[
flavor: $Histograph,
info: [name: name, data: hist,
openHeight: IF parent#NIL THEN hPlus ELSE hPlusCaption,
parent: parent,
wx: wx, wy: wy, border: border,
ww: dataWidth+firstSampleX+numberW+2,
wh: IF parent#NIL THEN hPlus ELSE hPlusCaption],
paint: FALSE];
IF flexible THEN Containers.ChildXBound[parent, viewer];
RETURN [viewer];
};
Error: PUBLIC ERROR [code: ATOM, message: ROPE] = CODE;
AddSample: PUBLIC PROC [viewer: Viewer, sample: REAL, paint: BOOLTRUE] = {
WITH viewer.data SELECT FROM
hist: HistographData =>
IF hist.historical
THEN AddSampleEntry[viewer, hist, sample, paint]
ELSE ERROR Error[$notHistorical, "operation requires historical"];
ENDCASE =>
IF NOT viewer.destroyed THEN ERROR Error[$notHistograph, "illegal viewer kind"];
};
StoreSample: PUBLIC PROC [viewer: Viewer, index: NAT, sample: REAL, paint: BOOLTRUE] = {
WITH viewer.data SELECT FROM
hist: HistographData =>
SELECT TRUE FROM
hist.historical => ERROR Error[$historical, "operation requires not historical"];
index >= hist.sampleMax => ERROR Error[$invalidIndex, "Invalid index"];
ENDCASE => StoreSampleEntry[viewer, hist, index, sample, paint];
ENDCASE =>
IF NOT viewer.destroyed THEN ERROR Error[$notHistograph, "illegal viewer kind"];
};
ModifySample: PUBLIC PROC [viewer: Viewer, index: NAT, sample: REAL, paint: BOOLTRUE] = {
WITH viewer.data SELECT FROM
hist: HistographData =>
SELECT TRUE FROM
hist.historical => ERROR Error[$historical, "operation requires not historical"];
index >= hist.sampleMax => ERROR Error[$index, "Invalid index"];
ENDCASE => ModifySampleEntry[viewer, hist, index, sample, paint];
ENDCASE =>
IF NOT viewer.destroyed THEN ERROR Error[$notHistograph, "illegal viewer kind"];
};
FetchSample: PUBLIC PROC [viewer: Viewer, index: NAT] RETURNS [REAL ← 0.0] = {
WITH viewer.data SELECT FROM
hist: HistographData =>
SELECT TRUE FROM
index >= hist.sampleMax => ERROR Error[$invalidIndex, "Invalid index"];
ENDCASE => RETURN [hist[index]];
ENDCASE =>
IF NOT viewer.destroyed THEN ERROR Error[$notHistograph, "illegal viewer kind"];
};
Reset: PUBLIC PROC [viewer: Viewer, paint: BOOLTRUE] = {
WITH viewer.data SELECT FROM
hist: HistographData => ResetEntry[viewer, hist, paint];
ENDCASE =>
IF NOT viewer.destroyed THEN ERROR Error[$notHistograph, "illegal viewer kind"];
};
Entry procedures
AddSampleEntry: ENTRY PROC [viewer: Viewer, hist: HistographData, sample: REAL, paint: BOOLTRUE] = {
comp: REAL ← 1.0-hist.averageFactor;
next: NAT ← hist.nextDataIndex;
hist[next] ← sample;
next ← next + 1;
IF next = hist.sampleMax THEN next ← 0;
hist.nextDataIndex ← next;
hist.lastSample ← sample;
hist.lastSampleAvg ← sample + hist.averageFactor * (hist.lastSampleAvg - sample);
IF paint THEN ViewerForkers.ForkPaint[viewer, client, FALSE, $Update, TRUE];
};
StoreSampleEntry: ENTRY PROC [viewer: Viewer, hist: HistographData, index: NAT, sample: REAL, paint: BOOL] = {
hist.lastSampleAvg ← (hist.lastSampleAvg + sample) - hist[index];
hist.lastSample ← hist[index] ← sample;
IF hist.nextDataIndex < hist.nextPlotIndex
THEN {
IF index < hist.nextDataIndex THEN hist.nextDataIndex ← index;
IF index >= hist.nextPlotIndex THEN hist.nextPlotIndex ← index+1;
}
ELSE {
hist.nextDataIndex ← index;
hist.nextPlotIndex ← index+1;
};
IF paint THEN ViewerForkers.ForkPaint[viewer, client, FALSE, $Update, TRUE];
};
ModifySampleEntry: ENTRY PROC [viewer: Viewer, hist: HistographData, index: NAT, sample: REAL, paint: BOOL] = {
hist.lastSampleAvg ← hist.lastSampleAvg + sample;
hist.lastSample ← hist[index] ← hist[index] + sample;
IF hist.nextDataIndex < hist.nextPlotIndex
THEN {
IF index < hist.nextDataIndex THEN hist.nextDataIndex ← index;
IF index >= hist.nextPlotIndex THEN hist.nextPlotIndex ← index+1;
}
ELSE {
hist.nextDataIndex ← index;
hist.nextPlotIndex ← index+1;
};
IF paint THEN ViewerForkers.ForkPaint[viewer, client, FALSE, $Update, TRUE];
};
ResetEntry: ENTRY PROC [viewer: Viewer, hist: HistographData, paint: BOOLTRUE] = {
FOR index: NAT IN [0..hist.sampleMax) DO hist[index] ← 0.0; ENDLOOP;
hist.nextDataIndex ← 0;
hist.nextPlotIndex ← 0;
hist.lastSampleAvg ← 0.0;
IF paint THEN ViewerForkers.ForkPaint[viewer, client, FALSE, NIL, TRUE];
};
GetDirtyRange: ENTRY PROC [hist: HistographData] RETURNS [start, limit: NAT] = {
Sample and reset the start and limit for the dirty range.
start ← hist.nextDataIndex;
limit ← hist.nextPlotIndex;
hist.nextDataIndex ← hist.nextPlotIndex ← hist.sampleMax;
};
Paint procedure
HistographPaint: ViewerClasses.PaintProc = {
[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL ← FALSE]
WITH self.data SELECT FROM
hist: HistographData => {
update: BOOL ← whatChanged = $Update;
print: BOOL ← whatChanged = $Print;
historical: BOOL ← hist.historical;
index: INTEGER ← hist.nextPlotIndex;
limit: NAT ← hist.nextDataIndex;
blackSet: BOOLFALSE;
baseX: INTEGER ← hist.firstSampleX;
baseY: INTEGER ← hist.firstSampleY;
max: INTEGER ← hist.sampleMax;
w: INTEGER ← hist.width;
h: INTEGER ← hist.height;
rightBound: INTEGER ← self.ww;
IF clear THEN update ← FALSE;
Imager.TranslateT[context, [baseX, baseY]];
IF hist.flexible AND rightBound # hist.lastViewerWidth THEN {
Whenever the width changes it is because we have been requested to resize ourselves (perhaps because the container asked us to). This means that we have to recalculate the display width.
update ← FALSE;
w ← (hist.lastViewerWidth ← rightBound) - hist.firstSampleX - hist.numberW;
Default width is based on the viewer width minus allowances for the left and right number areas.
SELECT w FROM
< hist.tickX => w ← hist.tickX;
We should always have one horizontal segment in the display
> max => w ← max;
We should never have more room than the data to display
ENDCASE;
hist.width ← w;
};
IF NOT update THEN {
The best thing to do here is start from scratch.
IF NOT print THEN {
Clear out the whole viewer (perhaps a little conservative, but cheap)
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, [x: self.wx, y: self.wy, w: rightBound, h: self.wh]];
};
Show the title & subtitle
hist.numberY ← numberYInit;
Imager.SetColor[context, Imager.black];
IF hist.subTitle # NIL THEN {
ext: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[hist.smallFont, hist.subTitle];
Imager.SetFont[context, hist.smallFont];
Imager.SetXYI[context, -hist.firstSampleX+subTitleIndent, hist.numberY];
Imager.ShowRope[context, hist.subTitle];
hist.numberY ← hist.numberY + Real.RoundI[ext.ascent] + subTitlePad;
};
IF hist.title # NIL THEN {
ext: ImagerFont.Extents ← ImagerFont.RopeBoundingBox[hist.largeFont, hist.title];
Imager.SetFont[context, hist.largeFont];
Imager.SetXYI[context, -hist.firstSampleX+titleIndent, hist.numberY];
Imager.ShowRope[context, hist.title];
hist.numberY ← hist.numberY + Real.RoundI[ext.ascent] + titlePad;
};
Imager.SetFont[context, hist.numberFont];
Do the basic frame (with tick marks along the base)
Imager.SetColor[context, Imager.black];
blackSet ← TRUE;
Imager.MaskRectangle[context, [x: -1, y: -2, w: w+2, h: 2]];
Imager.MaskRectangle[context, [x: -1, y: -2, w: 1.0, h: h+2]];
Imager.MaskRectangle[context, [x: w, y: -2, w: 1.0, h: h+2]];
FOR i: NAT ← 0, i + hist.tickX UNTIL i > w DO
Imager.MaskRectangle[context, [x: i, y: -5, w: 1, h: 5]];
ENDLOOP;
{
Do the tick marks on the vertical axes
delta: NAT = 2;
wx: INTEGER = w+delta*2;
eachTime: REAL = REAL[hist.tickY]/hist.height;
fract: REAL ← 1.0;
value: REAL ← hist.maxSample;
FOR i: INTEGER ← h, i - hist.tickY UNTIL i < 0 DO
tag: ROPE = Convert.RopeFromInt[Real.Round[value]];
ropeWidth: INT = Real.Round[ImagerFont.RopeWidth[hist.numberFont, tag].x];
Imager.SetXYI[context, 0-ropeWidth-delta*2, i-delta];
IF i # 0 THEN Imager.ShowRope[context, tag];
Imager.MaskRectangle[context, [x: -1, y: i, w: -delta, h: -1.0]];
Imager.MaskRectangle[context, [x: w+1, y: i, w: +delta, h: -1.0]];
Imager.SetXYI[context, wx, i-delta];
Imager.ShowRope[context, tag];
SELECT TRUE FROM
hist.vertiLog <= 1 => {
fract ← fract - eachTime;
value ← hist.maxSample*fract;
};
ENDCASE =>
value ← value / hist.vertiLog;
ENDLOOP;
};
};
IF historical
THEN {
IF NOT update THEN {
Adjust the index so that we will display (w-clearAhead) entries.
index ← limit - (w - clearAhead);
IF index < 0 THEN index ← index + max;
};
}
ELSE {
Get the dirty range (& reset it as well)
[index, limit] ← GetDirtyRange[hist];
IF NOT update THEN {index ← 0; limit ← max};
IF index >= limit THEN RETURN;
};
IF index # limit THEN {
scale: REAL = hist.scale;
pos: INTEGER;
avg: REAL ← hist.lastSampleAvg;
comp: REAL ← 1.0-hist.averageFactor;
count: NAT ← 0;
IF historical
THEN {
Compute the starting display position so we always end up at the same place for a given limit and width.
elems: INTEGERIF limit >= index THEN limit-index ELSE limit+max-index;
pos ← limit - elems;
DO
SELECT pos FROM
< 0 => pos ← pos + w;
>= w => pos ← pos - w;
ENDCASE => EXIT;
ENDLOOP;
}
ELSE {
pos ← index;
The display position is the same as the index
avg ← avg / max;
When not historical, lastSampleAvg is actually a sum
};
DO
sample: REAL ← hist[index];
IF update OR (historical AND count+(clearAhead+2) > w) THEN {
IF NOT print THEN {
Clear out the previous contents
Imager.SetColor[context, Imager.white];
blackSet ← FALSE;
Imager.MaskRectangle[context, [x: pos, y: 0, w: 1.0, h: h]];
IF historical THEN {
Clear out ahead as well
farPos: NAT ← pos+clearAhead;
IF farPos >= w THEN farPos ← farPos - w;
Imager.MaskRectangle[context, [x: farPos, y: 0, w: 1.0, h: h]];
};
};
};
IF sample > 0.0 THEN {
We can actually draw the sample!
sam: REAL ← 0.0;
SELECT TRUE FROM
hist.vertiLog <= 1 =>
sam ← sample * scale;
sample > 0.0 =>
sam ← h + RealFns.Log[hist.vertiLog, sample/hist.maxSample]*hist.tickY;
ENDCASE;
IF NOT blackSet THEN {Imager.SetColor[context, Imager.black]; blackSet ← TRUE};
Imager.MaskRectangle[context,
[x: pos, y: 0, w: 1.0, h: MAX[Real.RoundI[MIN[sam, h]], 1] ]];
};
count ← count + 1;
pos ← pos + 1;
IF pos = w THEN pos ← 0;
IF (index ← index + 1) = max AND historical THEN index ← 0;
IF index = limit THEN {
This is the last sample
Imager.SetColor[context, Imager.black];
IF historical THEN {
Draw in cursor as a dashed line & update the state
quarter: NAT ← h/4;
half: NAT ← quarter+quarter;
Imager.MaskRectangle[context, [x: pos, y: quarter-2, w: 1.0, h: 4]];
Imager.MaskRectangle[context, [x: pos, y: half-2, w: 1.0, h: 4]];
Imager.MaskRectangle[context, [x: pos, y: quarter+half-2, w: 1.0, h: 4]];
hist.nextPlotIndex ← index;
};
ShowNumber[hist, context, hist.lastSample, avg, update];
Note, hist.lastSample & hist.lastSampleAvg may be slightly out of date, but the next paint will come along and fix that up.
EXIT;
};
ENDLOOP;
};
};
ENDCASE;
};
Imaging procedures
ShowNumber: PROC [hist: HistographData, context: Context, sample: REAL, avg: REAL, needClear: BOOL] = {
text: REF TEXT ← RefText.ObtainScratch[16];
SELECT TRUE FROM
avg < 0.005 => text ← Convert.AppendRope[text, "0", FALSE];
avg < 0.05 => text ← Convert.AppendRope[text, "< 0.1", FALSE];
avg < 9.95 => text ← Convert.AppendF[text, avg, 1];
ENDCASE => text ← Convert.AppendInt[text, Real.Round[avg]];
ShowNumberText[hist, context, hist.numberFont, 0, text, needClear];
text.length ← 0;
SELECT TRUE FROM
sample = 0 => text ← Convert.AppendRope[text, "0", FALSE];
sample < 0.05 => text ← Convert.AppendRope[text, "< 0.1", FALSE];
sample < 9.95 => text ← Convert.AppendF[text, sample, 1];
ENDCASE => text ← Convert.AppendInt[text, Real.Round[sample]];
ShowNumberText[hist, context, hist.numberFont, 1, text, needClear];
RefText.ReleaseScratch[text];
};
ShowNumberText: PROC [hist: HistographData, context: Context, font: ImagerFont.Font, hd: INTEGER, text: REF TEXT, needClear: BOOL] = {
w: INTEGER ← Real.RoundI[ImagerFont.TextBoundingBox[font, text].rightExtent];
h: INTEGER ← hist.numberH;
Convert hd from line number to horizontal displacement
hd ← hist.numberY+(hd*(h+numberPad));
IF needClear THEN {
First, clear out the bounding box (with a pixel on top & bottom)
Imager.SetColor[context, Imager.white];
Imager.MaskRectangle[context, [x: -hist.firstSampleX, y: hd-1, w: hist.numberW, h: h+2]];
};
Then, draw in the requested text
Imager.SetColor[context, Imager.black];
Imager.SetXYI[context, -hist.firstSampleX+hist.numberW-w, hd];
Imager.SetFont[context, font];
Imager.ShowText[context, text];
};
DefaultFont: PROC [delta: NAT] RETURNS [font: Font] = {
name: ROPE ← fontPrefix;
IF fontPostfix # 0 THEN
name ← Rope.Concat[name, Convert.RopeFromInt[fontPostfix+delta]];
font ← ImagerFont.Find[name];
IF fontScale # 0 THEN font ← ImagerFont.Scale[font, fontScale+delta];
};
SetDefaultFont: PROC [which: ATOM, size: NAT] = {
SELECT which FROM
$tioga, $Tioga => {
fontPrefix ← "Xerox/TiogaFonts/Helvetica";
fontPostfix ← size;
fontScale ← 0;
};
$press, $Press => {
fontPrefix ← "Xerox/PressFonts/Helvetica-mrr";
fontPostfix ← 0;
fontScale ← size;
};
ENDCASE;
};
Test & registration
Test: PROC [sample: REAL, times: NAT ← 1, height: NAT ← 64, vLog: NAT ← 1, container, flexible, historical: BOOLTRUE] ~ {
hist: HistographData ← NIL;
IF topViewer = NIL OR topViewer.destroyed THEN {
Time for a new top viewer & test viewer
testViewer ← NIL;
IF container THEN
topViewer ← Containers.Create[
info: [name: "Test Histograph", iconic: TRUE],
paint: FALSE];
};
IF testViewer = NIL OR testViewer.destroyed THEN {
testViewer ← NewHistograph[maxSample: height, dataHeight: height, dataWidth: 8*60, name: "Test Histograph", title: "Title", subTitle: "subTitle", parent: IF container THEN topViewer ELSE NIL, wx: IF container THEN 4 ELSE 0, wy: IF container THEN 6 ELSE 0, childXbound: flexible, border: TRUE, historical: historical, vertiLog: vLog, tickX: 60, tickY: 20];
IF container AND topViewer.iconic THEN
ViewerOps.SetOpenHeight[topViewer, testViewer.wh+ViewerSpecs.captionHeight];
};
hist ← NARROW[testViewer.data];
IF topViewer = NIL THEN topViewer ← testViewer;
IF topViewer.iconic THEN ViewerOps.OpenIcon[topViewer];
ViewerOps.PaintViewer[testViewer, client, FALSE, $Update];
IF hist.historical
THEN
THROUGH [0..times) DO
AddSample[testViewer, sample, FALSE];
ENDLOOP
ELSE
FOR i: NAT IN [0..times) DO
ModifySample[testViewer, i, sample, FALSE];
ENDLOOP;
ViewerOps.PaintViewer[testViewer, client, FALSE, $Update];
};
ToContext: PROC [context: Imager.Context] = {
[] ← HistographPaint[testViewer, context, whatChanged, FALSE];
};
testViewer: ViewerClasses.Viewer ← NIL;
topViewer: ViewerClasses.Viewer ← NIL;
whatChanged: REF ← $Print;
graphClass: ViewerClasses.ViewerClass ← NEW[ViewerClasses.ViewerClassRec ←
[paint: HistographPaint]];
ViewerOps.RegisterViewerClass[$Histograph, graphClass];
END.