1 // This is the C++ portion of barchart version 2.
2 // Much of the user interface mundane stuff (menus, etc.) is still
3 // programmed in tk/tcl in barChart.tcl.
5 /* $Log: barChart.C,v $
6 /* Revision 1.2 1994/09/29 20:05:32 tamches
9 * Revision 1.1 1994/09/29 19:48:27 tamches
10 * initial implementation. A to-do list is kept in barChart.tcl
14 // tk/tcl has a very nice interface for mixing C++ and tk/tcl
15 // scripts. From within tcl, C++ code can be called via
16 // new commands; new commands are created with a call
17 // to Tcl_CreateCommand(...) Unfortunately, you can see
18 // a catch-22 here; in order to even call Tcl_CreateCommand(),
19 // we need to be out of tcl and in C++! The only way around
20 // this problem is to start the process in C++ and have it
21 // call tk/tcl. This is done with Tcl_EvalFile(...). Of
22 // course, just before this, we sneak in our Tcl_CreateCommand()
25 // Somewhere in the tk library, there is a main() that
26 // basically does this for us; you have to provide a -f tcl-file-name
27 // option so it knows what to send to Tcl_EvalFile(...)
28 // This main() assumes a function Tcl_InitApp(), which will
29 // be called before the Tcl_EvalFile() is performed...that's
30 // the time to sneak in our Tcl_CreateCommand() stuff...
33 #include <stdlib.h> // exit()
40 #include "visi/h/visualization.h"
42 #include "barChartTcl.h"
44 #pragma implementation "array2d.h"
48 BarChart *theBarChart; // main data structure; holds bar information. Does not
49 // hold x axis or y axis information (or contents), because
50 // they are managed just fine from tcl (barChart.tcl) at present.
51 // Created dynamically in DrawBarsInstallCommand. Cannot be
52 // created before then since necessary constructor arguments
53 // such as tk window name are not yet known.
55 /* ********************************************************************
56 * *********************** BarChart methods ***************************
57 * ********************************************************************
60 BarChart::BarChart(char *tkWindowName,
61 const bool dblBuffer, const bool noFlicker,
62 const int initNumMetrics, const int initNumResources,
63 const bool initFlushFlag) :
64 metricColors(initNumMetrics),
65 prevBarHeights(initNumMetrics, initNumResources),
66 barXoffsets(initNumMetrics, initNumResources),
67 barWidths (initNumMetrics, initNumResources),
68 barHeights (initNumMetrics, initNumResources),
69 barValues (initNumMetrics, initNumResources),
70 metricCurrMaxYs(initNumMetrics),
71 flushFlag (initFlushFlag)
74 theWindowName = new char[strlen(tkWindowName)+1];
75 if (theWindowName == NULL)
76 panic("BarChart constructor: out of memory!");
77 strcpy(theWindowName, tkWindowName);
79 theWindow = Tk_NameToWindow(MainInterp, tkWindowName, Tk_MainWindow(MainInterp));
80 if (theWindow == NULL)
81 panic("BarChart constructor: Tk_NameToWindow() failed!");
83 HaveSeenFirstGoodWid = false;
84 wid = Tk_WindowId(theWindow);
87 width = Tk_Width(theWindow);
88 height = Tk_Height(theWindow);
89 isMapped = Tk_IsMapped(theWindow);
90 display = Tk_Display(theWindow);
92 height -= borderPix*2;
94 greyColor = Tk_GetColor(MainInterp, theWindow, None, Tk_GetUid("grey"));
98 drawMode = dblBuffer ? DoubleBuffer : (noFlicker ? NoFlicker : Flicker);
100 RethinkMetricsAndResources();
101 // sets numMetrics, numResources, barXoffets, barWidths,
102 // barHeights, prevBarHeights, barValues, metricCurrMaxYs
105 BarChart::~BarChart() {
106 Tk_CancelIdleCall(lowestLevelDrawBars, NULL);
108 delete [] theWindowName;
110 XFreeGC(display, myGC);
112 Tk_FreeColor(greyColor);
113 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
114 Tk_FreeColor(metricColors[metriclcv]);
116 changeDoubleBuffering(false, false); // free offscreen pixmap, if necessary
119 void BarChart::changeDoubleBuffering(bool doubleBuffer, bool noFlicker) {
120 // note that we don't try to optimize and return immediately if
121 // the mode hasn't changed. Why? to accomodate possible change in size.
123 if (!HaveSeenFirstGoodWid)
126 DrawModes newDrawMode;
128 newDrawMode = DoubleBuffer;
130 newDrawMode = NoFlicker;
132 newDrawMode = Flicker;
134 if (drawMode == DoubleBuffer)
135 // erase offscreen pixmap and reallocate with new size
136 XFreePixmap(display, doubleBufferPixmap);
138 drawMode = newDrawMode;
140 if (drawMode == DoubleBuffer)
141 doubleBufferPixmap = XCreatePixmap(display, wid,
143 Tk_Height(theWindow),
144 Tk_Depth(theWindow));
145 // note that we use Tk_Width and Tk_Height instead of width and height.
146 // these values differ by (borderPix) in each dimension.
147 else if (drawMode == NoFlicker)
148 ResetPrevBarHeights();
151 bool BarChart::TryFirstGoodWid() {
152 // returns true if the wid is valid
154 if (!HaveSeenFirstGoodWid) {
155 wid = Tk_WindowId(theWindow);
157 return false; // sigh; still invalid
159 HaveSeenFirstGoodWid = true;
163 values.foreground = greyColor->pixel;
164 values.background = greyColor->pixel;
165 // values.graphics_exposures = False;
167 myGC = XCreateGC(display, wid,
168 GCForeground | GCBackground,
171 panic("BarChart constructor: XCreateGC() failed!");
173 // initialize offscreen pixmap
174 if (drawMode == DoubleBuffer)
175 doubleBufferPixmap = XCreatePixmap(display,
176 wid, // used only to determine the screen
178 Tk_Height(theWindow),
183 processResizeWindow();
189 void BarChart::processResizeWindow() {
190 if (!TryFirstGoodWid())
191 return; // our window is still invalid
193 width = Tk_Width(theWindow) - 2*borderPix; // subtract left+right border
194 height = Tk_Height(theWindow) - 2*borderPix; // subract top+bottom border
196 // update off-screen pixmap's dimensions to accomodate the resize
197 changeDoubleBuffering(drawMode==DoubleBuffer, drawMode==NoFlicker);
202 void BarChart::processExposeWindow() {
203 if (!TryFirstGoodWid())
204 return; // our window is still invalid
206 cout << "Welcome to BarChart::processExposeWindow" << endl;
207 if (drawMode == NoFlicker)
208 ResetPrevBarHeights();
213 char *gimmeColorName(const int metriclcv) {
214 static char *theNames[] = {
221 return theNames[metriclcv % 4];
224 void BarChart::RethinkMetricColors() {
225 metricColors.reallocate(numMetrics);
227 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
228 XColor *theColor = Tk_GetColor(MainInterp, theWindow, None, Tk_GetUid(gimmeColorName(metriclcv)));
230 metricColors[metriclcv] = theColor;
234 void BarChart::ClearScreen() {
235 XSetForeground(display, myGC, greyColor->pixel);
236 XSetBackground(display, myGC, greyColor->pixel);
238 XFillRectangle(display, wid, myGC,
239 borderPix, // start-x
240 borderPix, // start-y
241 width, // this vrble should have already been adjusted re: borderPix
245 void BarChart::ResetPrevBarHeights() {
246 prevBarHeights.reallocate(numMetrics, numResources);
248 if (drawMode == NoFlicker) {
249 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
250 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++)
251 prevBarHeights[metriclcv][resourcelcv]=0;
252 if (HaveSeenFirstGoodWid)
257 void BarChart::RethinkMetricsAndResources() {
258 // clear window, erase and reallocate barXoffsets, barWidths, prevBarHeights,
259 // barHeights, metricCurrMaxYs; set numMetrics, numResources -- all based on a
260 // complete re-reading from dataGrid[][]. (If visi had provided more fine-grained
261 // callbacks than ADDMETRICSRESOURCES, such a crude routine would not be necessary.)
263 // When done, redraw.
265 // #include "visualization.h" from visi-lib to get global
266 // variable "dataGrid"
268 numMetrics = dataGrid.NumMetrics();
269 numResources = dataGrid.NumResources();
271 cout << "Welcome to BarChart::RethinkMetricsAndResources; m=" << numMetrics << "; r=" << numResources << endl;
273 // the following is very unfortunate for existing metric/resource pairs;
274 // their values do not change and so should not be reset. This is
275 // particularly troublesome for metricCurrMaxYs[], where resetting their
276 // values (as we do here) can be considered a bug.
277 barXoffsets.reallocate(numMetrics, numResources);
278 barWidths .reallocate(numMetrics, numResources);
279 barHeights .reallocate(numMetrics, numResources);
280 prevBarHeights.reallocate(numMetrics, numResources);
281 barValues .reallocate(numMetrics, numResources);
282 metricCurrMaxYs.reallocate(numMetrics);
284 // reset bar values (very unfortunate for existing pairs)
285 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
286 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++)
287 barValues[metriclcv][resourcelcv]=0;
289 // reset max y values (unfortunate for existing pairs)
290 for (metriclcv=0; metriclcv<numMetrics; metriclcv++) {
292 sprintf(buffer, "%d", metriclcv);
293 char *str = Tcl_GetVar2(MainInterp, "metricMaxValues", buffer, TCL_GLOBAL_ONLY);
295 panic("BarChart::RethinkMetricsAndResources() -- could not read 'metricMaxValues'");
297 metricCurrMaxYs[metriclcv] = atof(str);
300 RethinkMetricColors(); // reallocates and rethinks metricColors[]
302 RethinkBarLayouts(); // as if there were a resize (no reallocations, but
303 // resets/rethinks barXoffsets[], barWidths[], barHeights[],
306 if (HaveSeenFirstGoodWid)
310 void BarChart::processNewData(int newBucketIndex) {
311 // assuming new data has arrived at the given bucket index for all
312 // metric/rsrc pairs, read the new information from dataGrid[][],
313 // update barHeights[][], barValues[][], and metricCurrMaxYs[] (if needed),
314 // and call lowLevelDrawBars()
316 // PASS 1: Update barValues[][] and check for y-axis overflow, calling
317 // setMetricNewMaxY() if overflow is detected.
319 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
320 visi_GridHistoArray &metricValues = dataGrid[metriclcv];
322 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
323 visi_GridCellHisto &theCell = metricValues[resourcelcv];
326 switch (DataFormat) {
328 newVal = theCell.Value(newBucketIndex);
331 // newVal = theCell.AggregateValue(0); // ???
332 newVal = dataGrid.AggregateValue(metriclcv, resourcelcv);
335 // newVal = theCell.SumValue(1); // ???
336 newVal = dataGrid.SumValue(metriclcv, resourcelcv);
342 barValues[metriclcv][resourcelcv] = newVal;
344 // the dreaded check for y-axis overflow (slows things down greatly if there
345 // is indeed overflow)
346 double currMaxY = metricCurrMaxYs[metriclcv];
347 if (newVal > currMaxY)
348 setMetricNewMaxY(metriclcv, newVal);
352 // PASS 2: Now that the y-axis overflows, if any, have been processed, we can
353 // calculate barHeights with confidence that the scale isn't going to
359 double BarChart::nicelyRoundedMetricNewYMaxValue(int metricindex, double newmaxval) {
360 assert(0<=metricindex && metricindex<numMetrics);
363 sprintf(buffer, "lindex [getMetricHints %s] 3", dataGrid.MetricName(metricindex));
365 if (TCL_OK != Tcl_Eval(MainInterp, buffer))
366 panic("BarChart::nicelyRoundedMetricNewYMaxValue() -- could not eval");
368 double hintedStep = atof(MainInterp->result);
370 double result = newmaxval + (hintedStep - fmod(newmaxval, hintedStep));
371 // if fmod() doesn't return a number greater than 0, this won't work quite right
376 void BarChart::setMetricNewMaxY(int metricindex, double newmaxval) {
377 // given an actual bar value (newmaxval) that overflows the current
378 // y-axis maximum value for the given metric, rethink what the
379 // y-axis maximum value for the given metric should be.
380 // Note that it may not be exactly "newmaxval"; we usually want
381 // to round to a relatively nice number.
383 assert(0<=metricindex && metricindex<numMetrics);
385 newmaxval = nicelyRoundedMetricNewYMaxValue(metricindex, newmaxval);
386 metricCurrMaxYs[metricindex] = newmaxval;
389 sprintf(buffer, "processNewMetricMax %d %g", metricindex, newmaxval);
390 if (TCL_OK != Tcl_Eval(MainInterp, buffer))
391 cerr << "warning -- BarChart::setMetricNewMaxY() could not inform barChart.tcl of new-max-y-value (no script processNewMetricMax?)" << endl;
394 void BarChart::RethinkBarLayouts() {
395 // assuming a complete resize (but not an added or deleted metric
396 // or resource or even new data values), fill in barXoffsets, barWidths, etc.
398 // does not touch metricCurrMaxYs or barValues
400 // note: the following loop starts with resources, then does metrics. should not
401 // cause any big problems, and more intuitive in this case...
402 char *fullResourceWidthStr = Tcl_GetVar(MainInterp, "resourceWidth", TCL_GLOBAL_ONLY);
403 if (NULL == fullResourceWidthStr)
404 panic("BarChart::RethinkBarLayouts() -- could not read 'resourceWidth' from tcl");
406 int totalResourceWidth = atoi(fullResourceWidthStr);
407 int fullResourceWidth = (totalResourceWidth * 90) / 100;
409 int resourceBorderWidth = (totalResourceWidth - fullResourceWidth) / 2;
410 int individualResourceWidth = (numMetrics == 0) ? 0 : fullResourceWidth / numMetrics;
412 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
413 int left = borderPix + (resourcelcv * totalResourceWidth) + resourceBorderWidth;
415 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
416 barXoffsets[metriclcv][resourcelcv] = left + metriclcv * individualResourceWidth;
417 barWidths [metriclcv][resourcelcv] = individualResourceWidth;
418 barHeights [metriclcv][resourcelcv] = 0; // all bars start off flat
422 if (drawMode == NoFlicker)
423 ResetPrevBarHeights();
426 void BarChart::RethinkBarHeights() {
427 // set the height of each bar to the fraction of window height
428 // that equals the fraction of the bar's current value to its
429 // metric max y value.
431 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
432 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
433 double theHeight = barValues[metriclcv][resourcelcv] /
434 metricCurrMaxYs[metriclcv];
435 theHeight *= this->height; // scale by window height (excluding border pixels)
436 barHeights[metriclcv][resourcelcv] = (int)theHeight;
440 if (HaveSeenFirstGoodWid)
444 bool currentlyInstalledLowLevelDrawBars=false;
446 void BarChart::lowLevelDrawBars() {
447 // perhaps we should check to see if the barchart program has
448 // been shut down before attempting to do anything? Nah,
449 // just be sure to call Tk_CancelIdleCall() on lowestLevelDrawBars()
450 // in the destructor.
452 isMapped = Tk_IsMapped(theWindow);
456 if (!HaveSeenFirstGoodWid)
459 if (!currentlyInstalledLowLevelDrawBars) {
460 currentlyInstalledLowLevelDrawBars = true;
462 Tk_DoWhenIdle(lowestLevelDrawBars, NULL);
466 void BarChart::lowestLevelDrawBarsDoubleBuffer() {
467 unsigned long bgPixel = greyColor->pixel;
469 // note that the double-buffer pixmap DOES include space for border
470 // pixels, even though we don't make use of such pixels. This was
471 // done because keeping the offscreen pixmap the same size as the
472 // window simplifies the drawing; e.g. barXoffsets[] already have
473 // borderPix added in to each element and it would be clumsy to
476 // Our XCopyArea() (when done drawing) is rigged (check the args) to
477 // not copy the border pixel area...
479 // clear the offscreen buffer. XClearArea() works only on windows, so fill a rectangle
480 // with the background color.
481 XSetForeground(display, myGC, bgPixel);
482 XSetBackground(display, myGC, bgPixel);
483 XFillRectangle(display, doubleBufferPixmap,
485 borderPix, // x-offset, relative to drawable
486 borderPix, // y-offset, relative to drawable
487 width, // does not include border pixels
488 height // does not include border pixels
492 XSync(display, False);
494 // do the drawing onto offscreen pixmap
495 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
496 XSetForeground(display, myGC, metricColors[metriclcv]->pixel);
498 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
499 XFillRectangle(display,
502 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
503 height - barHeights[metriclcv][resourcelcv] - borderPix, // top-y
504 barWidths[metriclcv][resourcelcv], // width
505 barHeights[metriclcv][resourcelcv] // height
511 XSync(display, False);
513 // copy offscreen pixmap onto screen
514 // cout << '{'; cout.flush();
517 doubleBufferPixmap, // source
520 borderPix, borderPix, // source x, y (note how we exclude the border)
521 width, height, // these vrbles are preset to exclude the borders
522 borderPix, borderPix // dest x, y (note how we exclude the border)
524 // cout << '}'; cout.flush();
527 XSync(display, False);
530 void BarChart::lowestLevelDrawBarsNoFlicker() {
531 // be smart about what gets drawn verses what gets erased; if a bar is lower than
532 // last time, erase the appropriate chunk on top; if a bar is higher than
533 // last time, draw the approrpriate chunk on top.
535 unsigned long bgPixel = greyColor->pixel;
537 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
538 unsigned long fgPixel = metricColors[metriclcv]->pixel;
540 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
541 int newHeight = barHeights [metriclcv][resourcelcv];
542 int oldHeight = prevBarHeights[metriclcv][resourcelcv];
544 if (newHeight > oldHeight) {
545 // the bar is higher than last time; draw the appropriate chunk on top
546 XSetForeground(display, myGC, fgPixel);
547 XSetBackground(display, myGC, bgPixel);
549 XFillRectangle(display, wid, myGC,
550 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
551 height - newHeight - borderPix, // top-y
552 barWidths[metriclcv][resourcelcv], // width
553 newHeight - oldHeight // height
556 else if (newHeight < oldHeight) {
557 // the bar is lower than last time; erase appropriate chunk on top
558 XSetForeground(display, myGC, bgPixel);
559 XSetBackground(display, myGC, fgPixel);
561 XFillRectangle(display, wid, myGC,
562 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
563 height - oldHeight - borderPix, // top-y
564 barWidths[metriclcv][resourcelcv], // width
565 oldHeight - newHeight // height
569 ; // no need to redraw bar at the same height as before
572 prevBarHeights[metriclcv][resourcelcv] = newHeight;
577 XSync(display, False);
580 void BarChart::lowestLevelDrawBarsFlicker() {
581 ClearScreen(); // clear window, leaving border pixels alone
583 XSync(display, False);
585 unsigned long bgPixel = greyColor->pixel;
587 // do the drawing onto offscreen pixmap
588 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
589 XSetForeground(display, myGC, metricColors[metriclcv]->pixel);
590 XSetBackground(display, myGC, bgPixel);
592 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
593 XFillRectangle(display, wid,
595 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
596 height - barHeights[metriclcv][resourcelcv] - borderPix, // top-y
597 barWidths[metriclcv][resourcelcv], // width
598 barHeights[metriclcv][resourcelcv] // height
605 XSync(display, False);
608 void BarChart::lowestLevelDrawBars(ClientData ignore) {
609 // cout << '['; cout.flush();
611 // NOTE: a --static-- member function --> no "this" exists!!
613 if (!theBarChart->HaveSeenFirstGoodWid) {
614 cout << "BarChart::lowestLevelDrawBars -- haven't yet mapped? (ignoring)" << endl;
618 currentlyInstalledLowLevelDrawBars = false; // now, new requests will be handled
619 // It seems wrong to delay this line until the bottom of the
620 // routine; it could cause new data to be ignored
622 switch (theBarChart->drawMode) {
624 theBarChart->lowestLevelDrawBarsDoubleBuffer();
627 theBarChart->lowestLevelDrawBarsNoFlicker();
630 theBarChart->lowestLevelDrawBarsFlicker();
635 // cout << ']'; cout.flush();
638 void BarChart::processNewScrollPosition(int newPos) {
639 if (!HaveSeenFirstGoodWid)
642 // cout << "BarChart::processNewScrollPosition -- new pos=" << newPos << endl;
644 // adjust the current x-pixel scroll offset value and
645 // simulate an expose event
646 currScrollOffset = -newPos;
648 // tk is clever with its canvas widgets to never start the leftmost
649 // drawing portion at less than 0; we must be just as clever to keep
650 // everything positioned correctly.
651 // if (currScrollOffset < 0 && scrollbarisfullwidth)
652 // currScrollOffset=0;
654 if (drawMode == NoFlicker)
655 ResetPrevBarHeights();
660 void BarChart::rethinkDataFormat() {
661 // assuming the data format has changed, read its
662 // value (from tcl) and adjust our internal settings accordingly.
664 char *dataFormatString = Tcl_GetVar(MainInterp, "DataFormat", TCL_GLOBAL_ONLY);
665 if (dataFormatString == NULL) {
666 cerr << "warning: BarChart::rethinkDataFormat() -- could not read tcl vrble 'DataFormat'; ignoring" << endl;
670 if (0==strcmp(dataFormatString, "Instantaneous"))
671 DataFormat = Current;
672 else if (0==strcmp(dataFormatString, "Average"))
673 DataFormat = Average;
674 else if (0==strcmp(dataFormatString, "Sum"))
677 cerr << "BarChart::rethinkDataFormat() -- unrecognized format '"
678 << dataFormatString << "'; ignoring" << endl;
682 // we have changed the data format. The max y-values need
683 // to be "re-thought" (else, what if we change from total to current;
684 // the max y value will be so high and won't ever get lowered)
685 RethinkMetricsAndResources();
686 // a bit more than is really needed!