2

in my program i have taken figure first then i import figue into canvas. Then i import canvas into scroll area. when i run my program ,then if i turn left scroll the yaxis tick label goes hide or i turn right scroll the yaxis tick label goes also hide. i have taken two axis. axis,axis2. i have set axes2.yaxis.tick_right() and axes.yaxis.tick_right().

I want that the yaxis tick label will stay at right side and alyas visible, whatever i turn scroll left or right .

import sys
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
import matplotlib.pyplot as plt
from matplotlib.figure import Figure
from PyQt5.QtWidgets import QMainWindow,QVBoxLayout
from PyQt5.QtWidgets import QApplication
from PyQt5 import QtCore, QtGui, QtWidgets
import datetime
from matplotlib.dates import num2date, date2num
from mpl_finance import candlestick_ochl as candlestick
import numpy as np
import matplotlib.ticker as ticker
import matplotlib.dates as mdates
import pylab as pl
class MainWindow_code_serarch(object):

    def setup_code_serarch(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(870, 680)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayoutWidget1 = QtWidgets.QWidget(self.centralwidget)
        self.verticalLayoutWidget1.setGeometry(QtCore.QRect(17, 30, 741, 13))
        self.verticalLayoutWidget1.setObjectName("verticalLayoutWidget")
        self.verticalLayout1 = QtWidgets.QVBoxLayout(self.verticalLayoutWidget1)
        self.verticalLayout1.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout1.setObjectName("verticalLayout1")
        self.verticalLayoutWidget = QtWidgets.QWidget(self.centralwidget)
        self.verticalLayoutWidget.setGeometry(QtCore.QRect(17, 10, 940, 603))
        self.verticalLayoutWidget.setObjectName("verticalLayoutWidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.verticalLayoutWidget)
        self.verticalLayout.setContentsMargins(0, 0, 0, 0)
        self.verticalLayout.setObjectName("verticalLayout")
        self.figure = Figure(figsize=(100,7.2), dpi=80, facecolor='k')
        self.canvas = FigureCanvas(self.figure)
        self.widget = QtWidgets.QWidget()
        self.scroll_area = QtWidgets.QScrollArea(self.widget)
        self.scroll_area.setWidget(self.canvas)
        self.verticalLayout.addWidget(self.scroll_area)
        axes,axes2 = self.figure.subplots(nrows=2, sharex=True)
        data = {
            'date': ['2018/10/30', '2018/11/03', '2018/11/04', '2018/11/05', '2018/11/07', '2018/11/10', '2018/11/11'],
            'open': [8824, 8726.31, 8642.14, 8531.51, 8630.25, 8602.50, 8640.22],
            'high': [8858, 8748.60, 8551.36, 8653.16, 8476.69, 8630, 8570.56],
            'low': [8688, 8743.67, 8550.76, 8449.50, 8631.83, 8602.18, 8743.22],
            'close': [8820, 8747.17, 8550.52, 8553., 8517.10, 8628.78, 8588.52],
            'volume': [17759.56, 120000.17, 18739.52, 38599.50, 16517.10, 17723.78, 15588.52]
        }
        x = date2num([datetime.datetime.strptime(d, '%Y/%m/%d').date() for d in data['date']])
        t= np.arange(len(data['date']))
        candle_trace = zip(t, data['open'], data['high'], data['low'], data['close'], data['volume'])
        candlestick(axes, candle_trace, width=.75, colorup='g', colordown='r')
        axes2.plot(t, [1, 2, 3, 4, 7, 8, 9])
        axes.set_position([0.02, 0.37, 0.88, 0.6])
        axes2.set_position([0.02, 0.15, 0.88, 0.22])
        axes.tick_params(axis='both', color='#ffffff', labelcolor='#ffffff')
        axes.yaxis.tick_right()
        axes2.tick_params(axis='both', color='#ffffff', labelcolor='#ffffff')
        axes2.grid(color='lightgray', linewidth=.5, linestyle=':')
        axes.grid(color='lightgray', linewidth=.5, linestyle=':')
        axes2.yaxis.tick_right()
        axes.autoscale_view()
        axes2.autoscale_view()
        axes.set_facecolor('#041105')
        axes2.set_facecolor('#041105')
        # N = len(dates)
        axes.set_xticks(range(0, len((x)), 1))
        axes.set_xticklabels([mdates.num2date(d).strftime('%b-%d') for d in x])
        axes.set_xticklabels([mdates.num2date(d).strftime('%Y-%m-%d') for d in x])
        axes2.set_xticklabels([mdates.num2date(d).strftime('%Y-%m-%d') for d in x])
        self.canvas.draw()
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 246, 21))
        self.menubar.setObjectName("menubar")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        # self.pushButton.clicked.connect(self.graphShowCode)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        # self.pushButton.setText(_translate("MainWindow", "OK"))
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = MainWindow_code_serarch()
    ui.setup_code_serarch(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

Output image: enter image description here

when i turn scroll left output image 2:enter image description here

HRarnob07
  • 161
  • 13
  • Here you are scolling the complete figure canvas. It seems what you're after is to create a scollbar without connecting it to the canvas and instead have it change the xlimits of your plot. – ImportanceOfBeingErnest Jan 11 '19 at 11:08
  • Thank you for understanding my problem. Can you give your explanation into code? – HRarnob07 Jan 11 '19 at 11:21

2 Answers2

8

Here is a way to scroll the content of an axes with a PyQt QtScrollBar. This is done by changing the limits of the axes depending on the scroll bar's value. To this end, a callback to the QtScrollBar's actionTriggered method is registered that changes the limits of the axes.

import sys
import matplotlib
# Make sure that we are using QT5
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from PyQt5 import QtWidgets, QtCore
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import numpy as np

class ScrollableWindow(QtWidgets.QMainWindow):
    def __init__(self, fig, ax, step=0.1):
        plt.close("all")
        if not QtWidgets.QApplication.instance():
            self.app = QtWidgets.QApplication(sys.argv)
        else:
            self.app = QtWidgets.QApplication.instance() 

        QtWidgets.QMainWindow.__init__(self)
        self.widget = QtWidgets.QWidget()
        self.setCentralWidget(self.widget)
        self.widget.setLayout(QtWidgets.QVBoxLayout())
        self.widget.layout().setContentsMargins(0,0,0,0)
        self.widget.layout().setSpacing(0)

        self.fig = fig
        self.ax = ax
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtWidgets.QScrollBar(QtCore.Qt.Horizontal)
        self.step = step
        self.setupSlider()
        self.nav = NavigationToolbar(self.canvas, self.widget)
        self.widget.layout().addWidget(self.nav)
        self.widget.layout().addWidget(self.canvas)
        self.widget.layout().addWidget(self.scroll)

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

    def setupSlider(self):
        self.lims = np.array(self.ax.get_xlim())
        self.scroll.setPageStep(self.step*100)
        self.scroll.actionTriggered.connect(self.update)
        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)
        print(self.scroll.value(), l1,l2)
        self.fig.canvas.draw_idle()


# create a figure and some subplots
fig, ax = plt.subplots()
t = np.linspace(0,3000,101)
x = np.cumsum(np.random.randn(len(t)))
ax.plot(t,x, marker="o")

# pass the figure to the custom window
a = ScrollableWindow(fig,ax)

enter image description here


For a Scrollbar within the figure, see Scrollable Bar graph matplotlib

ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • In Spyder/IPython in Windows, this only works once and then the terminal locks up when the window is closed :/ – endolith May 03 '19 at 04:32
  • @endolith Sorry, I shortened the app creation too much, should be fixed now – ImportanceOfBeingErnest May 03 '19 at 10:07
  • @ImportanceOfBeingErnest IMHO: The code is more readable is: `self.app = QtWidgets.QApplication.instance()` `if self.app is None:` `self.app = QtWidgets.QApplication(sys.argv)` :-) – eyllanesc May 03 '19 at 10:08
  • I get an `AttributeError: ScrollableWindow object has no attribute qapp` error with your example. Any idea why this may be? (using PyQt5 version 5.13.0) – Mark Loyman Jul 18 '19 at 10:41
  • 1
    @MarkLoyman Yes, there shouldn't be any `qapp` in the first place, I updated the code. – ImportanceOfBeingErnest Jul 18 '19 at 10:47
  • Thanks for the excellent example. In the gif, at the first frame the lower x limit is -150, but at the final frame after scrolling back to the left hand side of the plot, the lower limit is closer to -100. I've opened a separate question about this (https://stackoverflow.com/questions/71022277/unexpected-qscrollbar-value-in-pyqt5-pyside2), but I'm wondering whether you know why the scroll bar value doesn't return to 0 when you scroll it to the left? – Paul Feb 07 '22 at 17:14
0

The above answer works well, but does not hand back control to the spyder window or python command line. Also in spyder it could be run only once. Added a few more points seen from another thread on exiting from QT window and improved to this.

import sys
import matplotlib
# Make sure that we are using QT5
matplotlib.use('Qt5Agg')
import matplotlib.pyplot as plt
from PyQt5 import QtWidgets, QtCore
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import numpy as np

from PyQt5.QtWidgets import QMainWindow, QLabel, QGridLayout, QWidget
from PyQt5.QtCore import QSize

class HelloWindow(QMainWindow):
    def __init__(self, fig, ax , step = 0.1):


        plt.close("all")
        if not QtWidgets.QApplication.instance():
            self.app = QtWidgets.QApplication(sys.argv)
        else:
            self.app = QtWidgets.QApplication.instance() 

        
        QMainWindow.__init__(self)
 
        self.setMinimumSize(QSize(1500, 100))    
        self.setWindowTitle("Value Plotting") 

        self.step = step
        
        self.widget = QWidget() 
        self.setCentralWidget( self.widget )

        self.widget.setLayout(QtWidgets.QVBoxLayout())
        self.widget.layout().setContentsMargins(0,0,0,0)
        self.widget.layout().setSpacing(0)

        self.fig = fig
        self.ax = ax
        self.canvas = FigureCanvas(self.fig)
        self.canvas.draw()
        self.scroll = QtWidgets.QScrollBar(QtCore.Qt.Horizontal)
        self.step = step
        self.setupSlider()
        self.nav = NavigationToolbar(self.canvas, self.widget)#        self.widget.layout().addWidget(self.nav)
        self.widget.layout().addWidget(self.canvas)
        self.widget.layout().addWidget(self.scroll)

        menu = self.menuBar().addMenu('Action for quit')
        action = menu.addAction('Quit')
        action.triggered.connect(QtWidgets.QApplication.quit)

        self.canvas.draw()


        
    def setupSlider(self):
        self.lims = np.array(self.ax.get_xlim())
        self.scroll.setPageStep(self.step*100)
        self.scroll.actionTriggered.connect(self.update)
        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)
        print(self.scroll.value(), l1,l2)
        self.fig.canvas.draw_idle()

        
 
if __name__ == "__main__":
    def run_app(fig,ax):
        app = QtWidgets.QApplication(sys.argv)
        mainWin = HelloWindow(fig,ax)
        mainWin.show()
        app.exec_()


    fig, ax = plt.subplots()
    t = np.linspace(0,3000,101)
    x = np.cumsum(np.random.randn(len(t)))
    ax.plot(t,x, marker="o")

    run_app(fig,ax)

Found it running well on the python console. Exiting roughly in Spyder. Top close button not working, but exiting when operating from menu.

RGP
  • 1
  • 1