plopp 25.10.0__py3-none-any.whl → 26.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plopp/__init__.py +1 -1
- plopp/backends/matplotlib/canvas.py +3 -3
- plopp/backends/plotly/canvas.py +2 -2
- plopp/graphics/colormapper.py +14 -4
- plopp/graphics/graphicalview.py +5 -4
- plopp/plotting/common.py +3 -3
- plopp/plotting/inspector.py +100 -43
- plopp/plotting/mesh3d.py +1 -1
- plopp/plotting/plot.py +2 -2
- plopp/plotting/scatter.py +3 -3
- plopp/plotting/scatter3d.py +5 -5
- plopp/plotting/slicer.py +2 -2
- plopp/plotting/superplot.py +2 -2
- plopp/plotting/xyplot.py +2 -2
- plopp/widgets/__init__.pyi +4 -3
- plopp/widgets/clip3d.py +160 -41
- plopp/widgets/drawing.py +80 -3
- plopp/widgets/linesave.py +1 -1
- plopp/widgets/slice.py +2 -2
- plopp/widgets/style.py +1 -1
- plopp/widgets/tools.py +3 -3
- {plopp-25.10.0.dist-info → plopp-26.2.0.dist-info}/METADATA +3 -2
- {plopp-25.10.0.dist-info → plopp-26.2.0.dist-info}/RECORD +26 -26
- {plopp-25.10.0.dist-info → plopp-26.2.0.dist-info}/WHEEL +1 -1
- {plopp-25.10.0.dist-info → plopp-26.2.0.dist-info}/licenses/LICENSE +0 -0
- {plopp-25.10.0.dist-info → plopp-26.2.0.dist-info}/top_level.txt +0 -0
plopp/__init__.py
CHANGED
|
@@ -112,7 +112,7 @@ class Canvas:
|
|
|
112
112
|
grid: bool = False,
|
|
113
113
|
user_vmin: sc.Variable | float | None = None,
|
|
114
114
|
user_vmax: sc.Variable | float | None = None,
|
|
115
|
-
aspect: Literal['auto', 'equal'
|
|
115
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
116
116
|
cbar: bool = False,
|
|
117
117
|
legend: bool | tuple[float, float] = True,
|
|
118
118
|
xmin: sc.Variable | float | None = None,
|
|
@@ -123,7 +123,7 @@ class Canvas:
|
|
|
123
123
|
logy: bool = False,
|
|
124
124
|
xlabel: str | None = None,
|
|
125
125
|
ylabel: str | None = None,
|
|
126
|
-
norm: Literal['linear', 'log'
|
|
126
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
127
127
|
**ignored,
|
|
128
128
|
):
|
|
129
129
|
# Note on the `**ignored`` keyword arguments: the figure which owns the canvas
|
|
@@ -515,7 +515,7 @@ class Canvas:
|
|
|
515
515
|
"""
|
|
516
516
|
self.fig.canvas.toolbar.pan()
|
|
517
517
|
|
|
518
|
-
def panzoom(self, value: Literal['pan', 'zoom'
|
|
518
|
+
def panzoom(self, value: Literal['pan', 'zoom'] | None):
|
|
519
519
|
"""
|
|
520
520
|
Activate or deactivate the pan or zoom tool, depending on the input value.
|
|
521
521
|
"""
|
plopp/backends/plotly/canvas.py
CHANGED
|
@@ -46,7 +46,7 @@ class Canvas:
|
|
|
46
46
|
logy: bool | None = None,
|
|
47
47
|
xlabel: str | None = None,
|
|
48
48
|
ylabel: str | None = None,
|
|
49
|
-
norm: Literal['linear', 'log'
|
|
49
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
50
50
|
**ignored,
|
|
51
51
|
):
|
|
52
52
|
# Note on the `**ignored`` keyword arguments: the figure which owns the canvas
|
|
@@ -326,7 +326,7 @@ class Canvas:
|
|
|
326
326
|
"""
|
|
327
327
|
self.fig.update_layout(dragmode='pan')
|
|
328
328
|
|
|
329
|
-
def panzoom(self, value: Literal['pan', 'zoom'
|
|
329
|
+
def panzoom(self, value: Literal['pan', 'zoom'] | None):
|
|
330
330
|
"""
|
|
331
331
|
Activate or deactivate the pan or zoom tool, depending on the input value.
|
|
332
332
|
"""
|
plopp/graphics/colormapper.py
CHANGED
|
@@ -55,7 +55,12 @@ def _get_cmap(colormap: str | Colormap, nan_color: str | None = None) -> Colorma
|
|
|
55
55
|
else:
|
|
56
56
|
cmap = mpl.cm.get_cmap(colormap)
|
|
57
57
|
except (KeyError, ValueError):
|
|
58
|
+
# Case where we have just a single color
|
|
58
59
|
cmap = LinearSegmentedColormap.from_list('tmp', [colormap, colormap])
|
|
60
|
+
cmap.set_over(colormap)
|
|
61
|
+
cmap.set_under(colormap)
|
|
62
|
+
cmap.set_bad(colormap)
|
|
63
|
+
return cmap
|
|
59
64
|
|
|
60
65
|
# Add under and over values to the cmap
|
|
61
66
|
delta = 0.15
|
|
@@ -141,7 +146,7 @@ class ColorMapper:
|
|
|
141
146
|
clabel: str | None = None,
|
|
142
147
|
nan_color: str | None = None,
|
|
143
148
|
figsize: tuple[float, float] | None = None,
|
|
144
|
-
norm: Literal['linear', 'log'
|
|
149
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
145
150
|
vmin: sc.Variable | float | None = None,
|
|
146
151
|
vmax: sc.Variable | float | None = None,
|
|
147
152
|
):
|
|
@@ -152,9 +157,14 @@ class ColorMapper:
|
|
|
152
157
|
self._canvas = canvas
|
|
153
158
|
self.cax = self._canvas.cax if hasattr(self._canvas, 'cax') else None
|
|
154
159
|
self.cmap = _get_cmap(cmap, nan_color=nan_color)
|
|
155
|
-
self.mask_cmap = _get_cmap(
|
|
156
|
-
|
|
157
|
-
|
|
160
|
+
self.mask_cmap = _get_cmap(mask_cmap if mask_color is None else mask_color)
|
|
161
|
+
if mask_color is None:
|
|
162
|
+
# If no single color was used for masks, it means a colormap was used
|
|
163
|
+
# instead. However, `nan_color` was not passed to get_cmap for the masks
|
|
164
|
+
# (it only applied to non-masked data), so we still need to set the 'bad'
|
|
165
|
+
# color here. We choose to set it to the 'over' color for a lack of a
|
|
166
|
+
# better idea.
|
|
167
|
+
self.mask_cmap.set_bad(self.mask_cmap.get_over())
|
|
158
168
|
|
|
159
169
|
# Inside the autoscale, we need to distinguish between a min value that was set
|
|
160
170
|
# by the user and one that was found by looping over all the data.
|
plopp/graphics/graphicalview.py
CHANGED
|
@@ -51,11 +51,11 @@ class GraphicalView(View):
|
|
|
51
51
|
mask_cmap: str = 'gray',
|
|
52
52
|
mask_color: str | None = None,
|
|
53
53
|
cbar: bool = False,
|
|
54
|
-
norm: Literal['linear', 'log'
|
|
54
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
55
55
|
vmin: sc.Variable | float | None = None,
|
|
56
56
|
vmax: sc.Variable | float | None = None,
|
|
57
57
|
scale: dict[str, str] | None = None,
|
|
58
|
-
aspect: Literal['auto', 'equal'
|
|
58
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
59
59
|
grid: bool = False,
|
|
60
60
|
title: str | None = None,
|
|
61
61
|
figsize: tuple[float, float] | None = None,
|
|
@@ -312,5 +312,6 @@ class GraphicalView(View):
|
|
|
312
312
|
self.artists[key].remove()
|
|
313
313
|
del self.artists[key]
|
|
314
314
|
self.canvas.update_legend()
|
|
315
|
-
self.
|
|
316
|
-
|
|
315
|
+
if self._autoscale:
|
|
316
|
+
self.fit_to_data()
|
|
317
|
+
self.canvas.draw()
|
plopp/plotting/common.py
CHANGED
|
@@ -319,7 +319,7 @@ def input_to_nodes(obj: PlottableMulti, processor: Callable) -> list[Node]:
|
|
|
319
319
|
to_nodes = obj.items()
|
|
320
320
|
else:
|
|
321
321
|
to_nodes = [(getattr(obj, "name", None), obj)]
|
|
322
|
-
nodes = [Node(processor, inp, name=name) for name, inp in to_nodes]
|
|
322
|
+
nodes = [Node(processor, inp, name=str(name)) for name, inp in to_nodes]
|
|
323
323
|
for node in nodes:
|
|
324
324
|
if hasattr(processor, 'func'):
|
|
325
325
|
node.pretty_name = processor.func.__name__
|
|
@@ -347,7 +347,7 @@ def raise_multiple_inputs_for_2d_plot_error(origin):
|
|
|
347
347
|
|
|
348
348
|
|
|
349
349
|
def categorize_args(
|
|
350
|
-
aspect: Literal['auto', 'equal'
|
|
350
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
351
351
|
autoscale: bool = True,
|
|
352
352
|
cbar: bool = True,
|
|
353
353
|
clabel: str | None = None,
|
|
@@ -364,7 +364,7 @@ def categorize_args(
|
|
|
364
364
|
mask_cmap: str = 'gray',
|
|
365
365
|
mask_color: str = 'black',
|
|
366
366
|
nan_color: str | None = None,
|
|
367
|
-
norm: Literal['linear', 'log'
|
|
367
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
368
368
|
scale: dict[str, str] | None = None,
|
|
369
369
|
title: str | None = None,
|
|
370
370
|
vmax: sc.Variable | float | None = None,
|
plopp/plotting/inspector.py
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
from typing import Literal
|
|
5
5
|
|
|
6
|
+
import numpy as np
|
|
6
7
|
import scipp as sc
|
|
7
8
|
|
|
8
9
|
from ..core import Node
|
|
@@ -34,11 +35,40 @@ def _slice_xy(da: sc.DataArray, xy: dict[str, dict[str, int]]) -> sc.DataArray:
|
|
|
34
35
|
return da[y['dim'], y['value']][x['dim'], x['value']]
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
def _coord_to_centers(da: sc.DataArray, dim: str) -> sc.Variable:
|
|
39
|
+
coord = da.coords[dim]
|
|
40
|
+
if da.coords.is_edges(dim, dim=dim):
|
|
41
|
+
return sc.midpoints(coord, dim=dim)
|
|
42
|
+
return coord
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _mask_outside_polygon(
|
|
46
|
+
da: sc.DataArray, poly: dict[str, dict[str, sc.Variable]]
|
|
47
|
+
) -> sc.DataArray:
|
|
48
|
+
from matplotlib.path import Path
|
|
49
|
+
|
|
50
|
+
xdim = poly['x']['dim']
|
|
51
|
+
ydim = poly['y']['dim']
|
|
52
|
+
x = _coord_to_centers(da, xdim)
|
|
53
|
+
y = _coord_to_centers(da, ydim)
|
|
54
|
+
vx = poly['x']['value'].to(unit=x.unit).values
|
|
55
|
+
vy = poly['y']['value'].to(unit=y.unit).values
|
|
56
|
+
verts = np.column_stack([vx, vy])
|
|
57
|
+
path = Path(verts)
|
|
58
|
+
xx = sc.broadcast(x, sizes={**x.sizes, **y.sizes})
|
|
59
|
+
yy = sc.broadcast(y, sizes={**x.sizes, **y.sizes})
|
|
60
|
+
points = np.column_stack([xx.values.ravel(), yy.values.ravel()])
|
|
61
|
+
inside = sc.array(
|
|
62
|
+
dims=yy.dims, values=path.contains_points(points).reshape(yy.shape)
|
|
63
|
+
)
|
|
64
|
+
return da.assign_masks(__inside_polygon=~inside).sum({*x.dims, *y.dims})
|
|
65
|
+
|
|
66
|
+
|
|
37
67
|
def inspector(
|
|
38
68
|
obj: Plottable,
|
|
39
69
|
dim: str | None = None,
|
|
40
70
|
*,
|
|
41
|
-
aspect: Literal['auto', 'equal'
|
|
71
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
42
72
|
autoscale: bool = True,
|
|
43
73
|
cbar: bool = True,
|
|
44
74
|
clabel: str | None = None,
|
|
@@ -49,17 +79,19 @@ def inspector(
|
|
|
49
79
|
grid: bool = False,
|
|
50
80
|
legend: bool | tuple[float, float] = True,
|
|
51
81
|
logc: bool | None = None,
|
|
52
|
-
logy: bool | None = None,
|
|
53
82
|
mask_cmap: str = 'gray',
|
|
54
83
|
mask_color: str = 'black',
|
|
84
|
+
mode: Literal['point', 'polygon'] = 'point',
|
|
55
85
|
nan_color: str | None = None,
|
|
56
|
-
norm: Literal['linear', 'log'
|
|
86
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
57
87
|
operation: Literal['sum', 'mean', 'min', 'max'] = 'sum',
|
|
58
88
|
orientation: Literal['horizontal', 'vertical'] = 'horizontal',
|
|
59
89
|
title: str | None = None,
|
|
60
90
|
vmax: sc.Variable | float | None = None,
|
|
61
91
|
vmin: sc.Variable | float | None = None,
|
|
62
92
|
xlabel: str | None = None,
|
|
93
|
+
xmax: sc.Variable | float | None = None,
|
|
94
|
+
xmin: sc.Variable | float | None = None,
|
|
63
95
|
ylabel: str | None = None,
|
|
64
96
|
ymax: sc.Variable | float | None = None,
|
|
65
97
|
ymin: sc.Variable | float | None = None,
|
|
@@ -69,16 +101,29 @@ def inspector(
|
|
|
69
101
|
Inspector takes in a three-dimensional input and applies a reduction operation
|
|
70
102
|
(``'sum'`` by default) along one of the dimensions specified by ``dim``.
|
|
71
103
|
It displays the result as a two-dimensional image.
|
|
72
|
-
In addition, an 'inspection' tool is available in the toolbar
|
|
73
|
-
markers on the image
|
|
74
|
-
dimension and
|
|
75
|
-
figure.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
-
|
|
104
|
+
In addition, an 'inspection' tool is available in the toolbar. In ``mode='point'``
|
|
105
|
+
it allows placing point markers on the image to slice at that position, retaining
|
|
106
|
+
only the third dimension and displaying the resulting one-dimensional slice in the
|
|
107
|
+
right-hand side figure. In ``mode='polygon'`` it allows drawing a polygon to compute
|
|
108
|
+
the total intensity inside the polygon as a function of the third dimension.
|
|
109
|
+
|
|
110
|
+
Controls (point mode):
|
|
111
|
+
- Left-click to make new points
|
|
112
|
+
- Left-click and hold on point to move point
|
|
80
113
|
- Middle-click to delete point
|
|
81
114
|
|
|
115
|
+
Controls (polygon mode):
|
|
116
|
+
- Left-click to make new polygons
|
|
117
|
+
- Left-click and hold on polygon vertex to move vertex
|
|
118
|
+
- Right-click and hold to drag/move the entire polygon
|
|
119
|
+
- Middle-click to delete polygon
|
|
120
|
+
|
|
121
|
+
Notes
|
|
122
|
+
-----
|
|
123
|
+
|
|
124
|
+
Almost all the arguments for plot customization apply to the two-dimensional image
|
|
125
|
+
(unless specified).
|
|
126
|
+
|
|
82
127
|
Parameters
|
|
83
128
|
----------
|
|
84
129
|
obj:
|
|
@@ -93,13 +138,13 @@ def inspector(
|
|
|
93
138
|
autoscale:
|
|
94
139
|
Automatically scale the axes/colormap on updates if ``True``.
|
|
95
140
|
cbar:
|
|
96
|
-
Show colorbar if ``True
|
|
141
|
+
Show colorbar if ``True``.
|
|
97
142
|
clabel:
|
|
98
|
-
Label for colorscale
|
|
143
|
+
Label for colorscale.
|
|
99
144
|
cmax:
|
|
100
|
-
Upper limit for colorscale
|
|
145
|
+
Upper limit for colorscale.
|
|
101
146
|
cmin:
|
|
102
|
-
Lower limit for colorscale
|
|
147
|
+
Lower limit for colorscale.
|
|
103
148
|
errorbars:
|
|
104
149
|
Show errorbars if ``True`` (1d figure).
|
|
105
150
|
figsize:
|
|
@@ -111,18 +156,18 @@ def inspector(
|
|
|
111
156
|
``(x, y)`` coordinates of the legend's anchor point in axes coordinates
|
|
112
157
|
(1d figure).
|
|
113
158
|
logc:
|
|
114
|
-
If ``True``, use logarithmic scale for colorscale
|
|
115
|
-
logy:
|
|
116
|
-
If ``True``, use logarithmic scale for y-axis (1d figure).
|
|
159
|
+
If ``True``, use logarithmic scale for colorscale.
|
|
117
160
|
mask_cmap:
|
|
118
|
-
Colormap to use for masks
|
|
161
|
+
Colormap to use for masks.
|
|
119
162
|
mask_color:
|
|
120
163
|
Color of masks (overrides ``mask_cmap``).
|
|
164
|
+
mode:
|
|
165
|
+
Select ``'point'`` for point inspection or ``'polygon'`` for polygon selection
|
|
166
|
+
with total intensity inside the polygon plotted as a function of ``dim``.
|
|
121
167
|
nan_color:
|
|
122
|
-
Color to use for NaN values
|
|
168
|
+
Color to use for NaN values.
|
|
123
169
|
norm:
|
|
124
|
-
Set to ``'log'`` for a logarithmic
|
|
125
|
-
(2d figure). Legacy, prefer ``logy`` and ``logc`` instead.
|
|
170
|
+
Set to ``'log'`` for a logarithmic colorscale. Legacy, prefer ``logc`` instead.
|
|
126
171
|
operation:
|
|
127
172
|
The operation to apply along the third (undisplayed) dimension specified by
|
|
128
173
|
``dim``.
|
|
@@ -132,15 +177,19 @@ def inspector(
|
|
|
132
177
|
title:
|
|
133
178
|
The figure title.
|
|
134
179
|
vmax:
|
|
135
|
-
Upper limit for data to be displayed
|
|
136
|
-
|
|
180
|
+
Upper limit for data colorscale to be displayed.
|
|
181
|
+
Legacy, prefer ``cmax`` instead.
|
|
137
182
|
vmin:
|
|
138
|
-
Lower limit for data to be displayed
|
|
139
|
-
|
|
183
|
+
Lower limit for data colorscale to be displayed.
|
|
184
|
+
Legacy, prefer ``cmin`` instead.
|
|
140
185
|
xlabel:
|
|
141
|
-
Label for x-axis
|
|
186
|
+
Label for x-axis.
|
|
187
|
+
xmax:
|
|
188
|
+
Upper limit for x-axis (1d figure)
|
|
189
|
+
xmin:
|
|
190
|
+
Lower limit for x-axis (1d figure)
|
|
142
191
|
ylabel:
|
|
143
|
-
Label for y-axis
|
|
192
|
+
Label for y-axis.
|
|
144
193
|
ymax:
|
|
145
194
|
Upper limit for y-axis (1d figure).
|
|
146
195
|
ymin:
|
|
@@ -159,11 +208,9 @@ def inspector(
|
|
|
159
208
|
errorbars=errorbars,
|
|
160
209
|
grid=grid,
|
|
161
210
|
legend=legend,
|
|
162
|
-
logy=logy,
|
|
163
211
|
mask_color=mask_color,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
vmin=vmin,
|
|
212
|
+
xmax=xmax,
|
|
213
|
+
xmin=xmin,
|
|
167
214
|
ymax=ymax,
|
|
168
215
|
ymin=ymin,
|
|
169
216
|
)
|
|
@@ -190,6 +237,7 @@ def inspector(
|
|
|
190
237
|
figsize=figsize,
|
|
191
238
|
grid=grid,
|
|
192
239
|
logc=logc,
|
|
240
|
+
mask_cmap=mask_cmap,
|
|
193
241
|
mask_color=mask_color,
|
|
194
242
|
nan_color=nan_color,
|
|
195
243
|
norm=norm,
|
|
@@ -198,21 +246,30 @@ def inspector(
|
|
|
198
246
|
vmin=vmin,
|
|
199
247
|
xlabel=xlabel,
|
|
200
248
|
ylabel=ylabel,
|
|
201
|
-
ymax=ymax,
|
|
202
|
-
ymin=ymin,
|
|
203
249
|
**kwargs,
|
|
204
250
|
)
|
|
251
|
+
from ..widgets import Box, PointsTool, PolygonTool
|
|
205
252
|
|
|
206
|
-
|
|
253
|
+
if mode == 'point':
|
|
254
|
+
tool = PointsTool(
|
|
255
|
+
figure=f2d,
|
|
256
|
+
input_node=bin_edges_node,
|
|
257
|
+
func=_slice_xy,
|
|
258
|
+
destination=f1d,
|
|
259
|
+
tooltip="Activate inspector tool",
|
|
260
|
+
)
|
|
261
|
+
elif mode == 'polygon':
|
|
262
|
+
tool = PolygonTool(
|
|
263
|
+
figure=f2d,
|
|
264
|
+
input_node=bin_edges_node,
|
|
265
|
+
func=_mask_outside_polygon,
|
|
266
|
+
destination=f1d,
|
|
267
|
+
tooltip="Activate polygon inspector tool",
|
|
268
|
+
)
|
|
269
|
+
else:
|
|
270
|
+
raise ValueError(f'Mode "{mode}" is unknown.')
|
|
207
271
|
|
|
208
|
-
|
|
209
|
-
figure=f2d,
|
|
210
|
-
input_node=bin_edges_node,
|
|
211
|
-
func=_slice_xy,
|
|
212
|
-
destination=f1d,
|
|
213
|
-
tooltip="Activate inspector tool",
|
|
214
|
-
)
|
|
215
|
-
f2d.toolbar['inspect'] = pts
|
|
272
|
+
f2d.toolbar['inspect'] = tool
|
|
216
273
|
out = [f2d, f1d]
|
|
217
274
|
if orientation == 'horizontal':
|
|
218
275
|
out = [out]
|
plopp/plotting/mesh3d.py
CHANGED
|
@@ -53,7 +53,7 @@ def mesh3d(
|
|
|
53
53
|
figsize: tuple[int, int] = (600, 400),
|
|
54
54
|
logc: bool | None = None,
|
|
55
55
|
nan_color: str | None = None,
|
|
56
|
-
norm: Literal['linear', 'log'
|
|
56
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
57
57
|
title: str | None = None,
|
|
58
58
|
vmax: sc.Variable | float = None,
|
|
59
59
|
vmin: sc.Variable | float = None,
|
plopp/plotting/plot.py
CHANGED
|
@@ -19,7 +19,7 @@ from .common import (
|
|
|
19
19
|
def plot(
|
|
20
20
|
obj: PlottableMulti,
|
|
21
21
|
*,
|
|
22
|
-
aspect: Literal['auto', 'equal'
|
|
22
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
23
23
|
autoscale: bool = True,
|
|
24
24
|
cbar: bool = True,
|
|
25
25
|
clabel: str | None = None,
|
|
@@ -38,7 +38,7 @@ def plot(
|
|
|
38
38
|
mask_cmap: str = 'gray',
|
|
39
39
|
mask_color: str | None = None,
|
|
40
40
|
nan_color: str | None = None,
|
|
41
|
-
norm: Literal['linear', 'log'
|
|
41
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
42
42
|
scale: dict[str, str] | None = None,
|
|
43
43
|
title: str | None = None,
|
|
44
44
|
vmax: sc.Variable | float | None = None,
|
plopp/plotting/scatter.py
CHANGED
|
@@ -46,9 +46,9 @@ def scatter(
|
|
|
46
46
|
x: str = 'x',
|
|
47
47
|
y: str = 'y',
|
|
48
48
|
pos: str | None = None,
|
|
49
|
-
aspect: Literal['auto', 'equal'
|
|
49
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
50
50
|
autoscale: bool = True,
|
|
51
|
-
cbar: bool =
|
|
51
|
+
cbar: bool = False,
|
|
52
52
|
clabel: str | None = None,
|
|
53
53
|
cmap: str = 'viridis',
|
|
54
54
|
cmax: sc.Variable | float | None = None,
|
|
@@ -62,7 +62,7 @@ def scatter(
|
|
|
62
62
|
logy: bool | None = None,
|
|
63
63
|
mask_color: str = 'black',
|
|
64
64
|
nan_color: str | None = None,
|
|
65
|
-
norm: Literal['linear', 'log'
|
|
65
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
66
66
|
scale: dict[str, str] | None = None,
|
|
67
67
|
size: str | float | None = None,
|
|
68
68
|
title: str | None = None,
|
plopp/plotting/scatter3d.py
CHANGED
|
@@ -53,7 +53,7 @@ def scatter3d(
|
|
|
53
53
|
figsize: tuple[int, int] = (600, 400),
|
|
54
54
|
logc: bool | None = None,
|
|
55
55
|
nan_color: str | None = None,
|
|
56
|
-
norm: Literal['linear', 'log'
|
|
56
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
57
57
|
title: str | None = None,
|
|
58
58
|
vmax: sc.Variable | float = None,
|
|
59
59
|
vmin: sc.Variable | float = None,
|
|
@@ -126,7 +126,7 @@ def scatter3d(
|
|
|
126
126
|
A three-dimensional interactive scatter plot.
|
|
127
127
|
"""
|
|
128
128
|
from ..graphics import scatter3dfigure
|
|
129
|
-
from ..widgets import
|
|
129
|
+
from ..widgets import ClippingManager, ToggleTool
|
|
130
130
|
|
|
131
131
|
if 'ax' in kwargs:
|
|
132
132
|
raise ValueError(
|
|
@@ -160,11 +160,11 @@ def scatter3d(
|
|
|
160
160
|
vmin=vmin,
|
|
161
161
|
**kwargs,
|
|
162
162
|
)
|
|
163
|
-
|
|
163
|
+
clip_manager = ClippingManager(fig)
|
|
164
164
|
fig.toolbar['cut3d'] = ToggleTool(
|
|
165
|
-
callback=
|
|
165
|
+
callback=clip_manager.toggle_visibility,
|
|
166
166
|
icon='layer-group',
|
|
167
167
|
tooltip='Hide/show spatial cutting tool',
|
|
168
168
|
)
|
|
169
|
-
fig.bottom_bar.add(
|
|
169
|
+
fig.bottom_bar.add(clip_manager)
|
|
170
170
|
return fig
|
plopp/plotting/slicer.py
CHANGED
|
@@ -127,7 +127,7 @@ def slicer(
|
|
|
127
127
|
obj: PlottableMulti,
|
|
128
128
|
keep: list[str] | None = None,
|
|
129
129
|
*,
|
|
130
|
-
aspect: Literal['auto', 'equal'
|
|
130
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
131
131
|
autoscale: bool = True,
|
|
132
132
|
cbar: bool = True,
|
|
133
133
|
clabel: str | None = None,
|
|
@@ -146,7 +146,7 @@ def slicer(
|
|
|
146
146
|
mask_cmap: str = 'gray',
|
|
147
147
|
mask_color: str | None = None,
|
|
148
148
|
nan_color: str | None = None,
|
|
149
|
-
norm: Literal['linear', 'log'
|
|
149
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
150
150
|
scale: dict[str, str] | None = None,
|
|
151
151
|
title: str | None = None,
|
|
152
152
|
vmax: sc.Variable | float | None = None,
|
plopp/plotting/superplot.py
CHANGED
|
@@ -13,7 +13,7 @@ def superplot(
|
|
|
13
13
|
obj: Plottable,
|
|
14
14
|
keep: str | None = None,
|
|
15
15
|
*,
|
|
16
|
-
aspect: Literal['auto', 'equal'
|
|
16
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
17
17
|
autoscale: bool = True,
|
|
18
18
|
coords: list[str] | None = None,
|
|
19
19
|
enable_player: bool = False,
|
|
@@ -24,7 +24,7 @@ def superplot(
|
|
|
24
24
|
logx: bool | None = None,
|
|
25
25
|
logy: bool | None = None,
|
|
26
26
|
mask_color: str = 'black',
|
|
27
|
-
norm: Literal['linear', 'log'
|
|
27
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
28
28
|
scale: dict[str, str] | None = None,
|
|
29
29
|
title: str | None = None,
|
|
30
30
|
vmax: sc.Variable | float | None = None,
|
plopp/plotting/xyplot.py
CHANGED
|
@@ -40,7 +40,7 @@ def _make_data_array(x: sc.Variable, y: sc.Variable) -> sc.DataArray:
|
|
|
40
40
|
def xyplot(
|
|
41
41
|
x: sc.Variable | ndarray | list | Node,
|
|
42
42
|
y: sc.Variable | ndarray | list | Node,
|
|
43
|
-
aspect: Literal['auto', 'equal'
|
|
43
|
+
aspect: Literal['auto', 'equal'] | None = None,
|
|
44
44
|
autoscale: bool = True,
|
|
45
45
|
errorbars: bool = True,
|
|
46
46
|
figsize: tuple[float, float] | None = None,
|
|
@@ -48,7 +48,7 @@ def xyplot(
|
|
|
48
48
|
legend: bool | tuple[float, float] = True,
|
|
49
49
|
logx: bool | None = None,
|
|
50
50
|
logy: bool | None = None,
|
|
51
|
-
norm: Literal['linear', 'log'
|
|
51
|
+
norm: Literal['linear', 'log'] | None = None,
|
|
52
52
|
scale: dict[str, str] | None = None,
|
|
53
53
|
title: str | None = None,
|
|
54
54
|
vmax: sc.Variable | float | None = None,
|
plopp/widgets/__init__.pyi
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
from .box import Box, HBar, VBar
|
|
5
5
|
from .checkboxes import Checkboxes
|
|
6
|
-
from .clip3d import Clip3dTool,
|
|
7
|
-
from .drawing import DrawingTool, PointsTool
|
|
6
|
+
from .clip3d import Clip3dTool, ClippingManager
|
|
7
|
+
from .drawing import DrawingTool, PointsTool, PolygonTool
|
|
8
8
|
from .linesave import LineSaveTool
|
|
9
9
|
from .slice import RangeSliceWidget, SliceWidget, slice_dims
|
|
10
10
|
from .toolbar import Toolbar, make_toolbar_canvas2d, make_toolbar_canvas3d
|
|
@@ -15,12 +15,13 @@ __all__ = [
|
|
|
15
15
|
"ButtonTool",
|
|
16
16
|
"Checkboxes",
|
|
17
17
|
"Clip3dTool",
|
|
18
|
-
"
|
|
18
|
+
"ClippingManager",
|
|
19
19
|
"ColorTool",
|
|
20
20
|
"DrawingTool",
|
|
21
21
|
"HBar",
|
|
22
22
|
"LineSaveTool",
|
|
23
23
|
"PointsTool",
|
|
24
|
+
"PolygonTool",
|
|
24
25
|
"RangeSliceWidget",
|
|
25
26
|
"SliceWidget",
|
|
26
27
|
"ToggleTool",
|
plopp/widgets/clip3d.py
CHANGED
|
@@ -46,7 +46,7 @@ class Clip3dTool(ipw.HBox):
|
|
|
46
46
|
----------
|
|
47
47
|
limits:
|
|
48
48
|
The spatial extent of the points in the 3d figure in the XYZ directions.
|
|
49
|
-
|
|
49
|
+
kind:
|
|
50
50
|
The direction normal to the slice.
|
|
51
51
|
update:
|
|
52
52
|
A function to update the scene.
|
|
@@ -59,23 +59,23 @@ class Clip3dTool(ipw.HBox):
|
|
|
59
59
|
def __init__(
|
|
60
60
|
self,
|
|
61
61
|
limits: tuple[sc.Variable, sc.Variable, sc.Variable],
|
|
62
|
-
|
|
62
|
+
kind: Literal['x', 'y', 'z'],
|
|
63
63
|
update: Callable,
|
|
64
64
|
color: str = 'red',
|
|
65
65
|
linewidth: float = 1.5,
|
|
66
66
|
border_visible: bool = True,
|
|
67
67
|
):
|
|
68
68
|
self._limits = limits
|
|
69
|
-
self.
|
|
70
|
-
axis = 'xyz'.index(self.
|
|
69
|
+
self.kind = kind
|
|
70
|
+
axis = 'xyz'.index(self.kind)
|
|
71
71
|
self.dim = self._limits[axis].dim
|
|
72
72
|
self._unit = self._limits[axis].unit
|
|
73
73
|
self.visible = True
|
|
74
74
|
self._update = update
|
|
75
75
|
self._border_visible = border_visible
|
|
76
76
|
|
|
77
|
-
w_axis = 2 if self.
|
|
78
|
-
h_axis = 2 if self.
|
|
77
|
+
w_axis = 2 if self.kind == 'x' else 0
|
|
78
|
+
h_axis = 2 if self.kind == 'y' else 1
|
|
79
79
|
width = (self._limits[w_axis][1] - self._limits[w_axis][0]).value
|
|
80
80
|
height = (self._limits[h_axis][1] - self._limits[h_axis][0]).value
|
|
81
81
|
|
|
@@ -95,10 +95,10 @@ class Clip3dTool(ipw.HBox):
|
|
|
95
95
|
material=p3.LineBasicMaterial(color=color, linewidth=linewidth),
|
|
96
96
|
),
|
|
97
97
|
]
|
|
98
|
-
if self.
|
|
98
|
+
if self.kind == 'x':
|
|
99
99
|
for outline in self.outlines:
|
|
100
100
|
outline.rotateY(0.5 * np.pi)
|
|
101
|
-
if self.
|
|
101
|
+
if self.kind == 'y':
|
|
102
102
|
for outline in self.outlines:
|
|
103
103
|
outline.rotateX(0.5 * np.pi)
|
|
104
104
|
|
|
@@ -112,15 +112,15 @@ class Clip3dTool(ipw.HBox):
|
|
|
112
112
|
max=vmax,
|
|
113
113
|
value=[center[axis] - delta, center[axis] + delta],
|
|
114
114
|
step=dx * 0.01,
|
|
115
|
-
description=
|
|
115
|
+
description=self.kind.upper(),
|
|
116
116
|
style={'description_width': 'initial'},
|
|
117
|
-
layout={'width': '
|
|
117
|
+
layout={'width': '35.6em', 'padding': '0px'},
|
|
118
118
|
)
|
|
119
119
|
|
|
120
120
|
self.cut_visible = ipw.Button(
|
|
121
121
|
icon='eye-slash',
|
|
122
122
|
tooltip='Hide cut',
|
|
123
|
-
layout={'width': '
|
|
123
|
+
layout={'width': '1.23em', 'padding': '0px'},
|
|
124
124
|
)
|
|
125
125
|
|
|
126
126
|
for outline, val in zip(self.outlines, self.slider.value, strict=True):
|
|
@@ -158,20 +158,20 @@ class Clip3dTool(ipw.HBox):
|
|
|
158
158
|
# the cut, the border visibility is in sync with the parent button.
|
|
159
159
|
self._border_visible = value
|
|
160
160
|
|
|
161
|
-
def move(self,
|
|
161
|
+
def move(self, change: dict[str, Any]):
|
|
162
162
|
"""
|
|
163
163
|
Move the outline of the cut according to new position given by the slider.
|
|
164
164
|
"""
|
|
165
165
|
# Early return if relative difference between new and old value is small.
|
|
166
166
|
# This also prevents flickering of an existing cut when a new cut is added.
|
|
167
167
|
if (
|
|
168
|
-
np.abs(np.array(
|
|
168
|
+
np.abs(np.array(change['new']) - np.array(change['old'])).max()
|
|
169
169
|
< 0.01 * self.slider.step
|
|
170
170
|
):
|
|
171
171
|
return
|
|
172
|
-
for outline, val in zip(self.outlines,
|
|
172
|
+
for outline, val in zip(self.outlines, change['new'], strict=True):
|
|
173
173
|
pos = list(outline.position)
|
|
174
|
-
axis = 'xyz'.index(self.
|
|
174
|
+
axis = 'xyz'.index(self.kind)
|
|
175
175
|
pos[axis] = val
|
|
176
176
|
outline.position = pos
|
|
177
177
|
self._throttled_update()
|
|
@@ -182,15 +182,114 @@ class Clip3dTool(ipw.HBox):
|
|
|
182
182
|
self.slider.value[1], unit=self._unit
|
|
183
183
|
)
|
|
184
184
|
|
|
185
|
+
def make_selection(self, da: sc.DataArray) -> sc.Variable:
|
|
186
|
+
"""
|
|
187
|
+
Make a selection variable based on the current slider range.
|
|
188
|
+
"""
|
|
189
|
+
xmin, xmax = self.range
|
|
190
|
+
return (da.coords[self.dim] >= xmin) & (da.coords[self.dim] < xmax)
|
|
191
|
+
|
|
192
|
+
@debounce(0.3)
|
|
193
|
+
def _throttled_update(self):
|
|
194
|
+
self._update()
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ClipValueTool(ipw.HBox):
|
|
198
|
+
"""
|
|
199
|
+
A tool that provides a slider to extract a points in a three-dimensional
|
|
200
|
+
scatter plot based on a value selection criterion, and adds it to the scene as an
|
|
201
|
+
opaque cut. The slider controls the range of the selection.
|
|
202
|
+
|
|
203
|
+
.. versionadded:: 26.2.0
|
|
204
|
+
|
|
205
|
+
Parameters
|
|
206
|
+
----------
|
|
207
|
+
limits:
|
|
208
|
+
The range of values in the original data.
|
|
209
|
+
update:
|
|
210
|
+
A function to update the scene.
|
|
211
|
+
"""
|
|
212
|
+
|
|
213
|
+
def __init__(self, limits: sc.Variable, update: Callable):
|
|
214
|
+
self._limits = limits
|
|
215
|
+
self._unit = self._limits.unit
|
|
216
|
+
self.visible = True
|
|
217
|
+
self._update = update
|
|
218
|
+
self.kind = 'v'
|
|
219
|
+
|
|
220
|
+
center = self._limits.mean().value
|
|
221
|
+
vmin = self._limits[0].value
|
|
222
|
+
vmax = self._limits[1].value
|
|
223
|
+
dx = vmax - vmin
|
|
224
|
+
delta = 0.05 * dx
|
|
225
|
+
self.slider = ipw.FloatRangeSlider(
|
|
226
|
+
min=vmin,
|
|
227
|
+
max=vmax,
|
|
228
|
+
value=[center - delta, center + delta],
|
|
229
|
+
step=dx * 0.01,
|
|
230
|
+
description="Values",
|
|
231
|
+
style={'description_width': 'initial'},
|
|
232
|
+
layout={'width': '35.6em', 'padding': '0px'},
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
self.cut_visible = ipw.Button(
|
|
236
|
+
icon='eye-slash',
|
|
237
|
+
tooltip='Hide cut',
|
|
238
|
+
layout={'width': '1.23em', 'padding': '0px'},
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
self.unit_label = ipw.Label(f'[{self._unit}]')
|
|
242
|
+
self.cut_visible.on_click(self.toggle)
|
|
243
|
+
self.slider.observe(self.move, names='value')
|
|
244
|
+
|
|
245
|
+
super().__init__([self.slider, ipw.Label(f'[{self._unit}]'), self.cut_visible])
|
|
246
|
+
|
|
247
|
+
def toggle(self, owner: ipw.Button):
|
|
248
|
+
"""
|
|
249
|
+
Toggle the visibility of the cut on and off.
|
|
250
|
+
"""
|
|
251
|
+
self.visible = not self.visible
|
|
252
|
+
self.slider.disabled = not self.visible
|
|
253
|
+
owner.icon = 'eye-slash' if self.visible else 'eye'
|
|
254
|
+
owner.tooltip = 'Hide cut' if self.visible else 'Show cut'
|
|
255
|
+
self._update()
|
|
256
|
+
|
|
257
|
+
def toggle_border(self, _):
|
|
258
|
+
return
|
|
259
|
+
|
|
260
|
+
@property
|
|
261
|
+
def range(self):
|
|
262
|
+
return sc.scalar(self.slider.value[0], unit=self._unit), sc.scalar(
|
|
263
|
+
self.slider.value[1], unit=self._unit
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def make_selection(self, da: sc.DataArray) -> sc.Variable:
|
|
267
|
+
"""
|
|
268
|
+
Make a selection variable based on the current slider range.
|
|
269
|
+
"""
|
|
270
|
+
xmin, xmax = self.range
|
|
271
|
+
return (da.data >= xmin) & (da.data < xmax)
|
|
272
|
+
|
|
273
|
+
def move(self, change: dict[str, Any]):
|
|
274
|
+
# Early return if relative difference between new and old value is small.
|
|
275
|
+
# This also prevents flickering of an existing cut when a new cut is added.
|
|
276
|
+
if (
|
|
277
|
+
np.abs(np.array(change['new']) - np.array(change['old'])).max()
|
|
278
|
+
< 0.01 * self.slider.step
|
|
279
|
+
):
|
|
280
|
+
return
|
|
281
|
+
self._throttled_update()
|
|
282
|
+
|
|
185
283
|
@debounce(0.3)
|
|
186
284
|
def _throttled_update(self):
|
|
187
285
|
self._update()
|
|
188
286
|
|
|
189
287
|
|
|
190
|
-
class
|
|
288
|
+
class ClippingManager(ipw.HBox):
|
|
191
289
|
"""
|
|
192
290
|
A widget to make clipping planes for spatial cutting (see :class:`Clip3dTool`) to
|
|
193
291
|
make spatial cuts in the X, Y, and Z directions on a three-dimensional scatter plot.
|
|
292
|
+
The widget also allows to make value-based cuts using :class:`ClipValueTool`.
|
|
194
293
|
|
|
195
294
|
.. versionadded:: 24.04.0
|
|
196
295
|
|
|
@@ -226,33 +325,47 @@ class ClippingPlanes(ipw.HBox):
|
|
|
226
325
|
self.cuts = []
|
|
227
326
|
self._operation = 'or'
|
|
228
327
|
|
|
229
|
-
self.tabs = ipw.Tab(layout={'width': '
|
|
328
|
+
self.tabs = ipw.Tab(layout={'width': '41.6em'})
|
|
230
329
|
self._original_nodes = list(self._view.graph_nodes.values())
|
|
231
330
|
self._nodes = {}
|
|
232
331
|
|
|
332
|
+
self._value_limits = sc.concat(
|
|
333
|
+
[
|
|
334
|
+
min(n().data.min() for n in self._original_nodes),
|
|
335
|
+
max(n().data.max() for n in self._original_nodes),
|
|
336
|
+
],
|
|
337
|
+
dim="dummy",
|
|
338
|
+
)
|
|
339
|
+
|
|
233
340
|
self.add_cut_label = ipw.Label('Add cut:')
|
|
234
|
-
layout = {'width': '45px', 'padding': '0px 0px 0px 0px'}
|
|
235
341
|
self.add_x_cut = ipw.Button(
|
|
236
342
|
description='X',
|
|
237
343
|
icon='plus',
|
|
238
344
|
tooltip='Add X cut',
|
|
239
|
-
|
|
345
|
+
**BUTTON_LAYOUT,
|
|
240
346
|
)
|
|
241
347
|
self.add_y_cut = ipw.Button(
|
|
242
348
|
description='Y',
|
|
243
349
|
icon='plus',
|
|
244
350
|
tooltip='Add Y cut',
|
|
245
|
-
|
|
351
|
+
**BUTTON_LAYOUT,
|
|
246
352
|
)
|
|
247
353
|
self.add_z_cut = ipw.Button(
|
|
248
354
|
description='Z',
|
|
249
355
|
icon='plus',
|
|
250
356
|
tooltip='Add Z cut',
|
|
251
|
-
|
|
357
|
+
**BUTTON_LAYOUT,
|
|
358
|
+
)
|
|
359
|
+
self.add_v_cut = ipw.Button(
|
|
360
|
+
description='V',
|
|
361
|
+
icon='plus',
|
|
362
|
+
tooltip='Add Value cut',
|
|
363
|
+
**BUTTON_LAYOUT,
|
|
252
364
|
)
|
|
253
365
|
self.add_x_cut.on_click(lambda _: self._add_cut('x'))
|
|
254
366
|
self.add_y_cut.on_click(lambda _: self._add_cut('y'))
|
|
255
367
|
self.add_z_cut.on_click(lambda _: self._add_cut('z'))
|
|
368
|
+
self.add_v_cut.on_click(lambda _: self._add_cut('v'))
|
|
256
369
|
|
|
257
370
|
self.opacity = ipw.BoundedFloatText(
|
|
258
371
|
min=0,
|
|
@@ -263,7 +376,7 @@ class ClippingPlanes(ipw.HBox):
|
|
|
263
376
|
description='Opacity:',
|
|
264
377
|
tooltip='Set the opacity of the background',
|
|
265
378
|
style={'description_width': 'initial'},
|
|
266
|
-
layout={'width': '
|
|
379
|
+
layout={'width': '12.1em', 'padding': '0px 0px 0px 0px'},
|
|
267
380
|
)
|
|
268
381
|
self.opacity.observe(self._set_opacity, names='value')
|
|
269
382
|
|
|
@@ -283,7 +396,7 @@ class ClippingPlanes(ipw.HBox):
|
|
|
283
396
|
value='OR',
|
|
284
397
|
disabled=True,
|
|
285
398
|
tooltip='Operation to combine multiple cuts',
|
|
286
|
-
layout={'width': '
|
|
399
|
+
layout={'width': '5.9em', 'padding': '0px 0px 0px 0px'},
|
|
287
400
|
)
|
|
288
401
|
self.cut_operation.observe(self.change_operation, names='value')
|
|
289
402
|
|
|
@@ -300,7 +413,14 @@ class ClippingPlanes(ipw.HBox):
|
|
|
300
413
|
self.tabs,
|
|
301
414
|
ipw.VBox(
|
|
302
415
|
[
|
|
303
|
-
ipw.HBox(
|
|
416
|
+
ipw.HBox(
|
|
417
|
+
[
|
|
418
|
+
self.add_x_cut,
|
|
419
|
+
self.add_y_cut,
|
|
420
|
+
self.add_z_cut,
|
|
421
|
+
self.add_v_cut,
|
|
422
|
+
]
|
|
423
|
+
),
|
|
304
424
|
self.opacity,
|
|
305
425
|
ipw.HBox(
|
|
306
426
|
[
|
|
@@ -316,17 +436,20 @@ class ClippingPlanes(ipw.HBox):
|
|
|
316
436
|
|
|
317
437
|
self.layout.display = 'none'
|
|
318
438
|
|
|
319
|
-
def _add_cut(self,
|
|
439
|
+
def _add_cut(self, kind: Literal['x', 'y', 'z', 'v']):
|
|
320
440
|
"""
|
|
321
|
-
Add a cut
|
|
441
|
+
Add a cut of the specified kind.
|
|
322
442
|
"""
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
443
|
+
if kind == 'v':
|
|
444
|
+
cut = ClipValueTool(limits=self._value_limits, update=self.update_state)
|
|
445
|
+
else:
|
|
446
|
+
cut = Clip3dTool(
|
|
447
|
+
kind=kind,
|
|
448
|
+
limits=self._limits,
|
|
449
|
+
update=self.update_state,
|
|
450
|
+
border_visible=self.cut_borders_visibility.value,
|
|
451
|
+
)
|
|
452
|
+
self._view.canvas.add(cut.outlines)
|
|
330
453
|
self.cuts.append(cut)
|
|
331
454
|
self.tabs.children = [*self.tabs.children, cut]
|
|
332
455
|
self.tabs.selected_index = len(self.cuts) - 1
|
|
@@ -335,7 +458,8 @@ class ClippingPlanes(ipw.HBox):
|
|
|
335
458
|
|
|
336
459
|
def _remove_cut(self, _):
|
|
337
460
|
cut = self.cuts.pop(self.tabs.selected_index)
|
|
338
|
-
|
|
461
|
+
if cut.kind != 'v':
|
|
462
|
+
self._view.canvas.remove(cut.outlines)
|
|
339
463
|
self.tabs.children = self.cuts
|
|
340
464
|
self.update_state()
|
|
341
465
|
self.update_controls()
|
|
@@ -350,7 +474,7 @@ class ClippingPlanes(ipw.HBox):
|
|
|
350
474
|
self.delete_cut.disabled = not at_least_one_cut
|
|
351
475
|
self.cut_borders_visibility.disabled = not at_least_one_cut
|
|
352
476
|
self.cut_operation.disabled = not at_least_one_cut
|
|
353
|
-
self.tabs.titles = [cut.
|
|
477
|
+
self.tabs.titles = [cut.kind.upper() for cut in self.cuts]
|
|
354
478
|
self.opacity.disabled = not at_least_one_cut
|
|
355
479
|
opacity = self.opacity.value if at_least_one_cut else 1.0
|
|
356
480
|
self._set_opacity({'new': opacity})
|
|
@@ -403,12 +527,7 @@ class ClippingPlanes(ipw.HBox):
|
|
|
403
527
|
|
|
404
528
|
for n in self._original_nodes:
|
|
405
529
|
da = n.request_data()
|
|
406
|
-
selections = []
|
|
407
|
-
for cut in visible_cuts:
|
|
408
|
-
xmin, xmax = cut.range
|
|
409
|
-
selections.append(
|
|
410
|
-
(da.coords[cut.dim] >= xmin) & (da.coords[cut.dim] < xmax)
|
|
411
|
-
)
|
|
530
|
+
selections = [cut.make_selection(da) for cut in visible_cuts]
|
|
412
531
|
selection = OPERATIONS[self._operation](selections)
|
|
413
532
|
if selection.sum().value > 0:
|
|
414
533
|
if n.id not in self._nodes:
|
plopp/widgets/drawing.py
CHANGED
|
@@ -76,6 +76,7 @@ class DrawingTool(ToggleTool):
|
|
|
76
76
|
self._destination = destination
|
|
77
77
|
self._destination_is_fig = is_figure(self._destination)
|
|
78
78
|
self._get_artist_info = get_artist_info
|
|
79
|
+
self._get_artist_info_is_callable = None
|
|
79
80
|
self._tool.on_create(self.make_node)
|
|
80
81
|
self._tool.on_remove(self.remove_node)
|
|
81
82
|
if continuous_update:
|
|
@@ -85,7 +86,10 @@ class DrawingTool(ToggleTool):
|
|
|
85
86
|
self._tool.on_drag_release(self.update_node)
|
|
86
87
|
|
|
87
88
|
def make_node(self, artist):
|
|
88
|
-
|
|
89
|
+
info = self._get_artist_info(artist=artist, figure=self._figure)
|
|
90
|
+
if self._get_artist_info_is_callable is None:
|
|
91
|
+
self._get_artist_info_is_callable = callable(info)
|
|
92
|
+
draw_node = Node(info)
|
|
89
93
|
draw_node.pretty_name = f'Draw node {len(self._draw_nodes)}'
|
|
90
94
|
nodeid = draw_node.id
|
|
91
95
|
self._draw_nodes[nodeid] = draw_node
|
|
@@ -105,7 +109,10 @@ class DrawingTool(ToggleTool):
|
|
|
105
109
|
|
|
106
110
|
def update_node(self, artist):
|
|
107
111
|
n = self._draw_nodes[artist.nodeid]
|
|
108
|
-
|
|
112
|
+
if self._get_artist_info_is_callable:
|
|
113
|
+
n.func = self._get_artist_info(artist=artist, figure=self._figure)
|
|
114
|
+
else:
|
|
115
|
+
n.func = partial(self._get_artist_info, artist=artist, figure=self._figure)
|
|
109
116
|
n.notify_children(artist)
|
|
110
117
|
|
|
111
118
|
def remove_node(self, artist):
|
|
@@ -134,7 +141,7 @@ def _get_points_info(artist, figure):
|
|
|
134
141
|
Convert the raw (x, y) position of a point to a dict containing the dimensions of
|
|
135
142
|
each axis, and scalar values with units.
|
|
136
143
|
"""
|
|
137
|
-
return
|
|
144
|
+
return {
|
|
138
145
|
'x': {
|
|
139
146
|
'dim': figure.canvas.dims['x'],
|
|
140
147
|
'value': sc.scalar(artist.x, unit=figure.canvas.units['x']),
|
|
@@ -182,3 +189,73 @@ value:
|
|
|
182
189
|
**kwargs:
|
|
183
190
|
Additional arguments are forwarded to the ``ToggleTool`` constructor.
|
|
184
191
|
"""
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _get_polygon_info(artist, figure):
|
|
195
|
+
"""
|
|
196
|
+
Convert the raw polygon vertices to a dict containing the dimensions of
|
|
197
|
+
each axis, and arrays with units.
|
|
198
|
+
"""
|
|
199
|
+
xs, ys = artist.x, artist.y
|
|
200
|
+
return {
|
|
201
|
+
'x': {
|
|
202
|
+
'dim': figure.canvas.dims['x'],
|
|
203
|
+
'value': sc.array(
|
|
204
|
+
dims=['vertex'],
|
|
205
|
+
values=xs,
|
|
206
|
+
unit=figure.canvas.units['x'],
|
|
207
|
+
),
|
|
208
|
+
},
|
|
209
|
+
'y': {
|
|
210
|
+
'dim': figure.canvas.dims['y'],
|
|
211
|
+
'value': sc.array(
|
|
212
|
+
dims=['vertex'],
|
|
213
|
+
values=ys,
|
|
214
|
+
unit=figure.canvas.units['y'],
|
|
215
|
+
),
|
|
216
|
+
},
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def _make_polygons(**kwargs):
|
|
221
|
+
"""
|
|
222
|
+
Intermediate function needed for giving to `partial` to avoid making mpltoolbox a
|
|
223
|
+
hard dependency.
|
|
224
|
+
"""
|
|
225
|
+
from mpltoolbox import Polygons
|
|
226
|
+
|
|
227
|
+
return Polygons(**kwargs)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
PolygonTool = partial(
|
|
231
|
+
DrawingTool,
|
|
232
|
+
tool=partial(_make_polygons, mec='w'),
|
|
233
|
+
get_artist_info=_get_polygon_info,
|
|
234
|
+
icon='draw-polygon',
|
|
235
|
+
)
|
|
236
|
+
"""
|
|
237
|
+
Tool to draw polygon selections on a figure.
|
|
238
|
+
|
|
239
|
+
Controls:
|
|
240
|
+
- Left-click to make new polygons
|
|
241
|
+
- Left-click and hold on polygon vertex to move vertex
|
|
242
|
+
- Right-click and hold to drag/move the entire polygon
|
|
243
|
+
- Middle-click to delete polygon
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
figure:
|
|
248
|
+
The figure where the tool will draw the polygon.
|
|
249
|
+
input_node:
|
|
250
|
+
The node that provides the raw data which is shown in ``figure``.
|
|
251
|
+
func:
|
|
252
|
+
The function to be used to make a node whose parents will be the ``input_node``
|
|
253
|
+
and a node yielding the current state of the tool (current position, size).
|
|
254
|
+
destination:
|
|
255
|
+
Where the output from the ``func`` node will be then sent on. This can either
|
|
256
|
+
be a figure, or another graph node.
|
|
257
|
+
value:
|
|
258
|
+
Activate the tool upon creation if ``True``.
|
|
259
|
+
**kwargs:
|
|
260
|
+
Additional arguments are forwarded to the ``ToggleTool`` constructor.
|
|
261
|
+
"""
|
plopp/widgets/linesave.py
CHANGED
|
@@ -35,7 +35,7 @@ class LineSaveTool(VBar):
|
|
|
35
35
|
self.button = ipw.Button(description='Save line')
|
|
36
36
|
self.button.on_click(self.save_line)
|
|
37
37
|
self.container = VBar()
|
|
38
|
-
super().__init__([self.button, self.container], layout={'width': '
|
|
38
|
+
super().__init__([self.button, self.container], layout={'width': '26.5em'})
|
|
39
39
|
|
|
40
40
|
def _update_container(self):
|
|
41
41
|
self.container.children = [line['tool'] for line in self._lines.values()]
|
plopp/widgets/slice.py
CHANGED
|
@@ -32,7 +32,7 @@ class DimSlicer(ipw.VBox):
|
|
|
32
32
|
'value': (size - 1) // 2 if slider_constr is ipw.IntSlider else None,
|
|
33
33
|
'continuous_update': True,
|
|
34
34
|
'readout': False,
|
|
35
|
-
'layout': {"width": "
|
|
35
|
+
'layout': {"width": "15.2em", "margin": "0px 0px 0px 10px"},
|
|
36
36
|
}
|
|
37
37
|
self.dim_label = ipw.Label(value=dim)
|
|
38
38
|
self.slider = slider_constr(**widget_args)
|
|
@@ -40,7 +40,7 @@ class DimSlicer(ipw.VBox):
|
|
|
40
40
|
value=True,
|
|
41
41
|
tooltip="Continuous update",
|
|
42
42
|
indent=False,
|
|
43
|
-
layout={"width": "
|
|
43
|
+
layout={"width": "1.52em"},
|
|
44
44
|
)
|
|
45
45
|
self.label = ipw.Label(
|
|
46
46
|
value=coord_element_to_string(coord[dim, self.slider.value])
|
plopp/widgets/style.py
CHANGED
plopp/widgets/tools.py
CHANGED
|
@@ -54,7 +54,7 @@ class ToggleTool(ipw.ToggleButton):
|
|
|
54
54
|
|
|
55
55
|
class PlusMinusTool(ipw.HBox):
|
|
56
56
|
def __init__(self, plus, minus):
|
|
57
|
-
layout = {'width': '
|
|
57
|
+
layout = {'width': '1.23em', 'padding': '0px'}
|
|
58
58
|
self.callback = {'plus': plus.pop('callback'), 'minus': minus.pop('callback')}
|
|
59
59
|
self._plus = ipw.Button(icon='plus', **{**{'layout': layout}, **plus})
|
|
60
60
|
self._minus = ipw.Button(icon='minus', **{**{'layout': layout}, **minus})
|
|
@@ -230,10 +230,10 @@ class ColorTool(ipw.HBox):
|
|
|
230
230
|
"""
|
|
231
231
|
|
|
232
232
|
def __init__(self, text: str, color: str):
|
|
233
|
-
layout = ipw.Layout(display="flex", justify_content="flex-end", width='
|
|
233
|
+
layout = ipw.Layout(display="flex", justify_content="flex-end", width='11.4em')
|
|
234
234
|
self.text = ipw.Label(value=text, layout=layout)
|
|
235
235
|
self.color = ipw.ColorPicker(
|
|
236
|
-
concise=True, value=color, description='', layout={'width': "
|
|
236
|
+
concise=True, value=color, description='', layout={'width': "2.3em"}
|
|
237
237
|
)
|
|
238
238
|
self.button = ipw.Button(icon='times', **BUTTON_LAYOUT)
|
|
239
239
|
super().__init__([self.text, self.color, self.button])
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plopp
|
|
3
|
-
Version:
|
|
3
|
+
Version: 26.2.0
|
|
4
4
|
Summary: Visualization library for Scipp
|
|
5
5
|
Author: Scipp contributors
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -36,12 +36,13 @@ Requires-Dist: graphviz>=0.20.3; extra == "test"
|
|
|
36
36
|
Requires-Dist: h5py>=3.12; extra == "test"
|
|
37
37
|
Requires-Dist: ipympl>=0.8.4; extra == "test"
|
|
38
38
|
Requires-Dist: ipywidgets>=8.1.0; extra == "test"
|
|
39
|
+
Requires-Dist: ipykernel<7,>=6.26; extra == "test"
|
|
39
40
|
Requires-Dist: mpltoolbox>=24.6.0; extra == "test"
|
|
40
41
|
Requires-Dist: pandas>=2.2.2; extra == "test"
|
|
41
42
|
Requires-Dist: plotly>=5.15.0; extra == "test"
|
|
42
43
|
Requires-Dist: pooch>=1.5; extra == "test"
|
|
43
44
|
Requires-Dist: pyarrow>=13.0.0; extra == "test"
|
|
44
|
-
Requires-Dist: pytest>=
|
|
45
|
+
Requires-Dist: pytest>=8.0; extra == "test"
|
|
45
46
|
Requires-Dist: pythreejs>=2.4.1; extra == "test"
|
|
46
47
|
Requires-Dist: scipp>=25.5.0; extra == "test"
|
|
47
48
|
Requires-Dist: scipy>=1.10.0; extra == "test"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
plopp/__init__.py,sha256=
|
|
1
|
+
plopp/__init__.py,sha256=sDy1Bzh9cCQvH-JQ1m3e1V0aK-ZrDGycaE791H-nryA,1092
|
|
2
2
|
plopp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
plopp/backends/__init__.py,sha256=hMvOWD3ua_34nUgcb_jITMnRvjRUUqHSUYuXxkxSIMM,1491
|
|
4
4
|
plopp/backends/common.py,sha256=xz4iMmgE4UtRzQJQlX4Sc9sbTCmjPrd7b4dnpCbWuQs,3422
|
|
5
5
|
plopp/backends/matplotlib/__init__.py,sha256=QNPZgWV3wO8psaMkSB-GtTOP-OIe9jSfwDdEt2xgbZg,107
|
|
6
|
-
plopp/backends/matplotlib/canvas.py,sha256=
|
|
6
|
+
plopp/backends/matplotlib/canvas.py,sha256=0fRyNkreWEBxdjTr8LJ-OlYs9Z1IHGCiqMS76H07DTs,17216
|
|
7
7
|
plopp/backends/matplotlib/fast_image.py,sha256=1tF4G8kTmgmiybdD-KPIatU2dWwv0dgv4r8IMDQTTGE,6532
|
|
8
8
|
plopp/backends/matplotlib/figure.py,sha256=Emj8KCIFvesEfAoKIWn4M4TcgZhq6zFTfRTFnLZHGxE,5515
|
|
9
9
|
plopp/backends/matplotlib/image.py,sha256=YnczwdxsGmh0Nvrk1A8xL93ap2rzKI-0G428OCQmSCc,974
|
|
@@ -13,7 +13,7 @@ plopp/backends/matplotlib/scatter.py,sha256=DH-uvlGCWXrCd9qtBqUmg_qwgACnPTEjaUhT
|
|
|
13
13
|
plopp/backends/matplotlib/tiled.py,sha256=dy5bKixJP7MYoFJ-bAU4vc6baZaw6aKenD-WoahF_ZM,6238
|
|
14
14
|
plopp/backends/matplotlib/utils.py,sha256=24gmT2sBHS_wUitUThSLavwSK95uqbLAhadNdxC19I0,3479
|
|
15
15
|
plopp/backends/plotly/__init__.py,sha256=QNPZgWV3wO8psaMkSB-GtTOP-OIe9jSfwDdEt2xgbZg,107
|
|
16
|
-
plopp/backends/plotly/canvas.py,sha256=
|
|
16
|
+
plopp/backends/plotly/canvas.py,sha256=Zcopn9avEjGBFUnBxgKFy7uIi_8GiDeyPCIAAF6L6CI,11025
|
|
17
17
|
plopp/backends/plotly/figure.py,sha256=60QsXUFjr3CqXjxVpAbvd1c-wtq7T5fptotwybgUEcI,1356
|
|
18
18
|
plopp/backends/plotly/line.py,sha256=yVpJW-xWa0Wy_oMdmvDn2ESIJymT9Fg6wN7RhIRK6Dw,9533
|
|
19
19
|
plopp/backends/pythreejs/__init__.py,sha256=XvSD58Tj5WBrrOjkePcCrRW6RbbyrvhhWI_S8-AR92U,107
|
|
@@ -40,39 +40,39 @@ plopp/graphics/__init__.pyi,sha256=yZQkWxUfP9eKHSP47WjrBiu59Z-uhP5gE2eUrPDa6jU,6
|
|
|
40
40
|
plopp/graphics/basefig.py,sha256=N5gBQE8w2968biRV7a3wkxxq-BM14ncfX52y5HziWiQ,824
|
|
41
41
|
plopp/graphics/bbox.py,sha256=3WeMgu5LgWIXLJTYVT7_USwaAcS7cQPNCk6B0gvuNGo,3596
|
|
42
42
|
plopp/graphics/camera.py,sha256=fXc1YH7Tp-rwKkcYeE-mHrxMz3SKauj5xJCRuKv-QPs,5323
|
|
43
|
-
plopp/graphics/colormapper.py,sha256=
|
|
43
|
+
plopp/graphics/colormapper.py,sha256=Owe3l5cKNExJfh8_RPWGqhymhWrbmny7aDX9sx3yZP4,14962
|
|
44
44
|
plopp/graphics/figures.py,sha256=PLWQLnQNWz8epubYlK7HLSkvPGJmTAmHjJM2DqpvLe8,2735
|
|
45
|
-
plopp/graphics/graphicalview.py,sha256=
|
|
45
|
+
plopp/graphics/graphicalview.py,sha256=LpfQIYN-OQeg_manrMskfpjH6XYsOCIQuZnMNEb0BKw,11660
|
|
46
46
|
plopp/graphics/tiled.py,sha256=gXYZVAs6wRFKezzN3VlEvm7_z2zx3IDYfGZQZqrpL80,1386
|
|
47
47
|
plopp/plotting/__init__.py,sha256=s25RIbKplHSRn0jjtLADPQl72JLf_KLRMRCfeMDI_UA,205
|
|
48
48
|
plopp/plotting/__init__.pyi,sha256=95wYGfM5PT0Y0Ov_ue7rBrHvHxrtex-2WEYtfFzBvE0,475
|
|
49
|
-
plopp/plotting/common.py,sha256=
|
|
50
|
-
plopp/plotting/inspector.py,sha256=
|
|
51
|
-
plopp/plotting/mesh3d.py,sha256=
|
|
52
|
-
plopp/plotting/plot.py,sha256=
|
|
53
|
-
plopp/plotting/scatter.py,sha256=
|
|
54
|
-
plopp/plotting/scatter3d.py,sha256=
|
|
55
|
-
plopp/plotting/slicer.py,sha256=
|
|
56
|
-
plopp/plotting/superplot.py,sha256=
|
|
57
|
-
plopp/plotting/xyplot.py,sha256=
|
|
49
|
+
plopp/plotting/common.py,sha256=f9YxSNGYqisOTLit6ls-iQjx2bgs9xIaf3WEMlorNCE,13139
|
|
50
|
+
plopp/plotting/inspector.py,sha256=5XKvz1RfSTpTzRbTSUJ_9oa8SpPtaAht4eixisb0hDQ,8739
|
|
51
|
+
plopp/plotting/mesh3d.py,sha256=4QQPHuYVyur8nKkbTg7zQMQgFU3MjlsyAPFfpQSJMrU,4187
|
|
52
|
+
plopp/plotting/plot.py,sha256=kAjKSH0QZGgWJVpyfk3GCenrLOUx26D5jbvnZscAueU,5657
|
|
53
|
+
plopp/plotting/scatter.py,sha256=soKks05Fe5FafVrnJxhIj6OfFPhkG9zYrq2tADopi_I,6021
|
|
54
|
+
plopp/plotting/scatter3d.py,sha256=7_mgE5apgkVvseLLMUJSfUQWIwW5P50w589AgrCNUvo,5311
|
|
55
|
+
plopp/plotting/slicer.py,sha256=wdRdrvl0_f32U4yhe_7mbHT9ijjPPj9mW1KzAd_xupc,9071
|
|
56
|
+
plopp/plotting/superplot.py,sha256=htLBssBpuoMKlhP7gsNHbjsyLJWLF27DO3HXpmnpauw,4486
|
|
57
|
+
plopp/plotting/xyplot.py,sha256=3XyhLjWaIqpXOjBQONnD7VehO2cLoWkUiJFk471I_Mw,4262
|
|
58
58
|
plopp/utils/__init__.py,sha256=s25RIbKplHSRn0jjtLADPQl72JLf_KLRMRCfeMDI_UA,205
|
|
59
59
|
plopp/utils/__init__.pyi,sha256=n6xB4g9xWLz1tPI2gbX1N_hLKJhL0pbHOqarM4HNUdQ,246
|
|
60
60
|
plopp/utils/arg_parse.py,sha256=ucBYyOoJfFNEyc2XdnmHLLIbhV-eyOW-UY4JSnMKE-4,783
|
|
61
61
|
plopp/utils/deprecation.py,sha256=TW02FJht8_f3pHHRZYr2VurMrKniFQrmNpj4-z5ahKs,673
|
|
62
62
|
plopp/widgets/__init__.py,sha256=s25RIbKplHSRn0jjtLADPQl72JLf_KLRMRCfeMDI_UA,205
|
|
63
|
-
plopp/widgets/__init__.pyi,sha256=
|
|
63
|
+
plopp/widgets/__init__.pyi,sha256=0mbhLTfA-sULqa9kzmhV9WN6OJr9n-9W5Qb-aKQjIW4,878
|
|
64
64
|
plopp/widgets/box.py,sha256=v5Gd3sY0WXcc3UfkcXxZboPG02sfOMXIo50tmpxPwVs,1806
|
|
65
65
|
plopp/widgets/checkboxes.py,sha256=52yJryEu1upYafFxqoTOdAZmHeKzCI_CJiCWSezCtRA,2430
|
|
66
|
-
plopp/widgets/clip3d.py,sha256=
|
|
66
|
+
plopp/widgets/clip3d.py,sha256=NFJGCPsaGcTnlsP5ruW_jfFo4Ywx3zafuPiewpBSbYI,18409
|
|
67
67
|
plopp/widgets/debounce.py,sha256=V5aIcnb465oqXZX-UG_DR7GjvoG6D_C2jjEUBmguGDA,1292
|
|
68
|
-
plopp/widgets/drawing.py,sha256=
|
|
69
|
-
plopp/widgets/linesave.py,sha256=
|
|
70
|
-
plopp/widgets/slice.py,sha256=
|
|
71
|
-
plopp/widgets/style.py,sha256=
|
|
68
|
+
plopp/widgets/drawing.py,sha256=z8gzzD4pN52V0BxHc8lw-CArGQGk0POS-ZGAPQqCAEw,8483
|
|
69
|
+
plopp/widgets/linesave.py,sha256=gnxTT31xsSEroYMRriQNaxokM8cQPjhLPKwujoQTGVE,2459
|
|
70
|
+
plopp/widgets/slice.py,sha256=rL_BiryBB_pITin1N3o7wRTGQaQksI3EEaJTG_040Jw,5477
|
|
71
|
+
plopp/widgets/style.py,sha256=3qEdXNsat4bqk5BDqZie0Sqg2wvHn2zMrWvfFMq9W9M,185
|
|
72
72
|
plopp/widgets/toolbar.py,sha256=GH4KKsPGBnNl-bllGD8gF__PgmKpr_QS7zL81GYXUWE,4697
|
|
73
|
-
plopp/widgets/tools.py,sha256=
|
|
74
|
-
plopp-
|
|
75
|
-
plopp-
|
|
76
|
-
plopp-
|
|
77
|
-
plopp-
|
|
78
|
-
plopp-
|
|
73
|
+
plopp/widgets/tools.py,sha256=01JW4RSR72L2fs-2FkuSMVgBc1Eyq-NSNFIuPx7OWao,7176
|
|
74
|
+
plopp-26.2.0.dist-info/licenses/LICENSE,sha256=fCqnkzCwkGneGtgOidkO2b3ed6SGZT0yhuworuJG0K0,1553
|
|
75
|
+
plopp-26.2.0.dist-info/METADATA,sha256=JoXJTsXScaR-7BiSPMPBQvRIwslxSQ1gFJRO6LyNR-A,2900
|
|
76
|
+
plopp-26.2.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
77
|
+
plopp-26.2.0.dist-info/top_level.txt,sha256=Lk_GLFh37eX-3PO3c6bPkDpir7GjJmbQHYza3IpIphc,6
|
|
78
|
+
plopp-26.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|