Skip to content

Commit 1e3dd9f

Browse files
committed
enh: reduce delay after selecting axes in the Plot view
1 parent b4992e3 commit 1e3dd9f

File tree

3 files changed

+109
-42
lines changed

3 files changed

+109
-42
lines changed

CHANGELOG

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
2.26.2
2+
- enh: reduce delay after selecting axes in the Plot view
23
- setup: bump dclab to 0.71.2 (disk-caching of DCOR/HTTP/S3 data)
34
2.26.1
45
- fix: possibly fix `KeyError` on hover in QuickView by doing a

dcscope/gui/analysis/ana_plot.py

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ def _set_range_feat_state(self, feat, fmin=None, fmax=None):
276276
else:
277277
self.setEnabled(True)
278278
if feat is not None:
279-
lim = self.pipeline.get_min_max(
279+
lim = self.pipeline.get_min_max_coarse(
280280
feat=feat, plot_id=self.current_plot.identifier)
281281
if not (np.isinf(lim[0]) or np.isinf(lim[1])):
282282
self.widget_range_feat.setLimits(vmin=lim[0], vmax=lim[1])
@@ -307,16 +307,19 @@ def _set_range_xy_state(self, axis_x=None, range_x=None,
307307
[self.widget_range_x, self.widget_range_y],
308308
):
309309
if axis is not None:
310-
lim = self.pipeline.get_min_max(feat=axis, plot_id=plot_id)
310+
lim = self.pipeline.get_min_max_coarse(
311+
feat=axis,
312+
plot_id=plot_id)
311313
if not (np.isinf(lim[0]) or np.isinf(lim[1])):
312314
rc.blockSignals(True)
313315
rc.setLimits(vmin=lim[0],
314316
vmax=lim[1])
315317
if rang is None or rang[0] == rang[1]:
316318
# default range is limits + 5% margin
317-
rang = self.pipeline.get_min_max(feat=axis,
318-
plot_id=plot_id,
319-
margin=0.05)
319+
rang = self.pipeline.get_min_max_coarse(
320+
feat=axis,
321+
plot_id=plot_id,
322+
margin=0.05)
320323
rc.write_pipeline_state({"active": True,
321324
"start": rang[0],
322325
"end": rang[1],
@@ -347,12 +350,9 @@ def _set_kde_spacing(self, spacing_x=None, spacing_y=None):
347350
spinBox.setSingleStep(10**(-dec + 1))
348351
spinBox.setValue(spacing)
349352

350-
def _set_kde_spacing_auto(self, axis_x=None, axis_y=None):
353+
def _set_kde_spacing_simple(self, axis_x=None, axis_y=None):
351354
"""automatically estimate and set the KDE spacing
352355
353-
- uses :func:`dclab.kde.binning.bin_width_percentile`
354-
- uses _set_kde_spacing
355-
356356
Not to be confused with `on_spacing_auto`!
357357
"""
358358
if len(self.pipeline.slots) == 0:
@@ -361,32 +361,28 @@ def _set_kde_spacing_auto(self, axis_x=None, axis_y=None):
361361
return
362362
else:
363363
self.setEnabled(True)
364-
dslist, _ = self.pipeline.get_plot_datasets(
365-
self.current_plot.identifier)
366-
if dslist:
367-
spacings_xy = []
368-
for axis, scaleCombo in zip([axis_x, axis_y],
369-
[self.comboBox_scale_x,
370-
self.comboBox_scale_y]):
371-
if axis is None:
372-
# nothing to do
373-
spacings_xy.append(None)
374-
else:
375-
# determine good approximation
376-
spacings = []
377-
for ds in dslist:
378-
spa = ds.get_kde_spacing(
379-
a=ds[axis],
380-
feat=axis,
381-
scale=scaleCombo.currentData(),
382-
method=dclab.kde.binning.bin_width_percentile,
383-
)
384-
spacings.append(spa)
385-
spacings_xy.append(np.min(spacings))
386-
spacing_x, spacing_y = spacings_xy
387-
# sets the limits before setting the value
388-
self._set_kde_spacing(spacing_x=spacing_x,
389-
spacing_y=spacing_y)
364+
365+
spacings_xy = []
366+
for feat, scaleCombo in zip([axis_x, axis_y],
367+
[self.comboBox_scale_x,
368+
self.comboBox_scale_y]):
369+
if feat is None:
370+
spacings_xy.append(None)
371+
else:
372+
vmin, vmax = self.pipeline.get_min_max_coarse(
373+
feat=feat,
374+
plot_id=self.current_plot.identifier)
375+
376+
if scaleCombo.currentData() == "log":
377+
vmin = np.log(vmin)
378+
vmax = np.log(vmax)
379+
380+
spacings_xy.append((vmax-vmin)/300)
381+
382+
spacing_x, spacing_y = spacings_xy
383+
# sets the limits before setting the value
384+
self._set_kde_spacing(spacing_x=spacing_x,
385+
spacing_y=spacing_y)
390386

391387
@property
392388
def current_plot(self):
@@ -432,14 +428,14 @@ def on_axis_changed(self):
432428
gen = self.read_plot_state()["general"]
433429
if self.sender() == self.comboBox_axis_x:
434430
self._set_range_xy_state(axis_x=gen["axis x"])
435-
self._set_kde_spacing_auto(axis_x=gen["axis x"])
431+
self._set_kde_spacing_simple(axis_x=gen["axis x"])
436432
elif self.sender() == self.comboBox_axis_y:
437433
self._set_range_xy_state(axis_y=gen["axis y"])
438-
self._set_kde_spacing_auto(axis_y=gen["axis y"])
434+
self._set_kde_spacing_simple(axis_y=gen["axis y"])
439435
elif self.sender() == self.comboBox_scale_x:
440-
self._set_kde_spacing_auto(axis_x=gen["axis x"])
436+
self._set_kde_spacing_simple(axis_x=gen["axis x"])
441437
elif self.sender() == self.comboBox_scale_y:
442-
self._set_kde_spacing_auto(axis_y=gen["axis y"])
438+
self._set_kde_spacing_simple(axis_y=gen["axis y"])
443439

444440
@QtCore.pyqtSlot()
445441
def on_column_num_changed(self):

dcscope/pipeline/core.py

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import warnings
55

66
import dclab
7+
from dclab.rtdc_dataset import RTDC_Hierarchy
78
import numpy as np
89

910
from ..idiom import FEATURES_MONOTONOUS
@@ -481,7 +482,9 @@ def get_dataset(self, slot_index, filt_index=-1, apply_filter=True):
481482
ds = ray.get_dataset(apply_filter=apply_filter)
482483
return ds
483484

484-
def get_datasets(self, filt_index=-1, apply_filter=True):
485+
def get_datasets(self,
486+
filt_index: int | None = -1,
487+
apply_filter: bool = True):
485488
"""Return all datasets with filters applied
486489
487490
The parameters are passed to :func:`Pipeline.get_dataset`.
@@ -654,8 +657,75 @@ def get_min_max(self, feat, plot_id=None, margin=0.0):
654657
EmptyDatasetWarning)
655658
if margin:
656659
diff = fmax - fmin
657-
fmin -= margin*diff
658-
fmax += margin*diff
660+
fmin -= margin * diff
661+
fmax += margin * diff
662+
663+
if np.any(np.isinf([fmin, fmax])):
664+
# Set values to 0 if no
665+
fmin = fmax = 0
666+
return [fmin, fmax]
667+
668+
def get_min_max_coarse(self, feat, plot_id=None, margin=0.0):
669+
"""Return coarse minimum and maximum values for a feature
670+
671+
Coarse feature computation is faster then the accurate
672+
:func:`get_min_max`, because it uses metadata stored in the
673+
root dataset. The range is thus always equal to or larger
674+
than that returned by :func:`get_min_max`.
675+
676+
Parameters
677+
----------
678+
feat: str
679+
Feature name
680+
plot_id: str
681+
Plot identifier
682+
margin: float
683+
Fraction by which the minimum and maximum are
684+
extended. E.g. for plotting with a 5% margin
685+
use `margin=0.05`.
686+
687+
Returns
688+
-------
689+
[fmin, fmax]: list of float
690+
Minimum and maximum values of the feature. If the feature
691+
is empty or only-nan, an :class:`EmptyDatasetWarning` is
692+
issued and both return values are set to zero.
693+
"""
694+
if plot_id is not None:
695+
dslist_children = self.get_plot_datasets(plot_id)[0]
696+
dslist = []
697+
for ch in dslist_children:
698+
if isinstance(ch, RTDC_Hierarchy):
699+
dslist.append(ch.get_root_parent())
700+
else:
701+
dslist.append(ch)
702+
else:
703+
dslist = self.get_datasets(filt_index=None, apply_filter=False)
704+
705+
fmin = np.inf
706+
fmax = -np.inf
707+
for ds in dslist:
708+
if np.any(ds.filter.all):
709+
if feat in ds:
710+
vmin = ds[feat].min()
711+
if not np.isnan(vmin):
712+
fmin = min(vmin, fmin)
713+
714+
vmax = ds[feat].max()
715+
if not np.isnan(vmax):
716+
fmax = max(vmax, fmax)
717+
else:
718+
warnings.warn(f"Dataset {ds.identifier} does not "
719+
f"contain the feature '{feat}'!",
720+
MissingFeatureWarning)
721+
else:
722+
warnings.warn(f"Dataset {ds.identifier} does not "
723+
f"contain any events when filtered!",
724+
EmptyDatasetWarning)
725+
if margin:
726+
diff = fmax - fmin
727+
fmin -= margin * diff
728+
fmax += margin * diff
659729

660730
if np.any(np.isinf([fmin, fmax])):
661731
# Set values to 0 if no

0 commit comments

Comments
 (0)