1515#
1616from __future__ import annotations
1717
18+ from itertools import islice
1819from pathlib import Path
1920from typing import TYPE_CHECKING
2021
3940
4041from MDANSE .MLogging import LOG
4142from MDANSE_GUI .PlotUtils import MDANSEMatPlotLibNavBar
42- from MDANSE_GUI .Tabs .Plotters .Plotter import Plotter
43+ from MDANSE_GUI .Tabs .Plotters .Plotter import Plotter , ValidPlotters
4344from MDANSE_GUI .Utils import block_signals
4445from MDANSE_GUI .Widgets .NormalisationWidget import NormalisationWidget
4546from MDANSE_GUI .Widgets .RestrictedSlider import RestrictedSlider
4950
5051
5152class 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
202192class 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