Skip to content

native

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.native

CTRL_ROWS module-attribute

CTRL_ROWS = 6

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: tk.Frame, AbstractControl

Source code in FoSpy/ui/sliders/native.py
class ControlPanel(tk.Frame,AbstractControl):
    def __init__(self, rows=CTRL_ROWS, parent=None, add_buttons=False):
        _import_gate()

        tk.Frame.__init__(self, parent)
        AbstractControl.__init__(self, rows=rows)

        self._seps = []

        self.loner_grid = tk.Frame(self)
        self.loner_grid.pack(side='top', fill='x')
        self._seps.append(_hseparator(self))

        if add_buttons:
            self.button_panel = tk.Frame(self)
            self.button_panel.pack(side='bottom', fill='y', anchor='nw', expand=True)

        self.group_select = tk.Frame(self)
        self.group_select.pack(side='left',anchor='n', fill='none')

        self.groups = {}

    @classmethod
    def withButtons(cls, *args, **kwargs):
        kwargs.setdefault('add_buttons', True)
        obj = cls(*args, **kwargs)
        return obj, obj.button_panel

    def addWidget(self, w):
        r,c = self.nextRowCol()
        w.grid(in_=self.loner_grid, row=r, column=c, sticky='w')



    def addGroup(self, label, rows=CTRL_ROWS):
        if len(self.groups) == 0:
            self._seps.append(_vseparator(self))

            self.group_panel = tk.Frame(self)
            self.group_panel.pack(side='left', fill= 'x', anchor='n', expand=True)

        btn = tk.Button(self.group_select, text=label)
        btn.pack(fill='x')

        frame = ControlPanel(parent=self.group_panel, rows=rows)

        self.groups[frame] = btn

        btn.config(command=lambda f=frame: self._show_group(f))

        self._show_group(frame)

        return frame

    def _show_group(self, frame):
        for g, b in self.groups.items():
            g.pack_forget()

            # unselect button
            b.config(relief='raised')

        frame.pack(fill='x', expand=True)
        self.groups[frame].config(relief='sunken')

_seps instance-attribute

_seps = []

button_panel instance-attribute

button_panel = tk.Frame(self)

group_select instance-attribute

group_select = tk.Frame(self)

groups instance-attribute

groups = {}

loner_grid instance-attribute

loner_grid = tk.Frame(self)

__init__

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

    tk.Frame.__init__(self, parent)
    AbstractControl.__init__(self, rows=rows)

    self._seps = []

    self.loner_grid = tk.Frame(self)
    self.loner_grid.pack(side='top', fill='x')
    self._seps.append(_hseparator(self))

    if add_buttons:
        self.button_panel = tk.Frame(self)
        self.button_panel.pack(side='bottom', fill='y', anchor='nw', expand=True)

    self.group_select = tk.Frame(self)
    self.group_select.pack(side='left',anchor='n', fill='none')

    self.groups = {}

_show_group

_show_group(frame)
Source code in FoSpy/ui/sliders/native.py
def _show_group(self, frame):
    for g, b in self.groups.items():
        g.pack_forget()

        # unselect button
        b.config(relief='raised')

    frame.pack(fill='x', expand=True)
    self.groups[frame].config(relief='sunken')

addGroup

addGroup(label, rows=CTRL_ROWS)
Source code in FoSpy/ui/sliders/native.py
def addGroup(self, label, rows=CTRL_ROWS):
    if len(self.groups) == 0:
        self._seps.append(_vseparator(self))

        self.group_panel = tk.Frame(self)
        self.group_panel.pack(side='left', fill= 'x', anchor='n', expand=True)

    btn = tk.Button(self.group_select, text=label)
    btn.pack(fill='x')

    frame = ControlPanel(parent=self.group_panel, rows=rows)

    self.groups[frame] = btn

    btn.config(command=lambda f=frame: self._show_group(f))

    self._show_group(frame)

    return frame

addWidget

addWidget(w)
Source code in FoSpy/ui/sliders/native.py
def addWidget(self, w):
    r,c = self.nextRowCol()
    w.grid(in_=self.loner_grid, row=r, column=c, sticky='w')

withButtons classmethod

withButtons(*args, **kwargs)
Source code in FoSpy/ui/sliders/native.py
@classmethod
def withButtons(cls, *args, **kwargs):
    kwargs.setdefault('add_buttons', True)
    obj = cls(*args, **kwargs)
    return obj, obj.button_panel

SliderPlot

Bases: AbstractSlider

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

        _import_gate()

        from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
        from matplotlib.figure import Figure

        self.root = tk.Tk()
        self.fig = Figure(figsize=figsize)
        self.ax = self.fig.add_subplot(111)

        self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
        self.canvas.get_tk_widget().pack(side="left", fill="both", expand=True)

        self.panel, self._button_column = ControlPanel.withButtons(parent=self.root)
        self.panel.pack(side="right", fill="y")

        self._buttons = []

        super().__init__(specs=specs, cfg=cfg, x_label=x_label, y_label=y_label, x_ticks=x_ticks, y_ticks=y_ticks)

        self._build_controls()

        self._return = tk.BooleanVar(value=False)

        self.stickCollection = LineCollection([], colors='r')
        self.ax.add_collection(self.stickCollection)

    def _add_scalar(self, name, spec):
        frame = tk.LabelFrame(self.panel, text=spec["label"])

        none_val = spec["max"] if name.endswith("_max") else spec["min"]
        default = spec["default"] or spec.get("None", none_val)
        digits = _get_digits(spec)

        var_enabled = tk.BooleanVar(value=default is not None)
        var_slider = tk.DoubleVar(value=default)
        var_text = tk.StringVar(value=f"{default:.{digits}f}")

        #checkbox
        chk = tk.Checkbutton(frame, variable=var_enabled)
        chk.pack(side='left')

        #slider
        slider = tk.Scale(frame, from_=spec["min"], to=spec["max"], orient='horizontal', variable=var_slider, resolution=10**-digits)
        slider.pack(side="left", fill="x", expand=True)

        # textbox
        entry = tk.Entry(frame, textvariable=var_text, width=8)
        entry.pack(side="left")

        self.checks[name] = var_enabled
        self.sliders[name] = var_slider
        self.textboxes[name] = var_text

        var_slider.trace_add("write", lambda *_ ,n=name, d=digits: self.slider_changed(n, d))
        var_text.trace_add("write", lambda *_ ,n=name, d=digits, m=spec["min"], M=spec["max"]: self.textbox_changed(n, d, m, M))
        var_enabled.trace_add("write", lambda *_ ,n=name: self.toggle_slider(n))

        self.panel.addWidget(frame)

    def slider_changed(self, name, digits):
        val = self.sliders[name].get()

        text = self.textboxes[name]
        text.set(f"{val:.{digits}f}")

        self.update_plot()

    def textbox_changed(self, name, digits, min_, max_):
        tb = self.textboxes[name]
        slider = self.sliders[name]

        txt = tb.get()
        try:
            val = float(txt)
        except ValueError:
            # restore slider value
            sl_val = slider.get()
            tb.set(f"{sl_val:.{digits}f}")
            return

        # clamp
        val = max(min_, min(max_, val))

        slider.set(val)
        tb.set(f"{val:.{digits}f}")

        self.update_plot()

    def _add_button(self, label, callback):
        # TODO: unit test
        btn = tk.Button(self._button_column, text=label, command=callback)
        btn.pack(side='top', anchor='w', pady=2)

        self._buttons.append(btn)

    def _finish_buttons(self):
        # TODO: unit test

        super()._finish_buttons()


    def _ok_clicked(self):
        self._return.set(True)
        self.root.destroy()

    def setXlabel(self, label):
        self.ax.set_xlabel(label)

    def setYlabel(self, label):
        self.ax.set_ylabel(label)

    def setXticks(self, on):
        self.ax.tick_params(which='both', bottom=on, labelbottom=on)

    def setYticks(self, on):
        self.ax.tick_params(which='both', left=on, labelleft=on)


    def disable_slider(self, spec_name):
        sl = self.sliders[spec_name]
        tb = self.textboxes[spec_name]

        sl.config(state="disabled")
        tb.config(state="disabled")

        self.update_plot()


    def enable_slider(self, spec_name):
        sl = self.sliders[spec_name]
        tb = self.textboxes[spec_name]

        sl.config(state="normal")
        tb.config(state="normal")

        self.update_plot()


    def toggle_slider(self, spec_name):
        if self.get_check_enabled(spec_name):
            self.enable_slider(spec_name)
        else:
            self.disable_slider(spec_name)

    def get_slider_val(self, spec_name):
        return self.sliders[spec_name].get()

    def get_check_enabled(self, spec_name):
        return self.checks[spec_name].get()

    def reset_sticks(self):
        self.stickCollection.set_segments([])

    def add_sticks(self, segments, plotset='static'):
        existing = self.stickCollection.get_segments()
        self.stickCollection.set_segments(existing + segments)

    def draw_sticks(self):
        self.ax.draw_artist(self.stickCollection)

    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:
            lineset = self.ax.plot(x, yi, color=color, **kwargs)
            self.plots[plotset].append(lineset)

    def draw_width_bracket(self, x_left, x_right, y, cap_height=0.1,
                        plotset='static', color=None):

        # Ensure plotset exists
        self.plots.setdefault(plotset, [])

        # Choose color
        if color is None:
            color = self.plotcolors.get(plotset, 'r')

        # Horizontal line
        hline, = self.ax.plot(
            [x_left, x_right],
            [y, y],
            color=color,
            linewidth=1
        )

        # Left cap
        lcap, = self.ax.plot(
            [x_left, x_left],
            [y - cap_height, y + cap_height],
            color=color,
            linewidth=1
        )

        # Right cap
        rcap, = self.ax.plot(
            [x_right, x_right],
            [y - cap_height, y + cap_height],
            color=color,
            linewidth=1
        )

        # Store all three artists in the plotset
        for item in (hline, lcap, rcap):
            self.plots[plotset].append(item)

        # Redraw
        self.canvas.draw_idle()


    def reset_plotsets(self, *plotsets):
        for plotset in plotsets:
            if plotset not in self.plots:
                return
            for drawer in self.plots[plotset]:
                if isinstance(drawer, list):
                    for d in drawer:
                        d.remove()
                else:
                    drawer.remove()
            self.plots[plotset] = []

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

    def main_loop(self):
        self.update_plot()
        self.root.wait_variable(self._return)

_buttons instance-attribute

_buttons = []

_return instance-attribute

_return = tk.BooleanVar(value=False)

ax instance-attribute

ax = self.fig.add_subplot(111)

canvas instance-attribute

canvas = FigureCanvasTkAgg(self.fig, master=self.root)

fig instance-attribute

fig = Figure(figsize=figsize)

root instance-attribute

root = tk.Tk()

stickCollection instance-attribute

stickCollection = LineCollection([], colors='r')

__init__

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

    _import_gate()

    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    from matplotlib.figure import Figure

    self.root = tk.Tk()
    self.fig = Figure(figsize=figsize)
    self.ax = self.fig.add_subplot(111)

    self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
    self.canvas.get_tk_widget().pack(side="left", fill="both", expand=True)

    self.panel, self._button_column = ControlPanel.withButtons(parent=self.root)
    self.panel.pack(side="right", fill="y")

    self._buttons = []

    super().__init__(specs=specs, cfg=cfg, x_label=x_label, y_label=y_label, x_ticks=x_ticks, y_ticks=y_ticks)

    self._build_controls()

    self._return = tk.BooleanVar(value=False)

    self.stickCollection = LineCollection([], colors='r')
    self.ax.add_collection(self.stickCollection)

_add_button

_add_button(label, callback)
Source code in FoSpy/ui/sliders/native.py
def _add_button(self, label, callback):
    # TODO: unit test
    btn = tk.Button(self._button_column, text=label, command=callback)
    btn.pack(side='top', anchor='w', pady=2)

    self._buttons.append(btn)

_add_scalar

_add_scalar(name, spec)
Source code in FoSpy/ui/sliders/native.py
def _add_scalar(self, name, spec):
    frame = tk.LabelFrame(self.panel, text=spec["label"])

    none_val = spec["max"] if name.endswith("_max") else spec["min"]
    default = spec["default"] or spec.get("None", none_val)
    digits = _get_digits(spec)

    var_enabled = tk.BooleanVar(value=default is not None)
    var_slider = tk.DoubleVar(value=default)
    var_text = tk.StringVar(value=f"{default:.{digits}f}")

    #checkbox
    chk = tk.Checkbutton(frame, variable=var_enabled)
    chk.pack(side='left')

    #slider
    slider = tk.Scale(frame, from_=spec["min"], to=spec["max"], orient='horizontal', variable=var_slider, resolution=10**-digits)
    slider.pack(side="left", fill="x", expand=True)

    # textbox
    entry = tk.Entry(frame, textvariable=var_text, width=8)
    entry.pack(side="left")

    self.checks[name] = var_enabled
    self.sliders[name] = var_slider
    self.textboxes[name] = var_text

    var_slider.trace_add("write", lambda *_ ,n=name, d=digits: self.slider_changed(n, d))
    var_text.trace_add("write", lambda *_ ,n=name, d=digits, m=spec["min"], M=spec["max"]: self.textbox_changed(n, d, m, M))
    var_enabled.trace_add("write", lambda *_ ,n=name: self.toggle_slider(n))

    self.panel.addWidget(frame)

_finish_buttons

_finish_buttons()
Source code in FoSpy/ui/sliders/native.py
def _finish_buttons(self):
    # TODO: unit test

    super()._finish_buttons()

_ok_clicked

_ok_clicked()
Source code in FoSpy/ui/sliders/native.py
def _ok_clicked(self):
    self._return.set(True)
    self.root.destroy()

add_sticks

add_sticks(segments, plotset='static')
Source code in FoSpy/ui/sliders/native.py
def add_sticks(self, segments, plotset='static'):
    existing = self.stickCollection.get_segments()
    self.stickCollection.set_segments(existing + segments)

disable_slider

disable_slider(spec_name)
Source code in FoSpy/ui/sliders/native.py
def disable_slider(self, spec_name):
    sl = self.sliders[spec_name]
    tb = self.textboxes[spec_name]

    sl.config(state="disabled")
    tb.config(state="disabled")

    self.update_plot()

draw_sticks

draw_sticks()
Source code in FoSpy/ui/sliders/native.py
def draw_sticks(self):
    self.ax.draw_artist(self.stickCollection)

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/native.py
def draw_width_bracket(self, x_left, x_right, y, cap_height=0.1,
                    plotset='static', color=None):

    # Ensure plotset exists
    self.plots.setdefault(plotset, [])

    # Choose color
    if color is None:
        color = self.plotcolors.get(plotset, 'r')

    # Horizontal line
    hline, = self.ax.plot(
        [x_left, x_right],
        [y, y],
        color=color,
        linewidth=1
    )

    # Left cap
    lcap, = self.ax.plot(
        [x_left, x_left],
        [y - cap_height, y + cap_height],
        color=color,
        linewidth=1
    )

    # Right cap
    rcap, = self.ax.plot(
        [x_right, x_right],
        [y - cap_height, y + cap_height],
        color=color,
        linewidth=1
    )

    # Store all three artists in the plotset
    for item in (hline, lcap, rcap):
        self.plots[plotset].append(item)

    # Redraw
    self.canvas.draw_idle()

enable_slider

enable_slider(spec_name)
Source code in FoSpy/ui/sliders/native.py
def enable_slider(self, spec_name):
    sl = self.sliders[spec_name]
    tb = self.textboxes[spec_name]

    sl.config(state="normal")
    tb.config(state="normal")

    self.update_plot()

get_check_enabled

get_check_enabled(spec_name)
Source code in FoSpy/ui/sliders/native.py
def get_check_enabled(self, spec_name):
    return self.checks[spec_name].get()

get_slider_val

get_slider_val(spec_name)
Source code in FoSpy/ui/sliders/native.py
def get_slider_val(self, spec_name):
    return self.sliders[spec_name].get()

main_loop

main_loop()
Source code in FoSpy/ui/sliders/native.py
def main_loop(self):
    self.update_plot()
    self.root.wait_variable(self._return)

plotXY

plotXY(x, *y, plotset='static', color=None, **kwargs)
Source code in FoSpy/ui/sliders/native.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:
        lineset = self.ax.plot(x, yi, color=color, **kwargs)
        self.plots[plotset].append(lineset)

reset_plotsets

reset_plotsets(*plotsets)
Source code in FoSpy/ui/sliders/native.py
def reset_plotsets(self, *plotsets):
    for plotset in plotsets:
        if plotset not in self.plots:
            return
        for drawer in self.plots[plotset]:
            if isinstance(drawer, list):
                for d in drawer:
                    d.remove()
            else:
                drawer.remove()
        self.plots[plotset] = []

reset_sticks

reset_sticks()
Source code in FoSpy/ui/sliders/native.py
def reset_sticks(self):
    self.stickCollection.set_segments([])

setXlabel

setXlabel(label)
Source code in FoSpy/ui/sliders/native.py
def setXlabel(self, label):
    self.ax.set_xlabel(label)

setXticks

setXticks(on)
Source code in FoSpy/ui/sliders/native.py
def setXticks(self, on):
    self.ax.tick_params(which='both', bottom=on, labelbottom=on)

setYlabel

setYlabel(label)
Source code in FoSpy/ui/sliders/native.py
def setYlabel(self, label):
    self.ax.set_ylabel(label)

setYticks

setYticks(on)
Source code in FoSpy/ui/sliders/native.py
def setYticks(self, on):
    self.ax.tick_params(which='both', left=on, labelleft=on)

slider_changed

slider_changed(name, digits)
Source code in FoSpy/ui/sliders/native.py
def slider_changed(self, name, digits):
    val = self.sliders[name].get()

    text = self.textboxes[name]
    text.set(f"{val:.{digits}f}")

    self.update_plot()

textbox_changed

textbox_changed(name, digits, min_, max_)
Source code in FoSpy/ui/sliders/native.py
def textbox_changed(self, name, digits, min_, max_):
    tb = self.textboxes[name]
    slider = self.sliders[name]

    txt = tb.get()
    try:
        val = float(txt)
    except ValueError:
        # restore slider value
        sl_val = slider.get()
        tb.set(f"{sl_val:.{digits}f}")
        return

    # clamp
    val = max(min_, min(max_, val))

    slider.set(val)
    tb.set(f"{val:.{digits}f}")

    self.update_plot()

toggle_slider

toggle_slider(spec_name)
Source code in FoSpy/ui/sliders/native.py
def toggle_slider(self, spec_name):
    if self.get_check_enabled(spec_name):
        self.enable_slider(spec_name)
    else:
        self.disable_slider(spec_name)

update_plot

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

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)

_hseparator

_hseparator(parent=None)
Source code in FoSpy/ui/sliders/native.py
def _hseparator(parent=None):
    frame = tk.Frame(parent, height=2, bg="#888")
    frame.pack(fill='x', side='top', padx=4, pady=4)
    return frame

_import_gate

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

_vseparator

_vseparator(parent=None)
Source code in FoSpy/ui/sliders/native.py
def _vseparator(parent=None):
    frame = tk.Frame(parent, width=2, height=1, bg="#888")
    frame.pack(fill='y',side='left', padx=4, pady=4)
    return frame

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])}"
        )