Skip to content

PlotCurveItem: implement OpenGL curve fill#3068

Merged
j9ac9k merged 3 commits intopyqtgraph:masterfrom
pijyoi:curve-fill
Jun 29, 2024
Merged

PlotCurveItem: implement OpenGL curve fill#3068
j9ac9k merged 3 commits intopyqtgraph:masterfrom
pijyoi:curve-fill

Conversation

@pijyoi
Copy link
Copy Markdown
Contributor

@pijyoi pijyoi commented Jun 22, 2024

Continuation of #3055

This PR restricts itself to filling the area under a finite-valued curve.
It will not handle oddball modes like histogram nor the undocumented fillLevel="enclosed".

The implementation is as described in #3055 (comment) and is reasonably short.

Before this PR, if fillLevel is enabled, paintGL is not used, and Qt's own OpenGL rendering is used.
In the updated MouseSelection example below, fillLevel is enabled for the red curve. Hence, before this PR, the red curve gets drawn by Qt, not by paintGL.

It can be seen that the red curve is smooth, but the filling has quantization effects.
i.e. Qt's OpenGL rendering backend does the equivalent of origin shift for curves but not for filling.
Screenshot 2024-06-22 184756

With this PR that implements filling in paintGL, (and hence comes with origin shift to mitigate quantization effects)
Screenshot 2024-06-22 194230

https://doc.qt.io/qt-6/qbrush.html can be more than just a color. And this PR of course doesn't support anything besides a pure color QBrush.

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jun 23, 2024

Some quirks found while testing out this PR:

  1. https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/examples/Plotting.py

    • the bottom left fill plot doesn't go through the paintGL codepath because PlotDataItem hasn't tested for finite-ness and hence calls PlotCurveItem with connect="finite". We then reject going through paintGL codepath because we consider filling under the curve to only make sense for an unbroken curve.
    • related discussion: Documentation text review for PlotDataItem #3057 (comment)
  2. https://github.com/pyqtgraph/pyqtgraph/blob/master/pyqtgraph/examples/ColorGradientPlots.py doesn't work

    • only the 2nd curve gets drawn
      • it gets drawn because it doesn't go through paintGL codepath for the same reason as in (1)
    • the other curves go through paintGL codepath. The pens used are colormap gradients, have an Invalid color and hence get rendered as black. The solid curves can thus be seen if we change the background to white.

UPDATE: changed PlotDataItem connect="auto" to choose "all" when there is a fillLevel. This is also the solution suggested in #2312 (comment)

UPDATE: connect="finite" is now supported, so the case (1) above does now go through the paintGL codepath

@NilsNemitz
Copy link
Copy Markdown
Contributor

This looks great, I wish I had more time to follow it in detail...

I am not so excited about PlotDataItem changing the overall display strategy for non-finites based on the fill setting, which does not have an immediately obvious connection to it. I guess we could document that adjustment to be part of 'auto' mode, and leave the user with the option of setting 'finite' or 'all' manually if the automatic switch is not desirable.

Alternatives to consider:
If the change is in response to PlotDataItem not having done a test for finiteness yet, then I would support a solution that makes this test mandatory. I believe the current behavior was the minimum viable solution at the time 'auto' mode was implemented. I don't think that it represents a deeply considered overall strategy; It should be fair game for improvements.

If the question is "what should the filled region look like in the presence of non-drawing points?" then my naive expectations (from great to acceptable) would be

  1. Draw a separate ara for each stretch of finite/drawable points.
  2. After touching the last drawable point before one or more non-drawing points, the filled area vertically drops to the fill level value. It continues at this level (drawing a zero-height area) until the next drawable point, where it vertically jumps up to connect the point. ((This would mean deleting each sequence of non-finites and inserting a fill-level value at the same x coordinate as the points remaining before and after the gap. This may or may not be horribly inefficient.))
  3. Replace each non-drawing point with the fill level value only for painting the area. This may not be beautiful, but especially together with a curve that ends and restarts, it would still call attention to a missing value where no missing values are expected. ((And if the missing values are expected, it would hopefully prompt the user to read the manual and find the 'all' switch.))

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jun 23, 2024

I am not so excited about PlotDataItem changing the overall display strategy for non-finites based on the fill setting, which does not have an immediately obvious connection to it. I guess we could document that adjustment to be part of 'auto' mode, and leave the user with the option of setting 'finite' or 'all' manually if the automatic switch is not desirable.

Okay, I have reverted this.

If the question is "what should the filled region look like in the presence of non-drawing points?" then my naive expectations (from great to acceptable) would be

  1. Draw a separate ara for each stretch of finite/drawable points.

Yes, the original reasoning was that filling could only be defined for an unbroken curve.
But your suggestion sounds reasonable, and I have implemented option (1) for paintGL.

Options (2) and (3) are even more troublesome to implement.

So the situation now is that the paintGL codepath has a sane definition of filling for connect="finite".
Screenshot 2024-06-23 231922

This is not the case for the non-paintGL codepath, where the filling looks horrible.
It would be a much larger effort to implement this definition of filling to the non-paintGL codepath.

  1. There are actually 2 paths: thin lines path and thick lines path
  2. The code structure assumes a final single QPainterPath, whereas this implementation effectively calls for multiple QPainterPaths.
  3. The OpenGL code was written with a clean slate, with less legacy baggage (and corresponding code breakages)
    Screenshot 2024-06-23 230749

@NilsNemitz
Copy link
Copy Markdown
Contributor

The OpenGL solution looks very good to me!
It's nice to see it working like this right from the clean-slate implementation.

In the long run, it would be good to get the non-OpenGL version working in a similar way, but that obviously wasn't a high-priority concern before, and I don't think this PR changes anything to suddenly make it urgent. I think option 3 (for the fill area only: replace non-finite values by fill-level values) might work as a quick fix to upgrade that from "functionally useless" (as it is now) to merely "visually unpleasant". But that's not the topic of this PR :)

Do we have an open issue on the bad fill with non-finites in non-OpenGL mode? It would probably be good to reference this PR from there as illustration and goal.

@pijyoi pijyoi marked this pull request as ready for review June 24, 2024 05:18
@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jun 24, 2024

It seems that I might have over-exaggerated the complexity of implementing the new finite filling behavior for the non-OpenGL path. Curve filling was already implemented in chunks, so all that was needed was to do the same for each finite segment. Implemented in #3071

@pijyoi
Copy link
Copy Markdown
Contributor Author

pijyoi commented Jun 27, 2024

use glBlendFuncSeparate like in #3045, otherwise on WSL2 (wayland), the contents behind the window bleed through.

PlotCurveItem API ensures that they are either None or instances of
QPen, QBrush
@j9ac9k
Copy link
Copy Markdown
Member

j9ac9k commented Jun 29, 2024

Thanks @pijyoi this LGTM 👍🏻

@j9ac9k j9ac9k merged commit 59df8ab into pyqtgraph:master Jun 29, 2024
@pijyoi pijyoi deleted the curve-fill branch June 29, 2024 05:16
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