QCustomPlot Discussion and Comments

Adding new layer prevents interaction with graphsReturn to overview

Greetings,

I have studied the problem of layer ordering a lot (docs and this forum). I tried to fix my code in manners matching recommendations found in various topics in this forum. To no avail. So let me describe my setting.

1. Allocate new QCustomPlot-derived class.
2. In the constructor create two new layers: plotsLayer (above background) and ancillaryItemsLayer (above plotsLayer), such that the layers are like this:

All layer names:
Layer index 0: background
Layer index 1: plotsLayer
Layer index 2: ancillaryItemsLayer
Layer index 3: grid
Layer index 4: main
Layer index 5: axes
Layer index 6: legend
Layer index 7: overlay

In that same constructor I allocate a number of items that will be updated during operation of the plot, like four lines that delimit a selection rectangle (home-written because of some geometrical constraints), some text items describing numerical variable and so on.

Note, that for each new item (allocated on the heap), I call setLayer() like this:

mp_vStartTracerItem->setLayer("ancillaryItemsLayer");

3. During operation of the plot, the user might create as many QCPGraph instances as necessary to be stuffed in the same QCustomPlot-derived instance or one (and only one) QCPColorMap to be in a QCustomPlot-derived instance.

4. With this setting, everything looks fine beause I see that the tracers and markers actually overdraw the plots (graph or color map). Very cool.

5. Point is: when I organize that setup, I loose the ability to select graphs (which I need badly).

6. The other way I tried, but that is not working from a layer-ordering standpoint, is to stuff everything in the default "main" layer. In that case, I can select graphs, BUT I loose the visibility of the tracers and text items when the plot is a color map. I can still see them in the plot where only graphs are plotted because the plot area is not opaque.

One idea would be to still stuff everything on the "main" layer and control the Z-ordering of the various elements. For example, is it possible, after each addition of a plot (graph or color map) to force it below the other items in the *same* layer ? I understand from the documentation that the order with which the items are rendered in a layer is the order in which they were created. In my case, this would not work, because all the markers, lines, rectangle, text items are allocate at plot construction time well before the plot is populated with graphs and color maps.

What I am missing here ?

Thanks for reading up to here,

Sincerely,
Filippo

Update:

I could not find a way to work canonically (two layers, as described above). However, I now have kind of a workaround.

The plotsLayer is now the only one that I create above background, so that I see the grid.

I arrange myself to not allocate the text, lines, marker items in the base class, but I defer their creation the derived classes:

1. for trace (QCPGraph)-hosting widgets, I create the items at construction time, because the graph do not screen-off the items since they are so sparse.

2. for colormap (QCPColorMap)-hosting widgets (only one color map per plot widget), I create the items right after addition of the color map. Since the items are created *after* the color map, they render *after* that color map and are thus visible.

This is only a work around, because the whole idea of layers was to simplify this.

So, I am still eager to receive any advice or acknowledgement that this is bug. Hopefully fixed in the near future :-)

I forgot to mention the version of the lib: 2.0.1 (Debian testing box)

Sincerely,
Filippo

where is your interaction? some code?

I was having the same issue and dug into the code a bit more. QCustomPlot::mousePressEvent loops through all layers and their child items to gather any candidates that are within a tolerance of the mouse click. There is a subtlety in the code that the mouse press signal gets forwarded to the topmost layerable that was in the area of the click. If you are putting your QCPColorMap data towards the back of your visible items, it is likely that the event is being sent to one of the upper layerable items (which is then ignoring it because no slot has been assigned).

In my case, when I don't mess with any of the layering, the candidate layerables for the mouse click signal are (in order of precedence):
QCPColorMap
QCPLayoutGrid
QCPAxisRect
...and thus, the mouse signal is sent to the QCPColorMap object (what I want). However, when I create a new layer just above the background layer, and put my QCPColorMap into that layer, the candidates become:
QCPLayoutGrid
QCPColorMap
QCPAxisRect
However, in QCustomPlot::mouseReleaseEvent, there is no handler for a qobject_cast() to a type of QCPLayoutGrid, thus there is no signal raised for the click event.

It seems this is a bug, as in QCustomPlot::mousePressEvent, we should not be setting the mMouseSignalLayerable variable to the first candidate, but rather the first candidate that QCustomPlot::mouseReleaseEvent will properly handle. The handled classes are: QCPAbstractPlottable, QCPAxis, QCPAbstractItem, QCPLegend, and QCPAbstractLegendItem.

A simple (albeit brute-force) fix is to do a test qobject_cast() on the above classes in QCustomPlot::mousePressEvent, and if they are all nullptr, try the next candidate in the list.

     // no selection rect interaction, prepare for click signal emission and forward event to layerable under the cursor:
     QList<QVariant> details;
     QList<QCPLayerable*> candidates = layerableListAt(mMousePressPos, false, &details);
-    if (!candidates.isEmpty())
+    for (int i=0; i<candidates.size(); ++i)
     {
-      mMouseSignalLayerable = candidates.first(); // candidate for signal emission is always topmost hit layerable (signal emitted in release event)
-      mMouseSignalLayerableDetails = details.first();
+      mMouseSignalLayerable = candidates.at(i); // candidate for signal emission is always topmost hit layerable (signal emitted in release event)
+      mMouseSignalLayerableDetails = details.at(i);
+      if (qobject_cast<QCPAbstractPlottable*>(mMouseSignalLayerable) ||
+          qobject_cast<QCPAxis*>(mMouseSignalLayerable) ||
+          qobject_cast<QCPAbstractItem*>(mMouseSignalLayerable) ||
+          qobject_cast<QCPLegend*>(mMouseSignalLayerable) ||
+          qobject_cast<QCPAbstractLegendItem*>(mMouseSignalLayerable)) break;
     }
     // forward event to topmost candidate which accepts the event:
     for (int i=0; i<candidates.size(); ++i)