QCustomPlot Discussion and Comments

Stacked Bar Chart with Multiple CategoriesReturn to overview

I'm trying to draw a stacked bar where each column contains a different series of data: https://imgur.com/RrvcyeR
The only way I've come up with to achieve this is by throwing all the possible categories together and set their values to 0 when they don't appear in the column. (Plus a bit of hackery to prevent the zero size bars being visible.) As you can see this works OK, but it doesn't scale at all well. With a significant amount of data involved, the vast majority of the bars are zero height and iterating over all of these takes unacceptably long. Is there an alternative method to draw a plot like this, that doesn't involve faking it this way?

Thanks.

i cant quite tell what's different between what your picture shows and the default implementation of QCPBars.

By default, each column has the same values. In the example column 1 has Regenerative, Nuclear and Fossil Fuels, and so do all the other columns. In my use case, each column has a different set of possible values (hence the different colours in my image.)

Splitting the plot into layers helps a little, so that each column goes on its own layer. But this is still very wasteful as every column on each layer that isn't the one with data will render every other column as a series of zero height bars. There surely must be a better way?

Most of the performance is lost in QCPBars::getStackedBaseValue. The following optimisation helps some, but again it's not really addressing the underlying problem. (I haven't thoroughly checked if this works for all cases btw, but it's fine for my purposes.)

--- a/source/thirdparty/qcustomplot/qcustomplot.cpp
+++ b/source/thirdparty/qcustomplot/qcustomplot.cpp
@@ -24222,15 +24222,22 @@ void QCPBars::getPixelWidth(double key, double &lower, double &upper) const
 */
 double QCPBars::getStackedBaseValue(double key, bool positive) const
 {
-  if (mBarBelow)
+  // find bars of mBarBelow that are approximately at key and find largest one:
+  double epsilon = qAbs(key)*(sizeof(key)==4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point
+  if (key == 0)
+    epsilon = (sizeof(key)==4 ? 1e-6 : 1e-14);
+  std::ptrdiff_t itOffset = mDataContainer->findBegin(key-epsilon) - mDataContainer->constBegin();
+  std::ptrdiff_t itEndOffset = mDataContainer->findEnd(key+epsilon) - mDataContainer->constBegin();
+
+  QCPBars* barBelow = mBarBelow.data();
+  double below = 0.0;
+  while (barBelow != nullptr)
   {
     double max = 0; // don't initialize with mBaseValue here because only base value of bottom-most bar has meaning in a bar stack
-    // find bars of mBarBelow that are approximately at key and find largest one:
-    double epsilon = qAbs(key)*(sizeof(key)==4 ? 1e-6 : 1e-14); // should be safe even when changed to use float at some point
-    if (key == 0)
-      epsilon = (sizeof(key)==4 ? 1e-6 : 1e-14);
-    QCPBarsDataContainer::const_iterator it = mBarBelow.data()->mDataContainer->findBegin(key-epsilon);
-    QCPBarsDataContainer::const_iterator itEnd = mBarBelow.data()->mDataContainer->findEnd(key+epsilon);
+
+    QCPBarsDataContainer::const_iterator it = barBelow->mDataContainer->constBegin() + itOffset;
+    QCPBarsDataContainer::const_iterator itEnd = barBelow->mDataContainer->constBegin() + itEndOffset;
+
     while (it != itEnd)
     {
       if (it->key > key-epsilon && it->key < key+epsilon)
@@ -24241,10 +24248,12 @@ double QCPBars::getStackedBaseValue(double key, bool positive) const
       }
       ++it;
     }
-    // recurse down the bar-stack to find the total height:
-    return max + mBarBelow.data()->getStackedBaseValue(key, positive);
-  } else
-    return mBaseValue;
+
+    below += max;
+    barBelow = barBelow->mBarBelow.data();
+  }
+
+  return mBaseValue + below;
 }
 
 /*! \internal

Hi Tim,

since your requirement is quite specific (haven't come across this kind of bar chart yet, but I'm sure it's appropriate for your use case :) I'm afraid QCP doesn't support it out of the box with QCPBars.

Since you say you also need higher performance and "hacking" QCPBars in the way you already did didn't deliver that, my best suggestion is to create your own plottable. You will surely be able to get lots of inspiration from the QCPBars implementation for the dirty work of drawing the bars etc. But in this way, you can optimize your data structure and drawing code to your different topology. One approach would be to have a plottable which only draws one of your bar stacks, and its data points are the according divisions/colors.