Skip to content

Fix invisible InfiniteLine at plot edges#2762

Merged
j9ac9k merged 4 commits intopyqtgraph:masterfrom
bbc131:InfLineAtEdge
Jul 6, 2023
Merged

Fix invisible InfiniteLine at plot edges#2762
j9ac9k merged 4 commits intopyqtgraph:masterfrom
bbc131:InfLineAtEdge

Conversation

@bbc131
Copy link
Copy Markdown
Contributor

@bbc131 bbc131 commented Jun 29, 2023

I have an InfiniteLine in a plot, which have both the same limits, i.e. the line can be dragged until the limits of the plot but not further.
Now, if you do exactly this, the line won't be visible.
See below the before screenshot and the code I used to set it up.

This problem exists for all for edges of a plot and independent of axes being visible or not.
I think, if a plot displays a range of e.g. 0..10 and you place a line at 0 or 10, you should still see it. Therefore, I consider this as a bug.

Searching for the reasons I found the following two issues and tried to solve them in the present PR:

  1. AxisItem draws its baseline right within the plot and overlays the InfiniteLine
    For abothes example with visible range of 0..10, the axis' baseline is drawn e.g. at 0, and a InfiniteLine which is placed there will be hidden behind the axis baseline.
    This can easily be debugged by making the axis' baseline dashed.
    Therefore I changed this and let the axis baselines be drawn right outside of the plot-area (move by 1 px in the corresponding direction)
    Another idea regarding the axis baselines I had, was to let the InfiniteLine be drawn in front of the axis baseline, but this didn't work. The axis' z-value is 0.5 and I can set the InfiniteLine's z-value to anything larger than this but it's still not visible. I guess this has something to do with the parent-child relationships within the scene but I didn't analyzed this further.

  2. The InfiniteLine is invisible itself, because it's out of the boundingRect of the ViewBox
    This can easily be seen if the baselines of the axes are not drawn anymore. In this case you can see the InfiniteLine disappearing if its moved to the edge.
    To resolve this, I increased the boundingRect of the ViewBox slightly. Here I had to play around to find the values which work for me. I don't know why the left edge behaves different than the top edge!

Before
20230628_lines_at_edge_not_visible

After
20230628_lines_at_edge_visible

import pyqtgraph as pg


SHOW_ALL_AXES = True

app = pg.mkQApp()
plot = pg.PlotWidget()
plot.resize(400,300)
plot.show()

if SHOW_ALL_AXES:
    plot.showAxis("right")
    plot.showAxis("top")
else:
    plot.showAxis("left", False)
    plot.showAxis("bottom", False)

plot.setLimits(xMin=0,xMax=10, yMin=0, yMax=10)
plot.setXRange(0,10)
plot.setYRange(0,10)

for pos, angle in ((0,90), (10,90), (0,0), (10,0)):
    line = pg.InfiniteLine(pos=pos,bounds=(0,10), angle=angle, pen=pg.mkPen("#00ff00"))
    line.setMovable(True)
    plot.addItem(line)

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jun 30, 2023

Looks like there is some infinite recursion happening; can you investigate this test failure?

@bbc131
Copy link
Copy Markdown
Contributor Author

bbc131 commented Jun 30, 2023

Currently I don't understand why this recursion happens, but it origins from my changes to the boundingRect.

It doesn't happen, if I put this boundingRect adjustment into GraphicsWidget.boundingRect instead of ViewBox.boundingRect, which I also don't understand.
Maybe it has something to do with the caching of the boundingRect within GraphicsWidget?!
I need to investigate this further...

But since the issue with the boundingRect (2. in PR message) is independent of the issue with the axis baselines (1. in PR message) I would like to handle this separately and create another issue / PR.
Therefore I reverted the changes to the boundingRect.

So, at the present state, this PR solves the problems for at least the left and upper edge of the plot.

@pijyoi
Copy link
Copy Markdown
Contributor

pijyoi commented Jun 30, 2023

I find that the recursion error stops if you return a copy of the QRectF instead of modifying it.

def boundingRect(self):
    br = super().boundingRect()
    return br.adjusted(-0.5, 0, +0.5, +0.5)

I suppose if the parent class caches its boundingRect and returns a reference to that cached copy, then having the child modify it in-place would be catastrophic.

Could you explain the reason for the asymmetry in the bounds adjustment?

@pijyoi
Copy link
Copy Markdown
Contributor

pijyoi commented Jul 1, 2023

I did a look through the various items that provide an boundingRect implementation. Almost all of them (intentionally or otherwise) return a copy of QRectF.
The exceptions are:

  • PlotCurveItem
  • LinearRegionItem
  • InfiniteLine
  • GraphicsWidget

A related change to fix your PR would be to make GraphicsWidget return a copy:

def boundingRect(self):
    # snip
    return QtCore.QRectF(br)

It's unclear whether GraphicsWidget as a base class should protect itself by always returning a copy or whether it should rely on its child classes to be well-behaved enough to treat its returned QRectF as read-only.
The same comment might possibly apply to GraphicsWidget::shape().

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 1, 2023

I'm team modify GraphicsWidget, although @campagnola may have other opinions. We generally expect downstream users to subclass from GraphicsWidget in their own plot implementations, this seems safer than trying to ensure that other users know not to modify the rect that boundingRect returns instead of returning a new copy.

@bbc131
Copy link
Copy Markdown
Contributor Author

bbc131 commented Jul 4, 2023

Thank you for your help.

I added the fix to ViewBox.boundingRect() again, but this time returning a copy.
Further I changed GraphicsWidget.boundingRect() to return a copy.
I think, boundingRect() should always return a copy, just to prevent strange behaviour as I experienced here. Therefore I changed it now in both classes, although it wouldn't be necessary.

Regarding the asymmetry in the bounds adjustment, I removed it.
I changed it to (0, 0, +0.5, +0.5) instead of (-0.5, 0, +0.5, +0.5).
With my example code above, the InfiniteLine at the left edge still disappears in some rare cases. I need to change the width of the window to reproduce this.
Initially I thought, the -0.5 makes this less often happen, but it was probably just a delusive hope.

@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jul 6, 2023

Thanks @bbc131 for the PR and for incorporating suggested changes, thanks @pijyoi for your feedback. LGTM, merging.

@pijyoi
Copy link
Copy Markdown
Contributor

pijyoi commented Aug 31, 2025

Another idea regarding the axis baselines I had, was to let the InfiniteLine be drawn in front of the axis baseline, but this didn't work. The axis' z-value is 0.5 and I can set the InfiniteLine's z-value to anything larger than this but it's still not visible. I guess this has something to do with the parent-child relationships within the scene but I didn't analyzed this further.

This has to do with the following hierarchy:

PlotItem
+--> ViewBox (zValue = -100)
      +--> InfiniteLine
+--> AxisItem (zValue = 0.5)

zValues are only considered for ordering of siblings. Therefore, InfiniteLine, as a child of ViewBox, will always get drawn before AxisItem.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants