Skip to content

Commit c5b5d79

Browse files
committed
Slight cleanup and refactor of sliderpack
1 parent 2a953d6 commit c5b5d79

2 files changed

Lines changed: 89 additions & 93 deletions

File tree

MDANSE_GUI/Src/MDANSE_GUI/Tabs/Plotters/Plotter.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ def enum_to_str(operation: NormOperations) -> str:
9595
"operation": NormOperations.AVERAGE,
9696
}
9797

98+
ValidPlotters = Literal["Grid", "Heatmap", "Single"]
99+
98100

99101
class Plotter(RegisterFactory):
100102
"""Parent class to all classes used for displaying data."""

MDANSE_GUI/Src/MDANSE_GUI/Tabs/Visualisers/PlotWidget.py

Lines changed: 87 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#
1616
from __future__ import annotations
1717

18+
from itertools import islice
1819
from pathlib import Path
1920
from typing import TYPE_CHECKING
2021

@@ -39,7 +40,7 @@
3940

4041
from MDANSE.MLogging import LOG
4142
from MDANSE_GUI.PlotUtils import MDANSEMatPlotLibNavBar
42-
from MDANSE_GUI.Tabs.Plotters.Plotter import Plotter
43+
from MDANSE_GUI.Tabs.Plotters.Plotter import Plotter, ValidPlotters
4344
from MDANSE_GUI.Utils import block_signals
4445
from MDANSE_GUI.Widgets.NormalisationWidget import NormalisationWidget
4546
from MDANSE_GUI.Widgets.RestrictedSlider import RestrictedSlider
@@ -49,49 +50,40 @@
4950

5051

5152
class SliderPack(QWidget):
52-
"""Widget combining several RestrictedSlider instances."""
53+
"""Widget combining several RestrictedSlider instances.
54+
55+
Parameters
56+
----------
57+
n_sliders : int
58+
Number of sliders to add.
59+
"""
5360

5461
new_values = Signal(object)
5562

56-
def __init__(self, *args, n_sliders=2, **kwargs) -> None:
63+
def __init__(self, *args, n_sliders: int = 2, **kwargs) -> None:
5764
"""Create the widget with the specified number of sliders."""
5865
super().__init__(*args, **kwargs)
5966
layout = QGridLayout(self)
6067
self.setLayout(layout)
61-
self._labels = []
62-
self._sliders = []
63-
self._spinboxes = []
64-
self._current_values = []
65-
self._minarray = np.zeros(n_sliders)
66-
self._maxarray = np.ones(n_sliders)
67-
self._valarray = np.ones(n_sliders) * 0.5
68-
self._steparray = np.ones(n_sliders) * 0.01
69-
self._clickarray = np.array(n_sliders * [101], dtype=int)
70-
71-
for n in range(n_sliders):
72-
label = QLabel(self)
73-
slider = RestrictedSlider(self)
74-
slider.setOrientation(Qt.Orientation.Horizontal)
75-
76-
box = QDoubleSpinBox(self)
77-
box.setSingleStep(self._steparray[n])
7868

69+
self._labels = [QLabel(self) for _ in range(n_sliders)]
70+
self._sliders = [
71+
RestrictedSlider(Qt.Orientation.Horizontal, self) for _ in range(n_sliders)
72+
]
73+
self._spinboxes = [QDoubleSpinBox(self) for _ in range(n_sliders)]
74+
75+
for n, (label, slider, box) in enumerate(
76+
zip(self._labels, self._sliders, self._spinboxes, strict=True)
77+
):
7978
layout.addWidget(label, n, 0)
8079
layout.addWidget(slider, n, 1, 1, 2)
8180
layout.addWidget(box, n, 3)
8281

83-
self._labels.append(label)
84-
self._sliders.append(slider)
85-
self._spinboxes.append(box)
86-
87-
slider.valueChanged.connect(self.slider_to_box)
82+
box.setSingleStep(0.01)
8883
box.valueChanged.connect(self.box_to_slider)
8984
box.valueChanged.connect(self.collect_values)
90-
self._current_values.append(0)
91-
slider1 = self._sliders[0]
92-
slider2 = self._sliders[1]
93-
slider1.new_limit.connect(slider2.set_lower_limit)
94-
slider2.new_limit.connect(slider1.set_upper_limit)
85+
86+
slider.valueChanged.connect(self.slider_to_box)
9587

9688
@Slot(bool)
9789
def new_coupling(self, new_val: bool):
@@ -103,17 +95,17 @@ def new_coupling(self, new_val: bool):
10395
True for coupled sliders, false otherwise
10496
10597
"""
106-
self._sliders[0]._coupled = new_val
107-
self._sliders[1]._coupled = new_val
98+
for slider in islice(self._sliders, 2):
99+
slider._coupled = new_val
108100

109101
@Slot(object)
110102
def new_slider_labels(self, input_labels: list[str]):
111103
"""Change the text labels of the sliders to new values."""
112-
for number, element in enumerate(input_labels):
113-
self._labels[number].setText(element)
104+
for label, element in zip(self._labels, input_labels, strict=True):
105+
label.setText(element)
114106

115107
@Slot(object)
116-
def new_limits(self, input_limits: list[list[float]]):
108+
def new_limits(self, input_limits: list[tuple[float, float, float]]):
117109
"""Change the limits and step number of the sliders.
118110
119111
Since QSlider works with integer numbers, the float
@@ -122,28 +114,23 @@ def new_limits(self, input_limits: list[list[float]]):
122114
123115
Parameters
124116
----------
125-
input_limits : list[list[float]]
117+
input_limits : list[tuple[float, float, float]]
126118
For each slider, [minimum, maximum, step_size] values
127119
128120
"""
129-
for number, element in enumerate(input_limits):
130-
minimum, maximum, stepsize = element[0], element[1], element[2]
121+
for (minimum, maximum, stepsize), box, slider in zip(
122+
input_limits, self._spinboxes, self._sliders, strict=True
123+
):
131124
clicks = round((maximum - minimum) / stepsize)
132-
self._minarray[number] = minimum
133-
self._maxarray[number] = maximum
134-
self._steparray[number] = stepsize
135-
self._clickarray[number] = clicks
136-
temp_value = self._spinboxes[number].value()
137-
self._sliders[number].setMaximum(clicks)
138-
self._spinboxes[number].setMinimum(minimum)
139-
self._spinboxes[number].setMaximum(maximum)
140-
self._spinboxes[number].setSingleStep(stepsize)
141-
self._spinboxes[number].setDecimals(abs(int(np.floor(np.log10(stepsize)))))
142-
temp_value = min(maximum, temp_value)
143-
temp_value = max(minimum, temp_value)
144-
click_value = round((temp_value - minimum) / stepsize)
145-
self._sliders[number].setValue(click_value)
146-
self._spinboxes[number].setValue(temp_value)
125+
126+
slider.setRange(0, clicks)
127+
128+
temp_value = np.clip(box.value(), minimum, maximum)
129+
box.setRange(minimum, maximum)
130+
box.setSingleStep(stepsize)
131+
box.setDecimals(abs(int(np.floor(np.log10(stepsize)))))
132+
box.setValue(temp_value)
133+
self.box_to_slider()
147134

148135
def set_values(self, new_values: list[float]):
149136
"""Set both spinboxes and sliders to the new incoming values.
@@ -157,46 +144,49 @@ def set_values(self, new_values: list[float]):
157144
One new value per slider
158145
159146
"""
160-
nv = np.array(new_values)
161-
nv = np.maximum(nv, self._minarray)
162-
nv = np.minimum(nv, self._maxarray)
163-
clicks = np.round((nv - self._minarray) / self._steparray).astype(int)
164-
for n in range(len(nv)):
165-
self._spinboxes[n].setValue(nv[n])
166-
self._sliders[n].setValue(clicks[n])
147+
for box, val in zip(self._spinboxes, new_values, strict=True):
148+
box.setValue(np.clip(val, box.minimum(), box.maximum()))
149+
self.box_to_slider()
150+
151+
@property
152+
def slider_values(self) -> list[float]:
153+
"""Values returned from sliders (as ints [clicks])."""
154+
return [slider.value() for slider in self._sliders]
155+
156+
@property
157+
def box_values(self) -> list[float]:
158+
"""Values returned from boxes (as floats)."""
159+
return [box.value() for box in self._spinboxes]
167160

168161
@Slot()
169162
def slider_to_box(self):
170163
"""Update spin boxes if slider is moving."""
171-
vals = np.zeros_like(self._valarray)
172-
clicks = np.zeros_like(self._clickarray)
173-
for ns, slider in enumerate(self._sliders):
174-
clicks[ns] = slider.value()
175-
vals = self._minarray + clicks * self._steparray
176-
for ns, box in enumerate(self._spinboxes):
177-
box.setValue(vals[ns])
164+
with block_signals(self, *self._spinboxes):
165+
for box, slider in zip(
166+
self._spinboxes,
167+
self._sliders,
168+
strict=True,
169+
):
170+
box.setValue(box.minimum() + (slider.value() * box.singleStep()))
171+
self.box_to_slider()
178172

179173
@Slot()
180174
def box_to_slider(self):
181175
"""Update sliders if spin boxes have changed."""
182-
with block_signals(self):
183-
vals = np.zeros_like(self._valarray)
184-
clicks = np.zeros_like(self._clickarray)
185-
for ns, box in enumerate(self._spinboxes):
186-
vals[ns] = box.value()
187-
clicks = np.round((vals - self._minarray) / self._steparray).astype(int)
188-
for ns, slider in enumerate(self._sliders):
189-
slider.setValue(clicks[ns])
190-
self.slider_to_box()
176+
with block_signals(self, *self._sliders):
177+
for slider, box in zip(
178+
self._sliders,
179+
self._spinboxes,
180+
strict=True,
181+
):
182+
slider.setValue(round((box.value() - box.minimum()) / box.singleStep()))
183+
self.collect_values()
191184

192185
@Slot()
193186
def collect_values(self):
194187
"""Get and emit current values from all sliders/spinboxes."""
195-
result = []
196-
for box in self._spinboxes:
197-
result.append(box.value())
198-
self._current_values = result
199-
self.new_values.emit(result)
188+
self._current_values = self.box_values
189+
self.new_values.emit(self.box_values)
200190

201191

202192
class PlotWidget(QWidget):
@@ -207,9 +197,13 @@ class PlotWidget(QWidget):
207197
reset_slider_values = Signal(bool)
208198
change_slider_coupling = Signal(bool)
209199

210-
def __init__(self, *args, **kwargs) -> None:
200+
def __init__(
201+
self,
202+
*args,
203+
plotter_type: ValidPlotters = "Single",
204+
**kwargs,
205+
) -> None:
211206
"""Create an empty plot with the default plotter."""
212-
plotter_type = kwargs.pop("plotter_type", "Single")
213207
super().__init__(*args, **kwargs)
214208
self._plotter = None
215209
self._sliderpack = None
@@ -302,22 +296,22 @@ def plot_blank(
302296
)
303297

304298
@Slot()
305-
def use_legend(self, bool_flag: bool | None = None):
306-
if bool_flag is None:
307-
bool_flag = self._legend_box.isChecked()
299+
def use_legend(self, override: bool | None = None):
300+
if override is None:
301+
override = self._legend_box.isChecked()
308302
if self._plotting_context:
309-
self._plotting_context.use_legend = bool_flag
303+
self._plotting_context.use_legend = override
310304
if self._plotter:
311-
self._plotter.toggle_legend(bool_flag)
305+
self._plotter.toggle_legend(override)
312306

313307
@Slot()
314-
def use_grid(self, bool_flag: bool | None = None):
315-
if bool_flag is None:
316-
bool_flag = self._grid_box.isChecked()
308+
def use_grid(self, override: bool | None = None):
309+
if override is None:
310+
override = self._grid_box.isChecked()
317311
if self._plotting_context:
318-
self._plotting_context.use_grid = bool_flag
312+
self._plotting_context.use_grid = override
319313
if self._plotter:
320-
self._plotter.toggle_grid(bool_flag)
314+
self._plotter.toggle_grid(override)
321315

322316
def plot_data(self, update_only=False):
323317
"""Use the internal plotter instance to create a plot.

0 commit comments

Comments
 (0)