I would like to connect the PyQtGraph mouse wheel zoom function to a QSlider widget. So when i zoom in/out on the graph, I would get the range of the viewbox and the slider window should follow in sliding range. The closest example of how I would like it to be can be found in this PyQtGraph example: http://www.pyqtgraph.org/downloads/0.10.0/pyqtgraph-0.10.0-deb/pyqtgraph-0.10.0/examples/crosshair.py
So I would like to connect the following definitions in some way.
def update_plot(self):
self.axX = self.p6.getAxis('bottom')
self.xmin = self.axX.range[0]
self.xmax = self.axX.range[0]
print(self.axX.range)
return xmin, xmax
def update_slider(self, xmin, xmax):
self.size = self.w1.slider.value()
self.p6.setXRange(self.xmin+self.size,self.xmax+self.size)
print(self.size)
However, I can't seem to get it to work. I have attached the full code of my example below. Can you help me in any way?
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication, QHBoxLayout, QLabel, QSizePolicy, QSlider, QSpacerItem, \
QVBoxLayout, QWidget
import pyqtgraph as pg
import numpy as np
class Slider(QWidget):
def __init__(self, minimum, maximum, parent=None):
super(Slider, self).__init__(parent=parent)
self.verticalLayout = QVBoxLayout(self)
self.label = QLabel(self)
self.verticalLayout.addWidget(self.label)
self.horizontalLayout = QHBoxLayout()
spacerItem = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.slider = QSlider(self)
self.slider.setOrientation(Qt.Vertical)
self.horizontalLayout.addWidget(self.slider)
spacerItem1 = QSpacerItem(0, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem1)
self.verticalLayout.addLayout(self.horizontalLayout)
self.resize(self.sizeHint())
self.minimum = minimum
self.maximum = maximum
self.slider.valueChanged.connect(self.setLabelValue)
self.x = None
self.setLabelValue(self.slider.value())
def setLabelValue(self, value):
self.x = self.minimum + (float(value) / (self.slider.maximum() - self.slider.minimum())) * (
self.maximum - self.minimum)
self.label.setText("{0:.4g}".format(self.x))
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.horizontalLayout = QHBoxLayout(self)
# plot (p6)
self.win = pg.GraphicsWindow(title="Basic plotting examples")
self.horizontalLayout.addWidget(self.win)
self.p6 = self.win.addPlot(title="My Plot")
x = np.arange(1e5)
self.y1 = np.random.randn(x.size)
self.p6.plot(self.y1, pen="r")
self.p6.setMouseEnabled(x=True, y=False)
self.p6.setXRange(0,300)
self.p6.setLimits(xMin=0, xMax=len(self.y1))
self.p6.sigRangeChanged.connect(self.update_plot)
# slider (w1)
self.w1 = Slider(0, len(self.y1))
self.horizontalLayout.addWidget(self.w1)
self.w1.slider.setMinimum(0)
self.w1.slider.setMaximum(len(self.y1))
self.w1.slider.valueChanged.connect(self.update_slider)
def update_plot(self):
self.axX = self.p6.getAxis('bottom')
self.xmin = self.axX.range[0]
self.xmax = self.axX.range[0]
print(self.axX.range)
return self.xmin, self.xmax
def update_slider(self, xmin, xmax):
self.size = self.w1.slider.value()
self.p6.setXRange(self.xmin+self.size,self.xmax+self.size)
print(self.size)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
First of all, the question you asked first was actually more useful for the StackOverflow "conduct". After the editing you just left what is an useless plain copy/paste from a pyqtgraph example.
Don't do that.
People rarely go through editings, and seeing the current status of your question, they would think that you didn't put any efforts at all in trying to solve your problem (leading them to just ignore your question). I suggest you to undo your editing or, at least, restore the original question example code.
The main problem with your question is that zooming is not linear. From a normal user perspective, each step of a zoom slider should increase or decrease by a zoom factor (pyqtgraph does that, actually).
A slider is usually a linear interface, which means that each of its step should be equally proportional to its previous or next steps.
Simply put, if the "middle step" of the slider equals to the original size of an image and the "next step" equals to a double size, the user expects that the step next to that will result in doubling the size again. The same works backwards too, obviously.
Imagine it as a spinbox, or even a couple of +/-
buttons.
size = 2
print(size)
Out[1]: 2
def doZoom(size, step):
zoomFactor = 2
if step > 0:
size *= zoomFactor
else:
size /= zoomFactor
return size
size = doZoom(size, +1)
print(size)
Out[2]: 4
size = doZoom(size, +1)
print(size)
Out[3]: 8
size = doZoom(size, -1)
print(size)
Out[4]: 4
A possible solution is to implement a slider that takes into account all of this.
import sys
from math import log
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import (QApplication, QLabel, QSlider, QWidget,
QHBoxLayout, QVBoxLayout)
import pyqtgraph as pg
import numpy as np
class LabelSlider(QWidget):
def __init__(self, *args, **kwargs):
super(LabelSlider, self).__init__(*args, **kwargs)
layout = QVBoxLayout(self)
# IMPORTANT! QStyles sometimes create *huge* margins (6-10 pixels) around
# layout contents; we don't really need those in here
layout.setContentsMargins(0, 0, 0, 0)
self.label = QLabel()
layout.addWidget(self.label, alignment=Qt.AlignHCenter)
# use an arbitrary text for the minimum width to minimize size flickering
# on value changes
self.label.setMinimumWidth(self.fontMetrics().width("8.8888e+88"))
self.label.setAlignment(Qt.AlignCenter)
layout.addSpacing(10)
self.slider = QSlider()
# when adding a QSlider to a QLayout and specifying an alignment, the
# opposite of the orientation *has* to be omitted to ensure that it's
# centered in the other direction
layout.addWidget(self.slider, alignment=Qt.AlignHCenter)
# set the slider "transparent" for mouse events, so that the user will
# still see it as enabled but won't be able to interact with it
self.slider.setAttribute(Qt.WA_TransparentForMouseEvents)
#set a range high enough to limit rounding errors
self.slider.setMaximum(100000)
# expose the basic slider signal/methods for transparency, so that
# this LabelSlider will have a similar interface to that of a QSlider
self.value = self.slider.value
self.valueChanged = self.slider.valueChanged
def setValue(self, value, xLimit):
sliderValue = self.slider.maximum() - value * self.slider.maximum()
self.slider.setValue(sliderValue)
xLimitMin, xLimitMax = xLimit
limitRange = xLimitMax - xLimitMin
floatValue = xLimitMin + (value * limitRange / (limitRange)) * (
limitRange)
self.label.setText("{0:.4g}".format(floatValue))
# ensure that the widget is resized to fit the label contents too;
# sizeHint will be called afterwards
self.updateGeometry()
def sizeHint(self):
hint = super(LabelSlider, self).sizeHint()
# adjust the minimum hint width to accomodate the label contents
if hint.width() < self.label.width():
hint.setWidth(self.label.width())
return hint
class Widget(QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent=parent)
self.horizontalLayout = QHBoxLayout(self)
self.win = pg.GraphicsWindow(title="Basic plotting examples")
self.horizontalLayout.addWidget(self.win)
self.p6 = self.win.addPlot(title="My Plot")
x = np.arange(1e5)
self.y1 = np.random.randn(x.size)
self.p6.plot(self.y1, pen="r")
self.p6.setMouseEnabled(x=True, y=False)
self.p6.setXRange(0,300)
# set a minimum x range for the plot
# this value *HAS* to be > 0
self.p6.setLimits(xMin=0, xMax=len(self.y1), minXRange=1)
self.p6.sigRangeChanged.connect(self.update_plot)
self.slider = LabelSlider()
self.horizontalLayout.addWidget(self.slider)
def update_plot(self):
state = self.p6.getViewBox().state
# get the limits of the plot's ViewBox
limits = state["limits"]
minZoom, maxZoom = xLimit = limits["xLimits"]
xRangeMin, xRangeMax = limits["xRange"]
# if the minimum x range is set, use that instead
if xRangeMin is not None:
minZoom = xRangeMin
# ensure that the minimum x range is > 0
minZoom = max(1, minZoom)
if xRangeMax is not None:
maxZoom = xRangeMax
xMin, xMax = self.p6.getAxis("bottom").range
diff = xMax - xMin
# get the possible minimum and maximum values based on the wheel factor
factor = abs(state["wheelScaleFactor"])
minimum = log(maxZoom / 100000., factor)
maximum = log(minZoom / 100000., factor)
value = log(diff / 100000., factor)
# adjust the factor to a 0.0-1.0 range according to the possible zoom
realValue = (value - minimum) / (maximum - minimum)
# set the slider value according to the above value
self.slider.setValue(realValue, xLimit)
if __name__ == "__main__":
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())