qchart 和 qchartview 的运用的例子
qchart 存在一些问题
一般用在2000个点以下的场景,点多了,就会卡。 解决的办法就是 开启opengl加速。
但这时,对qchartview 进行transform 时, 绘制的点,并不能同时变换。原因是QT把opengl的图案是单独绘制的,要找到这个单独的view 进行变换才行。 但找不到。。。
这就不能使用的底层的函数了,只能用 qchart的setrange,变换的事情,交给qt 自己去实现。
这个例子,实现了 拖动 滚轮缩放 ,加了一个取值的限定框。
python 3.10 pyside6
import sys
from PySide6.QtWidgets import (
QApplication,
QMainWindow,
QWidget,
QVBoxLayout,
QGraphicsSceneWheelEvent,
QGraphicsSceneMouseEvent,
QLabel,
QHBoxLayout,
QFormLayout,
QPushButton,
QSlider,
)
from PySide6.QtCharts import QChartView, QChart, QScatterSeries, QValueAxis, QLineSeries
from PySide6.QtGui import QMouseEvent
from PySide6.QtCore import Qt, QPointF, Signal, QRectF, QTimer, QPoint
import random
import math
import threading
class zn_chart(QChart):
sg_mouse_db_click = Signal(QPointF)
def __init__(self):
super().__init__()
self._left_pos = None
# data series
self.se = QScatterSeries()
self.ax_x = QValueAxis()
self.ax_y = QValueAxis()
self.addSeries(self.se)
self.addAxis(self.ax_x, Qt.AlignmentFlag.AlignBottom)
self.addAxis(self.ax_y, Qt.AlignmentFlag.AlignLeft)
self.se.attachAxis(self.ax_x)
self.se.attachAxis(self.ax_y)
self.se.setBorderColor(self.se.color())
self.se.setMarkerSize(2.0)
self.ax_x.setRange(-51, 51)
self.ax_y.setRange(-51, 51)
self.setTitle("Line Chart")
self.se.setUseOpenGL(True)
# data
self._data: list[QPointF] = []
self._x_min: float = None
self._x_max: float = None
self._y_min: float = None
self._y_max: float = None
# limit line data
self.lm_x_min: float = None
self.lm_x_max: float = None
self.lm_se = QLineSeries()
self.addSeries(self.lm_se)
self.lm_se.attachAxis(self.ax_x)
self.lm_se.attachAxis(self.ax_y)
self.lm_se.setUseOpenGL(True)
self.lm_pts: list[QPointF] = []
self.lm_se.setMarkerSize(2.0)
self.lm_se.setVisible(True)
# signal
self.drag_start_pos: QPointF = None
self.drag_start_range: QRectF = None
self.on_drag: bool = False
# tm
self.tm: QTimer = None
self._tmp_data: list[QPointF] = []
self._tm_lock = threading.Lock()
@property
def range(self) -> QRectF:
return QRectF(
self.ax_x.min(),
self.ax_y.min(),
self.ax_x.max() - self.ax_x.min(),
self.ax_y.max() - self.ax_y.min(),
)
def set_range_x(self, p1: float, p2: float):
self.ax_x.setRange(p1, p2)
def set_range_y(self, p1: float, p2: float):
self.ax_y.setRange(p1, p2)
def setrange(self, r: QRectF):
self.ax_x.setRange(r.x(), r.x() + r.width())
self.ax_y.setRange(r.y(), r.y() + r.height())
def wheelEvent(self, event: QGraphicsSceneWheelEvent) -> None:
y = event.delta()
factor = 1
if y > 0:
factor = 1.1
elif y < 0:
factor = 1 / 1.1
if factor != 1:
f = 1 / factor
r = self.range
r_center_x = r.x() + r.width() * 0.5
r_center_y = r.y() + r.height() * 0.5
p_center = self.mapToValue(event.pos())
r_c_x2 = p_center.x() + f * (r_center_x - p_center.x())
r_c_y2 = p_center.y() + f * (r_center_y - p_center.y())
w2 = r.width() * f
h2 = r.height() * f
r2 = QRectF(r_c_x2 - w2 * 0.5, r_c_y2 - h2 * 0.5, w2, h2)
self.setrange(r2)
def on_drag_enter(self, p: QPointF) -> None:
self.drag_start_pos = p
self.drag_start_range = self.range
self.on_drag = True
def on_drag_exit(self) -> None:
self.on_drag = False
self.drag_start_pos = None
self.drag_start_range = None
def on_drag_move(self, p: QPointF) -> None:
if self.on_drag:
vp = self.plotArea()
now_p = p
dx = -(
(now_p.x() - self.drag_start_pos.x())
/ vp.width()
* self.drag_start_range.width()
)
dy = (
(now_p.y() - self.drag_start_pos.y())
/ vp.height()
* self.drag_start_range.height()
)
r2 = QRectF(
self.drag_start_range.x() + dx,
self.drag_start_range.y() + dy,
self.drag_start_range.width(),
self.drag_start_range.height(),
)
self.setrange(r2)
def mouseDoubleClickEvent(self, event: QGraphicsSceneMouseEvent) -> None:
p = self.mapToValue(event.pos())
self.sg_mouse_db_click.emit(p)
print(p)
return super().mouseDoubleClickEvent(event)
# data op
@property
def data(self) -> list[QPointF]:
return self._data
def data_add_xy(self, x: float, y: float):
self._data.append(QPointF(x, y))
self.se.append(x, y)
if self._x_min is None or x < self._x_min:
self._x_min = x
if self._x_max is None or x > self._x_max:
self._x_max = x
if self._y_min is None or y < self._y_min:
self._y_min = y
if self._y_max is None or y > self._y_max:
self._y_max = y
def data_add_p(self, d: QPointF | QPoint):
self.data_add_xy(d.x(), d.y())
def data_add_list_point(self, lp: list[QPointF | QPointF]):
for i in lp:
self.data_add_p(i)
self.se.append(lp)
def data_clear(self):
self._data.clear()
self.se.clear()
self._x_min = self._x_max = self._y_min = self._y_max = None
def data_load_more(self, sp: list[QPointF]):
with self._tm_lock:
if self.tm is not None:
self.tm.stop()
self.tm = None
self.data_clear()
self._tmp_data.clear()
self._tmp_data += sp[:]
self.tm = QTimer()
self.tm.setInterval(15)
self.tm.timeout.connect(self._do_add)
self.tm.start()
def _do_add(self):
with self._tm_lock:
if self.tm is None:
self._tmp_data.clear()
elif len(self._tmp_data) == 0:
self.tm.stop()
self.tm = None
print("over")
else:
if len(self._tmp_data) > 1000:
to_add = self._tmp_data[:1000]
self._tmp_data = self._tmp_data[1000:]
else:
to_add = self._tmp_data[:]
self._tmp_data.clear()
self.data_add_list_point(to_add)
# limit setting
def lm_refresh(self):
if (
self.lm_x_min is not None
and self.lm_x_max is not None
and self._y_max is not None
and self._y_min is not None
):
if self._y_max > self._y_min:
x1 = float(self.lm_x_min)
x2 = float(self.lm_x_max)
d = self._y_max - self._y_min
y1 = self._y_min - 0.2 * d
y2 = self._y_max + 0.2 * d
self.lm_pts.clear()
for i in [[x1, y1], [x1, y2], [x2, y2], [x2, y1], [x1, y1]]:
self.lm_pts.append(QPointF(*i))
self.lm_se.clear()
self.lm_se.append(self.lm_pts)
def lm_set_x_min(self, x: float):
xx = float(x)
if self.lm_x_max is None or xx <= self.lm_x_max:
self.lm_x_min = xx
self.lm_refresh()
def lm_set_x_max(self, x: float):
xx = float(x)
if self.lm_x_min is None or xx >= self.lm_x_min:
self.lm_x_max = xx
self.lm_refresh()
def lm_set_x_min_percent(self, v: int):
f = float(v / 100)
if self._x_min is not None and self._x_max is not None:
dd = self._x_max - self._x_min
self.lm_set_x_min(dd * f + self._x_min)
def lm_set_x_max_percent(self, v: int):
f = float(v / 100)
if self._x_min is not None and self._x_max is not None:
dd = self._x_max - self._x_min
self.lm_set_x_max(dd * f + self._x_min)
def lm_show(self):
self.lm_se.setVisible(True)
def lm_hide(self):
self.lm_se.setVisible(False)
def lm_new_from_data(self):
self.lm_set_x_min(self._x_min)
self.lm_set_x_max(self._x_max)
def lm_clear(self):
self.lm_se.clear()
self.lm_x_min = None
self.lm_x_max = None
self.lm_pts = []
class my_view(QChartView):
sg_drag_enter = Signal(QPointF)
sg_drag_exit = Signal()
sg_drag_move = Signal(QPointF)
on_drag: bool = False
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.MouseButton.LeftButton:
self.sg_drag_enter.emit(event.position())
self.on_drag = True
return super().mousePressEvent(event)
def mouseReleaseEvent(self, event: QMouseEvent) -> None:
self.sg_drag_exit.emit()
self.on_drag = False
return super().mouseReleaseEvent(event)
def mouseMoveEvent(self, event: QMouseEvent) -> None:
if self.on_drag:
self.sg_drag_move.emit(event.position())
return super().mouseMoveEvent(event)
def setChart(self, chart: QChart) -> None:
if isinstance(chart, zn_chart):
self.sg_drag_enter.connect(chart.on_drag_enter)
self.sg_drag_exit.connect(chart.on_drag_exit)
self.sg_drag_move.connect(chart.on_drag_move)
return super().setChart(chart)
class zn_slider(QSlider):
def __init__(self):
super().__init__(Qt.Orientation.Horizontal)
self.setMinimum(0)
self.setMaximum(100)
self.setSingleStep(1)
self.setValue(0)
self.setTickPosition(self.TickPosition.TicksBelow)
if __name__ == "__main__":
class test_wg(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("PySide6 QChart Example")
self.setGeometry(50, 50, 800, 600)
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
self.chart_view = my_view()
self.main_layout.addWidget(self.chart_view)
self.chart = zn_chart()
self.chart_view.setChart(self.chart)
##
self.c_ly = QHBoxLayout()
self.main_layout.addLayout(self.c_ly)
self.b1 = QPushButton("add data")
self.b1.clicked.connect(self.adddata)
self.c_ly.addWidget(self.b1)
self.b2 = QPushButton("init limit")
self.b2.clicked.connect(self.lm_init)
self.c_ly.addWidget(self.b2)
self.b3 = QPushButton("show limit")
self.b3.clicked.connect(self.chart.lm_show)
self.c_ly.addWidget(self.b3)
self.b4 = QPushButton("hide limit")
self.b4.clicked.connect(self.chart.lm_hide)
self.c_ly.addWidget(self.b4)
# slider
self.f_ly = QFormLayout()
self.s1 = zn_slider()
self.s1.valueChanged.connect(self.on_s1_change)
self.s1_lb = QLabel("下限")
self.s1_lb.setFixedWidth(120)
self.f_ly.addRow(self.s1_lb, self.s1)
self.s2 = zn_slider()
self.s2.setValue(100)
self.s2.valueChanged.connect(self.on_s2_change)
self.s2_lb = QLabel("上限")
self.s2_lb.setFixedWidth(120)
self.f_ly.addRow(self.s2_lb, self.s2)
self.main_layout.addLayout(self.f_ly)
# show
self.show()
def adddata(self):
self.chart.data_clear()
dt = []
for i in range(10000):
x = random.randint(0, 10000) / 100 - 50
xx = math.pi * 2 * x / 100
y = math.sin(xx) * 50
dt.append(QPointF(x, y))
self.chart.data_load_more(dt)
def lm_init(self):
self.chart.lm_set_x_min_percent(0)
self.chart.lm_set_x_max_percent(100)
self.chart.lm_show()
def on_s1_change(self, v: int):
self.chart.lm_set_x_min_percent(v)
if self.chart.lm_x_min is None:
self.s1_lb.setText(f"下限")
else:
self.s1_lb.setText(f"下限 {self.chart.lm_x_min:<.3f}")
def on_s2_change(self, v: int):
self.chart.lm_set_x_max_percent(v)
if self.chart.lm_x_max is None:
self.s2_lb.setText(f"上限")
else:
self.s2_lb.setText(f"上限 {self.chart.lm_x_max:<.3f}")
app = QApplication(sys.argv)
window = test_wg()
app.exec()