Skip to content

pyqtgraph

Full documentation pages are generated for docstring reference only and may contain symbols imported from other modules. Imported symbols are not distinguished from locally defined symbols and will appear in any module that they are imported into. For better information on where symbols should be imported from, review the sourcecode on the github.

FoSpy.ui.sliders.pyqtgraph

CTRL_ROWS module-attribute

CTRL_ROWS = 6

MAX_CTRL_HEIGHT module-attribute

MAX_CTRL_HEIGHT = 65

MIN_CTRL_WIDTH module-attribute

MIN_CTRL_WIDTH = 250

TB_WIDTH module-attribute

TB_WIDTH = 60

available module-attribute

available = True

import_e module-attribute

import_e = None

AbstractControl

Source code in FoSpy/ui/sliders/abstract.py
class ControlPanel:
    def __init__(self, rows=CTRL_ROWS):
        self._rowcol = self.row_col_iter()
        self._max_rows = rows

    def addWidget(self, w):
        pass

    def addGroup(self, label, rows=CTRL_ROWS):
        pass

    def row_col_iter(self):
        current_row = 0
        current_col = 0
        while True:
            if current_row >= self._max_rows-1:
                current_row = 0
                current_col += 1
            else:
                current_row += 1
            yield current_row, current_col


    def nextRowCol(self):
        return next(self._rowcol)

_max_rows instance-attribute

_max_rows = rows

_rowcol instance-attribute

_rowcol = self.row_col_iter()

__init__

__init__(rows=CTRL_ROWS)
Source code in FoSpy/ui/sliders/abstract.py
def __init__(self, rows=CTRL_ROWS):
    self._rowcol = self.row_col_iter()
    self._max_rows = rows

addGroup

addGroup(label, rows=CTRL_ROWS)
Source code in FoSpy/ui/sliders/abstract.py
def addGroup(self, label, rows=CTRL_ROWS):
    pass

addWidget

addWidget(w)
Source code in FoSpy/ui/sliders/abstract.py
def addWidget(self, w):
    pass

nextRowCol

nextRowCol()
Source code in FoSpy/ui/sliders/abstract.py
def nextRowCol(self):
    return next(self._rowcol)

row_col_iter

row_col_iter()
Source code in FoSpy/ui/sliders/abstract.py
def row_col_iter(self):
    current_row = 0
    current_col = 0
    while True:
        if current_row >= self._max_rows-1:
            current_row = 0
            current_col += 1
        else:
            current_row += 1
        yield current_row, current_col

AbstractSlider

Source code in FoSpy/ui/sliders/abstract.py
class SliderPlot:
    color1 = 'b'
    color2 = 'magenta'
    color3 = 'darkgreen'
    def __init__(self, specs={}, cfg={}, x_label=None, y_label=None, x_ticks=True, y_ticks=True):
        self.specs = specs
        self._unpacked_specs = {}
        self._unpack_specs()
        self.cfg = cfg
        self.sl_rows = _count_sliders(self._unpacked_specs)
        self.sliders = {}
        self.checks = {}
        self.textboxes = {}
        self.plots = {}
        self.plotcolors = {}

        self.bg_color = 'w'
        self.fg_color = 'k'

        if x_label is not None:
            self.setXlabel(x_label)
        if y_label is not None:
            self.setYlabel(y_label)

        self.setXticks(x_ticks)
        self.setYticks(y_ticks)

    def _unpack_specs(self, specs=None):
        specs = specs or self.specs

        for name, spec in specs.items():
            if spec["type"] == "group":
                self._unpack_specs(spec["specs"])
            else:
                self._unpacked_specs[name] = spec

    def update_plot(self, val=None):
        self.update_cfg()

    def update_cfg(self):
        for name, spec in self._unpacked_specs.items():
            if spec["type"] == "scalar":
                NoneVal = spec.get("None", None)
                enabled = self.get_check_enabled(name)
                self.cfg[name] = self.get_slider_val(name) if enabled else NoneVal

            elif spec["type"] == "range":
                lo_enabled = self.get_check_enabled(name + "_min")
                hi_enabled = self.get_check_enabled(name + "_max")

                lo = self.get_slider_val(name + "_min") if lo_enabled else None
                hi = self.get_slider_val(name + "_max") if hi_enabled else None

                self.cfg[name] = [lo, hi]

    def _add_group(self, label, specs, rows=CTRL_ROWS):
        # cache current panel
        panel = self.panel

        # reassign panel to nested group
        self.panel = panel.addGroup(label, rows=rows)

        # build controls in nested group
        self._build_controls(specs)

        # restore cached panel
        self.panel = panel

    def _add_range(self, name, spec):
        lo, hi = spec['default']

        specs = {
            name + "_min": {
                "label": "(min)",
                "min": spec["min"],
                "max": spec["max"],
                "default": lo,
                "digits": _get_digits(spec),
                "type": "scalar"
            },
            name + "_max": {
                "label": "(max)",
                "min": spec["min"],
                "max": spec["max"],
                "default": hi,
                "digits": _get_digits(spec),
                "type": "scalar"
            }
        }

        self._add_group(spec["label"], specs)

    def _build_controls(self, specs=None):
        specs = specs or self.specs
        widget_calls = {
            "scalar": lambda name, spec:
                self._add_scalar(name, spec),
            "range": lambda name, spec:
                self._add_range(name, spec),
            "group": lambda name, spec:
                self._add_group(spec["label"], spec["specs"])
        }
        def skip(name, spec):
            return None

        for name, spec in specs.items():
            widget_calls.get(spec["type"], skip)(name, spec)

        if specs == self.specs:
            self._finish_buttons()

    def _add_button(self, label, callback):
        # TODO: unit test
        raise NotImplementedError("Override in UI subclass")

    def _finish_buttons(self):
        # TODO: unit test
        for label, callback in (
            ("OK", self._ok_clicked),
            ("Reset to defaults", self.revert_cfg),
            ("Save as default", self.save_cfg)
        ):
            self._add_button(label, callback)

    def save_cfg(self):
        # TODO: unit test
        # TODO: abstract config save
        pass

    def revert_cfg(self):
        # TODO: unit test
        # TODO: abstract config revert
        pass

    def _ok_clicked(self):
        raise NotImplementedError("Override in UI subclass")

    def _add_scalar(self, name, spec):
        raise NotImplementedError("Override in UI subclass")

    def get_slider_val(self, spec_name):
        raise NotImplementedError("Override in UI subclass")

    def get_check_enabled(self, spec_name):
        raise NotImplementedError("Override in UI subclass")

    def reset_sticks(self):
        raise NotImplementedError("Override in UI subclass")

    def add_sticks(self, segments):
        raise NotImplementedError("Override in UI subclass")

    def draw_sticks(self):
        raise NotImplementedError("Override in UI subclass")

    def plotXY(self, x, *y, plotset='static',**kwargs):
        raise NotImplementedError("Override in UI subclass")

    def reset_plotsets(self, *plotsets):
        raise NotImplementedError("Override in UI subclass")

    def main_loop(self):
        raise NotImplementedError("Override in UI subclass")

    def setXlabel(self, label):
        raise NotImplementedError("Override in UI subclass")

    def setYlabel(self, label):
        raise NotImplementedError("Override in UI subclass")

    def setXticks(self, on):
        raise NotImplementedError("Override in UI subclass")

    def setYticks(self, on):
        raise NotImplementedError("Override in UI subclass")

_unpacked_specs instance-attribute

_unpacked_specs = {}

bg_color instance-attribute

bg_color = 'w'

cfg instance-attribute

cfg = cfg

checks instance-attribute

checks = {}

color1 class-attribute instance-attribute

color1 = 'b'

color2 class-attribute instance-attribute

color2 = 'magenta'

color3 class-attribute instance-attribute

color3 = 'darkgreen'

fg_color instance-attribute

fg_color = 'k'

plotcolors instance-attribute

plotcolors = {}

plots instance-attribute

plots = {}

sl_rows instance-attribute

sl_rows = _count_sliders(self._unpacked_specs)

sliders instance-attribute

sliders = {}

specs instance-attribute

specs = specs

textboxes instance-attribute

textboxes = {}

__init__

__init__(
    specs={},
    cfg={},
    x_label=None,
    y_label=None,
    x_ticks=True,
    y_ticks=True,
)
Source code in FoSpy/ui/sliders/abstract.py
def __init__(self, specs={}, cfg={}, x_label=None, y_label=None, x_ticks=True, y_ticks=True):
    self.specs = specs
    self._unpacked_specs = {}
    self._unpack_specs()
    self.cfg = cfg
    self.sl_rows = _count_sliders(self._unpacked_specs)
    self.sliders = {}
    self.checks = {}
    self.textboxes = {}
    self.plots = {}
    self.plotcolors = {}

    self.bg_color = 'w'
    self.fg_color = 'k'

    if x_label is not None:
        self.setXlabel(x_label)
    if y_label is not None:
        self.setYlabel(y_label)

    self.setXticks(x_ticks)
    self.setYticks(y_ticks)

_add_button

_add_button(label, callback)
Source code in FoSpy/ui/sliders/abstract.py
def _add_button(self, label, callback):
    # TODO: unit test
    raise NotImplementedError("Override in UI subclass")

_add_group

_add_group(label, specs, rows=CTRL_ROWS)
Source code in FoSpy/ui/sliders/abstract.py
def _add_group(self, label, specs, rows=CTRL_ROWS):
    # cache current panel
    panel = self.panel

    # reassign panel to nested group
    self.panel = panel.addGroup(label, rows=rows)

    # build controls in nested group
    self._build_controls(specs)

    # restore cached panel
    self.panel = panel

_add_range

_add_range(name, spec)
Source code in FoSpy/ui/sliders/abstract.py
def _add_range(self, name, spec):
    lo, hi = spec['default']

    specs = {
        name + "_min": {
            "label": "(min)",
            "min": spec["min"],
            "max": spec["max"],
            "default": lo,
            "digits": _get_digits(spec),
            "type": "scalar"
        },
        name + "_max": {
            "label": "(max)",
            "min": spec["min"],
            "max": spec["max"],
            "default": hi,
            "digits": _get_digits(spec),
            "type": "scalar"
        }
    }

    self._add_group(spec["label"], specs)

_add_scalar

_add_scalar(name, spec)
Source code in FoSpy/ui/sliders/abstract.py
def _add_scalar(self, name, spec):
    raise NotImplementedError("Override in UI subclass")

_build_controls

_build_controls(specs=None)
Source code in FoSpy/ui/sliders/abstract.py
def _build_controls(self, specs=None):
    specs = specs or self.specs
    widget_calls = {
        "scalar": lambda name, spec:
            self._add_scalar(name, spec),
        "range": lambda name, spec:
            self._add_range(name, spec),
        "group": lambda name, spec:
            self._add_group(spec["label"], spec["specs"])
    }
    def skip(name, spec):
        return None

    for name, spec in specs.items():
        widget_calls.get(spec["type"], skip)(name, spec)

    if specs == self.specs:
        self._finish_buttons()

_finish_buttons

_finish_buttons()
Source code in FoSpy/ui/sliders/abstract.py
def _finish_buttons(self):
    # TODO: unit test
    for label, callback in (
        ("OK", self._ok_clicked),
        ("Reset to defaults", self.revert_cfg),
        ("Save as default", self.save_cfg)
    ):
        self._add_button(label, callback)

_ok_clicked

_ok_clicked()
Source code in FoSpy/ui/sliders/abstract.py
def _ok_clicked(self):
    raise NotImplementedError("Override in UI subclass")

_unpack_specs

_unpack_specs(specs=None)
Source code in FoSpy/ui/sliders/abstract.py
def _unpack_specs(self, specs=None):
    specs = specs or self.specs

    for name, spec in specs.items():
        if spec["type"] == "group":
            self._unpack_specs(spec["specs"])
        else:
            self._unpacked_specs[name] = spec

add_sticks

add_sticks(segments)
Source code in FoSpy/ui/sliders/abstract.py
def add_sticks(self, segments):
    raise NotImplementedError("Override in UI subclass")

draw_sticks

draw_sticks()
Source code in FoSpy/ui/sliders/abstract.py
def draw_sticks(self):
    raise NotImplementedError("Override in UI subclass")

get_check_enabled

get_check_enabled(spec_name)
Source code in FoSpy/ui/sliders/abstract.py
def get_check_enabled(self, spec_name):
    raise NotImplementedError("Override in UI subclass")

get_slider_val

get_slider_val(spec_name)
Source code in FoSpy/ui/sliders/abstract.py
def get_slider_val(self, spec_name):
    raise NotImplementedError("Override in UI subclass")

main_loop

main_loop()
Source code in FoSpy/ui/sliders/abstract.py
def main_loop(self):
    raise NotImplementedError("Override in UI subclass")

plotXY

plotXY(x, *y, plotset='static', **kwargs)
Source code in FoSpy/ui/sliders/abstract.py
def plotXY(self, x, *y, plotset='static',**kwargs):
    raise NotImplementedError("Override in UI subclass")

reset_plotsets

reset_plotsets(*plotsets)
Source code in FoSpy/ui/sliders/abstract.py
def reset_plotsets(self, *plotsets):
    raise NotImplementedError("Override in UI subclass")

reset_sticks

reset_sticks()
Source code in FoSpy/ui/sliders/abstract.py
def reset_sticks(self):
    raise NotImplementedError("Override in UI subclass")

revert_cfg

revert_cfg()
Source code in FoSpy/ui/sliders/abstract.py
def revert_cfg(self):
    # TODO: unit test
    # TODO: abstract config revert
    pass

save_cfg

save_cfg()
Source code in FoSpy/ui/sliders/abstract.py
def save_cfg(self):
    # TODO: unit test
    # TODO: abstract config save
    pass

setXlabel

setXlabel(label)
Source code in FoSpy/ui/sliders/abstract.py
def setXlabel(self, label):
    raise NotImplementedError("Override in UI subclass")

setXticks

setXticks(on)
Source code in FoSpy/ui/sliders/abstract.py
def setXticks(self, on):
    raise NotImplementedError("Override in UI subclass")

setYlabel

setYlabel(label)
Source code in FoSpy/ui/sliders/abstract.py
def setYlabel(self, label):
    raise NotImplementedError("Override in UI subclass")

setYticks

setYticks(on)
Source code in FoSpy/ui/sliders/abstract.py
def setYticks(self, on):
    raise NotImplementedError("Override in UI subclass")

update_cfg

update_cfg()
Source code in FoSpy/ui/sliders/abstract.py
def update_cfg(self):
    for name, spec in self._unpacked_specs.items():
        if spec["type"] == "scalar":
            NoneVal = spec.get("None", None)
            enabled = self.get_check_enabled(name)
            self.cfg[name] = self.get_slider_val(name) if enabled else NoneVal

        elif spec["type"] == "range":
            lo_enabled = self.get_check_enabled(name + "_min")
            hi_enabled = self.get_check_enabled(name + "_max")

            lo = self.get_slider_val(name + "_min") if lo_enabled else None
            hi = self.get_slider_val(name + "_max") if hi_enabled else None

            self.cfg[name] = [lo, hi]

update_plot

update_plot(val=None)
Source code in FoSpy/ui/sliders/abstract.py
def update_plot(self, val=None):
    self.update_cfg()

ControlPanel

Bases: QtWidgets.QWidget, AbstractControl

Source code in FoSpy/ui/sliders/pyqtgraph.py
class ControlPanel(QtWidgets.QWidget, AbstractControl):
    def __init__(self, rows=CTRL_ROWS, parent=None):
        _import_gate()


        super().__init__(parent)
        AbstractControl.__init__(self, rows=rows)

        self.layout = QtWidgets.QVBoxLayout(self)

        self.loner_grid = QtWidgets.QGridLayout()
        self.layout.addLayout(self.loner_grid)

        group_layout = QtWidgets.QHBoxLayout()
        self.group_select = QtWidgets.QVBoxLayout()
        self.group_panel = QtWidgets.QStackedWidget()
        group_layout.addLayout(self.group_select)
        group_layout.addWidget(_separator())
        group_layout.addWidget(self.group_panel)
        self.layout.addLayout(group_layout)

        self.group_buttons = QtWidgets.QButtonGroup()
        self.group_buttons.setExclusive(True)



    def addWidget(self, w):
        self.loner_grid.addWidget(w, *self.nextRowCol())

    def addGroup(self, label, rows=CTRL_ROWS):
        stack = ControlPanel(rows=rows, parent=self)

        btn = QtWidgets.QPushButton(label)
        btn.setCheckable(True)
        btn.clicked.connect(lambda _, label=label, stack=stack: self.group_panel.setCurrentWidget(stack))

        self.group_buttons.addButton(btn)
        self.group_select.addWidget(btn)

        self.group_panel.addWidget(stack)

        if self.group_panel.count() == 1:
            self.group_panel.setCurrentWidget(stack)
            btn.setChecked(True)

        return stack

group_buttons instance-attribute

group_buttons = QtWidgets.QButtonGroup()

group_panel instance-attribute

group_panel = QtWidgets.QStackedWidget()

group_select instance-attribute

group_select = QtWidgets.QVBoxLayout()

layout instance-attribute

layout = QtWidgets.QVBoxLayout(self)

loner_grid instance-attribute

loner_grid = QtWidgets.QGridLayout()

__init__

__init__(rows=CTRL_ROWS, parent=None)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def __init__(self, rows=CTRL_ROWS, parent=None):
    _import_gate()


    super().__init__(parent)
    AbstractControl.__init__(self, rows=rows)

    self.layout = QtWidgets.QVBoxLayout(self)

    self.loner_grid = QtWidgets.QGridLayout()
    self.layout.addLayout(self.loner_grid)

    group_layout = QtWidgets.QHBoxLayout()
    self.group_select = QtWidgets.QVBoxLayout()
    self.group_panel = QtWidgets.QStackedWidget()
    group_layout.addLayout(self.group_select)
    group_layout.addWidget(_separator())
    group_layout.addWidget(self.group_panel)
    self.layout.addLayout(group_layout)

    self.group_buttons = QtWidgets.QButtonGroup()
    self.group_buttons.setExclusive(True)

addGroup

addGroup(label, rows=CTRL_ROWS)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def addGroup(self, label, rows=CTRL_ROWS):
    stack = ControlPanel(rows=rows, parent=self)

    btn = QtWidgets.QPushButton(label)
    btn.setCheckable(True)
    btn.clicked.connect(lambda _, label=label, stack=stack: self.group_panel.setCurrentWidget(stack))

    self.group_buttons.addButton(btn)
    self.group_select.addWidget(btn)

    self.group_panel.addWidget(stack)

    if self.group_panel.count() == 1:
        self.group_panel.setCurrentWidget(stack)
        btn.setChecked(True)

    return stack

addWidget

addWidget(w)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def addWidget(self, w):
    self.loner_grid.addWidget(w, *self.nextRowCol())

FallBackModule

Source code in FoSpy/ui/sliders/pyqtgraph.py
class FallBackModule:
    pass

SliderPlot

Bases: AbstractSlider

Source code in FoSpy/ui/sliders/pyqtgraph.py
class SliderPlot(AbstractSlider):
    def __init__(self, title="Interactive Plot", specs={}, cfg={}, x_label=None, y_label=None, x_ticks=True, y_ticks=True):
        _import_gate()

        if not available:
            raise ImportError(f"One or more required modules could not be imported. Exception:\n{import_e}")

        self.scheduled = []

        super().__init__(specs=specs, cfg=cfg, x_label=x_label, y_label=y_label, x_ticks=x_ticks, y_ticks=y_ticks)
        pg.setConfigOption('background', self.bg_color)
        pg.setConfigOption('foreground', self.fg_color)
        self.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
        self.win = QtWidgets.QMainWindow()
        self.win.setWindowTitle(f"PyQtGraph | {title}")
        self.plot = pg.PlotWidget()

        for sch in self.scheduled:
            sch()


        self.sl_transforms = {}



        central = QtWidgets.QWidget()
        self.layout = QtWidgets.QHBoxLayout(central)
        self.win.setCentralWidget(central)

        self.layout.addWidget(self.plot, stretch=3)

        self.panel = ControlPanel(rows=CTRL_ROWS, parent=central)
        self.layout.addWidget(self.panel, stretch=1)
        self._build_controls()


        # this block will no longer be necessary because
        # super()._build_controls() will call self._finish_buttons()
        # ok_btn = QtWidgets.QPushButton("OK")
        # ok_btn.clicked.connect(self._ok_clicked)
        # self.panel.layout.addStretch(1)
        # self.panel.layout.addWidget(ok_btn)

        self.sticks = []

        self._ok_pressed = False
        self._loop = QtCore.QEventLoop()

    def next_ctrl_row(self):
        if self._ctrl_row >= self._layout_rows-1:
            self._ctrl_col += 1
            self._ctrl_row = 0
        else:
            self._ctrl_row += 1

    def _ok_clicked(self):
        self._ok_pressed = True
        self.win.close()
        if self._loop.isRunning():
            self._loop.exit()

    def _build_controls(self, specs=None):
        super()._build_controls(specs=specs)

        self.panel.group_select.addStretch(1)
        self.panel.layout.addStretch(1)

    def _add_scalar(self, name, spec):
        box = QtWidgets.QGroupBox(spec["label"])
        box.setMinimumWidth(MIN_CTRL_WIDTH)
        box.setMaximumHeight(MAX_CTRL_HEIGHT)
        layout = QtWidgets.QHBoxLayout(box)

        # Checkbox
        check = QtWidgets.QCheckBox()
        check.setChecked(spec["default"] is not None)
        layout.addWidget(check)
        self.checks[name] = check

        # Find Slider Default
        if spec['default'] is not None:
            default = spec['default']
        elif name.endswith("_max"):
            default = spec['max']
        else:
            default = spec['min']

        digits = _get_digits(spec)
        step = 10**-digits
        sl_steps = int((spec["max"] - spec["min"]) / step)
        sl_transform = (
            lambda pos, spec=spec, sl_steps=sl_steps: spec["min"] + (pos * (spec["max"] - spec["min"]) / sl_steps),
            lambda val, spec=spec, sl_steps=sl_steps: int(round((val - spec["min"]) * sl_steps / (spec["max"] - spec["min"])))
        )
        self.sl_transforms[name] = sl_transform

        # Slider
        slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
        slider.setMinimum(0)
        slider.setMaximum(sl_steps)
        slider.setValue(self._to_slider_pos(name, default))
        layout.addWidget(slider)
        self.sliders[name] = slider

        # Textbox
        tb = QtWidgets.QLineEdit(f"{default:.{_get_digits(spec)}f}")
        tb.setFixedWidth(TB_WIDTH)
        layout.addWidget(tb)
        self.textboxes[name] = tb

        # Wiring
        slider.valueChanged.connect(lambda _, n=name: self._slider_changed(n))
        check.stateChanged.connect(lambda _, n=name: self._check_changed(n))
        tb.editingFinished.connect(lambda n=name: self._textbox_changed(n))

        self.panel.addWidget(box)

    def _add_button(self, label, callback):
        # TODO: unit test
        btn = QtWidgets.QPushButton(label)
        btn.clicked.connect(callback)
        self._button_column.addWidget(btn)


    def _finish_buttons(self):
        # TODO: unit test
        self._button_column = QtWidgets.QVBoxLayout()
        self._button_column.setAlignment(QtCore.Qt.AlignLeft)
        self.panel.layout.addLayout(self._button_column)


        return super()._finish_buttons()

    def _to_slider_pos(self, name, val):
        return self.sl_transforms[name][1](val)

    def _from_slider_pos(self, name, pos):
        return self.sl_transforms[name][0](pos)

    def setXlabel(self, label):
        if not hasattr(self, "plot"):
            self.scheduled.append(lambda label=label: self.setXlabel(label))
            return
        self.plot.setLabel("bottom", label)

    def setYlabel(self, label):
        if not hasattr(self, "plot"):
            self.scheduled.append(lambda label=label: self.setYlabel(label))
            return
        self.plot.setLabel("left", label)

    def setXticks(self, on):
        if not hasattr(self, "plot"):
            self.scheduled.append(lambda on=on: self.setXticks(on))
            return
        alpha = 1.0 if on else 0
        self.plot.getAxis("bottom").setStyle(tickAlpha=alpha, showValues=on)

    def setYticks(self, on):
        if not hasattr(self, "plot"):
            self.scheduled.append(lambda on=on: self.setYticks(on))
            return
        alpha = 1.0 if on else 0
        self.plot.getAxis("left").setStyle(tickAlpha=alpha, showValues=on)

    # Override abstract gets
    def get_slider_val(self, spec_name):
        slider = self.sliders[spec_name]

        return self._from_slider_pos(spec_name, slider.value())

    def get_check_enabled(self, spec_name):
        checked = self.checks[spec_name].isChecked()
        return checked

    def _slider_changed(self, spec_name):
        spec = self._spec_for(spec_name)
        val = self.get_slider_val(spec_name)
        digits = _get_digits(spec)

        self.textboxes[spec_name].setText(f"{val:.{digits}f}")
        self.update_plot()

    def _textbox_changed(self, spec_name):
        spec = self._spec_for(spec_name)
        tb = self.textboxes[spec_name]
        try:
            val = float(tb.text())
        except ValueError:
            val = self.get_slider_val(spec_name)

        # clamp to range:
        val = max(spec["min"], min(spec["max"], val))
        tb.setText(f"{val:.{_get_digits(spec)}f}")

        self.sliders[spec_name].setValue(self._to_slider_pos(spec_name, val))
        self.update_plot()

    def _check_changed(self, spec_name):
        self.update_plot()

    def _spec_for(self, spec_name):
        """Return the spec dict for scalar or range-min/max."""
        specs = self._unpacked_specs
        if spec_name.endswith("_min"):
            return {
                **specs[spec_name[:-4]],
                "default": specs[spec_name[:-4]]["default"][0]
            }
        if spec_name.endswith("_max"):
            return {
                **specs[spec_name[:-4]],
                "default": specs[spec_name[:-4]]["default"][1]
            }
        return specs[spec_name]

    def reset_sticks(self):
        for stick in self.sticks:
            self.plot.removeItem(stick)

        self.sticks.clear()

    def add_sticks(self, segments, plotset='static'):
        color = self.plotcolors.get(plotset, 'r')
        for (x0, y0), (x1, y1) in segments:
            stick = pg.PlotDataItem(
                x=[x0, x1],
                y=[y0, y1],
                pen=pg.mkPen(color,width=2)
            )
            self.sticks.append(stick)

    def draw_sticks(self):
        for item in self.sticks:
            self.plot.addItem(item)

    def draw_width_bracket(self, x_left, x_right, y, cap_height=0.1, plotset='static', color=None):
        self.plots.setdefault(plotset, [])
        if color is None:
            color = self.plotcolors.get(plotset, 'r')
        pen = pg.mkPen(color, width=1)


        hline = pg.PlotDataItem(
            x=[x_left, x_right],
            y=[y, y],
            pen=pen
        )

        l_cap, r_cap = [
            pg.PlotDataItem(
                x=[x, x],
                y=[y - cap_height, y + cap_height],
                pen=pen)
            for x in (x_left, x_right)
        ]

        for item in [hline, l_cap, r_cap]:
            self.plot.addItem(item)
            self.plots[plotset].append(item)

    def plotXY(self, x, *y, plotset='static', color=None, **kwargs):
        self.plots.setdefault(plotset, [])

        if color is None:
            color = self.plotcolors.get(plotset, 'b')

        for yi in y:
            item = self.plot.plot(x, yi, pen=pg.mkPen(color, width=2), **kwargs)
            self.plots[plotset].append(item)

    def reset_plotsets(self, *plotsets):
        for plotset in plotsets:
            if plotset not in self.plots:
                return

            for item in self.plots[plotset]:
                self.plot.removeItem(item)

            self.plots[plotset] = []

    def update_plot(self, val=None):
        super().update_plot(val)

    def main_loop(self):
        self.update_plot()

        self.win.show()
        self.app.processEvents()

        while not self._ok_pressed:
            self._loop.exec_()

        return None

_loop instance-attribute

_loop = QtCore.QEventLoop()

_ok_pressed instance-attribute

_ok_pressed = False

app instance-attribute

app = (
    QtWidgets.QApplication.instance()
    or QtWidgets.QApplication([])
)

layout instance-attribute

layout = QtWidgets.QHBoxLayout(central)

panel instance-attribute

panel = ControlPanel(rows=CTRL_ROWS, parent=central)

plot instance-attribute

plot = pg.PlotWidget()

scheduled instance-attribute

scheduled = []

sl_transforms instance-attribute

sl_transforms = {}

sticks instance-attribute

sticks = []

win instance-attribute

win = QtWidgets.QMainWindow()

__init__

__init__(
    title="Interactive Plot",
    specs={},
    cfg={},
    x_label=None,
    y_label=None,
    x_ticks=True,
    y_ticks=True,
)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def __init__(self, title="Interactive Plot", specs={}, cfg={}, x_label=None, y_label=None, x_ticks=True, y_ticks=True):
    _import_gate()

    if not available:
        raise ImportError(f"One or more required modules could not be imported. Exception:\n{import_e}")

    self.scheduled = []

    super().__init__(specs=specs, cfg=cfg, x_label=x_label, y_label=y_label, x_ticks=x_ticks, y_ticks=y_ticks)
    pg.setConfigOption('background', self.bg_color)
    pg.setConfigOption('foreground', self.fg_color)
    self.app = QtWidgets.QApplication.instance() or QtWidgets.QApplication([])
    self.win = QtWidgets.QMainWindow()
    self.win.setWindowTitle(f"PyQtGraph | {title}")
    self.plot = pg.PlotWidget()

    for sch in self.scheduled:
        sch()


    self.sl_transforms = {}



    central = QtWidgets.QWidget()
    self.layout = QtWidgets.QHBoxLayout(central)
    self.win.setCentralWidget(central)

    self.layout.addWidget(self.plot, stretch=3)

    self.panel = ControlPanel(rows=CTRL_ROWS, parent=central)
    self.layout.addWidget(self.panel, stretch=1)
    self._build_controls()


    # this block will no longer be necessary because
    # super()._build_controls() will call self._finish_buttons()
    # ok_btn = QtWidgets.QPushButton("OK")
    # ok_btn.clicked.connect(self._ok_clicked)
    # self.panel.layout.addStretch(1)
    # self.panel.layout.addWidget(ok_btn)

    self.sticks = []

    self._ok_pressed = False
    self._loop = QtCore.QEventLoop()

_add_button

_add_button(label, callback)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _add_button(self, label, callback):
    # TODO: unit test
    btn = QtWidgets.QPushButton(label)
    btn.clicked.connect(callback)
    self._button_column.addWidget(btn)

_add_scalar

_add_scalar(name, spec)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _add_scalar(self, name, spec):
    box = QtWidgets.QGroupBox(spec["label"])
    box.setMinimumWidth(MIN_CTRL_WIDTH)
    box.setMaximumHeight(MAX_CTRL_HEIGHT)
    layout = QtWidgets.QHBoxLayout(box)

    # Checkbox
    check = QtWidgets.QCheckBox()
    check.setChecked(spec["default"] is not None)
    layout.addWidget(check)
    self.checks[name] = check

    # Find Slider Default
    if spec['default'] is not None:
        default = spec['default']
    elif name.endswith("_max"):
        default = spec['max']
    else:
        default = spec['min']

    digits = _get_digits(spec)
    step = 10**-digits
    sl_steps = int((spec["max"] - spec["min"]) / step)
    sl_transform = (
        lambda pos, spec=spec, sl_steps=sl_steps: spec["min"] + (pos * (spec["max"] - spec["min"]) / sl_steps),
        lambda val, spec=spec, sl_steps=sl_steps: int(round((val - spec["min"]) * sl_steps / (spec["max"] - spec["min"])))
    )
    self.sl_transforms[name] = sl_transform

    # Slider
    slider = QtWidgets.QSlider(QtCore.Qt.Horizontal)
    slider.setMinimum(0)
    slider.setMaximum(sl_steps)
    slider.setValue(self._to_slider_pos(name, default))
    layout.addWidget(slider)
    self.sliders[name] = slider

    # Textbox
    tb = QtWidgets.QLineEdit(f"{default:.{_get_digits(spec)}f}")
    tb.setFixedWidth(TB_WIDTH)
    layout.addWidget(tb)
    self.textboxes[name] = tb

    # Wiring
    slider.valueChanged.connect(lambda _, n=name: self._slider_changed(n))
    check.stateChanged.connect(lambda _, n=name: self._check_changed(n))
    tb.editingFinished.connect(lambda n=name: self._textbox_changed(n))

    self.panel.addWidget(box)

_build_controls

_build_controls(specs=None)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _build_controls(self, specs=None):
    super()._build_controls(specs=specs)

    self.panel.group_select.addStretch(1)
    self.panel.layout.addStretch(1)

_check_changed

_check_changed(spec_name)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _check_changed(self, spec_name):
    self.update_plot()

_finish_buttons

_finish_buttons()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _finish_buttons(self):
    # TODO: unit test
    self._button_column = QtWidgets.QVBoxLayout()
    self._button_column.setAlignment(QtCore.Qt.AlignLeft)
    self.panel.layout.addLayout(self._button_column)


    return super()._finish_buttons()

_from_slider_pos

_from_slider_pos(name, pos)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _from_slider_pos(self, name, pos):
    return self.sl_transforms[name][0](pos)

_ok_clicked

_ok_clicked()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _ok_clicked(self):
    self._ok_pressed = True
    self.win.close()
    if self._loop.isRunning():
        self._loop.exit()

_slider_changed

_slider_changed(spec_name)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _slider_changed(self, spec_name):
    spec = self._spec_for(spec_name)
    val = self.get_slider_val(spec_name)
    digits = _get_digits(spec)

    self.textboxes[spec_name].setText(f"{val:.{digits}f}")
    self.update_plot()

_spec_for

_spec_for(spec_name)

Return the spec dict for scalar or range-min/max.

Source code in FoSpy/ui/sliders/pyqtgraph.py
def _spec_for(self, spec_name):
    """Return the spec dict for scalar or range-min/max."""
    specs = self._unpacked_specs
    if spec_name.endswith("_min"):
        return {
            **specs[spec_name[:-4]],
            "default": specs[spec_name[:-4]]["default"][0]
        }
    if spec_name.endswith("_max"):
        return {
            **specs[spec_name[:-4]],
            "default": specs[spec_name[:-4]]["default"][1]
        }
    return specs[spec_name]

_textbox_changed

_textbox_changed(spec_name)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _textbox_changed(self, spec_name):
    spec = self._spec_for(spec_name)
    tb = self.textboxes[spec_name]
    try:
        val = float(tb.text())
    except ValueError:
        val = self.get_slider_val(spec_name)

    # clamp to range:
    val = max(spec["min"], min(spec["max"], val))
    tb.setText(f"{val:.{_get_digits(spec)}f}")

    self.sliders[spec_name].setValue(self._to_slider_pos(spec_name, val))
    self.update_plot()

_to_slider_pos

_to_slider_pos(name, val)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _to_slider_pos(self, name, val):
    return self.sl_transforms[name][1](val)

add_sticks

add_sticks(segments, plotset='static')
Source code in FoSpy/ui/sliders/pyqtgraph.py
def add_sticks(self, segments, plotset='static'):
    color = self.plotcolors.get(plotset, 'r')
    for (x0, y0), (x1, y1) in segments:
        stick = pg.PlotDataItem(
            x=[x0, x1],
            y=[y0, y1],
            pen=pg.mkPen(color,width=2)
        )
        self.sticks.append(stick)

draw_sticks

draw_sticks()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def draw_sticks(self):
    for item in self.sticks:
        self.plot.addItem(item)

draw_width_bracket

draw_width_bracket(
    x_left,
    x_right,
    y,
    cap_height=0.1,
    plotset="static",
    color=None,
)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def draw_width_bracket(self, x_left, x_right, y, cap_height=0.1, plotset='static', color=None):
    self.plots.setdefault(plotset, [])
    if color is None:
        color = self.plotcolors.get(plotset, 'r')
    pen = pg.mkPen(color, width=1)


    hline = pg.PlotDataItem(
        x=[x_left, x_right],
        y=[y, y],
        pen=pen
    )

    l_cap, r_cap = [
        pg.PlotDataItem(
            x=[x, x],
            y=[y - cap_height, y + cap_height],
            pen=pen)
        for x in (x_left, x_right)
    ]

    for item in [hline, l_cap, r_cap]:
        self.plot.addItem(item)
        self.plots[plotset].append(item)

get_check_enabled

get_check_enabled(spec_name)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def get_check_enabled(self, spec_name):
    checked = self.checks[spec_name].isChecked()
    return checked

get_slider_val

get_slider_val(spec_name)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def get_slider_val(self, spec_name):
    slider = self.sliders[spec_name]

    return self._from_slider_pos(spec_name, slider.value())

main_loop

main_loop()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def main_loop(self):
    self.update_plot()

    self.win.show()
    self.app.processEvents()

    while not self._ok_pressed:
        self._loop.exec_()

    return None

next_ctrl_row

next_ctrl_row()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def next_ctrl_row(self):
    if self._ctrl_row >= self._layout_rows-1:
        self._ctrl_col += 1
        self._ctrl_row = 0
    else:
        self._ctrl_row += 1

plotXY

plotXY(x, *y, plotset='static', color=None, **kwargs)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def plotXY(self, x, *y, plotset='static', color=None, **kwargs):
    self.plots.setdefault(plotset, [])

    if color is None:
        color = self.plotcolors.get(plotset, 'b')

    for yi in y:
        item = self.plot.plot(x, yi, pen=pg.mkPen(color, width=2), **kwargs)
        self.plots[plotset].append(item)

reset_plotsets

reset_plotsets(*plotsets)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def reset_plotsets(self, *plotsets):
    for plotset in plotsets:
        if plotset not in self.plots:
            return

        for item in self.plots[plotset]:
            self.plot.removeItem(item)

        self.plots[plotset] = []

reset_sticks

reset_sticks()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def reset_sticks(self):
    for stick in self.sticks:
        self.plot.removeItem(stick)

    self.sticks.clear()

setXlabel

setXlabel(label)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def setXlabel(self, label):
    if not hasattr(self, "plot"):
        self.scheduled.append(lambda label=label: self.setXlabel(label))
        return
    self.plot.setLabel("bottom", label)

setXticks

setXticks(on)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def setXticks(self, on):
    if not hasattr(self, "plot"):
        self.scheduled.append(lambda on=on: self.setXticks(on))
        return
    alpha = 1.0 if on else 0
    self.plot.getAxis("bottom").setStyle(tickAlpha=alpha, showValues=on)

setYlabel

setYlabel(label)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def setYlabel(self, label):
    if not hasattr(self, "plot"):
        self.scheduled.append(lambda label=label: self.setYlabel(label))
        return
    self.plot.setLabel("left", label)

setYticks

setYticks(on)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def setYticks(self, on):
    if not hasattr(self, "plot"):
        self.scheduled.append(lambda on=on: self.setYticks(on))
        return
    alpha = 1.0 if on else 0
    self.plot.getAxis("left").setStyle(tickAlpha=alpha, showValues=on)

update_plot

update_plot(val=None)
Source code in FoSpy/ui/sliders/pyqtgraph.py
def update_plot(self, val=None):
    super().update_plot(val)

UINotAvailable

Bases: Exception

Source code in FoSpy/ui/available.py
class UINotAvailable(Exception):
    pass

_get_digits

_get_digits(spec)
Source code in FoSpy/ui/sliders/_utils.py
def _get_digits(spec):
    from ...config import values as cfg
    default = cfg.get("slider_digits.default", DEF_DIGITS)
    return spec.get("digits", default)

_import_gate

_import_gate()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _import_gate():
    global available, import_e
    if not available:
        raise import_e

_separator

_separator()
Source code in FoSpy/ui/sliders/pyqtgraph.py
def _separator():
    sep = QtWidgets.QFrame()
    sep.setFrameShape(QtWidgets.QFrame.VLine)
    sep.setFrameShadow(QtWidgets.QFrame.Sunken)
    sep.setLineWidth(2)   # thickness
    sep.setMidLineWidth(1)
    return sep

validate_ui

validate_ui(ui_name)
Source code in FoSpy/ui/available.py
def validate_ui(ui_name):
    ui_lower = ui_name.lower()
    if ui_lower not in _UI_REQUIREMENTS:
        valids = ", ".join(_UI_REQUIREMENTS.keys())
        raise UINotAvailable(f"UI '{ui_name}' is not a valid UI. Valid options are: {valids}.")

    if ui_lower not in AVAILABLE_UIS:
        raise UINotAvailable(
            f"The '{ui_name}' UI backend cannot be used because it is missing dependencies. "
            "Refer to the summary below and install the missing dependencies to use this UI.\n\n"
            f"{_summarize_reqs(_UI_REQUIREMENTS[ui_lower])}"
        )