0

I want to have the auto-scale button that is normally in the bottom left hand corner and OUTSIDE of each subplot for a pyqtgraph, but rather than auto-scaling to fit the data, scale to a specific coordinate. The issue is that if I add a button to each subplot, it uses the location x and y axis scale. I want it so that it is always in the bottom left hand corner of each subplot no matter the values (like the default A button). Affectively, I want the auto-scale button to show even more area on the x and y axes than all the data points

I tried using:

auto_button = QPushButton(f"Auto {i+1}")
            auto_button.setFixedSize(20, 10)
            auto_button.clicked.connect(lambda _, p=plot, xmin=self.xdata[i][0], xmax=self.xdata[i][-1],ymin=self.ylim[i][0], ymax=self.ylim[i][1]: self.set_custom_range(p, xmin, xmax, ymin, ymax))
            
proxy = QtWidgets.QGraphicsProxyWidget()
            proxy.setWidget(auto_button)
            proxy.setPos(10,10)
            plot.addItem(proxy)

but the size and position of that button will be local to the x axis and INSIDE the plot.

Is there a way I can have it so that it is exactly where the 'A' button is?

My code:

import sys
import random
import time
import multiprocessing
import numpy as np
from PyQt5 import QtWidgets, QtCore
import pyqtgraph as pg

class StockPriceUpdater(multiprocessing.Process):
    def __init__(self, pipe, stock_name, prices):
        super().__init__()
        self.pipe = pipe
        self.stock_name = stock_name
        self.prices = prices

    def run(self):
        while True:
            new_price = self.prices[-1] + random.uniform(-0.5, 0.5)
            self.prices = np.append(self.prices, new_price)
            self.pipe.send((self.stock_name, self.prices.copy()))
            time.sleep(10)

def autoBtnClicked2(self):
        print('We have a click')
        if self.autoBtn.mode == 'auto':
            print('auto_button true')
            # Changed this : self.enableAutoRange()
            x, y = self.curves[0].getOriginalDataset()
            self.vb.setRange(
                xRange=[x[0]-20, 2*x[-1]],
                yRange=[y[0], y[-1]]
            )
            # End of change
            self.autoBtn.hide()         
        else:
            print('auto_button false')

            self.disableAutoRange()

class StockPriceApp(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.setup_multiprocessing()

    def initUI(self):
        self.setWindowTitle('Stock Prices')
        central_widget = QtWidgets.QWidget()
        self.setCentralWidget(central_widget)
        layout = QtWidgets.QVBoxLayout(central_widget)

        self.graph_layout = pg.GraphicsLayoutWidget()
        layout.addWidget(self.graph_layout)

        self.plots = []
        self.curves = []
        self.xdata = [np.arange(10)] * 4
        
        self.ylim = {}
        self.xlim = {}
        self.ini_ydata = {}
        
        for i in range(4):
            plot = self.graph_layout.addPlot(row=i//2, col=i%2, title=f'Stock {i+1}')
            prices = 10 + np.cumsum(np.random.uniform(-0.5, 0.5, 10))
            
            plot.autoBtnClicked = autoBtnClicked2     # The adjustment
            
            self.update_axis_limits(plot, i, self.xdata[i], prices)
            self.ini_ydata[i] = prices
            curve = plot.plot(self.xdata[i], prices, pen='y')

            self.plots.append(plot)
            self.curves.append(curve)
            
        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update_plots)
        self.timer.start(1000)

    def position_button(self, view_box, proxy):
        rect = view_box.sceneBoundingRect()
        proxy.setPos(rect.left() + 10, rect.bottom() - 40)  

    def setup_multiprocessing(self):
        self.pipes = {}
        self.processes = {}
        for i in range(4):
            stock_name = f'Stock{i+1}'
            parent_pipe, child_pipe = multiprocessing.Pipe()
            self.pipes[stock_name] = parent_pipe
            process = StockPriceUpdater(child_pipe, stock_name, self.ini_ydata[i])
            process.start()
            self.processes[stock_name] = process

    def update_plots(self):
        for i, (stock_name, pipe) in enumerate(self.pipes.items()):
            if pipe.poll():
                _, prices = pipe.recv()
                self.xdata[i] = np.append(self.xdata[i], self.xdata[i][-1] + 1)
                self.curves[i].setData(self.xdata[i], prices)
                self.update_axis_limits(self.plots[i], i, self.xdata[i], prices)

    def set_custom_range(self, plot, x_min, x_max, y_min, y_max):
        plot.getViewBox().setRange(xRange=[x_min, x_max], yRange=[y_min, y_max])

    def update_axis_limits(self, plot, stock, xvals, yvals):
        xmin = xvals.min()
        xmax = xvals.max()
        offset = (xmax - xmin) / 3 
        xmax = xmax + offset
        
        if isinstance(yvals, list):
            yvals = np.array(yvals)
            
        ymin = yvals.min()
        ymax = yvals.max()
        offset = (ymax - ymin) / 10
        
        ymax = ymax + offset
        ymin = ymin - offset
        
        self.ylim[stock] = (ymin, ymax)
        self.xlim[stock] = (xmin, xmax)

        plot.getViewBox().setRange(xRange=[xmin, xmax], yRange=[ymin, ymax], padding=0)

    def closeEvent(self, event):
        for process in self.processes.values():
            process.terminate()
        event.accept()

def main():
    app = QtWidgets.QApplication(sys.argv)
    stock_app = StockPriceApp()
    stock_app.show()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
2
  • Can you also attach the observed and expected output? Commented Aug 13, 2024 at 9:22
  • The code should run as is. I am trying to recreate the functionality of the small A in bottom left corner that are in all the subplots, but with a slight change. My actual code is far more complicate, but this is a stripped down version. Commented Aug 13, 2024 at 12:10

1 Answer 1

0

You can change the functionality of that "A" button and customize it creating a new method and replacing the original one. This will not change the original package in other scrips or projects.

import pyqtgraph as pg

def autoBtnClicked2(self):
        if self.autoBtn.mode == 'auto':
            # Changed this : self.enableAutoRange()
            x, y = self.curves[0].getOriginalDataset()
            self.vb.setRange(
                xRange=[x[0]-5, x[-1]+5],
                yRange=[y[0]-2, y[-1]+2]
            )
            # End of change
            self.autoBtn.hide()         
        else:
            self.disableAutoRange()

pg.graphicsItems.PlotItem.PlotItem.autoBtnClicked = autoBtnClicked2

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.