#pyqtgraph
1 messages ยท Page 7 of 1
the base() is a QColor attribute
ahh, good, it doesn't AttributeError: 'PySide2.QtGui.QColor' object has no attribute 'base'
oh ...I think I meant name()
I was going to kick myself
sorry was going off memory ๐ฌ
ohh.. yeah.. that one varied too much in dark mode
i think it was something like 128,128,128
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
for sure, that solution is working the best
I think that is acceptable. (modifying the palette color a little)
The question is what to do if a stylesheet is applied.
right now we don't modify it at all
is there a good way to detect if a style sheet is applied in general (forget the specific colors)
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
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.
@wide prism fwiw I believe macOS overrides those colors
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
(@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)
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...
Nooooo..... I need to lie down now, I think.
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...
I set white, I get #FFFFFF.
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
(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".
I suspect if you set the background color to say (254, 254, 254) it would render that...
(although now I'm curious)
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.
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
the manual values?
No
just adjust the hue/saturation in addition to the lum
but this would only be for QDarkStyle
Which color are you talking about now? You have my attention.
other stylesheets wont work because we won't know what color they render QTreeWidgets/Branches
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.
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
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?
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?
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)
i've had a short interaction w/ the maintainer a while ago, seemed like a nice friendly guy
-------- (I just happen to believe that this will turn out to be much harder. But it is the greatest long-term solution, probably!)
Do that many people really use qdarkstyle?
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.
@harsh ravine QDarkStyle is sort of the preferred solution if you want dark mode on Windows
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...
ohh, gotcha.
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
not going to lie, not my favorite either
Hehe, that looks like Netscape Navigator.
that splash screen was amazing in 96
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 ๐
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
I am with you so far....
Which palette do you want to access?
the palette i set with pg.setConfigOption('palette',p)
How's your palette different from the main application palette that Qt keeps for us?
Why don't you do that to the main palette?
Yeah, but that's no different than overriding an option in app.palette.
Which I am still arguing is the solution here ๐
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?
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.
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
going to go to bed ๐
see ya
But they can already set whatever text color they want by changing app.palette.text ?
Bye!!!
mmm.. yeah
okay... walk me through this pg.configureApplication(qapp, style='dark')
when I specify my style is dark... what happens?
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.
The HiDPi stuff has to be done before the an application instance exists, unless that has recently changed
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)
Is there any reason we don't want to do this with mkQApp?
If there is nothing to duplicate, then it might just become pg.applyPalette(app, 'name')
the style parameter i mean
mkQApp should take the same parameter (and call this internally), but there should also be a way to do this later.
okay thats a good point
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?
you mean when you change the os theme?
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.
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.
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"
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
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?
The only thing that changes is the window frame color which we can't control(without a lot of work)
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?
Awww. So... If we set a stylesheet, and then set a palette... we are all good already?
Either what? Stylesheet blocks it, too?
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
Wait, but that is based on the darkMode flag, which is derived from the palette colors. So the palette DOES normally change, right?
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
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.
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
That was @rough furnace 's concern way back there.
the user would know if a palette they created was being set
Oh, I must have missed that
Psycho users get to have ugly apps if they want them.
fine by me
"what doesn't work: when style-sheets enter the picture, and if light/dark mode are toggled while a style-sheet is applied"
Oh yeah.... thats fixed in the most recent PR
--> no, that works if you apply a palette, because the system then no longer overrides the colors.
that works also.
I just imagined the people who use this app are more concerned with using it playing with code.
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.
what about the tree node colors?
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)
Without the fix that runs updateDepth the colors will be weird
The slightly lighter stuff?
Derive from palette.base, as we already have working.
oh, if we're going to provide the palette then that should be fine
Running update depth is a good idea, just don't parse stylesheets in it.
I thought you were speaking about more than qdarkstyle
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?)
๐ฅฒ we only parse it one time
but i completely agree, that code does not belong where it is
I like the caching yuo have, I am more concerned that this needs to be copied to every custom widget we have...
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
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.
That's alternateBase now?
no, thats the lightness from above
its normal hsl
h, s, l = color.hue(), color.saturation(), color.lightness()
lightness = lightness = 0.5 + (l - 0.5) * 0.8```
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)
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)
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?
Why would you have dark palette in light mode?
Some people do that
The last two hours of discussion are "tell people to have a dark palette in dark mode" ๐
photo/video editing applications are usually defaulting to dark mode regardless of the color theme of the OS
Yes. They set their own palette to dark, then.
Which the OS is happy to accept, as you just explored.
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
Take background, not window?
background doesn't exist in PySide6
I learned that the other night when one of my PRs failed almost all of the tests
The thing that is the text background.
I am quite confused now, that hue does not seem to match any of the background colors.
this is with background on pyside2
Is that QDarkStyle on the default MacOS dark palette?
yes the palette was set by the OS
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?
I'd take that ๐
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!
yeah, if things keep getting added it will turn this into more of an epic than a bug fix
This. 100% this.
All private Qt code. Don't think I can access this stuff from Python
https://code.woboq.org/qt5/qtbase/src/gui/text/qcssparser.cpp.html
https://code.woboq.org/qt5/qtbase/src/gui/text/qcssscanner.cpp.html
https://code.woboq.org/qt5/qtbase/src/gui/text/qcssparser_p.h.html
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 ๐
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
It would have been neat if widget.styleSheet() returned the fully assembled style sheet for that one widget. Unfortunately, it does not ๐ฆ
yeah, that would be great
Frustrating because Qt obviously does all of this already.
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
No, this seems to be just that: <private> C++ bits.
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
We will follow this up by an easy way to set a palette that matches QDarkStyle.
do you want to have that be a separate PR?
I think it can be, and unless you want to keep being sent around in circles, I think you would want it to be ๐
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?
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".
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
Does that work per widget? That seems good. The button doesn't exactly seem to match regular UI design anyway.
yes, you can use it for a widget or the entire application
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.
Does the current button look like a MacOS UI button on your system?
Unless you really like orange checkmarks that seems 100% better.
minus the transparency problem, yes
orange checks are from here
totally unrelated to the style change
So, why do these change in your example image, then?
The only 'action' buttons are the ones with transparency issues
Because one of the apps has focus
Ah ๐
the one without is grey
On some systems, the left-most pixels of "Collapse" still stick out under the non-transparent button, though...
This:
Yeah, I noticed that on mac in light mode
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
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.
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
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.
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
Should I make a neon one, too, to find all the widgets that don't react properly?
you should do that
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)
I'll make the light palette, i'm already taking way too much of your time on this
No worries, lazy holiday today.
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
oooh, luxurious. Sounds like good news for us ๐
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
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.
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_()
sorry just noticed this comment
@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
@uncut ore ๐
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
Alrighty, once I get some more questions I'll ask them here
๐
That's okay. Whatever it was, I took it to mean, apply the qdarkpalette to the application, which is (now) clearly not what it meant.
I commented on the PR
Oh, I'll have a look.
I wonder if plotting 1 million points is now the norm...
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
@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.
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?)"
"/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.
Seriously, what's up with people trying to plot n-million points
"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. ๐
Which is why I'm thinking improving the downsampling methods should go a long way
Let them run out of RAM instead!
like expanding a freeway, we'll never actually reduce traffic, we just increase demand
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
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
Careful mentioning a color by name instead of palette, you might wake @wide prism ๐
Joking aside, I think that's a great idea
๐ 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
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
Is there anecdotal evidence that the current numpy-implemented downSampleMethod=peak has performance issues?
Unfortunately, there is no "error" color...
But since this is our own application, you can do what you want ๐
the per-widget stylesheet is probably the recommended way to override an individual color.
from our failed academic paper submission; keep in mind that these numbers were taken before any of the arrayToQPath improvements you made
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
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
!
Side note -- only one example in pyqtgraph.examples uses axis labeling and it was hard to find. Perhaps we should add more axis labels? They're a fairly common operation in publication graphs
I personally think a lot of our examples could be updated; was hoping that the results of the user survey I ever create/distribute will help guide us here
(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
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
...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
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
Thanks for the correction; I clearly did not remember what they did correctly
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
yeah, that's what I remember; .... but quite possible source code doesn't match the docs
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.
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
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?
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
The idea of the sawtooth pattern is that you cannot see it.
For the same number of coords, pairs mode is faster as the number of line segments drawn would be halved
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.
The unbroken line also allows fill mode to work...
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.
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
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.
Nothing to hack. Just change connect=pairs in the UI
For 100k points with downsample peak enabled, fps went from 155 to 175
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...
Thanks for the review! @wide prism
Or if you do fill mode you then switch to connect='all'
That wouldn't speed up the default case, though.
Starts looking pretty good ๐
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 ๐
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
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?
at that rate, I don't think my data array can write to the "protected" array that fast
at work, I manage almost 1 PB worth of storage across a handful of systems ....
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.
800 MB/s ... I suspect some of the ZFS pools I manage could do that, .... but it would be a struggle
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.
It's fine, I learned something useful from it ๐. I don't expect this to be merged before the weekend is over so whenever you have time to look at / respond is good with me
Thanks for pointing this out btw. I saw the references to ParmeterTree but wasn't sure why. I figured if it was anything, it was the fact that I added a new file that wasn't documented
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...
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
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.
saw reference to the font size situation, what's the options?
(looking at the docs-build error and see if it's something)
It's a decorative - at the beginning of the line that sphinx thinks is an itemized list. Should be easy to fix.
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.
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
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
self._initialFontPointSize = self.font(0).pointSize()?
Thats fine by me.
Lgtm too
...and access is by?
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
Access via the def fontPointSize(self) method
That would be good with me. (pointSize or fontPointSize?)
I'm fine with pointSize
If thats something thats done other places then we should keep things consistent
Lgtm, then.
palette.py should only do relative imports
oops, PyCharm did that when I moved the file.
Does anyone use the imageItem drawing feature?
I don't get how it could be useful, its so slow.
Opinion: #ARGB is just wrong.
Even Qt silently agrees and doesn't want to discuss their choices in the stylesheet documentation.
I don't know anyone that uses it; but i'm not sure anyone has looked at the code there to speed it up or evaluate if there are glaring inefficiencies
yeah, I agree it's non-sense...if Qt didn't do that, mkColor would be a whole lot simpler ๐
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.
PyQt is missing some methods; afaik no one binding is great in this regard
Hope everyone has a good Thanksgiving!
Question about pg in the user interface channel: #user-interfaces message
@obsidian sapphire this looks right up your alley ๐
wow, yeah, that's a thing I do. maybe I can get them to contribute ๐
let me know if you think I should invite them in the channel, that's a thing I can do now
Did you make this widget?
No, I stole that somewhere. It is the same WidgetGallery thing that the Qt page uses:
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?
The little color squares?
Yup.
Where does it get the orange highlight from, on the tabbed interface on the bottom right?
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
Nice ๐
Oh, the orange is because those lines are a mix of Light and Mid
there are actually 2 thin lines very close together
Oh, it actually uses light and mid? I didn't see a UI element that did ๐
In Fusion it does
The ticks on the dial use Dark
The slider too, probably.. if it had any ticks
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 ๐
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
๐
We could use a palette that mimics Mac OS a little. The grays are a lot warmer than the QDarkStyle ones.
Err. Forgot that you get that as default. Can you export that? Might add it to the PR.
When you launch the tool, whatever widget you have it hooked up to has the current app palette applied to it.
For poor windows users.
!paste
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.
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
That defines more colors than I was aware of. I guess I need to play with the palette some more.
Seems very helpful ๐
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
if PR 2101 is done, it should be moved out of "Draft" state
@harsh ravine โ๏ธ
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?
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.
You could swap the order of inheritance, or make a call to super and specify A, and either way maybe mention in the documentation?
if I'm understanding this right, the diff for implementing this would be huge this was the worst take ever, there is only one place in the library this is called ๐
just tried this, but managed to blow up in a really spectacular fashion:
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
Hmmm you mean the swap failed? is one of the parent classes inheriting from the other?
I swapped the order of class C(B, A) to class C(A, B) and the majority of the test suite failed; and eventually got a segfault
Also I'm now running this but getting "hello from A" ๐ค
what version of python are you on?
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
How odd
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....
huh
here is the change-log
https://code.qt.io/cgit/pyside/pyside-setup.git/tree/doc/changelogs/changes-6.2.2
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.
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
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.
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
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
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
nice find on the other method; will definitely have to address that one too...
Question for y'all, does pyqtgraph play nice with pyside6?
yes, except for pyside6 6.2.2; which was just released and they did something really weird involving class inheritance
(and 6.0)
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)
huh, okay. Time to go down a debugging hole then! I appreciate the response~
feel free to post a bit more; i'll gladly chime in if we've seen something that might be relevant
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
is self a subclass of PlotWidget?
nah, just a custom class to hold some items together
@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
will do!
there have been some issues involving missing mouse event methods QMouseEvent.position() and QMoustEvent.localPos()
here is our PR introducing PySide6 support: https://github.com/pyqtgraph/pyqtgraph/pull/1502
here is a PR that had some fixes: https://github.com/pyqtgraph/pyqtgraph/pull/1771/files
looking over this, another issue was that we can no longer cast int(Qt.Enum)
Well that was a fast debug, pyqtgraph isn't compatible with the from __feature__ import snake_case feature in pyside6
oh! ... yeah, read about that feature, didn't even attempt to try it ourselves
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
And any form of overriding I would imagine, not just pyqtgraph?
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
wonder if there is an "easy" way we can respect that and be compatible w/ it
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
i think the snake_case thing got proposed for PyQt6 support but Phil (the guy that maintains PyQt) shot it down
It's easy, just add another regex shim to pyqtgraph.Qt ๐
oh man, I bet there is a re.sub call we could make to convert ๐
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!
@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)
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....
The mwe indentation is not correct in the pyside bug tracker
blerg, I'll edit..thanks for pointing it out... wish jira used markdown but nooooo
and now fixed
@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
... 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
?
Which editor are you using?
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.
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
Hey, sorry for not being very responsive lately. I managed to get sick last week so I've been taking it easy. I should be able to get it straightened out tomorrow.
naw you did all the work and got radio silence since your last commits over a week ago
hopefully you're feeling better now!
Would version 0.12.4 be the last version to support Python 3.7 or be the first version to require Python 3.8?
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?
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
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
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
you mean the other examples that only rely on numpy run fine?
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)
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
Nice!
we got some responses from the pyside devs regarding the signal/method name conflict bug: https://bugreports.qt.io/browse/PYSIDE-1730
@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.
Huh, Windows 11 introduces the transparent button issues
ooooo what version of Qt bindings are you using?
are you pinging the right person? it doesn't seem like they have permissions to interact with this channel.
yeah its' the right person, thanks for the notice, I need to add them here ๐ฌ
@lapis tapir ๐
Checked with pyside2/pyqt5
curious if the Qt6 bindings handle it better
Is that the case on Mac?
๐ 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
not spam, I'm intentionally "watch"ing the repo
that's what I meant to do. It should be good now
man, why am I so awful w/ Sphinx ... having to look up examples for everything as I'm trying to make corrections/changes
reading this only now, is there a way i can compile the doc myself to test it?
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
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
๐ thanks again for your contribution
this has been one of our most requested features
i needed it myself too ahha
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
ok just list the changes i will make them when i can
๐ thanks again, get some rest
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
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
ok bye for now
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...
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).
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
One of the benefits of distributing a GUI instead of a backend is more flexibility to code changes
@mortal grotto did you have further changes planned on 2086?
I don't think so. Can you test on your bindings to make sure things fire OK?
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:
@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)
@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?
Whats the issue? Is it any line edit?
"issue" might be extreme: https://github.com/pyqtgraph/pyqtgraph/pull/2121#issuecomment-991291471
Interesting, but looks normal on Mojave.
Mojave? ... "that's a name I haven't heard in a long time"
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
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 ๐ฆ
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
@harsh ravine merging your PR, thanks for your patience on this!
116 comments... jeez... so sorry
woo!
Its what I get for going after what I assumed was low hanging fruit
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!
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
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?
yes, on Mojave. I found a list of fonts here but monospace isn't listed https://en.wikipedia.org/wiki/List_of_typefaces_included_with_macOS
This list of fonts contains every font shipped with Mac OS X 10.0 through macOS 10.14, including any that shipped with language-specific updates from Apple (primarily Korean and Chinese fonts). For fonts shipped only with Mac OS X 10.5,
please see Apple's documentation.
oh wait, ... that didn't get fixed...
I fixed the font situation for the general examples, but not the console app...
I fixed the main example here, likely need the same change in the console.ui file
this is annoying as it requires you re-generate the template files
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
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
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
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)
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
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
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?
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
Arial I don't think is monospaced
I might be missing something, but I think Consolas is rather common
unless you have some other requirements
๐ @tight cliff that might work; is Consolas available on windows platforms w/o having to add it?
I don't seem to have Consolas installed
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
You're right. I installed the monospace version on my own
I don't think Consolas is part of ubuntu either
Oh, maybe not
Monaco is the default fixed width font on mac. Not sure about windows/linux availability
it's not on ubuntu 20.04 :/
nope
ahh ๐ฆ
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...
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
i generally nuke my machine and do fresh installs with new OSs
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
Have to look closely but the left is preferable; greater gap between the =
Yeah, left is easier to read, especially so if the line is selected in history
I have a hatred for these templates
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
hmm let me see
It should be okay. The only thing that shows in a diff is the version number in the comment.
yeah 6.1.3 is a perfectly good version to have it generated with ๐ ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ๏ฟฝ
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?
6.1.3 vs. 6.1.2? naw, if it works, that's fine...
It works, literally, the only change is the version in the comment there
just realized you generated w/ 6.1.2; that's not a problem (assuming it runs)
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
@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 ๐ )
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 ๐
omg did github go offline on me as I was writing in-line comments?
their status board is all green...
I just looked at my PR... I'm confused
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
Samsung sent me a TV without a remote or the proprietary cable that goes from the box to the TV. I understand your pain
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
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.
@obsidian sapphire mind chiming in here? https://github.com/pyqtgraph/pyqtgraph/pull/1359#discussion_r767203391
any suggestion for the self in lambdas thing?
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?
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.
i read https://github.com/pyqtgraph/pyqtgraph/pull/1598 but i i understood correctly there are no workarounds at the moment?
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
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
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
would functools.partial work in this case?
seems not based on #1598
Does MultiAxisPlotWidget need to inherit PlotWidget? Or should it just inherit GraphicsView directly?
i extended plotwidget
i tried rewriting using a static function defined outside the instance class in the lambda but the problem seems to persist
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
what do you mean by private method?
Have it start with a _
i tried actually
def _privateMethod(self):
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
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
Can you not get those objects from calling self.sender() inside the slot?
Yes it is; but we might have to do that ๐ฆ
no because axisItem calls the viewbox's wheelEvent as a function ant is the viewbox that emits the signal
oh no
pls no
are dynamically defined function off limits too like lambdas?
I don't think so; need to look back on that older PR and see how we traded/debugged it and check from there
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
the signal does not include a reference to the axis that trigghered the signal emission in the arguments
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)
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
@Slot(object)
def someSlot(self, argument):
emitter = self.sender()
...
ahh got it, so getting the emitter doesn't help you here
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...
(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
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....
- It only works with signals that have no parameters on their own
- 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
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
Python 3.11.0a3
i'll be using that then, thanks!
maiby integrating this in the signal class itself vould be the best thing if possible
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
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
`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
yes
The lambda/self issue only happens on pyqt bindings if I remember right
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?
@lapis tapir instead of putting each code-line with back-ticks, put the entire code block inside 3-backticks at the beginning and end
I know they're being saved to a dictionary, but I didn't look too closely, so maybe they were changed somewhere else?
works just the same as github; but the 4-backticks to capture a larger field doesn't work on discord: https://docs.github.com/en/github/writing-on-github/working-with-advanced-formatting/creating-and-highlighting-code-blocks
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
I noticed that it would work sometimes without deleting the pyc files too but didn't catch the order was random
nice catch
The offending line for random axes is axes = set(axes)
Could replace with axes = dict.fromkeys(axes)
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
Hmm, it worked for me
odd
Random axes is fixed by that line
I wonder if we have the same version
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
Where doe the k come from in the name?
Kilo
ahh okay, that makes sense.
I think a really proper fix would be to understand why the order of setting the signals matters. See makeLayout
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
oops, I read right past charts
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
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
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
I can't see a reason for doing that, but that doesn't mean much.
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
i'll switch to list(dict.fromkeys(stuff))
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?
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
yes it was intended so that no new plotitems are created if not needed, i fixed the issue
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?
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()
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
The lambda/self issue was only a problem on pyqt bindings if I remember right, not pyside bindings
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
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
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
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
I tested out the connect_lambda utility function and it works too.
@rough furnace let me know when you're free for a few minutes to go over this git stuff.
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
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
@lapis tapir would you like me to modify your branch to replace the lambda slots with that connect_lambda utility function?
i was experimenting a bit with wrapping, i'll do that.
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.
done
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')
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.
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
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()))
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')
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()))
ok, done.
i think connect_lambda should take more than one "self" argument though
and it's strange that chart needs to be wrapped
so "self" in the connect_lambda function really means some object that you want to hold weakly
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?
i'll fix it
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
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
I am not sure if bound methods hold a reference to the instance
See the WeakMethod doc
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
while you are here, regarding the signal_connections[signal, slot] = signal.connect(slot) request:
a couple of signals have self.slot as slot and this will break gc like lambdas do and a simple weakref breaks in pyside or pyqt i don't remember which, should i do something about it or are strings like i use now fine as keys?
Be back in 15 minutes or so (taking kids to school)
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)
i used a weakref and a lambda for the ones with self, if it's acceptable
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
- new signals
- 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...
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
can i mark the keys for signal connections as resolved?
(sorry just saw this) I'm not sure what you mean
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_()
was afk, am looking over it now...
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
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'
I seem to recall running into this in some other code
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
I guess it would cause problems if we were to actually rely on finding objects by name
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
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
It could use a bit of contrast. The legend was particularly hard to make out
oh, I didn't even know there was a legend!
Is there a reason it doesn't follow the rest of the plot's styles?
I like the colors used by NilsNemitz in examples/logAxis.py.
Oh yeah those are nice.
Maybe black/gray on white is good for print / pdf
Seems like that is something that should be able to modified in the export options
aha, but the context menu is disabled for MultiAxisPlotWidget
so, no interactive export, at least
There are probably some other colors that would work just as well for print and staring at on a computer screen.
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
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.
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
QColorDialog.ColorDialogOption.DontUseNativeDialog seems to make the eyedropper stop working when you leave the widget
at least it does on mac os
oof, mind opening an issue w/ that one with the code to run it; wonder if that's a Qt bug or if it's something we can fix ourselves
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
@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()
thanks this is easyer ahahha
is there a way to set the pen "lenght"
to change the "segment" lenght
yeah you can do it via QPen.setDashPattern http://doc.qt.io/qt-6/qpen.html#setDashPattern