#pyqtgraph

1 messages ยท Page 7 of 1

harsh ravine
#

let me try

rough furnace
#

the base() is a QColor attribute

harsh ravine
#

ahh, good, it doesn't AttributeError: 'PySide2.QtGui.QColor' object has no attribute 'base'

rough furnace
#

oh ...I think I meant name()

harsh ravine
#

I was going to kick myself

rough furnace
#

sorry was going off memory ๐Ÿ˜ฌ

harsh ravine
#

ohh.. yeah.. that one varied too much in dark mode

#

i think it was something like 128,128,128

rough furnace
#

i think the hsl modification that you all were talking about earlier is probably the best course of action, but this is an area @wide prism is far more knowledgable than me on

harsh ravine
#

for sure, that solution is working the best

wide prism
#

I think that is acceptable. (modifying the palette color a little)
The question is what to do if a stylesheet is applied.

harsh ravine
#

right now we don't modify it at all

rough furnace
#

is there a good way to detect if a style sheet is applied in general (forget the specific colors)

wide prism
#

Yes, you can just query that.

#

app.styleSheet()

harsh ravine
#
        if self.styleSheet:
            background = QtGui.QColor(255, 255, 255, 0)
            altBackground = background
            if self.styleSheet == 'Light':
                textColor = QtGui.QColor('#000000')
            else:
                textColor = QtGui.QColor('#F0F0F0')
#

thats what happens for stylesheets

wide prism
#

We respond by dropping customizability and falling back on hard-coded stuff.

#

That does make it work with our target style sheet it just... rubs.. me.. the wrong way.

rough furnace
#

@wide prism fwiw I believe macOS overrides those colors

harsh ravine
#

I tried using QtGui.QColor(255, 255, 255, 0) for the text but instead of painting a transparent color over the current color, it replaces it with a transparent color

wide prism
#

(@rough furnace) How and where? That's part of the stuff I don't understand. I thought that all that happens on Mac OS is that the pallette always gets set to something sane (except for AlternateBase, maybe)

rough furnace
#

this isn't a Qt thing, but an OS thing... I haven't looked up the apple OS docs, but I believe this is part of the effort to make applications look more uniform, so if it detects a pure "white" background, it will render it for the OS version of white...

wide prism
#

Nooooo..... I need to lie down now, I think.

rough furnace
#

fwiw, it actually works pretty well

#

I wouldn't be surprised if other OSs do something similar too;

#

I should take a color picker and actually measure what the color of the rendered window is...

wide prism
#

I set white, I get #FFFFFF.

rough furnace
#

I really should do more testing here, this info a reflection of my experience a while back

#

i should lookup the docs or something, I'm sure there is a way to overwrite it

wide prism
#

(and if I set #FF0000, then I get #FF0000; so I did change the right thing ๐Ÿ˜ )

#

I would say if you want "os background color" then set "os background color".

rough furnace
#

I suspect if you set the background color to say (254, 254, 254) it would render that...

#

(although now I'm curious)

wide prism
#

That is a distraction by the way. If the OS thinks your window is whiteish, and your stylesheet makes it black, then the color hack would still change #FFFFFF to "whiteish". Not to black.

#

So this will not help resolve a conflict between OS setting and stylesheet.

harsh ravine
#

If we want to keep things looking similar darkMode/lightMode with/without a stylesheet we can still do that. Its just a couple more conditionals and finding the values that make the color look good

wide prism
#

the manual values?

harsh ravine
#

No

#

just adjust the hue/saturation in addition to the lum

#

but this would only be for QDarkStyle

wide prism
#

Which color are you talking about now? You have my attention.

harsh ravine
#

other stylesheets wont work because we won't know what color they render QTreeWidgets/Branches

wide prism
#

The problem is that we don't know how to get the set of base colors from the stylesheet, right?

#

--> make the user tell us instead.

harsh ravine
#

I don't have strong feelings about this either way. If you think thats what we should do, i'm all for working out the details

#

I assume the CONFIG_OPTIONS that i linked earlier are part of pg.configureApplication?

#

If so, it would be easy enough to use the style and any other values we use in there as an app property and access them where ever we want

wide prism
#

No, configureApplication is something I made up to explain how the workflow might be in the future.

#

I think the proper place to store information about the application palette is the application palette ...

#

What options were you thinking about storing?

harsh ravine
#

at minimum a foreground color so the user can override it when aren't manually choosing the colors for them(rendering white text on a white background)

#

Do you qdarkstyle would merge a PR if we worked out how to style all of the widgets in pg and put in a pull request with them?

wide prism
#

I would expect that they might. Even before that, we could have an appendix to their stylesheet.

styleSheet = app.styleSheet()
styleSheet += styleSheetAppendix
app.setStyleSheet(styleSheet)
rough furnace
#

i've had a short interaction w/ the maintainer a while ago, seemed like a nice friendly guy

wide prism
#

-------- (I just happen to believe that this will turn out to be much harder. But it is the greatest long-term solution, probably!)

harsh ravine
#

Do that many people really use qdarkstyle?

wide prism
#

I thought so, but I may be wrong, especially if dark mode now starts working natively on more systems.

#

The minimum viable solution for this PR might be to hack the palette colors if they don't match the darkMode flag.
Then work on styling all the things.

rough furnace
#

@harsh ravine QDarkStyle is sort of the preferred solution if you want dark mode on Windows

wide prism
#

I use it, but I copy and paste initialization code, and adding one or two extra lines to that wouldn't bother me much. Once I figure out what they are...

harsh ravine
#

its a great stylesheet in terms of how complete it is, but man I can't stand the blue color

#

That was easy enough adding a palette option to CONFIG_OPTIONS, and setting a palette, then accessing it in updateDepth

rough furnace
#

not going to lie, not my favorite either

wide prism
#

Hehe, that looks like Netscape Navigator.

harsh ravine
#

that splash screen was amazing in 96

wide prism
#

My alternative app use case is a custom QPalette with a global stylesheet to theme special bits. Please don't override any colors in that case ๐Ÿ™‚

harsh ravine
#

well if you set your own stylesheet and a palette in the options then you have all the control

#

since the config options is in the outermost init file, we can access the palette in mkQApp I think

wide prism
#

I am with you so far....

harsh ravine
#

I lied

#

circular import

#

i think we could still figure something out

wide prism
#

Which palette do you want to access?

harsh ravine
#

the palette i set with pg.setConfigOption('palette',p)

wide prism
#

How's your palette different from the main application palette that Qt keeps for us?

harsh ravine
#

I set the text color to Qt.blue

wide prism
#

Why don't you do that to the main palette?

harsh ravine
#

Yeah, but that's no different than overriding an option in app.palette.

wide prism
#

Which I am still arguing is the solution here ๐Ÿ™‚

harsh ravine
#

I thought we wanted to give the user a way to set a palette for dark mode... or did you just want to force a predefined palette on them?

wide prism
#

No, let the user set the palette. In what case do you want to force Qt.blue on them?

#

Even if we pre-set the palette, the user can still replace it using the standard Qt tools.

harsh ravine
#

I don't want to force that on them. It was just a test in the parametertree example to see if I could access the color from updateDepth so they can set whichever text color they want

rough furnace
#

going to go to bed ๐Ÿ‘‹

harsh ravine
#

see ya

wide prism
#

But they can already set whatever text color they want by changing app.palette.text ?

#

Bye!!!

harsh ravine
#

mmm.. yeah

#

okay... walk me through this pg.configureApplication(qapp, style='dark')

#

when I specify my style is dark... what happens?

wide prism
#

This would take a QApplication.
It should probably apply all the stuff we now do in mkQApp (HiDPI etc)
It then sets app.setPalette( pg.darkPalette() )

#

It might know about more palettes later on (e.g. something gray instead of QDarkStyle blueish);

it might also want to set "fusion" style for Qt, that handles palette-only schemes better. But that's later fine tuning.

harsh ravine
#

The HiDPi stuff has to be done before the an application instance exists, unless that has recently changed

wide prism
#

Main point: One line will switch your palette to something usable; dark or light.

#

If you need more fine tuning, no problem.
Oh, didn't know about the HiDPI configuration. (I actually never used mkQApp, since I only just discovered that is does more than convenience stuff)

harsh ravine
#

Is there any reason we don't want to do this with mkQApp?

wide prism
#

If there is nothing to duplicate, then it might just become pg.applyPalette(app, 'name')

harsh ravine
#

the style parameter i mean

wide prism
#

mkQApp should take the same parameter (and call this internally), but there should also be a way to do this later.

harsh ravine
#

okay thats a good point

wide prism
#

One use for that might be to intercept a palette change and force the palette back to what you want.

#

Or substitute your own version of light/dark for whatever the system wants to push on you.

#

Did you see a recommended way to turn off the system-applied changes?

harsh ravine
#

you mean when you change the os theme?

wide prism
#

Yeah.
That is a possible flaw in my plan; If I keep important stuff in the palette, then it is a bit annoying if a random system thing just deletes it.

harsh ravine
#

I don't think it should be that much of an issue

#

There is an event for when the palette changes and we can patch an event filter onto the QApp. Whenever that event fires we can see if whichever flag the user specifies to keep the palette is true or false, if its true we can probably just reapply the palette they set.

wide prism
#

But then we do need to hold the backup in the config that you were basically proposing ๐Ÿ™‚

#

There's a flag that disables palette updates on Windows (https://doc.qt.io/qt-5/qguiapplication.html --> "A value of 2 will in addition cause the Windows Vista style to be deactivated and switch to the Windows style using a simplified palette in dark mode. ") but nothing general I found. I am afraid the PaletteChange event is just "hehe I just changed your colors. Too late to do anything about that now"

harsh ravine
#

Actually, it doesn't appear we need to do anything to keep the palette colors the same on mac

#

just set a palette on the app, changing the color mode doesn't change the looks at all

wide prism
#

So it updates when it isn't specifically set; but stops updating once you manually set it?
That would be extremely convenient! Can you double-check that?

harsh ravine
#

The only thing that changes is the window frame color which we can't control(without a lot of work)

wide prism
#

That is with a black/white palette manually set?
If you set no palette, it reversed in dark mode?

#

Can you connect something to QAPP.paletteChanged.connect(onPaletteChange) and see if that still arrives?

harsh ravine
wide prism
#

Awww. So... If we set a stylesheet, and then set a palette... we are all good already?

harsh ravine
#

paletteChanged does come through

#

Either one appears to do the trick

wide prism
#

Either what? Stylesheet blocks it, too?

harsh ravine
#

The only reason we see color changes in the tree is because we use setBackground/Foreground that uses a brush that isn't necessarily connected to the palette

wide prism
#

Wait, but that is based on the darkMode flag, which is derived from the palette colors. So the palette DOES normally change, right?

harsh ravine
#

looks like the palette changes if one is not specifically set

#

let me try something

#

aww, you can't set an empty palette and block system changes

wide prism
#

No, this is great. The problem was that there might still be incoming palette changes

  • although a palette has been set, overwriting what you manually configured
  • although a (partial) stylesheet has been, a clashing palette might make it ugly.
#

This behavior seems to avoid both.

#

We just need to demand that the user set a palette with a stylesheet.

harsh ravine
#

right, the stylesheet would need to cover everything but if its not complete or doesn't apply colors specifically to QWidget then a palette change could wreck the look

#

I'm not sure what could override the palette unless it was the system

wide prism
#

That was @rough furnace 's concern way back there.

harsh ravine
#

the user would know if a palette they created was being set

#

Oh, I must have missed that

wide prism
#

Psycho users get to have ugly apps if they want them.

harsh ravine
#

fine by me

wide prism
#

"what doesn't work: when style-sheets enter the picture, and if light/dark mode are toggled while a style-sheet is applied"

harsh ravine
#

Oh yeah.... thats fixed in the most recent PR

wide prism
#

--> no, that works if you apply a palette, because the system then no longer overrides the colors.

harsh ravine
#

that works also.

#

I just imagined the people who use this app are more concerned with using it playing with code.

wide prism
#

Stick to [apply a stylesheet AND apply a palette], and it all works, no local fixes needed.

#

Then they'll be in default color mode.

#

No code.

harsh ravine
#

what about the tree node colors?

wide prism
#

If they want custom colors, they need

  • one line to set e.g. QDarkStyle
  • one line to set a matching palette (we'll provide conveniently)
harsh ravine
#

Without the fix that runs updateDepth the colors will be weird

wide prism
#

The slightly lighter stuff?
Derive from palette.base, as we already have working.

harsh ravine
#

oh, if we're going to provide the palette then that should be fine

wide prism
#

Running update depth is a good idea, just don't parse stylesheets in it.

harsh ravine
#

I thought you were speaking about more than qdarkstyle

wide prism
#

If I want my own palette, then I have already invested so much time into it that putting it into my own QPalette is trivial. (Where do you think that one I posted on the PR comes from?)

harsh ravine
#

but i completely agree, that code does not belong where it is

wide prism
#

I like the caching yuo have, I am more concerned that this needs to be copied to every custom widget we have...

harsh ravine
#

it doesn't

#

only the ones where we change the colors based on whatever

#

actually, I don't know if its an issue other places.

#

I'm afraid to run the examples and find out

#

If we weren't modifying the colors with setBackground then the palette swap wouldn't be an issue

wide prism
#

If you do lightness = 0.5 + (lightness - 0.5) * 0.8 for the alternate background, do you need any other distinctions at all?

#

You still need to re-run that, but no light/dark mode detection or distinction.

#

If we force the use of a palette, then the darkMode flag should also be up-to-date at any time.

harsh ravine
#

doesn't work great for dark stylesheet

wide prism
#

That's alternateBase now?

harsh ravine
#

no, thats the lightness from above

wide prism
#

What is this gray? Is that with HslF ?

#

Normal Hsl is int values.

harsh ravine
#

its normal hsl

#
 h, s, l = color.hue(), color.saturation(), color.lightness()
 lightness = lightness = 0.5 + (l - 0.5) * 0.8```
wide prism
#

If you want to work with the ints, then it is probably

#

lightness = 127 + (lightness - 127) * 0.8

#

But maybe

#
h, s, l, alpha = background.getHslF()
l = 0.5 + (l-0.5) * 0.80 # move closer to gray
altBackground = QtGui.QColor.fromHslF(h, s, l)
harsh ravine
#

color = palette.window().color()
color = color.getHslF()
h, s, l,a = color
lightness = lightness = 0.5 + (l - 0.5) * 0.8
background = QtGui.QColor.fromHsl(h, s, lightness, 255)

wide prism
#

Well, that needs to be .fromHslF()

#

And 255 --> 1.0

#

Even l = 1.0 would still be pretty dark if it wants 0-255 ๐Ÿ™‚

#

Isn't alpha = 255 automatic?

#

"lightness = lightness =" happened in copying, I assume?

harsh ravine
#

sure did

#

this is dark palette in light mode

wide prism
#

Why would you have dark palette in light mode?

harsh ravine
#

Some people do that

wide prism
#

The last two hours of discussion are "tell people to have a dark palette in dark mode" ๐Ÿ™‚

harsh ravine
#

photo/video editing applications are usually defaulting to dark mode regardless of the color theme of the OS

wide prism
#

Yes. They set their own palette to dark, then.

#

Which the OS is happy to accept, as you just explored.

harsh ravine
#

the h/s need to be adjusted to get it to look like it matches, but I get the feeling you don't want to do that for specific stylesheets

wide prism
#

Take background, not window?

harsh ravine
#

background doesn't exist in PySide6

wide prism
#

base, then

#

Sorry.

harsh ravine
#

I learned that the other night when one of my PRs failed almost all of the tests

wide prism
#

The thing that is the text background.

harsh ravine
#

its a little darker

wide prism
#

I am quite confused now, that hue does not seem to match any of the background colors.

harsh ravine
#

this is with background on pyside2

wide prism
#

Is that QDarkStyle on the default MacOS dark palette?

harsh ravine
#

yes the palette was set by the OS

wide prism
#

Well, that isn't expected to match perfectly. Then. All good ๐Ÿ™‚

#

If you want it to match, set a matching palette. ๐Ÿคทโ€โ™‚๏ธ

#

It is readable.

#

In the thing I played with, I pulled background from palette.base, and modded that as above for alternateBackground. Looked alright.

#

What does that hsl logic above look like without the stylesheet, just the MacOS light/dark palette?

harsh ravine
#

better than it does in the current PR

wide prism
#

I'd take that ๐Ÿ™‚

harsh ravine
#

Yeah, I'm going to keep it

#

I'm looking into CSS parsers

wide prism
#

Maybe start by figuring out if Qt can parse stuff for us. It already does for its own widgets!

#

Let me know when you have a version of the PR I should look at.

CSS parsing is probably a new PR no matter what!

harsh ravine
#

yeah, if things keep getting added it will turn this into more of an epic than a bug fix

wide prism
#

This. 100% this.

wide prism
#

I believe QStyleOption is the way towards a style sheet aware widget. But I haven't been able to wrap my head around what it would need to do for a custom widget.

#

There's supposed to be better support coming in Qt6 ๐Ÿ™‚

harsh ravine
#

QStyleOption is generally used for trying to get some kind of specific behavior/look out of a widget

#

I tried it creating a button from QStyleOptionButton to cover replace the one 'actions' in the parameter tree but it was still transparent ๐Ÿ˜ฆ

#

thought it might behave differently if I applied a palette to it

#
class Button(QtWidgets.QPushButton):
    def __init__(self, parent=None):
        super(Button, self).__init__(parent)

    def sizeHint(self):
        size = super(Button, self).sizeHint()
        return size

    def paintEvent(self, a0: QtGui.QPaintEvent) -> None:
        p = QtGui.QPainter(self)
        p.setRenderHint(QtGui.QPainter.Antialiasing)
        opt = QStyleOptionButton()
        opt.initFrom(self)
        opt.features = QtWidgets.QStyleOptionButton.None_
        opt.state &= ~QtWidgets.QStyle.State_HasFocus
        opt.text = self.text()
        size = opt.rect.size()
        opt.rect.setSize(size)
        opt.state |= QtWidgets.QStyle.State_AutoRaise

        if self.isDown():
            opt.state |= QtWidgets.QStyle.State_Sunken
            self.setStyleSheet('color:pink')
        else:
            opt.state |=QtWidgets.QStyle.State_Raised
            self.setStyleSheet('color:white')
        self.style().drawControl(QtWidgets.QStyle.CE_PushButton, opt, p, self)

if __name__ == '__main__':

    app = QtWidgets.QApplication()
    w = QWidget()
    l = QGridLayout()
    w.setLayout(l)
    button = Button()
    l.addWidget(button)
    button.setText('Hello')
    f = button.font()
    f.setPointSize(30)
    button.setFont(f)
    w.show()
    app.exit(app.exec_())
#

its pretty easy to pick apart whats happening there if you want to play with it later

wide prism
#

It would have been neat if widget.styleSheet() returned the fully assembled style sheet for that one widget. Unfortunately, it does not ๐Ÿ˜ฆ

harsh ravine
#

yeah, that would be great

wide prism
#

Frustrating because Qt obviously does all of this already.

harsh ravine
#

Yeah, I'm not even sure a standard CSS parser would work because of the weird syntax

#

so much stuff that Qt does isn't available in Python

#

Unless i'm just overlooking a way to access the private C++ bits easily

wide prism
#

No, this seems to be just that: <private> C++ bits.

harsh ravine
#

Sometimes i wish it wasn't so hard to work with C++ in python

#

In C++ it would be so easy. C+= would be so easy.. #include <QtGui/5.15.2/QtGui/private/qcssparser_p.h>

#

At this point, I don't think we're going to get anything that looks better than we have without getting a look at the CSS or hard coding some values for the qdarkstyle sheets

#

but like you said, they can always set a palette

wide prism
#

We will follow this up by an easy way to set a palette that matches QDarkStyle.

harsh ravine
#

do you want to have that be a separate PR?

wide prism
#

I think it can be, and unless you want to keep being sent around in circles, I think you would want it to be ๐Ÿ™‚

harsh ravine
#

as long as they are new circles and we leave the parameter tree files out of this

#

How big of a deal do you think it would be if we were to change the action button's style?

#

@rough furnace didn't want to force a style on the application... but what about a button?

wide prism
#

I don't have an opinion on the buttons at this time. I am coming at the palette issue from the plot side, no experience with ParameterTree.

#

If you push this PR through, then you establish that we expect to have a usable palette.

#

Once we know that we expect a usable palette, we can theme the button from that without "forcing a style".

harsh ravine
#

we can't on QMacStyle which is the default

#

only a stylesheet can change a button

#

but if we add just one line to the action.py file...
button.setStyle(QtGui.QStyleFactory.create('Fusion'))

#

we can 'solve' the transparence issue

wide prism
#

Does that work per widget? That seems good. The button doesn't exactly seem to match regular UI design anyway.

harsh ravine
#

yes, you can use it for a widget or the entire application

wide prism
#

I use it on the app for the reason of buttons. But setting just the button to be palette-aware seems like a good move.

harsh ravine
wide prism
#

Does the current button look like a MacOS UI button on your system?

harsh ravine
wide prism
#

Unless you really like orange checkmarks that seems 100% better.

harsh ravine
#

minus the transparency problem, yes

#

orange checks are from here

#

totally unrelated to the style change

wide prism
#

So, why do these change in your example image, then?

harsh ravine
#

The only 'action' buttons are the ones with transparency issues

#

Because one of the apps has focus

wide prism
#

Ah ๐Ÿ™‚

harsh ravine
#

the one without is grey

wide prism
#

On some systems, the left-most pixels of "Collapse" still stick out under the non-transparent button, though...

#

This:

harsh ravine
#

Yeah, I noticed that on mac in light mode

wide prism
#

Anyway, I think going to "fusion" mode is totally acceptable here.

harsh ravine
#

The only other option that I see is changing how each parameter is constructed. Right now, when you create a new treewidgetItem we pass the param.title(), which is displayed on all of the items in the tree. if we change it to be an empty string instead, it works with macos buttons, but I don't know if thats going to create a different problem that we don't notice until later on

wide prism
#

As non-expert on ParameterTree, I would see the issue as "why is there a supposedly invisible label in bold text!?"
But that is definitely a separate PR.

harsh ravine
#

You don't happen to have a light palette that matches the lightPalette stylesheet, do you?

#

Another fix could be to override setText in the treeWidgetItem and check if the pram is an action and set the text to an empty string if it is

#

I think that would work

wide prism
#

That does seem reasonable at first glance.
I can try to parse the new QDarkLightStyleSheetThing into a QPalette if you want.

#

But I don't have one now, no.

harsh ravine
#

Thats okay, you don't have to do that. I can open the sample app they have and create a palette based on the colors

wide prism
#

Should I make a neon one, too, to find all the widgets that don't react properly?

harsh ravine
#

you should do that

wide prism
#

Will do. Your call on who does the light palette. I am willing to help out, but it wouldn't be right now. (By tomorrow, more likely)

harsh ravine
#

I'll make the light palette, i'm already taking way too much of your time on this

wide prism
#

No worries, lazy holiday today.

harsh ravine
#

I think I'm probably going to take the rest of the week off, so I should be able to get quite a bit done this week

#

I need to get that slider working for the parameterTree

wide prism
#

oooh, luxurious. Sounds like good news for us ๐Ÿ™‚

harsh ravine
#

This is the stylesheet I usually go with when I need a theme

#

This is what qdarkstyle should be doing

#

set a palette - get the colors in the stylesheet from the palette

wide prism
#

Conceptually, they seem to be doing just that. The code I saw explained that they make three foreground shades, three background shades and three accents. That doesn't quite match the QPalette, but it is basically a palette. It would be nice if they made that available from a function. Or even better, provide a somewhat close QPalette directly.

harsh ravine
#

but theirs is a python object called Palette. When you use a QPalette and apply it to the app you can access it from the stylesheet

#

If you want to access the palette:

palette = qdarkstyle.LightPalette.__dict__
# qdarkstyle.DarkPalette.__dict__
for color_id,color in palette.items():
    if color_id.startswith('COLOR'):
        print(color_id,color)
#

here's a slightly better one.


if __name__ == '__main__':

    app = QtWidgets.QApplication()
    w = QWidget()
    lay = QGridLayout()
    w.setLayout(lay)
    palette = qdarkstyle.LightPalette
    colors = palette.__dict__
    color = '#C40'
    lst = list(colors.items())
    for x in range(6):
        for y in range(3):
            l = QLabel()
            l.setFixedSize(250,100)
            item = lst.pop(0)
            if item[0].startswith('COLOR'):
                l.setText(item[0])
                l.setStyleSheet(f'background:{item[1]};color:{color};')
                lay.addWidget(l,x,y)

    w.show()
    app.exec_()
rough furnace
#

sorry just noticed this comment

wide prism
#

@harsh ravine , here's something slightly more... interesting.

def retrowave_QPalette():
    BLACK      = QtGui.QColor('#000000')
    BG_DARK    = QtGui.QColor('#14212c')
    BG_NORMAL  = QtGui.QColor('#1c404f')
    BG_LIGHT   = QtGui.QColor('#2e6670')
    FG_DARK    = QtGui.QColor('#ce83b7')
    FG_NORMAL  = QtGui.QColor('#00E0FF')
    FG_LIGHT   = QtGui.QColor('#99fff7')
    SEL_DARK   = QtGui.QColor('#BA0B85')
    SEL_NORMAL = QtGui.QColor('#E02776')
    SEL_LIGHT  = QtGui.QColor('#FCAA39')
    qpal = QtGui.QPalette( QtGui.QColor(BG_DARK) )
    for ptype in (  QtGui.QPalette.Active,  QtGui.QPalette.Inactive ):
        qpal.setColor( ptype, QtGui.QPalette.Base           , BG_DARK )
        qpal.setColor( ptype, QtGui.QPalette.Window         , BG_DARK )
        qpal.setColor( ptype, QtGui.QPalette.WindowText     , FG_NORMAL )
        qpal.setColor( ptype, QtGui.QPalette.AlternateBase  , BG_LIGHT )
        qpal.setColor( ptype, QtGui.QPalette.Button         , BG_NORMAL )
        qpal.setColor( ptype, QtGui.QPalette.ButtonText     , FG_LIGHT )
        qpal.setColor( ptype, QtGui.QPalette.Highlight      , SEL_NORMAL )
        qpal.setColor( ptype, QtGui.QPalette.HighlightedText, BG_DARK )
        qpal.setColor( ptype, QtGui.QPalette.Text           , FG_LIGHT )
        qpal.setColor( ptype, QtGui.QPalette.ToolTipBase    , FG_DARK )
        qpal.setColor( ptype, QtGui.QPalette.ToolTipText    , BLACK )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.Button    , BG_NORMAL )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, FG_DARK )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, FG_DARK )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.Text      , FG_DARK )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.Highlight,  BG_LIGHT )
    qpal.setColor( QtGui.QPalette.Disabled, QtGui.QPalette.HighlightedText, FG_DARK )
    return qpal
rough furnace
#

@uncut ore ๐Ÿ‘‹

uncut ore
#

๐Ÿ‘‹

#

New Channel unlocked

rough furnace
#

we haven't gone public yet as most of us maintainers primarily are on our own slack workspace; but we do monitor this space too

uncut ore
#

Alrighty, once I get some more questions I'll ask them here

rough furnace
#

๐Ÿ‘

harsh ravine
rough furnace
#

I commented on the PR

harsh ravine
#

Oh, I'll have a look.

fervent vale
#

I wonder if plotting 1 million points is now the norm...

rough furnace
#

I mean if we can do 60 fps with a million points, why not... I do think that numba-ifying the downsample methods is probably the easiest way to increase performance with that kind of data size

harsh ravine
#

@wide prism the PR is up if you want to take a look. It adds pg.applyPalette(app,'dark'), the palettes, and no longer cares about a stylesheet or darkMode.

#

also fails the Read the Docs build test. The error message doesn't really give much info so I'm not sure what that's about.

wide prism
#

I can try to have a look at that. Unless the error message is very specific, the best way to debug is to build the docs locally by going to the doc folder and running make html.

#

Do you get "no theme named 'sphinx_rtd_theme' found (missing theme.conf?)"

wide prism
#

"/home/docs/checkouts/readthedocs.org/user_builds/pyqtgraph/checkouts/2101/doc/source/../../pyqtgraph/parametertree/ParameterTree.py:docstring of pyqtgraph.parametertree.ParameterTree.ParameterTree.updatePalette:2: WARNING: Bullet list ends without a blank line; unexpected unindent."

#

Yeah, it didn't like the "- comment -" you have in one of the docstrings, and thinks it is a bulleted list.

rough furnace
#

Seriously, what's up with people trying to plot n-million points

wide prism
#

"If you build it, they will come" ๐Ÿ™‚
Any time factorio gets optimized, megabases get bigger.

If you let me plot a million datapoints, I will adjust my binning interval to exploit that.

#

Doesn't really mean that I needed to plot that many points, but it sure is convenient.

#

I guess I could start sampling at 500 ns intervals. ๐Ÿ˜ˆ

rough furnace
#

Which is why I'm thinking improving the downsampling methods should go a long way

wide prism
#

Let them run out of RAM instead!

rough furnace
#

like expanding a freeway, we'll never actually reduce traffic, we just increase demand

mortal grotto
#

I totally forgot that the example filter uses regex... Would people be in favor of a checkbox, avoiding regex, or just making users aware that the filter is regex?

#

Otherwise you get an error if you search for an open paren (

I can easily catch re.error when regex is enabled, but searching for a literal paren, i.e. \( may be confusing if you are unaware the search is regex

rough furnace
#

i think a regex filter is fine...

#

i use a regex filter on my application internally, I do catch a re.error and I highlight the text box red

mortal grotto
rough furnace
#

๐Ÿ˜† in my application I have different sets of colors based on light/dark theme; ...luckily I don't try and support a wide-array of palettes

#

I should probably update our CI here: https://github.blog/changelog/2021-11-23-github-actions-setup-python-now-supports-dependency-caching/

That should probably speed up our CI jobs

EDIT: I'm an idiot, we already use that ๐Ÿคฆโ€โ™‚๏ธ
EDIT2: Not quite an idiot, need to enable the caching...
EDIT3: don't think we can use it, we don't have a requirements.txt which looks like is what they cache
EDIT4: I can use "custom" requirements.txt but since we test against multiple versions of numpy/pyqt5/pyside2 I'm not seeing how we can sort this out, ...the packages that remain fixed (pytest, scipy, matplotlib, h5py, pyopengl), can cache...
EDIT5: Looks like we can cache it; we probably shouldn't use that requirements.txt file elsewhere tho

fervent vale
# wide prism I guess I could start sampling at 500 ns intervals. ๐Ÿ˜ˆ

500ns is only 2MHz sampling rate. Low by RF bandwidth standards. DVB is 8MHz, for e.g. There is a long standing issue at Gnuradio to replace some of their widgets with pyqtgraph. Typically, high sampling rates are not an issue, it usually comes with the understanding that only a smaller time segment can then be plotted

fervent vale
wide prism
rough furnace
#

while not visible in the plot, the take-away was that the downsampling methods really do not buy you much performance right now, furthermore, I think they all operate in the assumption that all the x-axis points are equally spaced

#

the improvement did not appear to scale with length of the array which is something I would hope to have happened with downsampling

fervent vale
#

So I just modified PlotSpeedTest to inherit from PlotDataItem

#

Then one can toggle downsample via the context menu

#

For 100k points, it went from 35fps to 155fps

rough furnace
#

!

mortal grotto
fervent vale
#

For 1e6 points it went from 3.6fps to 36fps

#

using the default peak method

rough furnace
#

(also peak downsample method should likely be updated to use connect='pairs' since we connect a series of vertical lines, and probably shouldn't be drawing the line connecting the vertical lines

fervent vale
#

Note however that the down sampling factor is automatically determined by the screen estate in use

#

From the source code, peak mode doesn't draw vertical lines

rough furnace
#

...been a while since I looked at it, but I could have sworn it was drawing a vertical line from the minimum to the maximum of values that represent that x-axis pixel

fervent vale
#

It takes the calculated ds or dx

#

Then halves that

#

So that it draws 2 points. One min one max each on a different x coord

rough furnace
#

Thanks for the correction; I clearly did not remember what they did correctly

fervent vale
#

Hmm, maybe I am reading the source code wrong

#

The docs describe peak mode as drawing a sawtooth

#

So that's why I assume the code was doing that

rough furnace
#

yeah, that's what I remember; .... but quite possible source code doesn't match the docs

wide prism
#

The idea is that you do that with more points than pixels on the screen. So it looks like a solid area.

#

What I personally like to do is keep min, max and mean, and then plot three lines for that. But that doesn't really fit into the PlotDataItem framework.

fervent vale
#

after reading the code more closely, it does look that the 2 x-coords are the same

#

So that would be an implementation error wrt the docs

wide prism
#

Does it draw the diagonal down to the next point? That would still be a sawtooth.

#

Is it faster to draw the sawtooth as a continuous line? Or would this be faster in 'pairs' mode?

fervent vale
#

Oh yes, max of previous point goes to min of next point

#

So it would indeed be a sawtooth

#

A falling sawtooth

#

It would be faster in pairs mode if the intent was to not draw the sawtooth pattern

wide prism
#

The idea of the sawtooth pattern is that you cannot see it.

fervent vale
#

For the same number of coords, pairs mode is faster as the number of line segments drawn would be halved

wide prism
#

My understanding is that -at the time of implementation- a long string of lines was fastest, and to draw the sawtooth was the fastest way to fill min-to-max for each horizontal pixel on the screen.

fervent vale
#

The unbroken line also allows fill mode to work...

wide prism
#

Hmm. That is certainly nice to have, and I am sure somebody uses it. But if pairs mode is dramatically faster...
PlotDataItem is aware of fill mode and baseline, it could switch strategies as needed.

If I understand correctly, the set of points is actually the same, right? But pairs mode would only draw the verticals.

fervent vale
#

It's a balance between where the slowdown is: pyqtgraph or Qt

#

Eg for the filling by chunks code, there's a pyqtgraph overhead to creating smaller chunks

#

But Qt fills much slower as the chunk gets larger

#

For pairs, pyqtgraph uses the old operator << to create the QPainterPath

wide prism
#

In your new test setup, can you hack PlotDataItem to draw downsample in pairs mode? The dataset should be the same. Would be nice to know if it takes 50% of the time then, or if it is basically unchanged.

#

Although the actual downsampling probably takes a fixed chunk of time already.

fervent vale
#

Nothing to hack. Just change connect=pairs in the UI

#

For 100k points with downsample peak enabled, fps went from 155 to 175

wide prism
#

Hehe... I don't think that it is intentional that the "pairs" switch works in downsample mode. Lucky, I guess ๐Ÿ™‚

#

I guess it might be a reasonable step one to force that on in downsample, no fill mode...

harsh ravine
#

Thanks for the review! @wide prism

rough furnace
wide prism
#

That wouldn't speed up the default case, though.

wide prism
wide prism
# fervent vale 500ns is only 2MHz sampling rate. Low by RF bandwidth standards. DVB is 8MHz, fo...

Yeah, things are going very fast now. HDMI won't even talk to you unless you can push 252 Mbps.
But usually all of this is just referenced to a trigger time, and timestamps never grow large enough to be a problem for a double float.

But SDR is a great example where you might want both high resolution and absolute timestamps.

I was just joking about running my data logging equipment at 500 ns (rather than 1s), which would probably require me to set up a storage farm very soon ๐Ÿ™‚

rough furnace
#

oh, are we talking storage "farms"?

#

@harsh ravine i'll try and look things over more closely tonight, I have relatives staying w/ me for the holidays, so my evenings are a bit more packed than usual.

#

I apologize for the drive-by comment earlier, should have read more context before I started typing

wide prism
#

How far would that get me?
Frequency counters spit out about 20 bytes per channel per update.
20 counters = 400 bytes
2 MHz update rate: 800 MB/s
Isn't that close to 70 TB in a day?

rough furnace
#

at work, I manage almost 1 PB worth of storage across a handful of systems ....

wide prism
#

Well, my counters don't go faster than 10 ms or so, so I'll grant you hypothetical harddisks, too ๐Ÿ™‚

The point being that I run at 1s rate because it creates an amount of data that is convenient to handle and store.
Twenty years ago, I would have downsampled further.
In twenty years, I might not even care to think about it.

rough furnace
#

800 MB/s ... I suspect some of the ZFS pools I manage could do that, .... but it would be a struggle

wide prism
#

So if pijyoi gives us performant million-point plots, I'll grow my dataset until it is just "tolerable" again. ๐Ÿ˜†

#

Oh, you might like this, then:

#

"Even after the drastic data reduction performed by the experiments, the CERN Data Centre processes on average one petabyte (one million gigabytes) of data per day. The LHC experiments produce about 90 petabytes of data per year"

#

A bit closer to my doorstep (https://link.springer.com/article/10.1007/s00190-021-01479-8):
"The K6/GALAS sampler has four 10GBASE-SR interfaces, and
it is capable of providing a total data rate of 16,384
Mbps via eight data streams of VDIF/VTP/UDP/IP Ethernet
packets. Each stream conveys 2048 Msps of 1-bit quantized
data for each observing band. Two off-the-shelf Linux-OS
personal computers(PCs) serve as data recording disk servers
for 16,384 Mbps of dual polarization data at KASHIM34
station. The 8192 Mbps data rate for a single polarization
requires only one PC at the transportable stations. Each PC
is equipped with two 10GBASE-SR network interfaces and
a RAID disk system."

#

..when the speed of your experiment is determined by how fast the top-tier research internet can run, and "station wagon full of tapes" is in fact the preferred method of data exchange ๐Ÿ˜†

#

I am glad my own stuff is happy to run on some random computer I grabbed off someone that wasn't using it anymore.

harsh ravine
harsh ravine
wide prism
#

If you keep running into issues with the doc, I think you just need to install sphinx and sphinx_rtd_theme (which should be available form the package manager of your choice) and then you should be able to compile docs locally. That has a slightly faster cycle than push, wait, guess, and push again...

rough furnace
#

i can help debug doc issues too if it's getting to be a head-ache, but yeah, generally debuging docs in CI is an unpleasant experience

harsh ravine
#

I removed the comment and I'm getting ready to push the changes once we agree on how to access the font size, so we'll see what happens when I do.

rough furnace
#

saw reference to the font size situation, what's the options?

#

(looking at the docs-build error and see if it's something)

wide prism
#

viewpoint on font size:
"This returns the effective pointSize for a ParameterItem. It therefore should be presented as a regular pointSize() method like everywhere else."
Discuss.

rough furnace
#

there are a bunch of comments in that thread, you have a link to the relevant comment handy? got it

#

difference being between

self.fontPointSize = self.font(0).pointSize

vs.

self.fontPointSize = partial(self.font(0).pointSize)

... for stuff like this I would normally think something like

def fontPointSize(self, *args):
    return self.font(0).pointSize(args)

but if we need self.fontPointSize as part of __init__ then I'm fine w/ using the PR author's discretion for something like this

harsh ravine
#

this is only used to return the current point size. The reason its in the init instead of a method is because if we use self.font(0).pointSize() to get the size we will increase the size of the font every time we run updateDepth

#

I really don't see this as being an issue since the only time it will run is when the palette changes which for 99.999% of the cases will only be when the program starts

#

it was only needed to address what happens when switching back and forth between light and dark themes on any OS with light/dark mode

#

It wasn't an issue before because update depth only ran the one time when populating the tree

#

I suppose it would also cause problems if the tree was being dynamically created from other input in the app, if this is even possible.

#

How's this instead?

@property
def fontPointSize(self):
    return 14
#

we can create a setter and use a variable if we think people are likely to want to change the font size

fervent vale
#

self._initialFontPointSize = self.font(0).pointSize()?

harsh ravine
#

Thats fine by me.

rough furnace
#

Lgtm too

wide prism
#

...and access is by?

rough furnace
#

Ok I'm going to hit the hay early, @harsh ravine if the doc build issue is being stubborn, ping me and I'll look at pushing a fix when I hop on tomorrow

fervent vale
#

Access via the def fontPointSize(self) method

wide prism
#

That would be good with me. (pointSize or fontPointSize?)

harsh ravine
#

I'm fine with pointSize

#

If thats something thats done other places then we should keep things consistent

wide prism
#

Lgtm, then.

harsh ravine
#

cool!

#

Read the Docs build succeeded! ๐Ÿฅณ

fervent vale
#

palette.py should only do relative imports

harsh ravine
#

oops, PyCharm did that when I moved the file.

harsh ravine
#

Does anyone use the imageItem drawing feature?

#

I don't get how it could be useful, its so slow.

wide prism
#

Opinion: #ARGB is just wrong.

#

Even Qt silently agrees and doesn't want to discuss their choices in the stylesheet documentation.

rough furnace
rough furnace
harsh ravine
#

How does PyQt always seem to includes more bindings than PySide

#

I thought PySide would gain some ground when they announced that Qt was going to be maintaining it.

rough furnace
#

PyQt is missing some methods; afaik no one binding is great in this regard

rough furnace
#

Hope everyone has a good Thanksgiving!

harsh ravine
rough furnace
obsidian sapphire
#

wow, yeah, that's a thing I do. maybe I can get them to contribute ๐Ÿ˜

rough furnace
harsh ravine
wide prism
harsh ravine
#

ooh, okay I found it in the examples.

#

Check this out

wide prism
#

What's the thing on the top right? Did you somehow manage to find an extended 128 color palette in Qt?

#

Otherwise, that has a more neutral, almost warm gray a main background. Does that match the Mac color better?

harsh ravine
#

The little color squares?

wide prism
#

Yup.

#

Where does it get the orange highlight from, on the tabbed interface on the bottom right?

harsh ravine
#

They are just push buttons with I made that you can drag/drop on the buttons on the left

#

you can also drag the buttons on the left on to other buttons to drop a color an a different role

wide prism
#

Nice ๐Ÿ™‚

harsh ravine
#

Oh, the orange is because those lines are a mix of Light and Mid

#

there are actually 2 thin lines very close together

wide prism
#

Oh, it actually uses light and mid? I didn't see a UI element that did ๐Ÿ™‚

harsh ravine
#

In Fusion it does

#

The ticks on the dial use Dark

#

The slider too, probably.. if it had any ticks

wide prism
#

Oh, I need to fix that up in my palette. Have you considered making the background another hint lighter? That might help the checkbox and radio buttons ๐Ÿ™‚

harsh ravine
#

The buttons on the left open up a color dialog when you click on them too

#

Oh, I don't care about what that UI looks like, I was just showing you the palette tool I made

#

You can copy a UI super fast with it

wide prism
#

๐Ÿ˜

#

We could use a palette that mimics Mac OS a little. The grays are a lot warmer than the QDarkStyle ones.

harsh ravine
wide prism
#

Err. Forgot that you get that as default. Can you export that? Might add it to the PR.

harsh ravine
#

When you launch the tool, whatever widget you have it hooked up to has the current app palette applied to it.

wide prism
#

For poor windows users.

harsh ravine
#

!paste

runic umbraBOT
#

Pasting large amounts of code

If your code is too long to fit in a codeblock in discord, you can paste your code here:
https://paste.pythondiscord.com/

After pasting your code, save it by clicking the floppy disk icon in the top right, or by typing ctrl + S. After doing that, the URL should change. Copy the URL and post it here so others can see it.

harsh ravine
#

I ran into trouble when trying to use Active/Inactive/Disabled groups

#

Any time I clicked into the tool, the other widget would switch to the Inactive palette, which made it very annoying to use.

#

Using All, and then Disabled seems to work though

#

I'll put it on github tomorrow after I finish it

wide prism
#

That defines more colors than I was aware of. I guess I need to play with the palette some more.

#

Seems very helpful ๐Ÿ‘

harsh ravine
#

Once you get use to where the buttons are, it really speeds up creating a theme.

#

If you do try to use all of the colors, start with an empty QPalette(), or do QPalette(QApplication.palette())

#

otherwise you might run into some that won't change when updated, like Highlight and the dark/mid/light

fervent vale
#

if PR 2101 is done, it should be moved out of "Draft" state

rough furnace
#

@harsh ravine โ˜๏ธ

fervent vale
#

Looks like PySide6 6.2.2 breaks pyqtgraph

#

GraphicsObject inherits from both GraphicsItem and QGraphicsObject, both of which define parentChanged

#

Why is QGraphicsObject.parentChanged getting called on PySide6 6.2.2?

fervent vale
#
    def hello(self):
        print('hello from A')

class B:
    def hello(self):
        print('hello from B')

class C(A, B):
    def func(self):
        self.hello()

c = C()
c.func()
#

A being GraphicsItem and B being QGraphicsObject, on PySide6 6.2.2, we are getting "hello from B"

#

While a quick fix would be to change self.hello() to A.hello(self), I am not sure what implications this violation of expectations has.

polar onyx
#

You could swap the order of inheritance, or make a call to super and specify A, and either way maybe mention in the documentation?

rough furnace
rough furnace
#

nice find @fervent vale this definitely going to be a problem....

#

on closer inspection, having QGraphicsObject.parentChanged be a signal and GraphicsItem.parentChanged be a method seems like it might be problematic on its own

polar onyx
rough furnace
polar onyx
rough furnace
polar onyx
#

3.9

#

Python doesn't introduce breaking changes like that anyway though

rough furnace
#

yeah, I think the pure python implementation works like that, this is some upstream change in the dependency

#

I think Pijyoi was using that code as an example to highlight the issue, that code block isn't meant to be a MWE

polar onyx
#

How odd

rough furnace
#

luckily the pyside devs are pretty nice and accessible, just posted on their gitter

#

@polar onyx if you're curious the actual MWE is if you install pyside6 6.2.2 and pyqtgraph you would have to do

import pyqtgraph as pg
graphicsObject = pg.GraphicsObject()
graphicsObject.parentChagned()

This behaves differently between PySide6 6.2.1 (and earlier, and all other Qt bindings) and PySide6 6.2.2

With PySide6 6.2.2 this will give you a signal is not callable....

polar onyx
#

huh

rough furnace
#

with PySide 6.2.2

Python 3.9.7 (default, Sep 17 2021, 12:53:27)
[Clang 12.0.5 (clang-1205.0.22.11)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyqtgraph as pg
>>> graphicsObject = pg.GraphicsObject()
>>> graphicsObject.parentChanged()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: native Qt signal is not callable

with PyQt6

>>> import pyqtgraph as pg
>>> graphicsObject = pg.GraphicsObject()
>>> graphicsObject.parentChanged()
>>>
#

ok, turns out there is only one line in the library that this impacts ๐Ÿคฆโ€โ™‚๏ธ easy enough to fix in the way pijyoi suggested, I'll submit the PR right now.

rough furnace
#

hmm... there are other methods in GraphicsItem that have matching methods in QGraphicsObject...the test suite doesn't seem to have found issues w/ those...but we should probably do the similar fix w/ those methods too

#

ok, there does not seem to be a change in behavior with regards to conflicting methods the issue seems to be specific to the Signal vs. method name conflict

rough furnace
#

I submitted a PR for a fix, as we call GraphicsItem.parentChanged() only in one place, a fix is easy. There is likely some discussion needed about what that fix actually is. I proposed an alternative option in the PR.

fervent vale
#
from PySide6 import QtCore

class Q(QtCore.QObject):
    signal = QtCore.Signal()
    def method(self): pass

class M:
    def signal(self): pass
    def method(self): pass

class C(M, Q): pass

c = C()
print(c.method)     # OK
print(c.signal)     # not OK on PySide6 6.2.2
print(super(C, c).signal)   # OK
#

Using super somehow works

rough furnace
#

I still think we should take the opportunity to get rid of the GraphicsItem.parentChanged() it can be a source of confusion; and we only call that method in 1 place in the library, and that method only has 1 line, calling another method...

#

or leave it, put a deprecation warning, and modify the call within GraphicsObject.itemChanged() to all GraphicsItem._updateView() instead

fervent vale
#

Both super().parentChanged() and GraphicsItem.parentChanged(self) are not quite the same meaning as self.parentChanged(). self.parentChanged() can call the parentChanged method of sub-classes of GraphicsObject

#

There appears to be one single GraphicsItem in pyqtgraph that does this over-ride: ScaleBar

#

Going through git history, self.parentChanged() used to be a direct call to self._updateView()

#

It appears that the change to self.parentChanged() in 2013/03/26 was made purely to support ScaleBar (also added in 2013/03/26)

#

I think there's an issue with ScaleBar::parentChanged. Each time that it is called, a signal-to-slot connection is made. i.e duplicate connections

rough furnace
#

nice find on the other method; will definitely have to address that one too...

rapid kestrel
#

Question for y'all, does pyqtgraph play nice with pyside6?

rough furnace
#

yes, except for pyside6 6.2.2; which was just released and they did something really weird involving class inheritance

mortal grotto
#

(and 6.0)

rough furnace
#

most of the chat here earlier has to do w/ how to deal w/ it

#

Qt 6.0 is dead to us

#

(pyside6 6.0 actually might work, PyQt6 6.0 was the real problematic one)

rapid kestrel
#

huh, okay. Time to go down a debugging hole then! I appreciate the response~

rough furnace
#

feel free to post a bit more; i'll gladly chime in if we've seen something that might be relevant

rapid kestrel
#

My thing for work got approved for the first milestone, so my next task is to incorporate live plotting. I'm using PySide6. Instantiating a self.plot = pg.PlotWidget() is causing a recursion error and crashing out the program. It seems to be stuck in the if hasattr(self.PlotItem, attr) init of PlotWidget.

I'm almost certain I've done something wrong, so down the debugging hole I gooo

mortal grotto
#

is self a subclass of PlotWidget?

rapid kestrel
#

nah, just a custom class to hold some items together

rough furnace
#

@rapid kestrel it's been a little while since we did a release, before you dive too deep into that hole, I would suggest installing from master and see if it's the same

rapid kestrel
#

will do!

rough furnace
#

there have been some issues involving missing mouse event methods QMouseEvent.position() and QMoustEvent.localPos()

#

looking over this, another issue was that we can no longer cast int(Qt.Enum)

rapid kestrel
#

Well that was a fast debug, pyqtgraph isn't compatible with the from __feature__ import snake_case feature in pyside6

rough furnace
#

oh! ... yeah, read about that feature, didn't even attempt to try it ourselves

rapid kestrel
#

Yeah, it's interesting and quite nice with the stubs. But it does change the name of functions under the hood (iirc) and I think that starts to mess with pyqtgraph

mortal grotto
#

And any form of overriding I would imagine, not just pyqtgraph?

rapid kestrel
#

Yeah, what I've done so far doesn't require overriding, so I haven't hit the issue yet. But if I use the snake_case feature, I need to be very consistent about it in all files, especially files that I import

rough furnace
#

wonder if there is an "easy" way we can respect that and be compatible w/ it

rapid kestrel
#

I can maybe take a look in January, I'll be working on this project but bored out of my skull at a testing site

rough furnace
#

i think the snake_case thing got proposed for PyQt6 support but Phil (the guy that maintains PyQt) shot it down

mortal grotto
#

It's easy, just add another regex shim to pyqtgraph.Qt ๐Ÿ˜†

rough furnace
mortal grotto
#
for obj in QtWidgets:
  obj.__getattr__ = <regex shim>

Certainly there will be no computational complexities or difficulties arising from this very simple code ๐Ÿคท

Now all that's left is __feature__.true_property!

rough furnace
#

@fervent vale i'm incorporating your pyside 6.2.2 workaround right now and updating the PR, sorry I've been slow on the issue tracker, nice work finding that solution ๐Ÿ‘

#

(been playing cyberpunk 2077 in the evenings the last week and a half)

rough furnace
#

i'm actually going to open a bug in the pyside6 bug tracker w/ the MWE you created; and reference that bug in the comment more specifically

#

it's never easy....

fervent vale
#

The mwe indentation is not correct in the pyside bug tracker

rough furnace
#

blerg, I'll edit..thanks for pointing it out... wish jira used markdown but nooooo

and now fixed

rough furnace
#

@harsh ravine sorry been away from my pyqtgraph work the last few weeks; I'm ready to merge your PR, only request I have is to remove the unused import of functools.partial

rough furnace
#

... has anyone found a way to address getting these errors when having a breakpoint with PyQt bindings?

QCoreApplication::exec: The event loop is already running

?

mortal grotto
#

Which editor are you using?

rough furnace
#

this was just running from the console...

#

(I have a directory named playground that I put files I use for testing purposes that I don't intend to add to the repo)

python playground/someExample.py```

if I hit a `breakpoint()` within the code-base in PyQt[56] bindings I just get console spam of the above.
rough furnace
#

https://twitter.com/pyblogsal/status/1468966167331188737

Wonder if it would be feasible to build numpy for python 3.11 alphas so we can test the library (assuming we can get PyQt bindings to build too)

You can tell that we are slowly getting closer to the first beta as the number of release blockers that we need to fix on every release starts to increase ๐Ÿ˜… But we did it! ๐ŸŽ‰Thanks to everyone that helped the release team get things ready ๐ŸคŸโค๏ธ
https://t.co/Byvb2YAXw0

harsh ravine
rough furnace
#

hopefully you're feeling better now!

fervent vale
#

Would version 0.12.4 be the last version to support Python 3.7 or be the first version to require Python 3.8?

rough furnace
#

Probably last to do 3.7; generally don't like deprecating for patch releases.

#

Not seeing a good reason to bump a minor release. You have a preference?

fervent vale
#

A last release before requiring 3.8 would be good.

#

Last week I tried running on a machine with Python 3.6. The only 3.7 feature that needed patching to make it run was the use of perf_counter_ns

rough furnace
#

at a glance I'm not seeing what new features we would roll out right away with requiring 3.8+; i'm sure there are some places the walrus operator would come in handy... I suppose the functools.lru_cache decorator will be nice too

#

trying to build numpy for python 3.11.0a3 ๐Ÿ˜ฌ

EDIT: and it failed, and I have no idea what I'm looking for in the console output

rough furnace
#

ok, got numpy/pyqt6 built for python 3.11.0a3; tests pass, but two examples blow up

optics_demo and MultiPlotSpeedTest

MultiPlotSpeedTest was kind enough to generate a crash report...

not sure if this means much of anything to anyone:

Exception Type:        EXC_BAD_ACCESS (SIGBUS)
Exception Codes:       KERN_PROTECTION_FAILURE at 0x000000010edc8010
Exception Codes:       0x0000000000000002, 0x000000010edc8010
Exception Note:        EXC_CORPSE_NOTIFY

VM Region Info: 0x10edc8010 is in 0x10edc8000-0x10edc9000;  bytes after start: 16  bytes before end: 4079
      REGION TYPE                    START - END         [ VSIZE] PRT/MAX SHRMOD  REGION DETAIL
      VM_ALLOCATE                 10edc4000-10edc8000    [   16K] rw-/rwx SM=PRV  
--->  shared memory               10edc8000-10edc9000    [    4K] r--/r-- SM=SHM  
      __TEXT                      10edc9000-10edd3000    [   40K] r-x/rwx SM=COW  ...bintl.8.dylib
fervent vale
#

you mean the other examples that only rely on numpy run fine?

rough furnace
#

yup

#

all the OpenGL examples run fine too

#

if I skip the two tests...

90 passed, 2 skipped, 2 deselected in 181.27s (0:03:01)
fervent vale
#

Just for the fun of it, Python 3.11.0a3, numpy 1.23.0.dev0+144.g4d6e41879, PyOpenGL on an old machine running Linux ran all tests and examples successfully

rough furnace
#

Nice!

rough furnace
rough furnace
#

@lapis tapir I think after @obsidian sapphire 's comments are addressed, we'll merge your PR and start the process on releasing 0.12.4. Please don't hesitate to reach out if we can help implement some of the suggested changes or the docs-bits.

mortal grotto
#

Huh, Windows 11 introduces the transparent button issues

rough furnace
half jewel
rough furnace
#

yeah its' the right person, thanks for the notice, I need to add them here ๐Ÿ˜ฌ

#

@lapis tapir ๐Ÿ‘‹

mortal grotto
rough furnace
#

curious if the Qt6 bindings handle it better

mortal grotto
#

Is that the case on Mac?

rough furnace
#

lol no

#

@mortal grotto 40 commits?

mortal grotto
#

๐Ÿ˜† Usually I can --rebase without issue to avoid unnecessary merge commits

#

Trying to revert now, haha

#

Didn't mean to spam people's email history

rough furnace
#

not spam, I'm intentionally "watch"ing the repo

mortal grotto
#

that's what I meant to do. It should be good now

rough furnace
#

man, why am I so awful w/ Sphinx ... having to look up examples for everything as I'm trying to make corrections/changes

lapis tapir
rough furnace
#

well, last CI run it passed ..but if you still want to for your own sake...

cd doc
pip install -r requirements.txt
make html SPHINXOPTS="-W --keep-going -n"
#

you can also see the result of the RTD build by clicking on details page here:

#

about to go AFK for the next ~5 hours, have a series of errands; I may comment w/ some more nit-picky stuff later this evening, but functionally this works, CI/docs are happy, this will be in the next release ๐Ÿ‘

#

thanks again for your patience w/ us on this!

#

you should not have had to wait more than a year for a thorough review/feedback from us

lapis tapir
#

hey no problem, it's 00:28 for me, will build the docs if i can then go to sleep, please dm me directly if needed i willrespond as soon ai i can

rough furnace
#

๐Ÿ‘ thanks again for your contribution

#

this has been one of our most requested features

lapis tapir
#

i needed it myself too ahha

rough furnace
#

haha yeah I did something pretty hacky for my first project involving pyqtgraph long before I was a maintainer (to achieve this functionality)

#

probably most of my other requests will be converting to camelCase or using _ to denote "private" methods/variables ...stuff like that

#

unless something comes out of left field, I'm not for-seeing any functional changes

lapis tapir
#

ok just list the changes i will make them when i can

rough furnace
#

๐Ÿ‘ thanks again, get some rest

lapis tapir
#

also getting this errors
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.addAxis:: WARNING: py:class reference target not found: iterable
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.makeLayout:: WARNING: py:class reference target not found: list of str
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.makeLayout:: WARNING: py:class reference target not found: None:
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.makeLayout:: WARNING: py:class reference target not found: list of PlotItems
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.makeLayout:: WARNING: py:class reference target not found: None:
pyqtgraph/widgets/MultiAxisPlotWidget.py:docstring of pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget.setAxisRange:: WARNING: py:class reference target not found: list of int
sorry never used sphinx

rough furnace
#

hmm....

#

output of pip list?

#

i'm no sphinx wizard; ... I haven't seen that, first thing comes to mind is that you actually have pyqtgraph installed in editable mode to the same virtual environment you're using

#

oh wait, just kidding, got the same errors

#

that's embarassing, should have caught those... I'll figure out what the fix is and comment them inline later

lapis tapir
#

ok bye for now

mortal grotto
#

Thinking of applying black to my two ~10k lines of code repos. Are there major downsides to this? I have one person working on a forked copy, will this erupt their project into flames?

There should be git tools to avoid blameing every line with that change, right?

#

I used two spaces before, so almost every single line will need formatting...

half jewel
#

There is indeed a git configuration option to ignore certain revisions in the blame output but github does not support it unfortunately.

#

I suspect the formatting changes will cause merge conflicts for days on unmerged work (especially with the fork).

rough furnace
#

There is a git blame ignore commit feature, don't know if the girhub webui respects that setting.

Main issue is with introducing merge conflicts if you have a series of outstanding PRs

mortal grotto
#

One of the benefits of distributing a GUI instead of a backend is more flexibility to code changes

rough furnace
#

@mortal grotto did you have further changes planned on 2086?

mortal grotto
#

I don't think so. Can you test on your bindings to make sure things fire OK?

rough furnace
#

yeah, give me a minute ๐Ÿ‘

#

yeah this is good on pyqt6 ๐Ÿ‘

#

oh right, that's what I was going to do, check for the white outline in the error bounding box

#

macOS dark mode:

#

macOS light mode:

rough furnace
#

@lapis tapir I think you can disregard those doc warnings, I think I was remembering wrong that we were having all our docs build with nit-picky mode enabled... that may never have been the case; your docs generate no warnings if you do not run w/ nit-picky mode (in other words, I think no further changes needed there)

rough furnace
#

@mortal grotto I can't replicate that white border on my mac system, I suspect it's a windows 11 thing? do you want to keep trying to modify the style-sheet to capture that border and color it accordingly?

harsh ravine
harsh ravine
#

Interesting, but looks normal on Mojave.

rough furnace
#

Mojave? ... "that's a name I haven't heard in a long time"

harsh ravine
#

One of the apps I need to test for work still has a 32 bit app for the 32bit quicktime codec so I can't upgrade

rough furnace
#

oof... I joke, I still have my 2010 macbook pro which is stuck on High Sierra, I just wish I could have dark theme on it ๐Ÿ˜ฆ

harsh ravine
#

I feel you. My 2013 macbook is on HighSierra, 2015 is on Mojave, and 2016 is on whatever it shipped with( I think Mojave)

#

I had to keep the 2013 on El Capitan until adobe killed off flash support last year

rough furnace
#

@harsh ravine merging your PR, thanks for your patience on this!

#

116 comments... jeez... so sorry

harsh ravine
#

woo!

harsh ravine
rough furnace
#

I assumed it was low hanging fruit too

#

I'm so surprised the system palette alternateBase() color didn't work; I really thought that would work!

harsh ravine
#

Yeah.. The QMacStyle confuses me even after looking at the source

#

Is the Monospace font for the console widget supposed to be a default font on mac os or are the users expected to install it?

#

When I try to run the console example I get qt.qpa.fonts: Populating font family aliases took 2170 ms. Replace uses of missing font family "Monospace" with one that exists to avoid this cost.

#

The replacement that happens really slows down the launch

rough furnace
#

Hmm... I ran into that a while back and thought I selected a font that was available on all platforms

#

is this on your older system?

harsh ravine
rough furnace
#

oh wait, ... that didn't get fixed...

#

I fixed the font situation for the general examples, but not the console app...

#

this is annoying as it requires you re-generate the template files

harsh ravine
#

We could always bundle the font and install it at runtime with QFontDatabase before setupUi runs

#

I'm positive that some Linux distros don't come with Courier fonts

#

Another thing you can do would be to regenerate the UI file and set the font to system and iterate over the installed fonts and find the first fixedPitch font, and set that as the font for the ouput widget

rough furnace
#

considering the number of fonts that would be inappropriate for these kinds of usages, I'm a bit reluctant to grab the first available fixed point one; ... I might be ok with bundling a font if we can identify a non-niche system that doesn't have Courier fonts

harsh ravine
#

I think it was Mint that I was remembering

#

Unless there are fixed width fonts like wingdings it probably won't be a huge problem

#

Oh actually nevermind, adobe blank is fixed

rough furnace
#

I don't mind doing a PR for redoing those templates; ... if you want to have at it, that works too (might be a day or two before I can get to it)... the templates are a bit annoying to generate since it requires you have a virtual environment for each binding (or I think some people are able to generate templates with all the bindings installed to the same virtual environment, but I haven't done that)

harsh ravine
#

I happen to have an environment with all of the bindings so I can regenerate the files

#

I'm not sure what you want to change though

rough furnace
#

the way you would do it is modify console.ui file to set the different font family; then I think there is a script in the tools directory to assist w/ regenerating the template_<binding>.py files

harsh ravine
#

Yeah, I know how to regen the files, I'm just not sure what we're changing.

#

Do you want to use Courier New instead?

rough furnace
#

yeah, do the same change that I linked above (change <family>FreeMono</family> to <family>Courier New</family>

#

unless you can think of another font family that is monospace that we have better likelihood on being available on all other platforms

harsh ravine
#

Okay, will do

#

Nah, not without bundling

#

Maybe Arial something or other?

rough furnace
#

Arial I don't think is monospaced

tight cliff
#

unless you have some other requirements

rough furnace
#

๐Ÿ‘‹ @tight cliff that might work; is Consolas available on windows platforms w/o having to add it?

harsh ravine
#

I don't seem to have Consolas installed

tight cliff
#

I have it on Windows I think

#

Unless it came with something else I installed

#

which shouldn't be the case since it's a new laptop, the only possible thing would be winterm

harsh ravine
rough furnace
#

I don't think Consolas is part of ubuntu either

tight cliff
#

Oh, maybe not

harsh ravine
#

Monaco is the default fixed width font on mac. Not sure about windows/linux availability

rough furnace
#

it's not on ubuntu 20.04 :/

harsh ravine
#

Courier it is

#

hey @rough furnace do you use bootcamp?

rough furnace
#

nope

harsh ravine
#

ahh ๐Ÿ˜ฆ

rough furnace
#

I have a windows desktop for gaming, and a linux VM I use for periodic CUDA testing

#

looks like I may have installed some fonts on my ubuntu VM before...

harsh ravine
#

I was hoping you'd know why my bootcamp partition started appearing as the default drive when I switched the HD from the 2013 to the 2015

#

Super annoying... I have to hold down alt to choose the mac partition

#

I was using bootcamp on the 2013 like... 3 years ago. It was removed so long ago, but the 2015 sees it

#

the bootcamp assistant doesn't even know about it lol

rough furnace
#

i generally nuke my machine and do fresh installs with new OSs

harsh ravine
#

I was going to but I didn't see Mojave in my app store downloads and I haven't bothered searching for an installer download

harsh ravine
#

left is antialiased right is not

#

any preference?

rough furnace
#

Have to look closely but the left is preferable; greater gap between the =

harsh ravine
#

Yeah, left is easier to read, especially so if the line is selected in history

harsh ravine
#

6.2.2 is wild

rough furnace
#

I have a hatred for these templates

harsh ravine
#

I downloaded 6.1. something and regenerated it and that fixed it

rough furnace
#

I mean if it works I don't really care; but probably should generate with latest 6.1 binding for Qt6 given we support 6.1

harsh ravine
#

hmm let me see

#

It should be okay. The only thing that shows in a diff is the version number in the comment.

rough furnace
#

yeah 6.1.3 is a perfectly good version to have it generated with ๐Ÿ‘ ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ

harsh ravine
#

I just ran 6.1.3 to test against the 6.1.2 code that I used in the PR

#

Do you want me to update with the new version?

rough furnace
#

6.1.3 vs. 6.1.2? naw, if it works, that's fine...

harsh ravine
#

It works, literally, the only change is the version in the comment there

rough furnace
#

just realized you generated w/ 6.1.2; that's not a problem (assuming it runs)

harsh ravine
#

its confusing to compare the different generated files

#

One of them reuses the font for both widgets, another creates 2 instances of the same QFont

rough furnace
#

@lapis tapir thanks for updating the PR, looking over the diff now (1 kid is "napping", other kid busy playing super mario brothers u; at this rate I might get a whole 5 minutes ๐Ÿ˜† )

harsh ravine
#

I got my kid a switch for christmas. Is super mario u a decent game?

#

the only game I've bought is katamari damacy

#

but thats mostly for me ๐Ÿ˜„

rough furnace
#

omg did github go offline on me as I was writing in-line comments?

#

their status board is all green...

harsh ravine
#

I just looked at my PR... I'm confused

rough furnace
#

haha haven't had a chance to look over it, trying to get feedback for #1359 so we can merge that one ASAP ๐Ÿ˜† now have to return the second stove-top vent hood that I ordered, had shipped, and was clearly previously installed, and damaged in the process... I'm not bitter... I have nothing better to do than to return stuff that the store sends me broken variants of...on multiple occasions

harsh ravine
#

Samsung sent me a TV without a remote or the proprietary cable that goes from the box to the TV. I understand your pain

rough furnace
#

oh, I see what happened w/ your PR ...

#

you must have rebased master or something

#

or you branched not off of master but a different branch

#

way I usually fix this is something like...

git switch master
git pull upstream master
git switch -c my-pr-temporary
git cherry-pick <commit-id in the PR branch you want to use>
# make sure this branch has the changes you want
git switch consoleFont
git reset --hard my-pr-temporary
git push --force-with-lease
#

going afk for a while

harsh ravine
#

okay - i have no clue what PyCharm's git interface is doing and I've run out of patience with it for right now. I'll see if this can be salvaged in the morning.

rough furnace
lapis tapir
lapis tapir
#

so err is there a reason my code works when built and installed for the first time and then afterwards the signals don't anymore? even restarting the example application completely does not fix the issue and only deleting __pycache__ and similar files fixes the issue? is this a self in lambda thing?

obsidian sapphire
#

I'm not going to be at a computer again until tomorrow night.

#

I'll read it, though, and see what I can say without having the code open.

#

Oh! Yeah, no references to self allowed in lambdas in handlers.

lapis tapir
#

i can create an object containing references to what self is and using that instead or is the problem deeper?

#

i actually need the data in self when the handler fires

fervent vale
#

Regarding installing all bindings to a single venv, running pytest examples will execute each example for each binding found. If one uses PYQTGRAPH_QT_LIB, then each example ends up getting run 4x for the chosen binding. On the other hand, pytest tests executes only once for the chosen binding. 4x because it found 4 bindings, but then when actually executing, binding chosen in PYQTGRAPH_QT_LIB is used

lapis tapir
#

i don'trecall having problems with that untill now, is this caused by recent changes in the library i don't know about? if you know where the cause of the self beeing prohibited originates from i can look at it and maybe understend what i can use and what i can't better

harsh ravine
#

would functools.partial work in this case?

lapis tapir
fervent vale
#

Does MultiAxisPlotWidget need to inherit PlotWidget? Or should it just inherit GraphicsView directly?

lapis tapir
#

i extended plotwidget

#

i tried rewriting using a static function defined outside the instance class in the lambda but the problem seems to persist

rough furnace
#

Sorry been afk; functions.partial doesn't help; basically just write a simple slot and make it a "private" method

#

Feels like a waste; but it's a workaround for a nasty garbage collection issue

lapis tapir
#

what do you mean by private method?

rough furnace
#

Have it start with a _

lapis tapir
#

i tried actually

rough furnace
#

def _privateMethod(self):

lapis tapir
#

signals["axis_view.sigYRangeChangedManually"] = axis_view.sigYRangeChangedManually.connect(
lambda mask: _disable_axis_auto_range(axis))

#

does not work, i defined _disable_axis_auto_range as a function outside the class too

#

point is i need to pass the axis (object) as parameter or axis_name and access self from the function

#

i can't replace the lambda with the function itself without doing terrible things

rough furnace
#

After the kids go to bed I'll take a look at doing it without a lambda

#

Ahh

lapis tapir
#

like i can define n functions each accessing a different key in a dict i populate when i can and then using the specific function but it's horrible

rough furnace
#

Can you not get those objects from calling self.sender() inside the slot?

rough furnace
lapis tapir
#

no because axisItem calls the viewbox's wheelEvent as a function ant is the viewbox that emits the signal

lapis tapir
#

pls no

#

are dynamically defined function off limits too like lambdas?

rough furnace
lapis tapir
#

it seems they don't work either

#

just tested

rough furnace
#

I dont have the PR in front of me but is writing a slot method that takes arguments that the signal passes not work here? ... Also we can usually get the qobject that is emitting the signal by calling self.sender() inside the slot in case that helps

#

also QSignalMapper might be able to assist too

lapis tapir
#

the signal does not include a reference to the axis that trigghered the signal emission in the arguments

rough furnace
#

you can usually get the QObject that emitted the signal by calling self.sender() inside the slot (we don't use this anywhere in the library AFAIK but I know it's a thing)

lapis tapir
#

mouse events in axisitem calls the viewbox's method insted of emitting signals so the sender is the viewbox not the axis that i need

rough furnace
#
@Slot(object)
def someSlot(self, argument):
   emitter = self.sender()
   ...
#

ahh got it, so getting the emitter doesn't help you here

lapis tapir
#

it seems that a change in axisitem is in oder ahahh

#

i can add a signal there and work from that

#

ok no i can't it won't fix the issue when mouse events actually start from the viewbox...

rough furnace
#

(I'll follow up in a few hours, kids are being demanding) ... I'll try and take a look at implementing a work-around later tonight... it might certainly be ugly

lapis tapir
#

sorry for taking the discord over, i will think about this

#

and thanks for helping

rough furnace
# lapis tapir sorry for taking the discord over, i will think about this

hardly anything to apologize for, we're here to make collaboration easier. You've implemented one of our most requested features, and you've run into a weird edge case that can cause memory leaks/segfaults (which some of us maintainers happen to know to be on the lookout for), we're more than happy to provide assistance working through it ๐Ÿ‘

#

reading a bit more on QSignalMapper there are a few issues that limits the current implementation....

  1. It only works with signals that have no parameters on their own
  2. It embeds/inserts parameters based on the QWidget emitting the signal to begin with...

Given that you can connect signals to other signals; we may want to connect sigXRangeChangedManually to (some yet to be defined) signal in AxisItem, and have MultiAxisPlotWidget listen to the signal from the AxisItem. If that signal has no parameters, we can probably use QSignalMapper then to embed the axis_name string to the signal from the AxisItem

fervent vale
#

There's a utility function here that works around lambda with self using weakref

rough furnace
#

what a find!

#

@fervent vale i know how you feel about adding more to functions.py but I do think something like this is a good candidate for going there

fervent vale
#

Python 3.11.0a3

lapis tapir
#

i'll be using that then, thanks!

#

maiby integrating this in the signal class itself vould be the best thing if possible

lapis tapir
#

signals["axis_view.sigXRangeChangedManually"] = connect_lambda(axis_view.sigXRangeChangedManually, self, lambda self, mask: self.disableAxisAutoRange(axis_name))
signals["axis_view.sigYRangeChangedManually"] = connect_lambda(axis_view.sigYRangeChangedManually, self, lambda self, mask: self.disableAxisAutoRange(axis_name))
signals["self.vb.sigResized"] = connect_lambda(self.vb.sigResized, self, lambda self, vb: chart.plotItem.setGeometry(vb.sceneBoundingRect()))
did not fix the issue for me

harsh ravine
#

What's the issue?

#

is the object gets deleted, or?

lapis tapir
#

if you build my pr you will see that the first time you run it it will work fine (click and drag the charts in the multiaxisplotwidget example) but if you close the example and re-open it only one of the charts will move

#

and it seems deleting all python cache files resets this behaviour

lapis tapir
#

`def connect_as_lambda(signal, func, *args, **kwargs):
weakref_args = []
for i in range(len(args)):
weakref_args.append(weakref.ref(args[i]))
del args
weakref_kwargs = {}
for key in list(kwargs.keys()):
weakref_kwargs[key] = weakref.ref(kwargs[key])
del kwargs
weakref_func = weakref.ref(func)

def slot(*a, **kwa):
    weakref_func()(*a, *[arg() for arg in weakref_args], **kwa, **{kwarg_name: kwarg() for kwarg_name, kwarg in weakref_kwargs.items()})

return signal.connect(slot)

def _disable_axis_auto_range(mask, axis):
axis.autorange = False

def _fix_geo(vb, chart):
chart.plotItem.setGeometry(vb.sceneBoundingRect())

signals["axis_view.sigXRangeChangedManually"] = connect_as_lambda(axis_view.sigXRangeChangedManually, _disable_axis_auto_range, axis)
signals["axis_view.sigXRangeChangedManually"] = connect_as_lambda(axis_view.sigYRangeChangedManually, _disable_axis_auto_range, axis)
signals["self.vb.sigResized"] = connect_as_lambda(self.vb.sigResized, _fix_geo, chart)`

did not work for me either

harsh ravine
#

How are you running the example?

#

directly or through exampleApp?

lapis tapir
#

yes

rough furnace
#

The lambda/self issue only happens on pyqt bindings if I remember right

harsh ravine
#

I was using a Pyside venv last night and I saw the same issue

#

are we sure this isn't being caused by the functions being defined in a loop?

rough furnace
#

@lapis tapir instead of putting each code-line with back-ticks, put the entire code block inside 3-backticks at the beginning and end

harsh ravine
#

I know they're being saved to a dictionary, but I didn't look too closely, so maybe they were changed somewhere else?

rough furnace
fervent vale
#

When running MultiAxisPlotWidgetExample.py, on each run, the order in which the axes are laid out changes

#

I ran a few times (w/o deleting any .pyc files), certain orders seem to allow correct drag / zoom of all plots

#

The offending line seems to be charts = set(charts)

#

Commenting it out seems to make the drag / zoom work for all

#

But the layout order of the axes is still random

harsh ravine
#

I noticed that it would work sometimes without deleting the pyc files too but didn't catch the order was random

#

nice catch

fervent vale
#

The offending line for random axes is axes = set(axes)

#

Could replace with axes = dict.fromkeys(axes)

harsh ravine
#

Did you test that @fervent vale ?

#

I wasn't able to get that to work any better than it is now

#

I did notice that the axes name changes, but I'm not sure why

fervent vale
#

Hmm, it worked for me

harsh ravine
#

odd

fervent vale
#

Random axes is fixed by that line

harsh ravine
#

I wonder if we have the same version

fervent vale
#

Drag / zoom needs a more elaborate fix to be correct for the case where the user does pass in a list of charts to show

harsh ravine
#

Where doe the k come from in the name?

fervent vale
#

Huh?

#

Oh

harsh ravine
fervent vale
#

Kilo

harsh ravine
#

ahh okay, that makes sense.

fervent vale
#

I think a really proper fix would be to understand why the order of setting the signals matters. See makeLayout

harsh ravine
#

I'm not sure whats going on with mine. I changed the one line you posted above, and thats the only change it says I have in the branch

#

It did work 4x in a row after I changed that though, but the 5th time , only the one line moved

fervent vale
#

It's 2 lines

#

charts and axes

#

charts fix is just a quickie fix but not correct

harsh ravine
#

oops, I read right past charts

harsh ravine
# fervent vale I think a really proper fix would be to understand why the order of setting the ...

I spent about 15 minutes running the plot with the broken code and checking out the chart order. this is what came out

('Bad       ', ' Bad        ', 'Bad  ',       'Bad')
('Dataset 1', 'Dataset 2', 'Dataset 4', 'Dataset 3')
('Dataset 1', 'Dataset 3', 'Dataset 4', 'Dataset 2')
('Dataset 2', 'Dataset 1', 'Dataset 4', 'Dataset 3')
('Dataset 2', 'Dataset 3', 'Dataset 4', 'Dataset 1')
('Dataset 2', 'Dataset 4', 'Dataset 1', 'Dataset 3')
('Dataset 2', 'Dataset 4', 'Dataset 3', 'Dataset 1')
('Dataset 3', 'Dataset 1', 'Dataset 4', 'Dataset 2')
('Dataset 3', 'Dataset 4', 'Dataset 1', 'Dataset 2')
('Dataset 3', 'Dataset 4', 'Dataset 2', 'Dataset 1')
('Dataset 4', 'Dataset 1', 'Dataset 2', 'Dataset 3')
('Dataset 4', 'Dataset 1', 'Dataset 3', 'Dataset 2')
('Dataset 4', 'Dataset 2', 'Dataset 1', 'Dataset 3')
('Dataset 4', 'Dataset 2', 'Dataset 3', 'Dataset 1')
('Dataset 4', 'Dataset 3', 'Dataset 1', 'Dataset 2')
('Dataset 4', 'Dataset 3', 'Dataset 2', 'Dataset 1')
('Good     ', 'Good       ', 'Good       ', 'Good')
('Dataset 1', 'Dataset 2', 'Dataset 3', 'Dataset 4')
('Dataset 2', 'Dataset 1', 'Dataset 3', 'Dataset 4')
('Dataset 2', 'Dataset 3', 'Dataset 1', 'Dataset 4')
('Dataset 3', 'Dataset 1', 'Dataset 2', 'Dataset 4')
('Dataset 3', 'Dataset 2', 'Dataset 1', 'Dataset 4')
#

Its not complete, but given what's there, I think there's only one more combo that will work: ('Dataset 1', 'Dataset 3', 'Dataset 2', 'Dataset 4')

#

I'm too tired to try and figure out what it means but maybe it will help one of you

fervent vale
#
    def _show_charts(self, charts=None):
        """Shows all the selected  charts."""
        # SELECT CHARTS
        if charts is None:
            charts = self.charts.keys()
        shown_charts = {chart_name: self.charts[chart_name] for chart_name in charts}
        for chart_name, chart in self.charts.items():
            chart.setVisible(chart_name in shown_charts)
        return shown_charts```
#

A possible fix for charts that removes usage of set

#

IMO, there's no need to ensure that the user passes in non-duplicate entries in their list

harsh ravine
#

that's nicer than what I had

#
    def _show_charts(self, charts=None):
        """Shows all the selected charts."""
        # SELECT CHARTS
        charts = charts or self.charts.keys()
        shown_charts = {chart_name: self.charts[chart_name] for chart_name in charts}
        for chart_name, chart in shown_charts.items():
            chart.show()
        return shown_charts
harsh ravine
rough furnace
#

doesn't look like there is any in lookups, but if there are, and order wants to be preserved, we can just fill out a dictionary too (and leave None as the value); but if there isn't any in lookups, then should probably just be a list

lapis tapir
#

i'll switch to list(dict.fromkeys(stuff))

lapis tapir
#

seems all the bugghing out that i was seeing were due to this and not the self in labdas issue.

#

i actually have a workoround i wrote yesturday for that should i push it even if it is very hacky?

fervent vale
#

Was it intended for charts added to the default axes to co-exist nicely with charts added to new axes?

#

If not, I think it would be totally fine to disallow it

lapis tapir
#

yes it was intended so that no new plotitems are created if not needed, i fixed the issue

fervent vale
#

I added a 2nd example

#

that has some other issues

lapis tapir
#

i'm workingat the moment, will test it this evening

#

i pushed my workarond for the self in lambda thing in the "lambda-workaround" branch, how can i test if it actually fixes the issue?

fervent vale
#

I don't really know. For programs that don't remove objects during their life-time, it wouldn't be detectable

#

There is a test test_ref_cycles

#
from PySide6 import QtWidgets
import weakref
import gc

class MyButton(QtWidgets.QPushButton):
    def __init__(self, *args, **kwds):
        super().__init__(*args, **kwds)
        self.clicked.connect(lambda : self.hello())
        # self.clicked.connect(self.hello)

    def hello(self):
        QtWidgets.QApplication.instance().exit()

app = QtWidgets.QApplication([])
button = MyButton("Hello")
button.show()
app.exec() if hasattr(app, 'exec') else app.exec_()

ref = weakref.ref(button)
del button
print(ref())
gc.collect()
print(ref())
#

A simple program to demonstrate that lambdas with self will keep the widget alive

#

So if your workaround could be ported to this simple example, you could test to see if it works

#

Hmm... it seems to demonstrate that PyQt{5,6} but not PySide{2,6} can get garbage collected by gc.collect()

harsh ravine
#

I think thats for the same reason that was causing the crash when subclassing QApplication in PySide2

#

If it is, Pyside2 with 3.7 should work just like PyQt5 in the newer versions

rough furnace
#

The lambda/self issue was only a problem on pyqt bindings if I remember right, not pyside bindings

harsh ravine
#

oh, hmm..

#

I still don't really understand why partial wouldn't work instead. It doesn't behave the same way as a lambda under the hood

#

If you're going to be around later this evening, I could use some help with the git nonsense

lapis tapir
rough furnace
# lapis tapir what is the correct and problematic output of this?

I slightly modified the import of the line to import from pyqtgraph.Qt instead of PySide directly; here is the result of the script

PySide bindings

pyqtgraph-pyside_515-py38 โฏ python playground/lambda_reference.py
<__main__.MyButton(0x6000003da300) at 0x112154880>
<__main__.MyButton(0x6000003da300) at 0x112154880>

PyQt bindings

pyqtgraph-pyqt_515-py38 โฏ python playground/lambda_reference.py
<__main__.MyButton object at 0x107bba280>
None

this is the console output on my machine after pressing the button....

#

you can notice that in the PyQt PySide bindings, there exist a reference to the button, despite it should have been garbage collected...

#

but if you comment out the self.clicked.connect... method to the lambda to the hello method...

PySide bindings

pyqtgraph-pyside_515-py38 โฏ python playground/lambda_reference.py
None
None

PyQt bindings

pyqtgraph-pyqt_515-py38 โฏ python playground/lambda_reference.py
None
None
#

basically we don't want any reference being maintained after del is called

#

and definitely not after gc.collect() is called

lapis tapir
#

ok with the self thing i have

None should be None

with my "fix" i get

<pyqtgraph.widgets.MultiAxisPlotWidget.MultiAxisPlotWidget object at 0x7fc2ce0e7f70> should be None

so it's even worse ahahahh

#

i know the issue i will try to fix it

rough furnace
#

here is demonstrating the self bit.. if you replace the lambda function with

        self.clicked.connect(lambda: QtWidgets.QApplication.instance().exit())

this works just fine too

fervent vale
#

I tested out the connect_lambda utility function and it works too.

harsh ravine
#

@rough furnace let me know when you're free for a few minutes to go over this git stuff.

rough furnace
#

Unfortunate timing; I'm preparing the guest room for my in-laws, I can hop on a voice channel if you're up for that

harsh ravine
#

I'll be around for most of the night so just ping me when you're ready and I'll jump in a voice channel

rough furnace
#

@lapis tapir would you like me to modify your branch to replace the lambda slots with that connect_lambda utility function?

lapis tapir
#

i was experimenting a bit with wrapping, i'll do that.

lapis tapir
#

done

fervent vale
#

Putting connect_lambda in functions.py is fine but I am going to recommend not including it in __all__

#

I think you need to redo functions.py to not modify unrelated lines. Think your editor "fixed" a lot of white space.

lapis tapir
#

done

lapis tapir
#

also: using collect_lambda breaks if you check something like vb used inside the widget. i think this is the case since it doesn't wrap all the objects passed to it

#
import weakref
import gc

import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets
app = pg.mkQApp()
pg.setConfigOption('background', 'w')
pg.setConfigOption('foreground', 'k')

mpw = pg.MultiAxisPlotWidget()

# LEGEND
mpw.addLegend(offset=(0, 0))
# TITLE
mpw.setTitle('MultiAxisPlotWidget Example')
# AXYS
mpw.addAxis('sx1', 'bottom', text='Samples1', units='sx1')
mpw.addAxis('sx2', 'bottom', text='Samples2', units='sx2')
mpw.addAxis('sx3', 'bottom', text='Samples3', units='sx3')
mpw.addAxis('sy1', 'left', text='Data1', units='sy1')
mpw.addAxis('sy2', 'left', text='Data2', units='sy2')
mpw.addAxis('sy3', 'left', text='Data3', units='sy3')
# CHARTS
mpw.addChart('Dataset 1', xAxisName='sx1', yAxisName='sy1')[0].setData(np.array(np.sin(np.linspace(0, 2 * np.pi, num=1000))))
mpw.addChart('Dataset 2', xAxisName='sx2', yAxisName='sy1')[0].setData(np.array(np.sin(np.linspace(0, 2 * np.pi, num=1000))) * 2)
mpw.addChart('Dataset 3', xAxisName='sx2', yAxisName='sy2')[0].setData(np.array(np.sin(np.linspace(0, 4 * np.pi, num=500))) * 3)
mpw.addChart('Dataset 4', xAxisName='sx3', yAxisName='sy3')[0].setData(np.array(np.sin(np.linspace(0, 4 * np.pi, num=500))) * 3)
# make and display chart
mpw.makeLayout()
mpw.update()
mpw.show()
pg.exec()

ref = weakref.ref(mpw)
refv = weakref.ref(mpw.vb)
del mpw
print(ref(), 'should be None')
print(refv(), 'should be None')
gc.collect()
print(ref(), 'should be None')
print(refv(), 'should be None')
fervent vale
#

I changed signals["self.vb.sigResized"] = self.vb.sigResized.connect(lambda vb: chart.plotItem.setGeometry(vb.sceneBoundingRect())) to signals["self.vb.sigResized"] = connect_lambda(self.vb.sigResized, chart, lambda chart, vb: chart.plotItem.setGeometry(vb.sceneBoundingRect()))

#

then on PyQt6, gc manages to collect it

#

Ok, so on PyQt{5,6}, gc can collect it

#

but on PySide{2,6}, the following appears when refv() is called: RuntimeError: Internal C++ object (ViewBox) already deleted.

lapis tapir
#

i pushed some changes if you want to test them, seems with my workaround pyqt6 clears the vb but the master version doesent, pyside still has problems with it, i'll try to make a hybrid version later this weekend

fervent vale
#

I changed your latest update to signals["self.vb.sigResized"] = connect_lambda(self.vb.sigResized, self, lambda self, vb: self.plot_items[chart.name].setGeometry(vb.sceneBoundingRect()))

#

and del mpw is enough to get it to not retain any more references for all 4 bindings

#

this also seems to work for all 4 bindings: ```
tgt_vb = self.plot_items[chart.name].vb
signals["self.vb.sigResized"] = self.vb.sigResized.connect(lambda vb: tgt_vb.setGeometry(vb.sceneBoundingRect()))

lapis tapir
#

are you getting RuntimeError: Internal C++ object (PlotDataItem) already deleted.?

#

testing with refs = [ weakref.ref(mpw), weakref.ref(mpw.pi), weakref.ref(mpw.vb), weakref.ref(mpw.layout), weakref.ref(mpw.pi.legend), weakref.ref(mpw.charts['Dataset 1']), weakref.ref(mpw.axes['sx1']), ] del mpw for ref in refs: print(ref(), 'should be None') gc.collect() for ref in refs: print(ref(), 'should be None')

fervent vale
#

the 6th item is the one that fails with the RuntimeError on PySide

#

on PyQt, the 6th item cannot be garbage collected

#

the following passes your new test for all 4 bindings:

chart = self.plot_items[chart.name]
signals["self.vb.sigResized"] = connect_lambda(self.vb.sigResized, chart, lambda chart, vb: chart.setGeometry(vb.sceneBoundingRect()))
lapis tapir
#

ok, done.

#

i think connect_lambda should take more than one "self" argument though

#

and it's strange that chart needs to be wrapped

fervent vale
#

so "self" in the connect_lambda function really means some object that you want to hold weakly

lapis tapir
#

should i rename it?

#

and should i add comething like *args and pass all those as weakref to the function to support multiple parameters or do you think it's useless?

fervent vale
#

hmm, the docs for "self" in connect_lambda is wrong

#

The axis to be used... blah

lapis tapir
#

i'll fix it

fervent vale
#

Anyway, I think this connect_lambda function should only be used as a last resort, not as a first choice

#

the issue with the lambda function is that it may not be obvious to the programmer that certain variables are being captured

#

so in the beginning, we were saying "lambdas can't use self" as a rule

lapis tapir
#

i think the problem is the parent reference stored in the object, so there is a loop parent.child.parent == parent keeping the parent from beeing deleted

#

the same is for methods since self.method stores a reference to the self object

fervent vale
#

I am not sure if bound methods hold a reference to the instance

#

See the WeakMethod doc

lapis tapir
#

to check it do

#

object.method.__self__

rough furnace
#

Just getting up, I agree with @fervent vale that the connect_lambda function is a last resort. FYI after today I'm talking a few weeks vacation from work so hopefully I can contribute on a helpful manner

lapis tapir
rough furnace
#

Be back in 15 minutes or so (taking kids to school)

lapis tapir
#

shure ๐Ÿ‘

#

pushed a workaround version

rough furnace
#

ok, so slightly longer than 15 minutes ๐Ÿ˜†

#

I think strings as keys are; could do something like [signal.__name__, slot.__name__] too

#

(actually, that might not work if you have the same signal from different objects)

lapis tapir
#

i used a weakref and a lambda for the ones with self, if it's acceptable

rough furnace
#

We'll try and break it; would you mind adding a comment next/above the connect calls indicating this is a workaround for signals being connected to lambda functions referencing self?

#

I do think there is likely a way to work-around this with

  1. new signals
  2. QSignalMapper

but if we can't detect anything wrong w/ the implementation you have, I don't think that's fair to you to have to modify it further...

lapis tapir
#

will add the comment near the connect_lambda functions

i don't know how qsignalmapper works but the problem is that if a reference to an object is stored inside the object itself or one of it's children garbage collection breaks.
keep in mind also that bound methods like self.methods have an instance of self inside,

#

if those store references to the method used they probably don't work either

#

if there are examples of what you are suggesting i can test them

lapis tapir
#

can i mark the keys for signal connections as resolved?

rough furnace
#

(sorry just saw this) I'm not sure what you mean

harsh ravine
#
import random

from PySide2.QtCore import QSignalMapper, Signal
from PySide2.QtWidgets import QApplication, QHBoxLayout, QPushButton, QWidget

class Button(QPushButton):
    def __init__(self, data, parent=None):
        super().__init__()
        self.data = data

class B(QWidget):
    clicked = Signal(int)

class Window(QWidget):

    def __init__(self, parent=None):
        super().__init__()
        self._layout = QHBoxLayout()
        self.setLayout(self._layout)
        self.map = QSignalMapper()

    def doIt(self, things2map):
        for w in things2map:
            self._layout.addWidget(w)
            self.map.setMapping(w, w.data)
            w.clicked.connect(self.map.map)
        self.map.mapped.connect(obj.clicked)


if __name__ == '__main__':
    app = QApplication([])
    w = Window()
    buttons = []
    obj = B()
    w.show()

    for x in range(50, 56):
        data = text = int(x * random.random())
        b = Button(data)
        b.setText(f'{text}')
        buttons.append(b)

    w.doIt(buttons)
    
    @obj.clicked.connect
    def clicked(data):
        print(data)

    app.exec_()
rough furnace
#

oh, my suggestion here regarding (signal, slot) was strictly as an alternative for using signal as a key to a dictionary and thus overwriting previous values; wasn't necessarily advocating to use it as such...and you're right, the extra reference here would be problematic, you could do a WeakKeyDictionary but I don't think that's necessary

harsh ravine
#

I wonder why this is only a problem now...

Error: ../pyqtgraph/examples/exampleLoaderTemplate.ui: Warning: The name 'layoutWidget' (QWidget) is already in use, defaulting to 'layoutWidget1'.

while executing '/Users/chris/projects/pyqtgraph/venv/lib/python3.7/site-packages/PySide6/uic -g python ../pyqtgraph/examples/exampleLoaderTemplate.ui'
fervent vale
#

I seem to recall running into this in some other code

harsh ravine
#

hmm, first time I've seen it.

#

Its not causing any problems, but I tried regenerating the UI on multiple versions of PySide and also reverting the UI file and editing it in Designer and Creator

#

and it happened with PySide2 and PySide6

fervent vale
#

I guess it would cause problems if we were to actually rely on finding objects by name

harsh ravine
#

I guess.. It's not a widget that was intentionally created. Its one of the ones you get when you use a layout in the editor

fervent vale
#

Does anybody else find MultiAxisPlotWidgetExample.py's gray on white plots color scheme difficult to see? Particularly on a hidpi screen, the lines look really faint

harsh ravine
#

It could use a bit of contrast. The legend was particularly hard to make out

fervent vale
#

oh, I didn't even know there was a legend!

harsh ravine
#

Is there a reason it doesn't follow the rest of the plot's styles?

fervent vale
#

I like the colors used by NilsNemitz in examples/logAxis.py.

harsh ravine
#

Oh yeah those are nice.

fervent vale
harsh ravine
#

Seems like that is something that should be able to modified in the export options

fervent vale
#

aha, but the context menu is disabled for MultiAxisPlotWidget

#

so, no interactive export, at least

harsh ravine
#

There are probably some other colors that would work just as well for print and staring at on a computer screen.

fervent vale
#

Chris_, Qt5 and Qt6 no longer use the same scale for QFont.setWeight

#

Qt5 uses 75 for Bold while Qt6 uses 700

#

It would be simpler to just use setBold instead of setWeight

#

So in the ui file, bold property alone is sufficient. No need a weight property of 75

harsh ravine
#

Oh, I missed it in the PyQt file. I commented it out in the PySide version because its a change QtCreator made with out me asking it to.

fervent vale
#

Since default is to be removed, then the following lines from ExampleApp.py can be simplified.

qtLib = str(self.ui.qtLibCombo.currentText())
env = None
if qtLib != 'default':
    env = dict(os.environ, PYQTGRAPH_QT_LIB=qtLib)
else:
    env = dict(os.environ)

probably can drop the str too

harsh ravine
#

QColorDialog.ColorDialogOption.DontUseNativeDialog seems to make the eyedropper stop working when you leave the widget

#

at least it does on mac os

rough furnace
harsh ravine
#

Oh, yeah, I'll open an issue.

#

I tested it with a basic QColorDialog and it does the same thing, so I don't know if its anything we can fix, short of building a color picker.

#

At least if you want to continue to use a non native dialog

fervent vale
#

@lapis tapir, in your updated example with the mkStripedPen(), you need to use the long enum spellings in order for it to run on PyQt6

#
    gradient.setCoordinateMode(QGradient.CoordinateMode.LogicalMode)
    gradient.setSpread(QGradient.Spread.RepeatSpread)
    pen = QPen(brush, 2, Qt.PenStyle.SolidLine, Qt.PenCapStyle.SquareCap, Qt.PenJoinStyle.BevelJoin)
#

although I suspect mkStripedPen() could be substituted with pyqtgraph.colormap.getPen()

lapis tapir
#

thanks this is easyer ahahha

#

is there a way to set the pen "lenght"

#

to change the "segment" lenght

rough furnace