Mika
Mika

Reputation: 199

Zooming out the QChart with QRubberBand, to fit the size of the QChartView

I have a QChartView with QChart. I've added QRubberBand to the QChartView to zoom the chart. Plus I overrode wheelEvent, to scroll the chart by X axis.

So, now I want to prevent 'outscrolling' and 'outzooming' over some value. By this names I mean, that I need to stop zoom out, when chart size reaches the original chart size (so, minimal size of the chart should be it's original size) and I need to stop scrolling event, when I reach the min or max chart value (for the same reason).

I've tried to use size of the QChart and QChartView to handle QRubberBand resizing as beginning, but it didn't help.

Here is my small try, but I can't find out any compatible solution for QWheelEvent and QMouseEvent (but may be there is any build in methods for these tasks, so I should not override that two events). Here is the code:

import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget
from PySide6 import QtCharts, QtGui

import random


class Chart(QtCharts.QChart):
    def __init__(self):
        super().__init__()

    def wheelEvent(self, event):
        print(event.delta())
        if event.delta() > 0:
            self.scroll(-10, 0)
            print('e')
        else:
            self.scroll(10, 0)

    def create_series(self):
        for _ in range(2):
            series = QtCharts.QLineSeries()
            for i in range(100):
                series.append(i, random.randint(0, 10))
            self.addSeries(series)

    def setup(self):
        self.createDefaultAxes()
        self.legend().setVisible(True)
        self.legend().setAlignment(Qt.AlignLeft)


class ChartView(QtCharts.QChartView):
    def __init__(self, chart):
        super().__init__()
        self.setChart(chart)
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.setRubberBand(QtCharts.QChartView.HorizontalRubberBand)

    def mousePressEvent(self, event):
        if event.button() == Qt.RightButton:
            if self.chart().size().width() > self.size().width():
                print("Fit")
                super().mousePressEvent(event)
            else:
                print(self, "Doesn't fit")
            print(self.chart().size().width())
            print(self.size().width())
        else:
            super().mousePressEvent(event)


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.layout = QVBoxLayout()

        for _ in range(2):
            chart_view = self.create_chart_view()
            self.layout.addWidget(chart_view)

        self.central_widget = QWidget()
        self.central_widget.setLayout(self.layout)
        self.setCentralWidget(self.central_widget)

    @staticmethod
    def create_chart():
        chart = Chart()
        chart.create_series()
        chart.setup()

        return chart

    def create_chart_view(self):
        chart_view = ChartView(chart=self.create_chart())
        chart_view.chart().axes(Qt.Horizontal)[0].rangeChanged.connect(
            lambda: (self.sync_chart_views(chart_view))
        )

        return chart_view

    def sync_chart_views(self, source_chart_view):
        x_range = (source_chart_view.chart().axes(Qt.Horizontal)[0].min(), source_chart_view.chart().axes(Qt.Horizontal)[0].max())
        for chart_view in self.findChildren(QtCharts.QChartView):
            if chart_view != source_chart_view:
                chart_view.chart().axes(Qt.Horizontal)[0].setRange(x_range[0], x_range[1])


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())

Upvotes: 0

Views: 310

Answers (1)

Mika
Mika

Reputation: 199

Here is the solution. The main thing is to call super().mouseReleaseEvent(event) before chart axes values setup.

import sys
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QToolTip
from PySide6 import QtCharts, QtGui


import random


class Chart(QtCharts.QChart):
    def __init__(self):
        super().__init__()
        self.create_series()
        self.createDefaultAxes()

        self.legend().setVisible(True)
        self.legend().setAlignment(Qt.AlignLeft)
        self.x_min_value = self.axes(Qt.Horizontal)[0].min()
        self.x_max_value = self.axes(Qt.Horizontal)[0].max()
        self.y_min_value = self.axes(Qt.Vertical)[0].min()
        self.y_max_value = self.axes(Qt.Vertical)[0].max()
        self.layout().setContentsMargins(0, 0, 0, 0)

    def create_series(self, s=3):
        for j in range(s):
            series = QtCharts.QLineSeries()
            if j == 0:
                for i in range(150):
                    if i % 10 == 0:
                        series.append(i, random.randint(0, 10))
                    else:
                        series.append(i, 2)
                self.addSeries(series)

    def wheelEvent(self, event):
        print(event.delta())
        if event.delta() > 0 and self.axes(Qt.Horizontal)[0].min() > self.x_min_value:
            self.scroll(-10, 0)
            print('e')
        elif event.delta() < 0 and self.axes(Qt.Horizontal)[0].max() < self.x_max_value:
            self.scroll(10, 0)


class ChartView(QtCharts.QChartView):
    def __init__(self, chart):
        super().__init__()

        self.setChart(chart)
        [series.hovered.connect(self.display_plot_value) for series in chart.series()]
        self.setRenderHint(QtGui.QPainter.Antialiasing)
        self.setRubberBand(QtCharts.QChartView.HorizontalRubberBand)
        self.x_min_value = chart.x_min_value
        self.x_max_value = chart.x_max_value
        self.y_min_value = chart.y_min_value
        self.y_max_value = chart.y_max_value

    @staticmethod
    def display_plot_value(point, state):
        pos = QtGui.QCursor.pos()
        if state:
            tooltip_text = f"X: {round(point.x(), 3)}, Y: {round(point.y(), 3)}"
            QToolTip.showText(pos, tooltip_text)
        else:
            QToolTip.hideText()

    def mouseReleaseEvent(self, event) -> None:
        super().mouseReleaseEvent(event)
        if event.button() == Qt.RightButton:
            if self.chart().axes(Qt.Horizontal)[0].min() < self.x_min_value:
                self.chart().axes(Qt.Horizontal)[0].setRange(self.x_min_value, self.chart().axes(Qt.Horizontal)[0].max())
            if self.chart().axes(Qt.Horizontal)[0].max() > self.x_max_value:
                self.chart().axes(Qt.Horizontal)[0].setRange(self.chart().axes(Qt.Horizontal)[0].min(), self.x_max_value)
            if self.chart().axes(Qt.Vertical)[0].min() < self.y_min_value:
                self.chart().axes(Qt.Vertical)[0].setRange(self.y_min_value, self.chart().axes(Qt.Vertical)[0].max())
            if self.chart().axes(Qt.Vertical)[0].max() > self.y_max_value:
                self.chart().axes(Qt.Vertical)[0].setRange(self.chart().axes(Qt.Vertical)[0].min(), self.y_max_value)
            return
        elif event.button() == Qt.LeftButton:
            if self.chart().mapToValue(event.position()).x() > self.x_max_value:
                self.chart().axes(Qt.Horizontal)[0].setMax(self.x_max_value)
            if self.chart().mapToValue(event.position()).x() < self.x_min_value:
                self.chart().axes(Qt.Horizontal)[0].setMin(self.x_min_value)
            if self.chart().mapToValue(event.position()).y() > self.y_max_value:
                self.chart().axes(Qt.Vertical)[0].setMax(self.y_max_value)
            if self.chart().mapToValue(event.position()).y() < self.y_min_value:
                self.chart().axes(Qt.Vertical)[0].setMin(self.y_min_value)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.central_widget = QWidget()
        self.central_widget.setObjectName('central_widget')
        self.layout = QVBoxLayout(self.central_widget)
        self.layout.setSpacing(0)
        self.layout.setContentsMargins(0, 0, 0, 0)

        for _ in range(6):
            chart_view = self.create_chart_view()
            self.layout.addWidget(chart_view)

        self.setCentralWidget(self.central_widget)


    def create_chart_view(self):
        chart = Chart()
        chart_view = ChartView(chart=chart)
        return chart_view



if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec())


Upvotes: 0

Related Questions