PySide2 如何将分离的图例放置到另一个小部件?
PySide2 How to place a detached legend to another widget?
最近,我发现我的 QChart 图例可以分离并 placed 到另一个独立的小部件中。 Qt 文档说 QLegend class 方法 QLegend::detachFromChart() 可以将图例与图表分开。
不幸的是,当我尝试将图例添加到另一个小部件布局时,出现以下错误:
Traceback (most recent call last):
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/loader.py", line 111, in seterror_argument
return errorhandler.seterror_argument(args, func_name)
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/errorhandler.py", line 97, in seterror_argument
update_mapping()
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/mapping.py", line 240, in update
top = __import__(mod_name)
File "/home/artem/.local/lib/python3.6/site-packages/numpy/__init__.py", line 142, in <module>
from . import core
File "/home/artem/.local/lib/python3.6/site-packages/numpy/core/__init__.py", line 67, in <module>
raise ImportError(msg.format(path))
ImportError: Something is wrong with the numpy installation. While importing we detected an older version of numpy in ['/home/artem/.local/lib/python3.6/site-packages/numpy']. One method of fixing this is to repeatedly uninstall numpy until none is found, then reinstall this version.
Fatal Python error: seterror_argument did not receive a result
Current thread 0x00007efc67a96740 (most recent call first):
File "/home/artem/\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b/Projects/QtChartsExamples/test/main.py", line 20 in <module>
这是一个简单的例子:
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
import sys
import random
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.x = 0
self.y = 0
self.percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
core_series.setName(f"CPU {i+1}: {self.percent[i]: .1f} %")
colour = [random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255)]
pen = QtGui.QPen(QtGui.QColor(colour[0],
colour[1],
colour[2])
)
pen.setWidth(1)
core_series.setPen(pen)
core_series.append(self.x, self.y)
self.addSeries(core_series)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
for i in self.series():
i.attachAxis(self.axisX)
i.attachAxis(self.axisY)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(chart.legend())
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(400, 300)
window.show()
sys.exit(app.exec_())
弄清楚这有什么问题会很有趣。我真的可以这样做吗?
我没有收到您指示的消息,但收到以下消息:
Traceback (most recent call last):
File "main.py", line 70, in <module>
hbox.addWidget(chart.legend())
TypeError: 'PySide2.QtWidgets.QBoxLayout.addWidget' called with wrong argument types:
PySide2.QtWidgets.QBoxLayout.addWidget(QLegend)
Supported signatures:
PySide2.QtWidgets.QBoxLayout.addWidget(PySide2.QtWidgets.QWidget, int = 0, PySide2.QtCore.Qt.Alignment = Default(Qt.Alignment))
PySide2.QtWidgets.QBoxLayout.addWidget(PySide2.QtWidgets.QWidget)
这更有意义,因为 QLegend 是一个 QGraphicsWidget 而不是 QWidget,所以您不能将它放在布局中。所以一种可能的解决方案是使用 QGraphicsView:
import sys
import random
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
self.percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
core_series.setName(f"CPU {i+1}: {self.percent[i]: .1f} %")
colour = random.sample(range(255), 3)
pen = QtGui.QPen(QtGui.QColor(*colour))
pen.setWidth(1)
core_series.setPen(pen)
core_series.attachAxis(self.axisX)
core_series.attachAxis(self.axisY)
for i in range(100):
core_series.append(i, random.uniform(10, 90))
self.addSeries(core_series)
class LegendWidget(QtWidgets.QGraphicsView):
def __init__(self, legend, parent=None):
super().__init__(parent)
self.m_legend = legend
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
scene.addItem(self.m_legend)
def resizeEvent(self, event):
if isinstance(self.m_legend, QtCharts.QLegend):
self.m_legend.setMinimumSize(self.size())
super().resizeEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
legend_widget = LegendWidget(chart.legend())
hbox.addWidget(legend_widget)
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(400, 300)
window.show()
sys.exit(app.exec_())
如您所见,它也不能很好地工作,所以与其移动 QLegend,不如使用带有 QIcon 的 QListWidget:
import sys
import random
from functools import partial
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.legend().hide()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
self.x = 0
# percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
colour = random.sample(range(255), 3)
pen = QtGui.QPen(QtGui.QColor(*colour))
pen.setWidth(1)
core_series.setPen(pen)
self.addSeries(core_series)
core_series.attachAxis(self.axisX)
core_series.attachAxis(self.axisY)
timer = QtCore.QTimer(self, timeout=self.onTimeout, interval=100)
timer.start()
@QtCore.Slot()
def onTimeout(self):
percent = cpu_percent(percpu=True)
for i, (serie, value) in enumerate(zip(self.series(), percent)):
serie.append(self.x, value)
serie.setName(f"CPU {i+1}: {value: .1f} %")
self.axisX.setRange(max(0, self.x - 100), max(100, self.x))
self.x += 1
def create_icon(pen):
pixmap = QtGui.QPixmap(512, 512)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setBrush(pen.brush())
painter.drawEllipse(pixmap.rect().adjusted(50, 50, -50, -50))
painter.end()
icon = QtGui.QIcon(pixmap)
return icon
class LegendWidget(QtWidgets.QListWidget):
def __init__(self, series, parent=None):
super().__init__(parent)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setSeries(series)
self.horizontalScrollBar().hide()
def setSeries(self, series):
self.clear()
for i, serie in enumerate(series):
it = QtWidgets.QListWidgetItem()
it.setIcon(create_icon(serie.pen()))
self.addItem(it)
wrapper = partial(self.onNameChanged, serie, i)
serie.nameChanged.connect(wrapper)
wrapper()
def onNameChanged(self, serie, i):
it = self.item(i)
it.setText(serie.name())
self.setFixedWidth(self.sizeHintForColumn(0))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
legend_widget = LegendWidget(chart.series())
hbox.addWidget(legend_widget)
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(1280, 480)
window.show()
sys.exit(app.exec_())
最近,我发现我的 QChart 图例可以分离并 placed 到另一个独立的小部件中。 Qt 文档说 QLegend class 方法 QLegend::detachFromChart() 可以将图例与图表分开。
不幸的是,当我尝试将图例添加到另一个小部件布局时,出现以下错误:
Traceback (most recent call last):
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/loader.py", line 111, in seterror_argument
return errorhandler.seterror_argument(args, func_name)
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/errorhandler.py", line 97, in seterror_argument
update_mapping()
File "/home/artem/.local/lib/python3.6/site-packages/shiboken2/files.dir/shibokensupport/signature/mapping.py", line 240, in update
top = __import__(mod_name)
File "/home/artem/.local/lib/python3.6/site-packages/numpy/__init__.py", line 142, in <module>
from . import core
File "/home/artem/.local/lib/python3.6/site-packages/numpy/core/__init__.py", line 67, in <module>
raise ImportError(msg.format(path))
ImportError: Something is wrong with the numpy installation. While importing we detected an older version of numpy in ['/home/artem/.local/lib/python3.6/site-packages/numpy']. One method of fixing this is to repeatedly uninstall numpy until none is found, then reinstall this version.
Fatal Python error: seterror_argument did not receive a result
Current thread 0x00007efc67a96740 (most recent call first):
File "/home/artem/\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b/Projects/QtChartsExamples/test/main.py", line 20 in <module>
这是一个简单的例子:
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
import sys
import random
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.x = 0
self.y = 0
self.percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
core_series.setName(f"CPU {i+1}: {self.percent[i]: .1f} %")
colour = [random.randrange(0, 255),
random.randrange(0, 255),
random.randrange(0, 255)]
pen = QtGui.QPen(QtGui.QColor(colour[0],
colour[1],
colour[2])
)
pen.setWidth(1)
core_series.setPen(pen)
core_series.append(self.x, self.y)
self.addSeries(core_series)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
for i in self.series():
i.attachAxis(self.axisX)
i.attachAxis(self.axisY)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
hbox.addWidget(chart.legend())
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(400, 300)
window.show()
sys.exit(app.exec_())
弄清楚这有什么问题会很有趣。我真的可以这样做吗?
我没有收到您指示的消息,但收到以下消息:
Traceback (most recent call last):
File "main.py", line 70, in <module>
hbox.addWidget(chart.legend())
TypeError: 'PySide2.QtWidgets.QBoxLayout.addWidget' called with wrong argument types:
PySide2.QtWidgets.QBoxLayout.addWidget(QLegend)
Supported signatures:
PySide2.QtWidgets.QBoxLayout.addWidget(PySide2.QtWidgets.QWidget, int = 0, PySide2.QtCore.Qt.Alignment = Default(Qt.Alignment))
PySide2.QtWidgets.QBoxLayout.addWidget(PySide2.QtWidgets.QWidget)
这更有意义,因为 QLegend 是一个 QGraphicsWidget 而不是 QWidget,所以您不能将它放在布局中。所以一种可能的解决方案是使用 QGraphicsView:
import sys
import random
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
self.percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
core_series.setName(f"CPU {i+1}: {self.percent[i]: .1f} %")
colour = random.sample(range(255), 3)
pen = QtGui.QPen(QtGui.QColor(*colour))
pen.setWidth(1)
core_series.setPen(pen)
core_series.attachAxis(self.axisX)
core_series.attachAxis(self.axisY)
for i in range(100):
core_series.append(i, random.uniform(10, 90))
self.addSeries(core_series)
class LegendWidget(QtWidgets.QGraphicsView):
def __init__(self, legend, parent=None):
super().__init__(parent)
self.m_legend = legend
scene = QtWidgets.QGraphicsScene(self)
self.setScene(scene)
scene.addItem(self.m_legend)
def resizeEvent(self, event):
if isinstance(self.m_legend, QtCharts.QLegend):
self.m_legend.setMinimumSize(self.size())
super().resizeEvent(event)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
legend_widget = LegendWidget(chart.legend())
hbox.addWidget(legend_widget)
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(400, 300)
window.show()
sys.exit(app.exec_())
如您所见,它也不能很好地工作,所以与其移动 QLegend,不如使用带有 QIcon 的 QListWidget:
import sys
import random
from functools import partial
from PySide2 import QtGui, QtWidgets, QtCore
from PySide2.QtCharts import QtCharts
from psutil import cpu_percent, cpu_count
class cpu_chart(QtCharts.QChart):
def __init__(self, parent=None):
super().__init__(parent)
self.legend().setAlignment(QtCore.Qt.AlignLeft)
self.legend().setContentsMargins(0.0, 0.0, 5.0, 0.0)
self.legend().setMarkerShape(QtCharts.QLegend.MarkerShapeCircle)
self.legend().detachFromChart()
self.legend().hide()
self.axisX = QtCharts.QValueAxis()
self.axisY = QtCharts.QValueAxis()
self.axisX.setVisible(False)
self.addAxis(self.axisX, QtCore.Qt.AlignBottom)
self.addAxis(self.axisY, QtCore.Qt.AlignLeft)
self.axisX.setRange(0, 100)
self.axisY.setTickCount(5)
self.axisY.setRange(0, 100)
self.x = 0
# percent = cpu_percent(percpu=True)
for i in range(cpu_count()):
core_series = QtCharts.QSplineSeries()
colour = random.sample(range(255), 3)
pen = QtGui.QPen(QtGui.QColor(*colour))
pen.setWidth(1)
core_series.setPen(pen)
self.addSeries(core_series)
core_series.attachAxis(self.axisX)
core_series.attachAxis(self.axisY)
timer = QtCore.QTimer(self, timeout=self.onTimeout, interval=100)
timer.start()
@QtCore.Slot()
def onTimeout(self):
percent = cpu_percent(percpu=True)
for i, (serie, value) in enumerate(zip(self.series(), percent)):
serie.append(self.x, value)
serie.setName(f"CPU {i+1}: {value: .1f} %")
self.axisX.setRange(max(0, self.x - 100), max(100, self.x))
self.x += 1
def create_icon(pen):
pixmap = QtGui.QPixmap(512, 512)
pixmap.fill(QtCore.Qt.transparent)
painter = QtGui.QPainter(pixmap)
painter.setBrush(pen.brush())
painter.drawEllipse(pixmap.rect().adjusted(50, 50, -50, -50))
painter.end()
icon = QtGui.QIcon(pixmap)
return icon
class LegendWidget(QtWidgets.QListWidget):
def __init__(self, series, parent=None):
super().__init__(parent)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.setSeries(series)
self.horizontalScrollBar().hide()
def setSeries(self, series):
self.clear()
for i, serie in enumerate(series):
it = QtWidgets.QListWidgetItem()
it.setIcon(create_icon(serie.pen()))
self.addItem(it)
wrapper = partial(self.onNameChanged, serie, i)
serie.nameChanged.connect(wrapper)
wrapper()
def onNameChanged(self, serie, i):
it = self.item(i)
it.setText(serie.name())
self.setFixedWidth(self.sizeHintForColumn(0))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = QtWidgets.QMainWindow()
chart = cpu_chart()
chart.setAnimationOptions(QtCharts.QChart.SeriesAnimations)
chart_view = QtCharts.QChartView(chart)
chart_view.setRenderHint(QtGui.QPainter.Antialiasing)
container = QtWidgets.QWidget()
hbox = QtWidgets.QHBoxLayout()
legend_widget = LegendWidget(chart.series())
hbox.addWidget(legend_widget)
hbox.addWidget(chart_view)
container.setLayout(hbox)
window.setCentralWidget(container)
window.resize(1280, 480)
window.show()
sys.exit(app.exec_())