Conversation
|
I think you left out From #2561, I found that getting the bounding rectangle right is not so straightforward. Below is a script that demonstrates two issues.
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
class ObjectBounds(QtWidgets.QGraphicsItem):
def paint(self, painter, *args):
pen = QtGui.QPen(QtCore.Qt.GlobalColor.red, 0, QtCore.Qt.PenStyle.DashLine)
rect = self.boundingRect()
painter.setPen(pen)
painter.drawRect(rect)
print(rect)
def boundingRect(self):
return self.parentItem().boundingRect()
pg.mkQApp()
pw = pg.PlotWidget()
pw.show()
np.random.seed(8)
n = 5
data = [np.random.normal(500, 30, 1000) for _ in range(n)]
bpi = pg.BoxplotItem()
pen = pg.mkPen('y', width=6)
bpi.setData(data=data, pen=pen, symbol='star', symbolBrush='g')
pw.addItem(bpi)
rect = ObjectBounds(bpi)
pg.exec() |
|
@pijyoi Thanks for pointing out these issues, let me try to fix them. |
|
@pijyoi bounding rect should be correct now. |
|
No, it's the screenshot from latest changes. I can't explain why there are still blank spaces surround bounding rect. I'm running on macos + pyqt5 |
|
Ah, I see that the bounding rectangle is tight but the viewbox is not in your screenshot. Could you try out the example in #2561 on both #2561 and #2565 on your macOS system and see if there are any differences? Or even better, try out the script in #2565 (comment) |
@pijyoi No sure if you are still interested, but here is the image output from macos + pyqt5: |
If I follow the thread correctly, this is what happened before:
The screenshot that you showed (#2562 (comment)) seems to indicate that implementing (ii) but not (iii) is not the cause of the large blank space described in (1) |
|
@pijyoi It seems the large blank space is due to pyqt5, could you confirm it on your side? |
|
I noticed a difference in stdout.
Using pyqt5, there is only one line printed: Using pyqt6, there are multiple lines, and the first line is the same as pyqt5, then I am not familiar with pyqt6, but I hope it can give you some insight. FYI, I am still running in my current branch, which was 2 years ago. I will test it again using latest main branch and post the results here if result is different. Update: latest main has no difference. |
|
On Windows, both on PyQt5 and PySide6, I am getting the no blank space image. I am guessing that there's something happening with macOS and Qt5 that got fixed in Qt6. It's possible that implementing |
|
I need to move outlier drawing code to But it is not working as expected, I think I did something wrong with QPainter:
I put the code in another branch, would you please take a look? noonchen@ca1ae8f#diff-07cd0e5f16a681a9fabeb53a0f4bee545b8bbec8b16ad04f0c615fae9e22e82c FYI, if I disable the outlier, there is no blank space using pyqt5. bpi.setData(data=data, pen=pen, symbol='star', symbolBrush='g', outlier=False)
|
|
If you look at the width of the blank space, it corresponds to 0.707 * symbolSize (10) ~= 7. Quoting #2565 (comment) It seems to me that the computation of the data bounds needs to be decoupled with the rendering. def calculateDataBounds(self):
loc, data = self.opts["loc"], self.opts["data"]
if data is None:
return QtCore.QRectF()
lst_lower = []
lst_upper = []
for dataset in data:
dataset = np.asarray(dataset)
if self.opts["outlier"]:
lower, upper = np.min(dataset), np.max(dataset)
else:
lower, upper = self.whiskerFunc(dataset)
lst_lower.append(lower)
lst_upper.append(upper)
miny = np.min(lst_lower)
maxy = np.max(lst_upper)
if loc is None:
loc = np.arange(len(data))
loc = np.array(loc)
minx, maxx = np.min(loc), np.max(loc)
width = 0.8 if self.opts["width"] is None else self.opts["width"]
minx -= width/2
maxx += width/2
if not self.opts["locAsX"]:
minx, maxx, miny, maxy = miny, maxy, minx, maxx
return QtCore.QRectF(QtCore.QPointF(minx, miny), QtCore.QPointF(maxx, maxy)) |
|
Thanks @pijyoi , Now the viewbox is normal using pyqt5, and there is only one printed line.
And users can set pen and brush to None to hide boxes.
|
| if (self.opts["pen"] is None and | ||
| self.opts["brush"] is None and | ||
| self.opts["medianPen"] is None): | ||
| self.opts["width"] = 0.001 |
There was a problem hiding this comment.
This effect can be attributed to the following behaviour:
https://codebrowser.dev/qt5/qtbase/src/gui/painting/qpaintengine.cpp.html#_ZN12QPaintEngine9drawLinesEPK6QLineFi
If the two endpoints are the same, then a point is drawn instead. This point is of dimensions 1x1. So it occupies 1 unit on the horizontal axis and 1 unit on the vertical axis. And this appears as a "line" to us.
Actually, why is there even a need to have the branch to special case the width value? You could just remove the whole "if" branch.
|
Could you please rebase your branch onto |
|
Setting width to 0.001 is arbitrary and fails to give a tight bounding box when non-default locations are used. import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import QtCore, QtGui, QtWidgets
class ObjectBounds(QtWidgets.QGraphicsItem):
def paint(self, painter, *args):
pen = QtGui.QPen(QtCore.Qt.GlobalColor.red, 0, QtCore.Qt.PenStyle.DashLine)
rect = self.boundingRect()
painter.setPen(pen)
painter.drawRect(rect)
print(rect)
def boundingRect(self):
return self.parentItem().boundingRect()
rng = np.random.default_rng(0)
data = [rng.normal(500, 30, 1000) for _ in range(5)]
pg.mkQApp()
pw = pg.PlotWidget()
bpi = pg.BoxplotItem()
pen = pg.mkPen('y', width=6)
xscale = 1e-4
bpi.setData(loc=np.arange(len(data)) * xscale, width=xscale * 0.8, data=data, pen=None, medianPen=None, symbolBrush='g')
pw.addItem(bpi)
rect = ObjectBounds(bpi)
pw.show()
pg.exec() |
|
What about set to f32_min? it can be a small fix but not sure if its acceptable for you.
…---Original---
From: ***@***.***>
Date: Fri, Oct 10, 2025 09:40 AM
To: ***@***.***>;
Cc: ***@***.******@***.***>;
Subject: Re: [pyqtgraph/pyqtgraph] Add Boxplot feature (PR #2562)
pijyoi left a comment (pyqtgraph/pyqtgraph#2562)
Setting width to 0.001 is arbitrary and fails to give a tight bounding box when non-default locations are used.
import numpy as np import pyqtgraph as pg from pyqtgraph.Qt import QtCore, QtGui, QtWidgets class ObjectBounds(QtWidgets.QGraphicsItem): def paint(self, painter, *args): pen = QtGui.QPen(QtCore.Qt.GlobalColor.red, 0, QtCore.Qt.PenStyle.DashLine) rect = self.boundingRect() painter.setPen(pen) painter.drawRect(rect) print(rect) def boundingRect(self): return self.parentItem().boundingRect() rng = np.random.default_rng(0) data = [rng.normal(500, 30, 1000) for _ in range(5)] pg.mkQApp() pw = pg.PlotWidget() bpi = pg.BoxplotItem() pen = pg.mkPen('y', width=6) xscale = 1e-4 bpi.setData(loc=np.arange(len(data)) * xscale, width=xscale * 0.8, data=data, pen=None, medianPen=None, symbolBrush='g') pw.addItem(bpi) rect = ObjectBounds(bpi) pw.show() pg.exec()
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
|
|
Something like the following seems to work. It also permits the user to pass It also demonstrates another thing: normally in the library, diff --git a/pyqtgraph/graphicsItems/BoxplotItem.py b/pyqtgraph/graphicsItems/BoxplotItem.py
index e0c452fe..dd72fc44 100644
--- a/pyqtgraph/graphicsItems/BoxplotItem.py
+++ b/pyqtgraph/graphicsItems/BoxplotItem.py
@@ -92,13 +92,16 @@ class BoxplotItem(GraphicsObject):
`symbolBrush`: Brush for filling outlier symbols.
'''
self.opts.update(opts)
- # set box width to a tiny number if not draw
- if (self.opts["pen"] is None and
+
+ if self.opts["width"] is None:
+ self.opts["width"] = DEFAULT_BOX_WIDTH
+
+ if (
+ self.opts["pen"] is None and
self.opts["brush"] is None and
- self.opts["medianPen"] is None):
- self.opts["width"] = 0.001
- else:
- self.opts["width"] = self.opts["width"] or DEFAULT_BOX_WIDTH
+ self.opts["medianPen"] is None
+ ):
+ self.opts["width"] = 0
# prepare pen and brush object
self._pen = fn.mkPen(self.opts["pen"])
@@ -158,6 +161,9 @@ class BoxplotItem(GraphicsObject):
mask = np.logical_or(dataset<lower, dataset>upper)
self.outlierData[pos] = dataset[mask]
+ if self.opts["width"] == 0:
+ continue
+
p.setPen(self._pen)
# whiskers
if locAsX: |
|
@pijyoi Thanks! It works and much better than a small width value, I should've thought it 👍 |
|
@pijyoi Is there anything else requires modification before merging? |
|
This PR is more or less fine to me. Merging can only be done by maintainers. |
Should I at someone explicitly or simply wait? |
|
Just be patient and wait |
|
rebased |
|
Thanks @noonchen for this PR and your continued efforts to get it working across the combination of dependencies we support, and thank you @pijyoi for reviewing and providing feedback. I'm going to merge this, but we should add documentation so it doesn't become a hidden feature like |











Add
BoxplotItemand example code.Closes #2542 .