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.4 1994/10/04 19:01:05 tamches
7 /* Cleaned up the resource bar color choices
9 * Revision 1.3 1994/09/30 23:13:41 tamches
10 * reads resource width from tcl as "currResourceWidth", to accomodate
11 * new barChart.tcl code which adjusts this variable when resources
12 * are added/deleted. (previously it had been constant)
14 * Revision 1.2 1994/09/29 20:05:32 tamches
17 * Revision 1.1 1994/09/29 19:48:27 tamches
18 * initial implementation. A to-do list is kept in barChart.tcl
22 // tk/tcl has a very nice interface for mixing C++ and tk/tcl
23 // scripts. From within tcl, C++ code can be called via
24 // new commands; new commands are created with a call
25 // to Tcl_CreateCommand(...) Unfortunately, you can see
26 // a catch-22 here; in order to even call Tcl_CreateCommand(),
27 // we need to be out of tcl and in C++! The only way around
28 // this problem is to start the process in C++ and have it
29 // call tk/tcl. This is done with Tcl_EvalFile(...). Of
30 // course, just before this, we sneak in our Tcl_CreateCommand()
33 // Somewhere in the tk library, there is a main() that
34 // basically does this for us; you have to provide a -f tcl-file-name
35 // option so it knows what to send to Tcl_EvalFile(...)
36 // This main() assumes a function Tcl_InitApp(), which will
37 // be called before the Tcl_EvalFile() is performed...that's
38 // the time to sneak in our Tcl_CreateCommand() stuff...
41 #include <stdlib.h> // exit()
48 #include "visi/h/visualization.h"
50 #include "barChartTcl.h"
52 #pragma implementation "array2d.h"
56 // main data structure; holds bar information. Does not
57 // hold x axis or y axis information (or contents), because
58 // they are managed just fine from tcl (barChart.tcl) at present.
59 // Created dynamically in DrawBarsInstallCommand. Cannot be
60 // created before then since necessary constructor arguments
61 // such as tk window name are not yet known.
63 BarChart *theBarChart;
65 /* ********************************************************************
66 * *********************** BarChart methods ***************************
67 * ********************************************************************
70 BarChart::BarChart(char *tkWindowName,
71 const bool dblBuffer, const bool noFlicker,
72 const int initNumMetrics, const int initNumResources,
73 const bool initFlushFlag) :
74 metricColors(initNumMetrics),
75 prevBarHeights(initNumMetrics, initNumResources),
76 barXoffsets(initNumMetrics, initNumResources),
77 barWidths (initNumMetrics, initNumResources),
78 barHeights (initNumMetrics, initNumResources),
79 barValues (initNumMetrics, initNumResources),
80 metricCurrMaxYs(initNumMetrics),
81 flushFlag (initFlushFlag)
84 theWindowName = new char[strlen(tkWindowName)+1];
85 if (theWindowName == NULL)
86 panic("BarChart constructor: out of memory!");
87 strcpy(theWindowName, tkWindowName);
89 theWindow = Tk_NameToWindow(MainInterp, tkWindowName, Tk_MainWindow(MainInterp));
90 if (theWindow == NULL)
91 panic("BarChart constructor: Tk_NameToWindow() failed!");
93 HaveSeenFirstGoodWid = false;
94 wid = Tk_WindowId(theWindow);
97 width = Tk_Width(theWindow);
98 height = Tk_Height(theWindow);
99 isMapped = Tk_IsMapped(theWindow);
100 display = Tk_Display(theWindow);
101 width -= borderPix*2;
102 height -= borderPix*2;
104 greyColor = Tk_GetColor(MainInterp, theWindow, None, Tk_GetUid("grey"));
106 DataFormat = Current;
108 drawMode = dblBuffer ? DoubleBuffer : (noFlicker ? NoFlicker : Flicker);
110 RethinkMetricsAndResources();
111 // sets numMetrics, numResources, barXoffets, barWidths,
112 // barHeights, prevBarHeights, barValues, metricCurrMaxYs
115 BarChart::~BarChart() {
116 Tk_CancelIdleCall(lowestLevelDrawBars, NULL);
118 delete [] theWindowName;
120 XFreeGC(display, myGC);
122 Tk_FreeColor(greyColor);
123 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
124 Tk_FreeColor(metricColors[metriclcv]);
126 changeDoubleBuffering(false, false); // free offscreen pixmap, if necessary
129 void BarChart::changeDoubleBuffering(bool doubleBuffer, bool noFlicker) {
130 // note that we don't try to optimize and return immediately if
131 // the mode hasn't changed. Why? to accomodate possible change in size.
133 if (!HaveSeenFirstGoodWid)
136 DrawModes newDrawMode;
138 newDrawMode = DoubleBuffer;
140 newDrawMode = NoFlicker;
142 newDrawMode = Flicker;
144 if (drawMode == DoubleBuffer)
145 // erase offscreen pixmap and reallocate with new size
146 XFreePixmap(display, doubleBufferPixmap);
148 drawMode = newDrawMode;
150 if (drawMode == DoubleBuffer)
151 doubleBufferPixmap = XCreatePixmap(display, wid,
153 Tk_Height(theWindow),
154 Tk_Depth(theWindow));
155 // note that we use Tk_Width and Tk_Height instead of width and height.
156 // these values differ by (borderPix) in each dimension.
157 else if (drawMode == NoFlicker)
158 ResetPrevBarHeights();
161 bool BarChart::TryFirstGoodWid() {
162 // returns true if the wid is valid
164 if (!HaveSeenFirstGoodWid) {
165 wid = Tk_WindowId(theWindow);
167 return false; // sigh; still invalid
169 HaveSeenFirstGoodWid = true;
173 values.foreground = greyColor->pixel;
174 values.background = greyColor->pixel;
175 // values.graphics_exposures = False;
177 myGC = XCreateGC(display, wid,
178 GCForeground | GCBackground,
181 panic("BarChart constructor: XCreateGC() failed!");
183 // initialize offscreen pixmap
184 if (drawMode == DoubleBuffer)
185 doubleBufferPixmap = XCreatePixmap(display,
186 wid, // used only to determine the screen
188 Tk_Height(theWindow),
193 processResizeWindow();
199 void BarChart::processResizeWindow() {
200 if (!TryFirstGoodWid())
201 return; // our window is still invalid
203 width = Tk_Width(theWindow) - 2*borderPix; // subtract left+right border
204 height = Tk_Height(theWindow) - 2*borderPix; // subract top+bottom border
206 // update off-screen pixmap's dimensions to accomodate the resize
207 changeDoubleBuffering(drawMode==DoubleBuffer, drawMode==NoFlicker);
212 void BarChart::processExposeWindow() {
213 if (!TryFirstGoodWid())
214 return; // our window is still invalid
216 cout << "Welcome to BarChart::processExposeWindow" << endl;
217 if (drawMode == NoFlicker)
218 ResetPrevBarHeights();
223 char *gimmeColorName(const int metriclcv) {
224 static char *theNames[] = {
236 // it's safe to return a pointer to a local variable in this case,
237 // because it (the array and its contents) is statically allocated.
238 return theNames[metriclcv % 8];
241 void BarChart::RethinkMetricColors() {
242 metricColors.reallocate(numMetrics);
244 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
245 XColor *theColor = Tk_GetColor(MainInterp, theWindow, None, Tk_GetUid(gimmeColorName(metriclcv)));
247 metricColors[metriclcv] = theColor;
251 void BarChart::ClearScreen() {
252 XSetForeground(display, myGC, greyColor->pixel);
253 XSetBackground(display, myGC, greyColor->pixel);
255 XFillRectangle(display, wid, myGC,
256 borderPix, // start-x
257 borderPix, // start-y
258 width, // this vrble should have already been adjusted re: borderPix
262 void BarChart::ResetPrevBarHeights() {
263 prevBarHeights.reallocate(numMetrics, numResources);
265 if (drawMode == NoFlicker) {
266 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
267 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++)
268 prevBarHeights[metriclcv][resourcelcv]=0;
269 if (HaveSeenFirstGoodWid)
274 void BarChart::RethinkMetricsAndResources() {
275 // clear window, erase and reallocate barXoffsets, barWidths,
276 // prevBarHeights, barHeights, metricCurrMaxYs; set numMetrics,
277 // numResources -- all based on a complete re-reading from dataGrid[][].
278 // (If visi had provided more fine-grained callbacks than
279 // ADDMETRICSRESOURCES, such a crude routine would not be necessary.)
281 // When done, redraw.
283 // #include "visualization.h" from visi-lib to get global
284 // variable "dataGrid"
286 numMetrics = dataGrid.NumMetrics();
287 numResources = dataGrid.NumResources();
289 cout << "Welcome to BarChart::RethinkMetricsAndResources; m=" << numMetrics << "; r=" << numResources << endl;
291 // the following is very unfortunate for existing metric/resource pairs;
292 // their values do not change and so should not be reset. This is
293 // particularly troublesome for metricCurrMaxYs[], where resetting their
294 // values (as we do here) can be considered a bug.
295 barXoffsets.reallocate(numMetrics, numResources);
296 barWidths .reallocate(numMetrics, numResources);
297 barHeights .reallocate(numMetrics, numResources);
298 prevBarHeights.reallocate(numMetrics, numResources);
299 barValues .reallocate(numMetrics, numResources);
300 metricCurrMaxYs.reallocate(numMetrics);
302 // reset bar values (very unfortunate for existing pairs)
303 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++)
304 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++)
305 barValues[metriclcv][resourcelcv]=0;
307 // reset max y values (unfortunate for existing pairs)
308 for (metriclcv=0; metriclcv<numMetrics; metriclcv++) {
310 sprintf(buffer, "%d", metriclcv);
311 char *str = Tcl_GetVar2(MainInterp, "metricMaxValues", buffer, TCL_GLOBAL_ONLY);
313 panic("BarChart::RethinkMetricsAndResources() -- could not read 'metricMaxValues'");
315 metricCurrMaxYs[metriclcv] = atof(str);
318 RethinkMetricColors(); // reallocates and rethinks metricColors[]
320 RethinkBarLayouts(); // as if there were a resize (no reallocations, but
321 // resets/rethinks barXoffsets[], barWidths[], barHeights[],
324 if (HaveSeenFirstGoodWid)
328 void BarChart::processNewData(int newBucketIndex) {
329 // assuming new data has arrived at the given bucket index for all
330 // metric/rsrc pairs, read the new information from dataGrid[][],
331 // update barHeights[][], barValues[][], and metricCurrMaxYs[] (if needed),
332 // and call lowLevelDrawBars()
334 // PASS 1: Update barValues[][] and check for y-axis overflow, calling
335 // setMetricNewMaxY() if overflow is detected.
337 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
338 visi_GridHistoArray &metricValues = dataGrid[metriclcv];
340 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
341 visi_GridCellHisto &theCell = metricValues[resourcelcv];
344 switch (DataFormat) {
346 newVal = theCell.Value(newBucketIndex);
349 // newVal = theCell.AggregateValue(0); // ???
350 newVal = dataGrid.AggregateValue(metriclcv, resourcelcv);
353 // newVal = theCell.SumValue(1); // ???
354 newVal = dataGrid.SumValue(metriclcv, resourcelcv);
360 barValues[metriclcv][resourcelcv] = newVal;
362 // the dreaded check for y-axis overflow (slows things down greatly if there
363 // is indeed overflow)
364 double currMaxY = metricCurrMaxYs[metriclcv];
365 if (newVal > currMaxY)
366 setMetricNewMaxY(metriclcv, newVal);
370 // PASS 2: Now that the y-axis overflows, if any, have been processed, we can
371 // calculate barHeights with confidence that the scale isn't going to
377 double BarChart::nicelyRoundedMetricNewYMaxValue(int metricindex, double newmaxval) {
378 assert(0<=metricindex && metricindex<numMetrics);
381 sprintf(buffer, "lindex [getMetricHints %s] 3", dataGrid.MetricName(metricindex));
383 if (TCL_OK != Tcl_Eval(MainInterp, buffer))
384 panic("BarChart::nicelyRoundedMetricNewYMaxValue() -- could not eval");
386 double hintedStep = atof(MainInterp->result);
388 double result = newmaxval + (hintedStep - fmod(newmaxval, hintedStep));
389 // if fmod() doesn't return a number greater than 0, this won't work quite right
394 void BarChart::setMetricNewMaxY(int metricindex, double newmaxval) {
395 // given an actual bar value (newmaxval) that overflows the current
396 // y-axis maximum value for the given metric, rethink what the
397 // y-axis maximum value for the given metric should be.
398 // Note that it may not be exactly "newmaxval"; we usually want
399 // to round to a relatively nice number.
401 assert(0<=metricindex && metricindex<numMetrics);
403 newmaxval = nicelyRoundedMetricNewYMaxValue(metricindex, newmaxval);
404 metricCurrMaxYs[metricindex] = newmaxval;
407 sprintf(buffer, "processNewMetricMax %d %g", metricindex, newmaxval);
408 if (TCL_OK != Tcl_Eval(MainInterp, buffer))
409 cerr << "warning -- BarChart::setMetricNewMaxY() could not inform barChart.tcl of new-max-y-value (no script processNewMetricMax?)" << endl;
412 void BarChart::RethinkBarLayouts() {
413 // assuming a complete resize (but not an added or deleted metric
414 // or resource or even new data values), fill in barXoffsets, barWidths, etc.
416 // does not touch metricCurrMaxYs or barValues
418 // note: the following loop starts with resources, then does metrics. should not
419 // cause any big problems, and more intuitive in this case...
420 char *fullResourceWidthStr = Tcl_GetVar(MainInterp, "currResourceWidth", TCL_GLOBAL_ONLY);
421 if (NULL == fullResourceWidthStr)
422 panic("BarChart::RethinkBarLayouts() -- could not read 'currResourceWidth' from tcl");
424 int totalResourceWidth = atoi(fullResourceWidthStr);
425 int fullResourceWidth = (totalResourceWidth * 90) / 100;
427 int resourceBorderWidth = (totalResourceWidth - fullResourceWidth) / 2;
428 int individualResourceWidth = (numMetrics == 0) ? 0 : fullResourceWidth / numMetrics;
430 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
431 int left = borderPix + (resourcelcv * totalResourceWidth) + resourceBorderWidth;
433 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
434 barXoffsets[metriclcv][resourcelcv] = left + metriclcv * individualResourceWidth;
435 barWidths [metriclcv][resourcelcv] = individualResourceWidth;
436 barHeights [metriclcv][resourcelcv] = 0; // all bars start off flat
440 if (drawMode == NoFlicker)
441 ResetPrevBarHeights();
444 void BarChart::RethinkBarHeights() {
445 // set the height of each bar to the fraction of window height
446 // that equals the fraction of the bar's current value to its
447 // metric max y value.
449 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
450 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
451 double theHeight = barValues[metriclcv][resourcelcv] /
452 metricCurrMaxYs[metriclcv];
453 theHeight *= this->height; // scale by window height (excluding border pixels)
454 barHeights[metriclcv][resourcelcv] = (int)theHeight;
458 if (HaveSeenFirstGoodWid)
462 bool currentlyInstalledLowLevelDrawBars=false;
464 void BarChart::lowLevelDrawBars() {
465 // perhaps we should check to see if the barchart program has
466 // been shut down before attempting to do anything? Nah,
467 // just be sure to call Tk_CancelIdleCall() on lowestLevelDrawBars()
468 // in the destructor.
470 isMapped = Tk_IsMapped(theWindow);
474 if (!HaveSeenFirstGoodWid)
477 if (!currentlyInstalledLowLevelDrawBars) {
478 currentlyInstalledLowLevelDrawBars = true;
480 Tk_DoWhenIdle(lowestLevelDrawBars, NULL);
484 void BarChart::lowestLevelDrawBarsDoubleBuffer() {
485 unsigned long bgPixel = greyColor->pixel;
487 // note that the double-buffer pixmap DOES include space for border
488 // pixels, even though we don't make use of such pixels. This was
489 // done because keeping the offscreen pixmap the same size as the
490 // window simplifies the drawing; e.g. barXoffsets[] already have
491 // borderPix added in to each element and it would be clumsy to
494 // Our XCopyArea() (when done drawing) is rigged (check the args) to
495 // not copy the border pixel area...
497 // clear the offscreen buffer. XClearArea() works only on windows, so fill a rectangle
498 // with the background color.
499 XSetForeground(display, myGC, bgPixel);
500 XSetBackground(display, myGC, bgPixel);
501 XFillRectangle(display, doubleBufferPixmap,
503 borderPix, // x-offset, relative to drawable
504 borderPix, // y-offset, relative to drawable
505 width, // does not include border pixels
506 height // does not include border pixels
510 XSync(display, False);
512 // do the drawing onto offscreen pixmap
513 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
514 XSetForeground(display, myGC, metricColors[metriclcv]->pixel);
516 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
517 XFillRectangle(display,
520 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
521 height - barHeights[metriclcv][resourcelcv] - borderPix, // top-y
522 barWidths[metriclcv][resourcelcv], // width
523 barHeights[metriclcv][resourcelcv] // height
529 XSync(display, False);
531 // copy offscreen pixmap onto screen
532 // cout << '{'; cout.flush();
535 doubleBufferPixmap, // source
538 borderPix, borderPix, // source x, y (note how we exclude the border)
539 width, height, // these vrbles are preset to exclude the borders
540 borderPix, borderPix // dest x, y (note how we exclude the border)
542 // cout << '}'; cout.flush();
545 XSync(display, False);
548 void BarChart::lowestLevelDrawBarsNoFlicker() {
549 // be smart about what gets drawn verses what gets erased; if a bar is lower than
550 // last time, erase the appropriate chunk on top; if a bar is higher than
551 // last time, draw the approrpriate chunk on top.
553 unsigned long bgPixel = greyColor->pixel;
555 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
556 unsigned long fgPixel = metricColors[metriclcv]->pixel;
558 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
559 int newHeight = barHeights [metriclcv][resourcelcv];
560 int oldHeight = prevBarHeights[metriclcv][resourcelcv];
562 if (newHeight > oldHeight) {
563 // the bar is higher than last time; draw the appropriate chunk on top
564 XSetForeground(display, myGC, fgPixel);
565 XSetBackground(display, myGC, bgPixel);
567 XFillRectangle(display, wid, myGC,
568 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
569 height - newHeight - borderPix, // top-y
570 barWidths[metriclcv][resourcelcv], // width
571 newHeight - oldHeight // height
574 else if (newHeight < oldHeight) {
575 // the bar is lower than last time; erase appropriate chunk on top
576 XSetForeground(display, myGC, bgPixel);
577 XSetBackground(display, myGC, fgPixel);
579 XFillRectangle(display, wid, myGC,
580 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
581 height - oldHeight - borderPix, // top-y
582 barWidths[metriclcv][resourcelcv], // width
583 oldHeight - newHeight // height
587 ; // no need to redraw bar at the same height as before
590 prevBarHeights[metriclcv][resourcelcv] = newHeight;
595 XSync(display, False);
598 void BarChart::lowestLevelDrawBarsFlicker() {
599 ClearScreen(); // clear window, leaving border pixels alone
601 XSync(display, False);
603 unsigned long bgPixel = greyColor->pixel;
605 // do the drawing onto offscreen pixmap
606 for (int metriclcv=0; metriclcv<numMetrics; metriclcv++) {
607 XSetForeground(display, myGC, metricColors[metriclcv]->pixel);
608 XSetBackground(display, myGC, bgPixel);
610 for (int resourcelcv=0; resourcelcv<numResources; resourcelcv++) {
611 XFillRectangle(display, wid,
613 currScrollOffset + barXoffsets[metriclcv][resourcelcv], // left-x
614 height - barHeights[metriclcv][resourcelcv] - borderPix, // top-y
615 barWidths[metriclcv][resourcelcv], // width
616 barHeights[metriclcv][resourcelcv] // height
623 XSync(display, False);
626 void BarChart::lowestLevelDrawBars(ClientData ignore) {
627 // cout << '['; cout.flush();
629 // NOTE: a --static-- member function --> no "this" exists!!
631 if (!theBarChart->HaveSeenFirstGoodWid) {
632 cout << "BarChart::lowestLevelDrawBars -- haven't yet mapped? (ignoring)" << endl;
636 currentlyInstalledLowLevelDrawBars = false; // now, new requests will be handled
637 // It seems wrong to delay this line until the bottom of the
638 // routine; it could cause new data to be ignored
640 switch (theBarChart->drawMode) {
642 theBarChart->lowestLevelDrawBarsDoubleBuffer();
645 theBarChart->lowestLevelDrawBarsNoFlicker();
648 theBarChart->lowestLevelDrawBarsFlicker();
653 // cout << ']'; cout.flush();
656 void BarChart::processNewScrollPosition(int newPos) {
657 if (!HaveSeenFirstGoodWid)
660 // cout << "BarChart::processNewScrollPosition -- new pos=" << newPos << endl;
662 // adjust the current x-pixel scroll offset value and
663 // simulate an expose event
664 currScrollOffset = -newPos;
666 // tk is clever with its canvas widgets to never start the leftmost
667 // drawing portion at less than 0; we must be just as clever to keep
668 // everything positioned correctly.
669 // if (currScrollOffset < 0 && scrollbarisfullwidth)
670 // currScrollOffset=0;
672 if (drawMode == NoFlicker)
673 ResetPrevBarHeights();
678 void BarChart::rethinkDataFormat() {
679 // assuming the data format has changed, read its
680 // value (from tcl) and adjust our internal settings accordingly.
682 char *dataFormatString = Tcl_GetVar(MainInterp, "DataFormat", TCL_GLOBAL_ONLY);
683 if (dataFormatString == NULL) {
684 cerr << "warning: BarChart::rethinkDataFormat() -- could not read tcl vrble 'DataFormat'; ignoring" << endl;
688 if (0==strcmp(dataFormatString, "Instantaneous"))
689 DataFormat = Current;
690 else if (0==strcmp(dataFormatString, "Average"))
691 DataFormat = Average;
692 else if (0==strcmp(dataFormatString, "Sum"))
695 cerr << "BarChart::rethinkDataFormat() -- unrecognized format '"
696 << dataFormatString << "'; ignoring" << endl;
700 // we have changed the data format. The max y-values need
701 // to be "re-thought" (else, what if we change from total to current;
702 // the max y value will be so high and won't ever get lowered)
703 RethinkMetricsAndResources();
704 // a bit more than is really needed!