【问题标题】:Zoom and scroll bars on the pyqt5 chartpyqt5图表上的缩放和滚动条
【发布时间】:2019-08-27 17:21:09
【问题描述】:

我自己使用了example。我可以添加滚动条和滑块。

滑块对其影响做出反应。无法放大然后沿轴滚动图形。 图表“matplotlib”中有一个很好的example 说明如何做到这一点

但是,我想在 pyqt5 中做所有事情。我在 pyqt5 中找不到类似的函数“set_x lim”。

理想情况下,我希望得到一个能够缩放、滚动的图表。

这是代码的主要部分:

ui = QMainWindow()
central_widget = QWidget()
vbox=QtWidgets.QVBoxLayout(central_widget)
Scroll = QScrollBar(Qt.Horizontal, central_widget)
Slider = QSlider(Qt.Horizontal, central_widget)
Slider.setRange(1, 100)
chartview = QChartView(chart, central_widget)
vbox.addWidget(chartview)
vbox.addWidget(Slider)
vbox.addWidget(Scroll)

def scale():
    step=Slider.value()/100
    print(step)
    update()

def update(evt=None):
    r = Scroll.value() / ((1 + step) * 100)
    #????????????????????
    print(r)

Slider.actionTriggered.connect(scale)
Scroll.actionTriggered.connect(update)

ui.setGeometry(70, 70, 400, 300)
ui.setCentralWidget(central_widget)

ui.show()
sys.exit(app.exec_())

这就是我在 pyqt5+matplotlib 中的做法,基于示例:

class ScrollableWindow(QtWidgets.QMainWindow):
    def __init__(self, fig, ax, step=0.1):
        plt.close("all")
        self.qapp = QtWidgets.QApplication([])

        QtWidgets.QMainWindow.__init__(self)
        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.vbox=QtWidgets.QVBoxLayout(self.widget)
        self.gbox = QtWidgets.QHBoxLayout(self.widget)

        self.vkl = 1
        self.ln=0
        self.ln1 = 0
        self.step = step
        self.fig = fig
        self.ax = ax
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtWidgets.QScrollBar(QtCore.Qt.Horizontal)
        self.slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        self.slider.setRange(1, 100)
        self.setupSlider()
        self.nav = NavigationToolbar(self.canvas, self.widget)
        self.vbox.addWidget(self.canvas)
        self.vbox.addWidget(self.scroll)
        self.gbox.addWidget(self.slider)
        self.gbox.addWidget(self.nav)
        self.gbox.addStretch(1)
        self.vbox.addLayout(self.gbox)
        self.setLayout(self.vbox)

        self.canvas.draw()
        self.show()
        self.qapp.exec_()


    def scale(self):
        self.step = self.slider.sliderPosition()/100.0
        self.update()

    def setupSlider(self):
        self.lims = np.array(self.ax.get_xlim())
        self.scroll.setPageStep(self.step * 100)#ax.get_xlim() 
        self.scroll.actionTriggered.connect(self.update)
        self.slider.actionTriggered.connect(self.scale)
        self.update()

    def update(self, evt=None):
        r = self.scroll.value() / ((1 + self.step) * 100)
        l1 = self.lims[0] + r * np.diff(self.lims)
        l2 = l1 + np.diff(self.lims) * self.step
        self.ax.set_xlim(l1, l2) # Show only this section of the x-axis coordinate
        if l1>0 and l2>0 and l2<(len(z)-1): # auto height chart
            xmin = np.amin(z[int(l1):int(l2)])
            xmax = np.amax(z[int(l1):int(l2)])
            self.ax.set_ylim([xmin, xmax])


        self.fig.canvas.draw_idle()

x = pd.read_csv('file.txt',
                    index_col='DATE',
                    parse_dates=True,
                    infer_datetime_format=True)
z = x.iloc[:, 3].values
N = len(z)
ind = np.arange(N)

fig, ax = plt.subplots()
ax.plot(ind, z)

a = ScrollableWindow(fig,ax)

【问题讨论】:

  • 我理解你关于滚动条的问题,但不是关于缩放的问题,我解释一下:缩放可以应用在一个轴或两个轴上,还有 zoomIn 和 zoomOut,在你的情况下是 zoomIn、zoomOut 或两者都有?
  • 我需要放大,只在 x 轴(水平)上缩小。我添加了GIF 动画,因为水平方向有所增加。 pyqt5+matplotlib.
  • 您可以提供一个minimal reproducible example,说明您目前拥有的内容以及生成您在 gif 中显示的内容的代码,以将其作为我可能的解决方案的基础
  • 我在图表上还有顶部和底部调整到窗口的高度,这样顶部和底部就没有空白距离。我会把它放在主窗口中。这在我什至来自你的例子。
  • 如果您需要帮助,请提供我所要求的内容

标签: python pyqt5


【解决方案1】:

由于您没有解释缩放要求(请参阅我的评论),因此我不会在此答案中实现该部分。

QtCharts中滚动的一般逻辑是设置轴的min和max属性,而那些min和max取决于QXSerie和QXAxis的类型,例如如果是QLineSeries,QValueAxis取值可以都是真实的,但如果它是带有 QCategoryAxis 的 QCandlestickSeries,则它们仅采用类别的名称。

假设您使用与我之前的答案相同的逻辑,那么类别对应于一串整数值,这些值是 QLineSeries 显示的值,因此当您计算限制时,您应该参考。

另一方面,在 QtCharts 中没有 get_xlim,因此您必须手动计算它,考虑到这种情况,它与数据的数量相匹配。

from PyQt5 import QtCore, QtGui, QtWidgets, QtChart
import math
import numpy as np
import pandas as pd


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.step = 0.1
        self._chart_view = QtChart.QChartView()
        self.scrollbar = QtWidgets.QScrollBar(
            QtCore.Qt.Horizontal,
            sliderMoved=self.onAxisSliderMoved,
            pageStep=self.step * 100,
        )
        self.slider = QtWidgets.QSlider(
            QtCore.Qt.Horizontal, sliderMoved=self.onZoomSliderMoved
        )

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QVBoxLayout(central_widget)
        for w in (self._chart_view, self.scrollbar, self.slider):
            lay.addWidget(w)

        self.resize(640, 480)

        self._chart = QtChart.QChart()

        self._candlestick_serie = QtChart.QCandlestickSeries()
        self._line_serie = QtChart.QLineSeries()

        tm = []
        df = pd.read_csv(
            "https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv"
        )
        name_of_columns = ("AAPL.Open", "AAPL.High", "AAPL.Low", "AAPL.Close", "mavg")

        for i, (o, h, l, c, v) in enumerate(
            zip(*(df[name] for name in name_of_columns))
        ):
            self._candlestick_serie.append(QtChart.QCandlestickSet(o, h, l, c))
            self._line_serie.append(QtCore.QPointF(i, v))
            tm.append(str(i))

        min_x, max_x = 0, i

        self._chart.addSeries(self._candlestick_serie)
        self._chart.addSeries(self._line_serie)
        self._chart.createDefaultAxes()
        self._chart.legend().hide()
        # self._chart.setAnimationOptions(QtChart.QChart.SeriesAnimations)

        self._chart.axisX(self._candlestick_serie).setCategories(tm)
        self._chart.axisX(self._candlestick_serie).setVisible(False)

        self._chart_view.setChart(self._chart)
        self.adjust_axes(100, 200)
        self.lims = np.array([min_x, max_x])

        self.onAxisSliderMoved(self.scrollbar.value())

    def adjust_axes(self, value_min, value_max):
        self._chart.axisX(self._candlestick_serie).setRange(
            str(value_min), str(value_max)
        )
        self._chart.axisX(self._line_serie).setRange(value_min, value_max)

    @QtCore.pyqtSlot(int)
    def onAxisSliderMoved(self, value):
        r = value / ((1 + self.step) * 100)
        l1 = self.lims[0] + r * np.diff(self.lims)
        l2 = l1 + np.diff(self.lims) * self.step
        self.adjust_axes(math.floor(l1), math.ceil(l2))

    @QtCore.pyqtSlot(int)
    def onZoomSliderMoved(self, value):
        print(value)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

考虑到您提供的示例,Qt Charts 的代码如下:

import numpy as np
import pandas as pd

from PyQt5 import QtCore, QtGui, QtWidgets, QtChart


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self._chart_view = QtChart.QChartView()
        self.scrollbar = QtWidgets.QScrollBar(
            QtCore.Qt.Horizontal, sliderMoved=self.recalculate_range, pageStep=10
        )
        self.slider = QtWidgets.QSlider(
            QtCore.Qt.Horizontal,
            sliderMoved=self.recalculate_range,
            minimum=0,
            maximum=100,
            value=60,
        )

        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)

        lay = QtWidgets.QVBoxLayout(central_widget)
        for w in (self._chart_view, self.scrollbar, self.slider):
            lay.addWidget(w)

        self.resize(640, 480)

        self._chart = QtChart.QChart()

        self._line_serie = QtChart.QLineSeries(name="Data")

        x = pd.read_csv(
            "file.txt", index_col="DATE", parse_dates=True, infer_datetime_format=True
        )
        self._z = x.iloc[:, 3].values
        N = len(self._z)

        for i, v in enumerate(self._z):
            self._line_serie.append(QtCore.QPointF(i, v))

        self.lims = 0, N

        self._chart.addSeries(self._line_serie)
        self._chart.createDefaultAxes()
        # self._chart.legend().hide()
        # self._chart.setAnimationOptions(QtChart.QChart.SeriesAnimations)

        self._chart_view.setChart(self._chart)
        self.recalculate_range()

    def recalculate_range(self):
        m, M = self.lims
        step = self.slider.sliderPosition() / 100.0
        r = self.scrollbar.value() / ((1 + step) * 100.0)

        xmin = m + r * np.diff(self.lims)
        xmax = xmin + np.diff(self.lims) * step

        self._chart.axisX(self._line_serie).setRange(xmin, xmax)

        if xmin > 0 and xmax > 0 and xmax < M:

            ymin = np.amin(self._z[int(xmin) : int(xmax)])
            ymax = np.amax(self._z[int(xmin) : int(xmax)])
            self._chart.axisY(self._line_serie).setRange(ymin, ymax)


if __name__ == "__main__":
    import sys

    app = QtWidgets.QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec_())

【讨论】:

  • 谢谢,eyllanesc!
【解决方案2】:

为了记录,我写了一个依赖较少的小函数,它做得很好(更好/更简单)。

我使用了两个滑块而不是一个滑块和一个 scollbar,但这是一个细节。

有了这个,您可以放大图表,并正确平移所有内容(如果您缩放 2% 左右,您将能够在 2% 缺失的情况下从 0% 平移到 100%,并且没有最大值高于您的系列最大值)

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""This function is using two QSliders to zoom and pan on a QChart"""


def zoom_and_pan(chart, max_val, slider_zoom, slider_pan):
    """Use two sliders to zoom and pan on a QChart

    :args:

    :arg chart:         Chart on which zoom and plot
    :arg max_val:       Maximum value used to plot the graph
                        (usually the last x value of your serie)
    :arg slider_zoom:   Slider [0, 1+] to zoom on the graph
    :arg slider_pan:    Slider [0, 1+] to pan the graph

    :type chart:        QChart()
    :type max_val:      Float()
    :type slider_zoom:  QSlider()
    :type slider_pan:   QSlider()"""

    zoom_ratio = slider_zoom.sliderPosition() / (
        slider_zoom.maximum() * 1.001)
    step = 1 - zoom_ratio
    pan_level = slider_pan.sliderPosition() * zoom_ratio / slider_pan.maximum()
    min_chart = pan_level * max_val
    if slider_pan.sliderPosition() == slider_pan.maximum():
        max_chart = max_val
    else:
        max_chart = max_val * step + min_chart
    chart.axisX().setRange(min_chart, max_chart)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-01-01
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 2016-07-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多