1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 #ifdef HAVE_CONFIG_H
34 #include "config.h"
35 #endif // HAVE_CONFIG_H
36 
37 #ifdef HAVE_NODEKITS
38 
39 #include <Inventor/annex/Profiler/nodekits/SoScrollingGraphKit.h>
40 
41 #include <cstdlib>
42 #include <cstdio>
43 
44 #include <boost/scoped_ptr.hpp>
45 #include <boost/scoped_array.hpp>
46 #include <boost/intrusive_ptr.hpp>
47 
48 #include <Inventor/SbTime.h>
49 #include <Inventor/SbColor.h>
50 #include <Inventor/errors/SoDebugError.h>
51 #include <Inventor/sensors/SoFieldSensor.h>
52 #include <Inventor/nodes/SoSeparator.h>
53 #include <Inventor/nodes/SoBaseColor.h>
54 #include <Inventor/nodes/SoCoordinate3.h>
55 #include <Inventor/nodes/SoTranslation.h>
56 #include <Inventor/nodes/SoText2.h>
57 #include <Inventor/nodes/SoScale.h>
58 #include <Inventor/nodes/SoLineSet.h>
59 #include <Inventor/nodes/SoIndexedFaceSet.h>
60 
61 #include "misc/SbHash.h"
62 #include "nodekits/SoSubKitP.h"
63 
64 // *************************************************************************
65 
66 //namespace {
67 
68 class Graph {
69 public:
70   SbName key;
71   int index;
72   SbColor color;
73   float baseval;
74   float basetarget;
75 };
76 
77 class Datum {
78 public:
Datum(void)79   Datum(void) : next(NULL) { }
80   SbTime when;
81   SbList<float> datum;
82   Datum * next;
83 }; // Datum
84 
85 //} // anonymous
86 
87 // *************************************************************************
88 
89 class SoScrollingGraphKitP {
90 public:
SoScrollingGraphKitP(void)91   SoScrollingGraphKitP(void) : kit(NULL), first(NULL), last(NULL) {
92     this->cachedmaxvalue = 0.0f;
93     this->cachedrealmaxvalue = 0.0f;
94   }
~SoScrollingGraphKitP(void)95   ~SoScrollingGraphKitP(void) {
96     SbList<const char *> keys;
97     this->graphs.makeKeyList(keys);
98     for (int c = 0; c < keys.getLength(); ++c) {
99       Graph * obj = NULL;
100       if (this->graphs.get(keys[c], obj)) { delete obj; }
101     }
102     this->graphs.clear();
103 
104     while (this->first) {
105       Datum * datum = this->first;
106       this->first = this->first->next;
107       delete datum;
108     }
109   }
110 
111   boost::intrusive_ptr<SoSeparator> chart;
112   boost::scoped_ptr<SoFieldSensor> addValuesSensor;
113 
114   void pullStatistics(void);
115   Graph * getGraph(const SbName & key);
116   Graph * getGraph(int idx);
117   void addDatum(Datum * newDatum);
118 
119   void generateStackedBarsChart(void);
120 
121   SbHash<const char *, Graph *> graphs;
122 
123   SoScrollingGraphKit * kit;
124   Datum * first;
125   Datum * last;
126 
127   float cachedmaxvalue;
128   float cachedrealmaxvalue;
129 }; // SoScrollingGraphKitP
130 
131 // *************************************************************************
132 
133 #define PRIVATE(obj) ((obj)->pimpl)
134 
135 SO_KIT_SOURCE(SoScrollingGraphKit);
136 
137 void
initClass(void)138 SoScrollingGraphKit::initClass(void)
139 {
140   SO_KIT_INIT_CLASS(SoScrollingGraphKit, SoBaseKit, "BaseKit");
141 }
142 
SoScrollingGraphKit(void)143 SoScrollingGraphKit::SoScrollingGraphKit(void)
144 {
145   PRIVATE(this)->kit = this;
146 
147   SO_KIT_INTERNAL_CONSTRUCTOR(SoScrollingGraphKit);
148   SO_KIT_ADD_CATALOG_ENTRY(scene, SoSeparator, TRUE, this, "", FALSE);
149   SO_KIT_INIT_INSTANCE();
150 
151   SO_KIT_DEFINE_ENUM_VALUE(GraphicsType, LINES);
152   SO_KIT_DEFINE_ENUM_VALUE(GraphicsType, STACKED_BARS);
153   SO_KIT_DEFINE_ENUM_VALUE(GraphicsType, DEFAULT_GRAPHICS);
154   SO_KIT_SET_SF_ENUM_TYPE(graphicsType, GraphicsType);
155 
156   SO_KIT_DEFINE_ENUM_VALUE(RangeType, ABSOLUTE_ACCUMULATIVE);
157   //SO_KIT_DEFINE_ENUM_VALUE(RangeType, ABSOLUTE_OVERWRITE);
158   //SO_KIT_DEFINE_ENUM_VALUE(RangeType, RELATIVE_ACCUMULATIVE);
159   //SO_KIT_DEFINE_ENUM_VALUE(RangeType, RELATIVE_OVERWRITE);
160   SO_KIT_DEFINE_ENUM_VALUE(RangeType, DEFAULT_RANGETYPE);
161   SO_KIT_SET_SF_ENUM_TYPE(rangeType, RangeType);
162 
163   SO_KIT_ADD_FIELD(graphicsType, (DEFAULT_GRAPHICS));
164   SO_KIT_ADD_FIELD(rangeType, (DEFAULT_RANGETYPE));
165   SO_KIT_ADD_FIELD(seconds, (SbTime(5.0)));
166   SO_KIT_ADD_FIELD(colors, (SbColor(0.0f, 0.0f, 0.0f)));
167   SbColor default_colors[] = {
168     SbColor(1.0f, 0.0f, 0.0f),
169     SbColor(0.0f, 1.0f, 0.0f),
170     SbColor(0.0f, 0.0f, 1.0f),
171     SbColor(1.0f, 0.0f, 1.0f),
172     SbColor(1.0f, 1.0f, 0.0f),
173     SbColor(0.0f, 1.0f, 1.0f)
174   };
175   const int numdefaultcolors =
176     sizeof(default_colors) / sizeof(default_colors[0]);
177   this->colors.setNum(numdefaultcolors);
178   this->colors.setValues(0, numdefaultcolors, default_colors);
179   this->colors.setDefault(TRUE);
180 
181   SO_KIT_ADD_FIELD(viewportSize, (SbVec3f(512.0f, 512.0f, 0.0f)));
182   SO_KIT_ADD_FIELD(position, (SbVec3f(4.0f, 4.0f, 0.0f)));
183   SO_KIT_ADD_FIELD(size, (SbVec3f(256.0f, 100.0f, 0.0f)));
184 
185   SO_KIT_ADD_FIELD(addKeys, (SbName::empty()));
186   this->addKeys.setNum(0);
187   this->addKeys.setDefault(TRUE);
188   SO_KIT_ADD_FIELD(addValues, (0.0f));
189   this->addValues.setNum(0);
190   this->addValues.setDefault(TRUE);
191 
192   PRIVATE(this)->addValuesSensor.reset(new SoFieldSensor);
193   PRIVATE(this)->addValuesSensor->setFunction(SoScrollingGraphKit::addValuesCB);
194   PRIVATE(this)->addValuesSensor->setData(this);
195   PRIVATE(this)->addValuesSensor->attach(&(this->addValues));
196 
197   PRIVATE(this)->chart = static_cast<SoSeparator *>(this->getAnyPart("scene", TRUE));
198 }
199 
~SoScrollingGraphKit(void)200 SoScrollingGraphKit::~SoScrollingGraphKit(void)
201 {
202 }
203 
204 void
addValuesCB(void * closure,SoSensor * COIN_UNUSED_ARG (sensor))205 SoScrollingGraphKit::addValuesCB(void * closure, SoSensor * COIN_UNUSED_ARG(sensor))
206 {
207   SoScrollingGraphKit * kit = static_cast<SoScrollingGraphKit *>(closure);
208 
209   PRIVATE(kit)->pullStatistics();
210   switch (kit->graphicsType.getValue()) {
211   case SoScrollingGraphKit::LINES:
212     //PRIVATE(kit)->generateLineChart();
213     //break;
214   case SoScrollingGraphKit::STACKED_BARS:
215     PRIVATE(kit)->generateStackedBarsChart();
216     break;
217   default:
218     //PRIVATE(kit)->generateLineChart();
219     PRIVATE(kit)->generateStackedBarsChart();
220     break;
221   }
222 }
223 
224 void
pullStatistics(void)225 SoScrollingGraphKitP::pullStatistics(void)
226 {
227   SbTime now(SbTime::getTimeOfDay());
228   const int numvalues = kit->addValues.getNum();
229   if (kit->addKeys.getNum() != numvalues) {
230     SoDebugError::post("SoScrollingGraphKitP::pullStatistics",
231                        "data out of sync (%d keys, %d values)",
232                        numvalues, kit->addKeys.getNum());
233   }
234 
235   Datum * datum = new Datum;
236   datum->when = now;
237 
238   int c;
239   for (c = 0; c < numvalues; ++c) { // make sure all the grraph structs needed are created
240     SbName key = kit->addKeys[c];
241     /*Graph * graph = */this->getGraph(key);
242   }
243   const int numgraphs = this->graphs.getNumElements();
244   for (c = 0; c < numgraphs; ++c) { // initialize default for all graphs
245     datum->datum.append(0.0f);
246   }
247   for (c = 0; c < numvalues; ++c) { // fill values for the graphs we have data for
248     SbName key = kit->addKeys[c];
249     Graph * graph = this->getGraph(key);
250     datum->datum[graph->index] = kit->addValues[c];
251   }
252   this->addDatum(datum);
253 }
254 
255 void
addDatum(Datum * newDatum)256 SoScrollingGraphKitP::addDatum(Datum * newDatum)
257 {
258   if (this->first == NULL) {
259     this->first = this->last = newDatum;
260   } else {
261     this->last->next = newDatum;
262     this->last = newDatum;
263   }
264 
265   SbTime maxtime(this->kit->seconds.getValue());
266   while ((this->first->next != NULL) &&
267          ((newDatum->when - this->first->next->when) > maxtime)) {
268     Datum * datum = this->first;
269     this->first = this->first->next;
270     delete datum;
271   }
272 }
273 
274 Graph *
getGraph(const SbName & key)275 SoScrollingGraphKitP::getGraph(const SbName & key)
276 {
277   assert(key != SbName::empty());
278   Graph * graph = NULL;
279   if (!this->graphs.get(key.getString(), graph)) {
280     graph = new Graph;
281     graph->key = key;
282     graph->index = this->graphs.getNumElements();
283     graph->color = this->kit->colors[graph->index % this->kit->colors.getNum()];
284     graph->baseval = graph->basetarget = 0.0f;
285     this->graphs.put(key.getString(), graph);
286 
287     printf("Adding graph #%d for '%s', color #%02x%02x%02x\n",
288            graph->index + 1, key.getString(), (uint8_t)(graph->color[0] * 255.0f),
289            (uint8_t) (graph->color[1] * 255.0), (uint8_t) (graph->color[2] * 255.0));
290   }
291   assert(graph);
292   return graph;
293 }
294 
295 Graph *
getGraph(int idx)296 SoScrollingGraphKitP::getGraph(int idx)
297 {
298   SbList<const char *> keys;
299   this->graphs.makeKeyList(keys);
300   for (int i = 0; i < keys.getLength(); ++i) {
301     Graph * graph = NULL;
302     this->graphs.get(keys[i], graph);
303     if (graph->index == idx) return graph;
304   }
305   assert(!"serious problem - did not find graph index data");
306   return NULL;
307 }
308 
309 
310 #define LINEUP 1
311 #define SMOOTH_DECAY 1
312 
313 template <class Type>
314 Type *
new_node(const char * buffer)315 new_node(const char * buffer)
316 {
317   Type * node = new Type;
318   if (!node->set(buffer)) {
319     assert(!"node field arguments error");
320     node->ref();
321     node->unref();
322     return NULL;
323   }
324   return node;
325 }
326 
327 inline
328 float
decayvalue(float current,float target,float decay=0.95f)329 decayvalue(float current, float target, float decay = 0.95f)
330 {
331 #if SMOOTH_DECAY
332   return (current * decay) + (target * (1.0f - decay));
333 #else
334   return target;
335 #endif
336 }
337 
338 
339 
340 void
generateStackedBarsChart(void)341 SoScrollingGraphKitP::generateStackedBarsChart(void)
342 {
343   const int numgraphs = this->graphs.getNumElements();
344   if (numgraphs == 0) return;
345 
346   boost::scoped_array<SoBaseColor *> colors(new SoBaseColor * [numgraphs]);
347   boost::scoped_array<SoCoordinate3 *> coords(new SoCoordinate3 * [numgraphs]);
348   boost::scoped_array<SoLineSet *> lines(new SoLineSet * [numgraphs]);
349   boost::scoped_array<SoTranslation *> texttrans(new SoTranslation * [numgraphs]);
350   boost::scoped_array<SoText2 *> textnodes(new SoText2 * [numgraphs]);
351 
352   if (this->chart->getNumChildren() != (numgraphs * 4 + 3) ||
353       !(this->chart->getChild(2+2)->isOfType(SoLineSet::getClassTypeId()))) {
354     this->chart->removeAllChildren();
355 
356     this->chart->addChild(new_node<SoTranslation>("translation -1 -0.99 0"));
357     this->chart->addChild(new_node<SoScale>("scaleFactor 2 0.5 1"));
358 
359     for (int c = 0; c < numgraphs; ++c) {
360       this->chart->addChild(colors[c] = new SoBaseColor);
361       { // the text for the graph name
362         SoSeparator * sep = new SoSeparator;
363         sep->addChild(texttrans[c] = new SoTranslation);
364         sep->addChild(textnodes[c] = new SoText2);
365         this->chart->addChild(sep);
366       }
367       this->chart->addChild(coords[c] = new SoCoordinate3);
368       this->chart->addChild(lines[c] = new SoLineSet);
369     }
370     {
371       SoSeparator * sep = new SoSeparator;
372       sep->addChild(new_node<SoBaseColor>("rgb 1 1 1"));
373       sep->addChild(new SoTranslation);
374       sep->addChild(new SoText2);
375       this->chart->addChild(sep);
376     }
377   } else {
378     for (int c = 0; c < numgraphs; ++c) {
379       colors[c] = static_cast<SoBaseColor *>(this->chart->getChild(2 + c * 4 + 0));
380       assert(colors[c]->isOfType(SoBaseColor::getClassTypeId()));
381       { // the text for the graph name
382         SoSeparator * sep = static_cast<SoSeparator *>(this->chart->getChild(2 + c * 4 + 1));
383         assert(sep->isOfType(SoSeparator::getClassTypeId()));
384         texttrans[c] = static_cast<SoTranslation *>(sep->getChild(0));
385         textnodes[c] = static_cast<SoText2 *>(sep->getChild(1));
386       }
387       coords[c] = static_cast<SoCoordinate3 *>(this->chart->getChild(2 + c * 4 + 2));
388       assert(coords[c]->isOfType(SoCoordinate3::getClassTypeId()));
389       lines[c] = static_cast<SoLineSet *>(this->chart->getChild(2 + c * 4 + 3));
390       assert(lines[c]->isOfType(SoLineSet::getClassTypeId()));
391     }
392   }
393 
394   int numdata = 0;
395   SbTime end(SbTime::zero());
396   Datum * datum = this->first;
397   float maxvalue = 0.0f;
398 #if LINEUP
399   SbList<float> maxvalues;
400   {
401     while (datum) {
402       ++numdata;
403       while (maxvalues.getLength() < datum->datum.getLength()) {
404         maxvalues.append(0.0f);
405       }
406       for (int j = 0; j < datum->datum.getLength(); ++j) {
407         float val = datum->datum[j];
408         if (val > maxvalues[j]) { maxvalues[j] = val; }
409       }
410       end = datum->when;
411       datum = datum->next;
412     }
413     for (int j = 0; j < maxvalues.getLength(); ++j) {
414       Graph * graph = this->getGraph(j);
415       if (j == 0) {
416         graph->basetarget = 0.0f;
417       } else {
418         graph->basetarget = maxvalue; // maxvalues[j-1];
419       }
420       maxvalue += maxvalues[j];
421       graph->baseval = decayvalue(graph->baseval, graph->basetarget);
422     }
423   }
424 #else
425   while (datum) {
426     ++numdata;
427     float sum = 0.0f;
428     for (int j = 0; j < datum->datum.getLength(); ++j) {
429       sum += datum->datum[j];
430     }
431     if (sum > maxvalue) maxvalue = sum;
432     end = datum->when;
433     datum = datum->next;
434   }
435 #endif
436 
437   this->cachedrealmaxvalue = maxvalue;
438   this->cachedmaxvalue = decayvalue(this->cachedmaxvalue, this->cachedrealmaxvalue);
439 
440   // printf("values: %d, maxvalue: %f\n", numdata, maxvalue);
441 
442   // printf("num data values: %d\n", numdata);
443   const float seconds = (float) this->kit->seconds.getValue().getValue();
444 
445   for (int graphidx = 0; graphidx < numgraphs; ++graphidx) {
446     Graph * graph = this->getGraph(graphidx);
447 
448     colors[graphidx]->rgb = graph->color;
449 
450     coords[graphidx]->point.setNum(numdata * 2);
451     SbVec3f * coordarray = coords[graphidx]->point.startEditing();
452 
453     datum = this->first;
454     int idx = 0;
455     float maxypos = 0.0f, maxprevypos = 0.0f;
456     while (datum) {
457       /*const int numvalues = */datum->datum.getLength();
458 
459       float value = 0.0f, prev = 0.0f;
460 #if LINEUP
461       // prev = (graph->index == 0) ? 0.0f : maxvalues[graph->index - 1];
462       prev = graph->baseval;
463       value = prev + datum->datum[graph->index];
464 #else
465       for (int j = 0; j <= graph->index && j < numvalues; ++j) {
466         prev = value;
467         value += datum->datum[j];
468       }
469 #endif
470 
471       const float xpos = 1.0f - ((float)((end - datum->when).getValue()) / seconds);
472       const float ypos = (this->cachedrealmaxvalue > 0.0f) ? (value / this->cachedrealmaxvalue) : value;
473       const float prevypos = (this->cachedrealmaxvalue > 0.0f) ? (prev / this->cachedrealmaxvalue) : prev;
474 
475       //printf("coord %d: %f %f\n", idx, xpos, ypos);
476       coordarray[idx*2] = SbVec3f(xpos, prevypos, 0.0f);
477       coordarray[idx*2+1] = SbVec3f(xpos, ypos, 0.0f);
478 
479       ++idx;
480       datum = datum->next;
481 
482       if (maxypos < ypos) maxypos = ypos;
483       if (maxprevypos < prevypos) maxprevypos = prevypos;
484     }
485     coords[graphidx]->point.finishEditing();
486 
487     lines[graphidx]->numVertices.setNum(idx);
488     int32_t * countarray = lines[graphidx]->numVertices.startEditing();
489     for (int j = 0; j < idx; ++j) {
490       countarray[j] = 2;
491     }
492     lines[graphidx]->numVertices.finishEditing();
493 
494     textnodes[graphidx]->string.setValue(graph->key.getString());
495     texttrans[graphidx]->translation.setValue(SbVec3f(0.0f, (maxypos + graph->baseval * 2.0f) * 0.5f, 0.0f));
496   }
497 
498 
499   // graph space is now between 0-1 in both directions
500 
501   {
502     SoSeparator * sep = static_cast<SoSeparator *>(this->chart->getChild(numgraphs * 4 + 2));
503     assert(sep && sep->isOfType(SoSeparator::getClassTypeId()));
504     assert(sep->getNumChildren() == 3);
505     SoTranslation * trans = static_cast<SoTranslation *>(sep->getChild(1));
506     assert(trans && trans->isOfType(SoTranslation::getClassTypeId()));
507     trans->translation.setValue(0.98f, 1.0f, 0.0f);
508     SoText2 * text = static_cast<SoText2 *>(sep->getChild(2));
509     assert(text && text->isOfType(SoText2::getClassTypeId()));
510 
511     SbString content;
512     content.sprintf("%6.0f ms _", this->cachedmaxvalue * 1000.0f);
513     text->justification.setValue(SoText2::RIGHT);
514     text->string.setValue(content.getString());
515   }
516 }
517 
518 // *************************************************************************
519 
520 #undef PRIVATE
521 #undef LINEUP
522 #undef SMOOTH_DECAY
523 
524 #endif // HAVE_NODEKITS
525