hvplot 0.9.3a1__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.
- hvplot/__init__.py +322 -0
- hvplot/_version.py +16 -0
- hvplot/backend_transforms.py +329 -0
- hvplot/converter.py +2855 -0
- hvplot/cudf.py +26 -0
- hvplot/dask.py +42 -0
- hvplot/data/crime.csv +56 -0
- hvplot/datasets.yaml +48 -0
- hvplot/fugue.py +64 -0
- hvplot/ibis.py +21 -0
- hvplot/intake.py +32 -0
- hvplot/interactive.py +968 -0
- hvplot/networkx.py +625 -0
- hvplot/pandas.py +30 -0
- hvplot/plotting/__init__.py +63 -0
- hvplot/plotting/andrews_curves.py +99 -0
- hvplot/plotting/core.py +2288 -0
- hvplot/plotting/lag_plot.py +34 -0
- hvplot/plotting/parallel_coordinates.py +85 -0
- hvplot/plotting/scatter_matrix.py +220 -0
- hvplot/polars.py +21 -0
- hvplot/sample_data.py +30 -0
- hvplot/streamz.py +21 -0
- hvplot/tests/__init__.py +0 -0
- hvplot/tests/conftest.py +44 -0
- hvplot/tests/data/README.md +5 -0
- hvplot/tests/data/RGB-red.byte.tif +0 -0
- hvplot/tests/plotting/__init__.py +0 -0
- hvplot/tests/plotting/testcore.py +108 -0
- hvplot/tests/plotting/testohlc.py +34 -0
- hvplot/tests/plotting/testscattermatrix.py +138 -0
- hvplot/tests/test_links.py +99 -0
- hvplot/tests/testbackend_transforms.py +89 -0
- hvplot/tests/testcharts.py +452 -0
- hvplot/tests/testfugue.py +46 -0
- hvplot/tests/testgeo.py +468 -0
- hvplot/tests/testgeowithoutgv.py +60 -0
- hvplot/tests/testgridplots.py +259 -0
- hvplot/tests/testhelp.py +75 -0
- hvplot/tests/testibis.py +17 -0
- hvplot/tests/testinteractive.py +1442 -0
- hvplot/tests/testnetworkx.py +26 -0
- hvplot/tests/testoperations.py +385 -0
- hvplot/tests/testoptions.py +596 -0
- hvplot/tests/testoverrides.py +74 -0
- hvplot/tests/testpanel.py +70 -0
- hvplot/tests/testpatch.py +135 -0
- hvplot/tests/testplotting.py +69 -0
- hvplot/tests/teststreaming.py +28 -0
- hvplot/tests/testtransforms.py +39 -0
- hvplot/tests/testui.py +383 -0
- hvplot/tests/testutil.py +378 -0
- hvplot/tests/util.py +82 -0
- hvplot/ui.py +1032 -0
- hvplot/util.py +677 -0
- hvplot/utilities.py +129 -0
- hvplot/xarray.py +62 -0
- hvplot-0.9.3a1.dist-info/LICENSE +29 -0
- hvplot-0.9.3a1.dist-info/METADATA +243 -0
- hvplot-0.9.3a1.dist-info/RECORD +63 -0
- hvplot-0.9.3a1.dist-info/WHEEL +5 -0
- hvplot-0.9.3a1.dist-info/entry_points.txt +2 -0
- hvplot-0.9.3a1.dist-info/top_level.txt +1 -0
hvplot/ui.py
ADDED
|
@@ -0,0 +1,1032 @@
|
|
|
1
|
+
import holoviews as _hv
|
|
2
|
+
import numpy as np
|
|
3
|
+
import panel as pn
|
|
4
|
+
import param
|
|
5
|
+
|
|
6
|
+
from holoviews.core.util import datetime_types, dt_to_int, is_number, max_range
|
|
7
|
+
from holoviews.element import tile_sources
|
|
8
|
+
from holoviews.plotting.util import list_cmaps
|
|
9
|
+
from panel.viewable import Viewer
|
|
10
|
+
|
|
11
|
+
from .converter import HoloViewsConverter as _hvConverter
|
|
12
|
+
from .plotting import hvPlot as _hvPlot
|
|
13
|
+
from .util import is_geodataframe, is_xarray, instantiate_crs_str
|
|
14
|
+
|
|
15
|
+
# Defaults
|
|
16
|
+
KINDS = {
|
|
17
|
+
# these are for the kind selector
|
|
18
|
+
'dataframe': sorted(
|
|
19
|
+
set(_hvConverter._kind_mapping)
|
|
20
|
+
- set(_hvConverter._gridded_types)
|
|
21
|
+
- set(_hvConverter._geom_types)
|
|
22
|
+
| set(['points'])
|
|
23
|
+
),
|
|
24
|
+
'gridded': sorted(set(_hvConverter._gridded_types) - set(['dataset'])),
|
|
25
|
+
'geom': _hvConverter._geom_types,
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
KINDS['2d'] = (
|
|
29
|
+
['bivariate', 'heatmap', 'hexbin', 'labels', 'vectorfield', 'points']
|
|
30
|
+
+ KINDS['gridded']
|
|
31
|
+
+ KINDS['geom']
|
|
32
|
+
)
|
|
33
|
+
KINDS['stats'] = ['hist', 'kde', 'boxwhisker', 'violin', 'heatmap', 'bar', 'barh']
|
|
34
|
+
KINDS['all'] = sorted(set(KINDS['dataframe'] + KINDS['gridded'] + KINDS['geom']))
|
|
35
|
+
|
|
36
|
+
CMAPS = [cm for cm in list_cmaps() if not cm.endswith('_r_r')]
|
|
37
|
+
DEFAULT_CMAPS = _hvConverter._default_cmaps
|
|
38
|
+
GEO_FEATURES = ['borders', 'coastline', 'land', 'lakes', 'ocean', 'rivers', 'states', 'grid']
|
|
39
|
+
GEO_TILES = [None] + sorted(tile_sources)
|
|
40
|
+
GEO_KEYS = [
|
|
41
|
+
'crs',
|
|
42
|
+
'crs_kwargs',
|
|
43
|
+
'projection',
|
|
44
|
+
'projection_kwargs',
|
|
45
|
+
'global_extent',
|
|
46
|
+
'project',
|
|
47
|
+
'features',
|
|
48
|
+
'feature_scale',
|
|
49
|
+
]
|
|
50
|
+
AGGREGATORS = [None, 'count', 'min', 'max', 'mean', 'sum', 'any']
|
|
51
|
+
MAX_ROWS = 10000
|
|
52
|
+
CONTROLS_WIDTH = 200
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def explorer(data, **kwargs):
|
|
56
|
+
"""Explore your data and design your plot via an interactive user interface.
|
|
57
|
+
|
|
58
|
+
This function returns an interactive Panel component that enable you to quickly change the
|
|
59
|
+
settings of your plot via widgets.
|
|
60
|
+
|
|
61
|
+
Reference: https://hvplot.holoviz.org/getting_started/explorer.html
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
data : pandas.DataFrame | xarray.DataArray | xarray.Dataset
|
|
66
|
+
Data structure to explore.
|
|
67
|
+
kwargs : optional
|
|
68
|
+
Arguments that `data.hvplot()` would also accept like `kind='bar'`.
|
|
69
|
+
|
|
70
|
+
Returns
|
|
71
|
+
-------
|
|
72
|
+
hvplotExporer
|
|
73
|
+
Panel component to explore the data and design your plot.
|
|
74
|
+
|
|
75
|
+
Example
|
|
76
|
+
-------
|
|
77
|
+
|
|
78
|
+
>>> import hvplot.pandas
|
|
79
|
+
>>> import pandas as pd
|
|
80
|
+
>>> df = pd.DataFrame({"x": [1, 2, 3], "y": [1, 4, 9]})
|
|
81
|
+
>>> hvplot.explorer(df)
|
|
82
|
+
|
|
83
|
+
You can also specify initial values
|
|
84
|
+
|
|
85
|
+
>>> hvplot.explorer(df, kind='bar', x='x')
|
|
86
|
+
"""
|
|
87
|
+
return hvPlotExplorer.from_data(data, **kwargs)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _create_param_pane(inst, widgets_kwargs=None, parameters=None):
|
|
91
|
+
widgets_kwargs = widgets_kwargs or {}
|
|
92
|
+
# Augment widgets kwargs by default, throttling all Number/Range parameters
|
|
93
|
+
# and setting their sizing mode.
|
|
94
|
+
for pname in inst.param:
|
|
95
|
+
if pname == 'name':
|
|
96
|
+
continue
|
|
97
|
+
if pname not in widgets_kwargs:
|
|
98
|
+
widgets_kwargs[pname] = {}
|
|
99
|
+
if isinstance(inst.param[pname], (param.Number, param.Range)):
|
|
100
|
+
widgets_kwargs[pname]['throttled'] = True
|
|
101
|
+
widgets_kwargs[pname]['sizing_mode'] = 'stretch_width'
|
|
102
|
+
kwargs = {
|
|
103
|
+
'show_name': False,
|
|
104
|
+
'widgets': widgets_kwargs,
|
|
105
|
+
'width': CONTROLS_WIDTH,
|
|
106
|
+
}
|
|
107
|
+
if parameters:
|
|
108
|
+
kwargs['parameters'] = parameters
|
|
109
|
+
pane = pn.Param(inst.param, **kwargs)
|
|
110
|
+
return pane
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class Controls(Viewer):
|
|
114
|
+
explorer = param.ClassSelector(class_=Viewer, precedence=-1)
|
|
115
|
+
|
|
116
|
+
_widgets_kwargs = {}
|
|
117
|
+
|
|
118
|
+
__abstract = True
|
|
119
|
+
|
|
120
|
+
def __init__(self, df, **params):
|
|
121
|
+
self._data = df
|
|
122
|
+
super().__init__(**params)
|
|
123
|
+
|
|
124
|
+
def __panel__(self):
|
|
125
|
+
return _create_param_pane(self, widgets_kwargs=self._widgets_kwargs)
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def kwargs(self):
|
|
129
|
+
return {
|
|
130
|
+
k: v
|
|
131
|
+
for k, v in self.param.values().items()
|
|
132
|
+
if k not in ('name', 'explorer') and v is not None and v != ''
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class Colormapping(Controls):
|
|
137
|
+
clim = param.Range(
|
|
138
|
+
label='Colorbar Limits (clim)',
|
|
139
|
+
doc="""
|
|
140
|
+
Upper and lower limits of the colorbar.""",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
cnorm = param.Selector(
|
|
144
|
+
label='Colorbar Normalization (cnorm)',
|
|
145
|
+
default='linear',
|
|
146
|
+
objects=['linear', 'log', 'eq_hist'],
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
color = param.String(default=None)
|
|
150
|
+
|
|
151
|
+
colorbar = param.Boolean(default=None)
|
|
152
|
+
|
|
153
|
+
cmap = param.Selector(default=DEFAULT_CMAPS['linear'], label='Colormap', objects=CMAPS)
|
|
154
|
+
|
|
155
|
+
rescale_discrete_levels = param.Boolean(default=True)
|
|
156
|
+
|
|
157
|
+
symmetric = param.Boolean(
|
|
158
|
+
default=False,
|
|
159
|
+
doc="""
|
|
160
|
+
Whether the data are symmetric around zero.""",
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
_widgets_kwargs = {'clim': {'placeholder': '(min, max)'}}
|
|
164
|
+
|
|
165
|
+
def __init__(self, data, **params):
|
|
166
|
+
super().__init__(data, **params)
|
|
167
|
+
if 'symmetric' not in params:
|
|
168
|
+
self.symmetric = self.explorer._converter._plot_opts.get('symmetric', False)
|
|
169
|
+
|
|
170
|
+
@property
|
|
171
|
+
def colormapped(self):
|
|
172
|
+
if self.explorer.kind in _hvConverter._colorbar_types:
|
|
173
|
+
return True
|
|
174
|
+
return self.color is not None and self.color in self._data
|
|
175
|
+
|
|
176
|
+
@param.depends('color', 'explorer.kind', 'symmetric', watch=True)
|
|
177
|
+
def _update_coloropts(self):
|
|
178
|
+
if not self.colormapped or self.cmap not in list(DEFAULT_CMAPS.values()):
|
|
179
|
+
return
|
|
180
|
+
if self.explorer.kind in _hvConverter._colorbar_types:
|
|
181
|
+
key = 'diverging' if self.symmetric else 'linear'
|
|
182
|
+
self.colorbar = True
|
|
183
|
+
elif self.color in self._data:
|
|
184
|
+
kind = self._data[self.color].dtype.kind
|
|
185
|
+
if kind in 'OSU':
|
|
186
|
+
key = 'categorical'
|
|
187
|
+
elif self.symmetric:
|
|
188
|
+
key = 'diverging'
|
|
189
|
+
else:
|
|
190
|
+
key = 'linear'
|
|
191
|
+
else:
|
|
192
|
+
return
|
|
193
|
+
self.cmap = DEFAULT_CMAPS[key]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
class Style(Controls):
|
|
197
|
+
alpha = param.Magnitude(default=1)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class Axes(Controls):
|
|
201
|
+
legend = param.Selector(default='bottom_right', objects=_hvConverter._legend_positions)
|
|
202
|
+
|
|
203
|
+
logx = param.Boolean(default=False)
|
|
204
|
+
|
|
205
|
+
logy = param.Boolean(default=False)
|
|
206
|
+
|
|
207
|
+
height = param.Integer(default=None, bounds=(0, None))
|
|
208
|
+
|
|
209
|
+
width = param.Integer(default=None, bounds=(0, None))
|
|
210
|
+
|
|
211
|
+
responsive = param.Boolean(default=True)
|
|
212
|
+
|
|
213
|
+
shared_axes = param.Boolean(default=True)
|
|
214
|
+
|
|
215
|
+
xlim = param.Range()
|
|
216
|
+
|
|
217
|
+
ylim = param.Range()
|
|
218
|
+
|
|
219
|
+
logx = param.Boolean(default=False)
|
|
220
|
+
|
|
221
|
+
logy = param.Boolean(default=False)
|
|
222
|
+
|
|
223
|
+
def __init__(self, data, **params):
|
|
224
|
+
super().__init__(data, **params)
|
|
225
|
+
self._update_ranges()
|
|
226
|
+
|
|
227
|
+
@param.depends('explorer.xlim', 'explorer.ylim', watch=True)
|
|
228
|
+
def _update_ranges(self):
|
|
229
|
+
xlim = self.explorer.xlim()
|
|
230
|
+
if xlim is not None and is_number(xlim[0]) and is_number(xlim[1]) and xlim[0] != xlim[1]:
|
|
231
|
+
xlim = self._convert_to_int(xlim)
|
|
232
|
+
self.param.xlim.precedence = 0
|
|
233
|
+
self.param.xlim.bounds = xlim
|
|
234
|
+
else:
|
|
235
|
+
self.param.xlim.precedence = -1
|
|
236
|
+
ylim = self.explorer.ylim()
|
|
237
|
+
if ylim is not None and is_number(ylim[0]) and is_number(ylim[1]) and ylim[0] != ylim[1]:
|
|
238
|
+
ylim = self._convert_to_int(ylim)
|
|
239
|
+
self.param.ylim.precedence = 0
|
|
240
|
+
self.param.ylim.bounds = ylim
|
|
241
|
+
else:
|
|
242
|
+
self.param.ylim.precedence = -1
|
|
243
|
+
|
|
244
|
+
@staticmethod
|
|
245
|
+
def _convert_to_int(val):
|
|
246
|
+
"""
|
|
247
|
+
Converts datetime to int to avoid the error in https://github.com/holoviz/hvplot/issues/964.
|
|
248
|
+
|
|
249
|
+
This function is a workaround and should be removed when a better solution is found.
|
|
250
|
+
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
if isinstance(val[0], datetime_types) and isinstance(val[1], datetime_types):
|
|
254
|
+
val = (dt_to_int(val[0], 'ms'), dt_to_int(val[1], 'ms'))
|
|
255
|
+
|
|
256
|
+
return val
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class Labels(Controls):
|
|
260
|
+
title = param.String(doc='Title for the plot')
|
|
261
|
+
|
|
262
|
+
xlabel = param.String(doc='Axis labels for the x-axis.')
|
|
263
|
+
|
|
264
|
+
ylabel = param.String(doc='Axis labels for the y-axis.')
|
|
265
|
+
|
|
266
|
+
clabel = param.String(
|
|
267
|
+
label='Colorbar Label (clabel)',
|
|
268
|
+
doc="""
|
|
269
|
+
Axis labels for the colorbar.""",
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
fontscale = param.Number(
|
|
273
|
+
default=1,
|
|
274
|
+
doc="""
|
|
275
|
+
Scales the size of all fonts by the same amount, e.g. fontscale=1.5
|
|
276
|
+
enlarges all fonts (title, xticks, labels etc.) by 50%.""",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
rot = param.Integer(
|
|
280
|
+
default=0,
|
|
281
|
+
bounds=(0, 360),
|
|
282
|
+
label='X Tick Labels Rotation (rot)',
|
|
283
|
+
doc="""
|
|
284
|
+
Rotates the axis ticks along the x-axis by the specified
|
|
285
|
+
number of degrees.""",
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class Geographic(Controls):
|
|
290
|
+
tiles = param.ObjectSelector(
|
|
291
|
+
default=None,
|
|
292
|
+
objects=GEO_TILES,
|
|
293
|
+
doc="""
|
|
294
|
+
Whether to overlay the plot on a tile source. Tiles sources
|
|
295
|
+
can be selected by name or a tiles object or class can be passed,
|
|
296
|
+
the default is 'Wikipedia'.""",
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
geo = param.Boolean(
|
|
300
|
+
default=False,
|
|
301
|
+
doc="""
|
|
302
|
+
Whether the plot should be treated as geographic (and assume
|
|
303
|
+
PlateCarree, i.e. lat/lon coordinates). Require GeoViews.""",
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
crs = param.Selector(
|
|
307
|
+
default=None,
|
|
308
|
+
doc="""
|
|
309
|
+
Coordinate reference system of the data specified as Cartopy
|
|
310
|
+
CRS object, proj.4 string or EPSG code.""",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
crs_kwargs = param.Dict(
|
|
314
|
+
default={},
|
|
315
|
+
doc="""
|
|
316
|
+
Keyword arguments to pass to selected CRS.""",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
projection = param.ObjectSelector(
|
|
320
|
+
default=None,
|
|
321
|
+
doc="""
|
|
322
|
+
Projection to use for cartographic plots.""",
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
projection_kwargs = param.Dict(
|
|
326
|
+
default={},
|
|
327
|
+
doc="""
|
|
328
|
+
Keyword arguments to pass to selected projection.""",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
global_extent = param.Boolean(
|
|
332
|
+
default=None,
|
|
333
|
+
doc="""
|
|
334
|
+
Whether to expand the plot extent to span the whole globe.""",
|
|
335
|
+
)
|
|
336
|
+
|
|
337
|
+
project = param.Boolean(
|
|
338
|
+
default=False,
|
|
339
|
+
doc="""
|
|
340
|
+
Whether to project the data before plotting (adds initial
|
|
341
|
+
overhead but avoids projecting data when plot is dynamically
|
|
342
|
+
updated).""",
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
features = param.ListSelector(
|
|
346
|
+
default=None,
|
|
347
|
+
objects=GEO_FEATURES,
|
|
348
|
+
doc="""
|
|
349
|
+
A list of features or a dictionary of features and the scale
|
|
350
|
+
at which to render it. Available features include 'borders',
|
|
351
|
+
'coastline', 'lakes', 'land', 'ocean', 'rivers' and 'states'.""",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
feature_scale = param.ObjectSelector(
|
|
355
|
+
default='110m',
|
|
356
|
+
objects=['110m', '50m', '10m'],
|
|
357
|
+
doc="""
|
|
358
|
+
The scale at which to render the features.""",
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
_widgets_kwargs = {'geo': {'type': pn.widgets.Toggle}}
|
|
362
|
+
|
|
363
|
+
def __init__(self, data, **params):
|
|
364
|
+
gv_available = False
|
|
365
|
+
try:
|
|
366
|
+
import geoviews # noqa
|
|
367
|
+
|
|
368
|
+
gv_available = True
|
|
369
|
+
except ImportError:
|
|
370
|
+
pass
|
|
371
|
+
|
|
372
|
+
geo_params = GEO_KEYS + ['geo']
|
|
373
|
+
if not gv_available and any(p in params for p in geo_params):
|
|
374
|
+
raise ImportError('GeoViews must be installed to enable the geographic options.')
|
|
375
|
+
super().__init__(data, **params)
|
|
376
|
+
if not gv_available:
|
|
377
|
+
for p in geo_params:
|
|
378
|
+
self.param[p].constant = True
|
|
379
|
+
# Workaround: Checkbox widgets don't yet have a tooltip
|
|
380
|
+
self.param['geo'].label = 'geo (require GeoViews)'
|
|
381
|
+
else:
|
|
382
|
+
self._update_crs_projection()
|
|
383
|
+
|
|
384
|
+
@param.depends('geo', watch=True)
|
|
385
|
+
def _update_crs_projection(self):
|
|
386
|
+
enabled = bool(self.geo or self.project)
|
|
387
|
+
for key in GEO_KEYS:
|
|
388
|
+
self.param[key].constant = not enabled
|
|
389
|
+
self.geo = enabled
|
|
390
|
+
if not enabled:
|
|
391
|
+
return
|
|
392
|
+
|
|
393
|
+
from cartopy.crs import CRS
|
|
394
|
+
|
|
395
|
+
crs_list = sorted(
|
|
396
|
+
k
|
|
397
|
+
for k in param.concrete_descendents(CRS).keys()
|
|
398
|
+
if not k.startswith('_') and k != 'CRS'
|
|
399
|
+
)
|
|
400
|
+
crs_list.insert(0, 'GOOGLE_MERCATOR')
|
|
401
|
+
crs_list.insert(0, 'PlateCarree')
|
|
402
|
+
crs_list.remove('PlateCarree')
|
|
403
|
+
|
|
404
|
+
self.param.crs.objects = crs_list
|
|
405
|
+
self.param.projection.objects = crs_list
|
|
406
|
+
updates = {}
|
|
407
|
+
if self.projection is None:
|
|
408
|
+
updates['projection'] = crs_list[0]
|
|
409
|
+
|
|
410
|
+
if self.global_extent is None:
|
|
411
|
+
updates['global_extent'] = True
|
|
412
|
+
|
|
413
|
+
if self.features is None:
|
|
414
|
+
updates['features'] = ['coastline']
|
|
415
|
+
|
|
416
|
+
self.param.update(**updates)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class Operations(Controls):
|
|
420
|
+
datashade = param.Boolean(
|
|
421
|
+
default=False,
|
|
422
|
+
doc="""
|
|
423
|
+
Whether to apply rasterization and shading using datashader
|
|
424
|
+
library returning an RGB object.""",
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
rasterize = param.Boolean(
|
|
428
|
+
default=False,
|
|
429
|
+
doc="""
|
|
430
|
+
Whether to apply rasterization using the datashader library
|
|
431
|
+
returning an aggregated Image.""",
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
aggregator = param.Selector(
|
|
435
|
+
default=None,
|
|
436
|
+
objects=AGGREGATORS,
|
|
437
|
+
doc="""
|
|
438
|
+
Aggregator to use when applying rasterize or datashade operation.""",
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
dynspread = param.Boolean(
|
|
442
|
+
default=False,
|
|
443
|
+
doc="""
|
|
444
|
+
Allows plots generated with datashade=True or rasterize=True
|
|
445
|
+
to increase the point size to make sparse regions more visible.""",
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
x_sampling = param.Number(
|
|
449
|
+
default=None,
|
|
450
|
+
doc="""
|
|
451
|
+
Specifies the smallest allowed sampling interval along the x-axis.""",
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
y_sampling = param.Number(
|
|
455
|
+
default=None,
|
|
456
|
+
doc="""
|
|
457
|
+
Specifies the smallest allowed sampling interval along the y-axis.""",
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
@param.depends('datashade', watch=True)
|
|
461
|
+
def _toggle_rasterize(self):
|
|
462
|
+
if self.datashade:
|
|
463
|
+
self.rasterize = False
|
|
464
|
+
|
|
465
|
+
@param.depends('rasterize', watch=True)
|
|
466
|
+
def _toggle_datashade(self):
|
|
467
|
+
if self.rasterize:
|
|
468
|
+
self.datashade = False
|
|
469
|
+
|
|
470
|
+
@param.depends('rasterize', 'datashade', watch=True, on_init=True)
|
|
471
|
+
def _update_options(self):
|
|
472
|
+
enabled = self.rasterize or self.datashade
|
|
473
|
+
self.param.dynspread.constant = not enabled
|
|
474
|
+
self.param.x_sampling.constant = not enabled
|
|
475
|
+
self.param.y_sampling.constant = not enabled
|
|
476
|
+
self.param.aggregator.constant = not enabled
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class Advanced(Controls):
|
|
480
|
+
opts = param.Dict(
|
|
481
|
+
label='HoloViews .opts()',
|
|
482
|
+
doc="""
|
|
483
|
+
Options applied via HoloViews .opts().
|
|
484
|
+
Examples:
|
|
485
|
+
- image: {"color_levels": 11}
|
|
486
|
+
- line: {"line_dash": "dashed"}
|
|
487
|
+
- scatter: {'size': 5, 'marker': '^'}""",
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
_widgets_kwargs = {'opts': {'placeholder': "{'size': 5, 'marker': '^'}"}}
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
class StatusBar(param.Parameterized):
|
|
494
|
+
live_update = param.Boolean(
|
|
495
|
+
default=True,
|
|
496
|
+
doc="""
|
|
497
|
+
Whether to automatically update the plot when a param is changed""",
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
class hvPlotExplorer(Viewer):
|
|
502
|
+
kind = param.Selector()
|
|
503
|
+
|
|
504
|
+
x = param.Selector()
|
|
505
|
+
|
|
506
|
+
y = param.Selector()
|
|
507
|
+
|
|
508
|
+
y_multi = param.ListSelector(default=[], label='Y')
|
|
509
|
+
|
|
510
|
+
by = param.ListSelector(default=[])
|
|
511
|
+
|
|
512
|
+
groupby = param.ListSelector(default=[])
|
|
513
|
+
|
|
514
|
+
# Controls that will show up as new tabs, must be ClassSelector
|
|
515
|
+
|
|
516
|
+
axes = param.ClassSelector(class_=Axes)
|
|
517
|
+
|
|
518
|
+
colormapping = param.ClassSelector(class_=Colormapping)
|
|
519
|
+
|
|
520
|
+
labels = param.ClassSelector(class_=Labels)
|
|
521
|
+
|
|
522
|
+
geographic = param.ClassSelector(class_=Geographic)
|
|
523
|
+
|
|
524
|
+
operations = param.ClassSelector(class_=Operations)
|
|
525
|
+
|
|
526
|
+
statusbar = param.ClassSelector(class_=StatusBar)
|
|
527
|
+
|
|
528
|
+
style = param.ClassSelector(class_=Style)
|
|
529
|
+
|
|
530
|
+
advanced = param.ClassSelector(class_=Advanced)
|
|
531
|
+
|
|
532
|
+
code = param.String(
|
|
533
|
+
precedence=-1,
|
|
534
|
+
doc="""
|
|
535
|
+
Code to generate the plot.""",
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
@classmethod
|
|
539
|
+
def from_data(cls, data, **params):
|
|
540
|
+
if is_geodataframe(data):
|
|
541
|
+
# cls = hvGeomExplorer
|
|
542
|
+
raise TypeError('GeoDataFrame objects not yet supported.')
|
|
543
|
+
elif is_xarray(data):
|
|
544
|
+
cls = hvGridExplorer
|
|
545
|
+
else:
|
|
546
|
+
cls = hvDataFrameExplorer
|
|
547
|
+
return cls(data, **params)
|
|
548
|
+
|
|
549
|
+
def __panel__(self):
|
|
550
|
+
return self._layout
|
|
551
|
+
|
|
552
|
+
def __init__(self, df, **params):
|
|
553
|
+
x, y = params.get('x'), params.get('y')
|
|
554
|
+
if 'y' in params:
|
|
555
|
+
params['y_multi'] = params.pop('y') if isinstance(params['y'], list) else [params['y']]
|
|
556
|
+
statusbar_params = {k: params.pop(k) for k in params.copy() if k in StatusBar.param}
|
|
557
|
+
converter = _hvConverter(
|
|
558
|
+
df, x, y, **{k: v for k, v in params.items() if k not in ('x', 'y', 'y_multi')}
|
|
559
|
+
)
|
|
560
|
+
# Collect kwargs passed to the constructor but meant for the controls
|
|
561
|
+
extras = {k: params.pop(k) for k in params.copy() if k not in self.param}
|
|
562
|
+
super().__init__(**params)
|
|
563
|
+
self._data = df
|
|
564
|
+
self._converter = converter
|
|
565
|
+
groups = {group: KINDS[group] for group in self._groups}
|
|
566
|
+
# Create pane for the main controls as done by the Controls instances.
|
|
567
|
+
self._controls = _create_param_pane(
|
|
568
|
+
self,
|
|
569
|
+
parameters=['kind', 'x', 'y', 'groupby', 'by'],
|
|
570
|
+
widgets_kwargs={'kind': {'options': [], 'groups': groups}},
|
|
571
|
+
)
|
|
572
|
+
self.param.watch(self._toggle_controls, 'kind')
|
|
573
|
+
self.param.watch(self._check_y, 'y_multi')
|
|
574
|
+
self.param.watch(self._check_by, 'by')
|
|
575
|
+
self._populate()
|
|
576
|
+
self._control_tabs = pn.Tabs(tabs_location='left')
|
|
577
|
+
self.statusbar = StatusBar(**statusbar_params)
|
|
578
|
+
self._statusbar = pn.Param(
|
|
579
|
+
self.statusbar, show_name=False, default_layout=pn.Row, margin=(5, 56, 0, 56)
|
|
580
|
+
)
|
|
581
|
+
controls = [
|
|
582
|
+
p.class_
|
|
583
|
+
for p in self.param.objects().values()
|
|
584
|
+
if isinstance(p, param.ClassSelector) and issubclass(p.class_, Controls)
|
|
585
|
+
]
|
|
586
|
+
controller_params = {}
|
|
587
|
+
for cls in controls:
|
|
588
|
+
controller_params[cls] = {k: extras.pop(k) for k in extras.copy() if k in cls.param}
|
|
589
|
+
if extras:
|
|
590
|
+
raise TypeError(f'__init__() got keyword(s) not supported by any control: {extras}')
|
|
591
|
+
self._controllers = {
|
|
592
|
+
cls.name.lower(): cls(df, explorer=self, **cparams)
|
|
593
|
+
for cls, cparams in controller_params.items()
|
|
594
|
+
}
|
|
595
|
+
self.param.update(**self._controllers)
|
|
596
|
+
self.param.watch(self._refresh, list(self.param))
|
|
597
|
+
for controller in self._controllers.values():
|
|
598
|
+
controller.param.watch(self._refresh, list(controller.param))
|
|
599
|
+
self.statusbar.param.watch(self._refresh, list(self.statusbar.param))
|
|
600
|
+
self._alert = pn.pane.Alert(
|
|
601
|
+
alert_type='danger', visible=False, sizing_mode='stretch_width'
|
|
602
|
+
)
|
|
603
|
+
self._hv_pane = pn.pane.HoloViews(
|
|
604
|
+
sizing_mode='stretch_both',
|
|
605
|
+
min_height=250,
|
|
606
|
+
margin=(5, 5, 5, 20),
|
|
607
|
+
widget_location='bottom',
|
|
608
|
+
widget_layout=pn.Row,
|
|
609
|
+
)
|
|
610
|
+
self._code_pane = pn.pane.Markdown(
|
|
611
|
+
sizing_mode='stretch_both', min_height=250, margin=(5, 5, 0, 20)
|
|
612
|
+
)
|
|
613
|
+
self._layout = pn.Column(
|
|
614
|
+
self._alert,
|
|
615
|
+
self._statusbar,
|
|
616
|
+
pn.layout.Divider(),
|
|
617
|
+
pn.Row(
|
|
618
|
+
self._control_tabs,
|
|
619
|
+
# Using .layout on the HoloViews pane to display the widgets
|
|
620
|
+
# https://github.com/holoviz/panel/issues/5628#issuecomment-1763443895
|
|
621
|
+
pn.Tabs(('Plot', self._hv_pane.layout), ('Code', self._code_pane)),
|
|
622
|
+
sizing_mode='stretch_both',
|
|
623
|
+
),
|
|
624
|
+
sizing_mode='stretch_width',
|
|
625
|
+
height=600,
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
# initialize
|
|
629
|
+
self.param.trigger('kind')
|
|
630
|
+
|
|
631
|
+
def _populate(self):
|
|
632
|
+
"""
|
|
633
|
+
Populates the options of the controls based on the data type.
|
|
634
|
+
"""
|
|
635
|
+
variables = self._converter.variables
|
|
636
|
+
indexes = getattr(self._converter, 'indexes', [])
|
|
637
|
+
variables_no_index = [v for v in variables if v not in indexes]
|
|
638
|
+
for pname in self.param:
|
|
639
|
+
if pname == 'kind':
|
|
640
|
+
continue
|
|
641
|
+
p = self.param[pname]
|
|
642
|
+
if isinstance(p, param.Selector):
|
|
643
|
+
if pname == 'x':
|
|
644
|
+
p.objects = variables
|
|
645
|
+
else:
|
|
646
|
+
p.objects = variables_no_index
|
|
647
|
+
|
|
648
|
+
# Setting the default value if not set
|
|
649
|
+
if (pname == 'x' or pname == 'y') and getattr(self, pname, None) is None:
|
|
650
|
+
setattr(self, pname, p.objects[0])
|
|
651
|
+
|
|
652
|
+
def _plot(self):
|
|
653
|
+
y = self.y_multi if 'y_multi' in self._controls.parameters else self.y
|
|
654
|
+
if isinstance(y, list) and len(y) == 1:
|
|
655
|
+
y = y[0]
|
|
656
|
+
kwargs = {}
|
|
657
|
+
for v in self.param.values().values():
|
|
658
|
+
# Geo is not enabled so not adding it to kwargs
|
|
659
|
+
if isinstance(v, Geographic) and not v.geo:
|
|
660
|
+
continue
|
|
661
|
+
|
|
662
|
+
if isinstance(v, Advanced):
|
|
663
|
+
opts_kwargs = v.kwargs.get('opts', {})
|
|
664
|
+
elif isinstance(v, Controls):
|
|
665
|
+
kwargs.update(v.kwargs)
|
|
666
|
+
|
|
667
|
+
if kwargs.get('geo'):
|
|
668
|
+
if 'crs' not in kwargs:
|
|
669
|
+
xmax = np.max(np.abs(self.xlim()))
|
|
670
|
+
self.geographic.crs = 'PlateCarree' if xmax <= 360 else 'GOOGLE_MERCATOR'
|
|
671
|
+
kwargs['crs'] = self.geographic.crs
|
|
672
|
+
for key in ['crs', 'projection']:
|
|
673
|
+
crs_kwargs = kwargs.pop(f'{key}_kwargs', {})
|
|
674
|
+
kwargs[key] = instantiate_crs_str(kwargs.pop(key), **crs_kwargs)
|
|
675
|
+
|
|
676
|
+
feature_scale = kwargs.pop('feature_scale', None)
|
|
677
|
+
kwargs['features'] = {feature: feature_scale for feature in kwargs.pop('features', [])}
|
|
678
|
+
|
|
679
|
+
kwargs['min_height'] = 400
|
|
680
|
+
df = self._data
|
|
681
|
+
if len(df) > MAX_ROWS and not (
|
|
682
|
+
self.kind in KINDS['stats'] or kwargs.get('rasterize') or kwargs.get('datashade')
|
|
683
|
+
):
|
|
684
|
+
df = df.sample(n=MAX_ROWS)
|
|
685
|
+
self._layout.loading = True
|
|
686
|
+
try:
|
|
687
|
+
self._hvplot = _hvPlot(df)(
|
|
688
|
+
kind=self.kind, x=self.x, y=y, by=self.by, groupby=self.groupby, **kwargs
|
|
689
|
+
)
|
|
690
|
+
if opts_kwargs:
|
|
691
|
+
self._hvplot.opts(**opts_kwargs)
|
|
692
|
+
self._hv_pane.object = self._hvplot
|
|
693
|
+
# Working around a Panel issue that cause the widgets not to
|
|
694
|
+
# be well aligned when in a Row layout.
|
|
695
|
+
# See https://github.com/holoviz/panel/issues/6110
|
|
696
|
+
if len(self._hv_pane.widget_box) > 1:
|
|
697
|
+
for w in self._hv_pane.widget_box:
|
|
698
|
+
w.margin = (20, 5, 5, 5)
|
|
699
|
+
self._alert.visible = False
|
|
700
|
+
except Exception as e:
|
|
701
|
+
self._alert.param.update(
|
|
702
|
+
object=f'**Rendering failed with following error**: {e}', visible=True
|
|
703
|
+
)
|
|
704
|
+
finally:
|
|
705
|
+
self._layout.loading = False
|
|
706
|
+
|
|
707
|
+
def _refresh(self, *events):
|
|
708
|
+
if not self.statusbar.live_update:
|
|
709
|
+
return
|
|
710
|
+
self._plot()
|
|
711
|
+
with param.parameterized.discard_events(self):
|
|
712
|
+
self.code = self.plot_code()
|
|
713
|
+
self._code_pane.object = f"""```python\n{self.code}\n```"""
|
|
714
|
+
|
|
715
|
+
@property
|
|
716
|
+
def _var_name(self):
|
|
717
|
+
return 'data'
|
|
718
|
+
|
|
719
|
+
@property
|
|
720
|
+
def _single_y(self):
|
|
721
|
+
if self.kind in KINDS['2d']:
|
|
722
|
+
return True
|
|
723
|
+
return False
|
|
724
|
+
|
|
725
|
+
@property
|
|
726
|
+
def _groups(self):
|
|
727
|
+
raise NotImplementedError('Must be implemented by subclasses.')
|
|
728
|
+
|
|
729
|
+
def _toggle_controls(self, event=None):
|
|
730
|
+
# Control high-level parameters
|
|
731
|
+
visible = True
|
|
732
|
+
if event and event.new in ('table', 'dataset'):
|
|
733
|
+
parameters = ['kind', 'columns']
|
|
734
|
+
visible = False
|
|
735
|
+
elif event and event.new in KINDS['2d']:
|
|
736
|
+
parameters = ['kind', 'x', 'y', 'by', 'groupby']
|
|
737
|
+
elif event and event.new in ('hist', 'kde', 'density'):
|
|
738
|
+
self.x = None
|
|
739
|
+
parameters = ['kind', 'y_multi', 'by', 'groupby']
|
|
740
|
+
else:
|
|
741
|
+
parameters = ['kind', 'x', 'y_multi', 'by', 'groupby']
|
|
742
|
+
self._controls.parameters = parameters
|
|
743
|
+
# Control other tabs
|
|
744
|
+
tabs = [('Fields', self._controls)]
|
|
745
|
+
if visible:
|
|
746
|
+
tabs += [
|
|
747
|
+
('Axes', self.axes),
|
|
748
|
+
('Labels', self.labels),
|
|
749
|
+
('Style', self.style),
|
|
750
|
+
('Operations', self.operations),
|
|
751
|
+
('Geographic', self.geographic),
|
|
752
|
+
('Advanced', self.advanced),
|
|
753
|
+
]
|
|
754
|
+
if event and event.new not in ('area', 'kde', 'line', 'ohlc', 'rgb', 'step'):
|
|
755
|
+
tabs.insert(5, ('Colormapping', self.colormapping))
|
|
756
|
+
self._control_tabs[:] = tabs
|
|
757
|
+
|
|
758
|
+
def _check_y(self, event):
|
|
759
|
+
if len(event.new) > 1 and self.by:
|
|
760
|
+
self.y = event.old
|
|
761
|
+
|
|
762
|
+
def _check_by(self, event):
|
|
763
|
+
if (
|
|
764
|
+
event.new
|
|
765
|
+
and 'y_multi' in self._controls.parameters
|
|
766
|
+
and self.y_multi
|
|
767
|
+
and len(self.y_multi) > 1
|
|
768
|
+
):
|
|
769
|
+
self.by = []
|
|
770
|
+
|
|
771
|
+
# ----------------------------------------------------------------
|
|
772
|
+
# Public API
|
|
773
|
+
# ----------------------------------------------------------------
|
|
774
|
+
|
|
775
|
+
def hvplot(self):
|
|
776
|
+
"""Return the plot as a HoloViews object."""
|
|
777
|
+
return self._hvplot.clone()
|
|
778
|
+
|
|
779
|
+
def plot_code(self, var_name=None):
|
|
780
|
+
"""Return a string representation that can be easily copy-pasted
|
|
781
|
+
in a notebook cell to create a plot from a call to the `.hvplot`
|
|
782
|
+
accessor, and that includes all the customized settings of the explorer.
|
|
783
|
+
|
|
784
|
+
>>> hvexplorer.plot_code(var_name='data')
|
|
785
|
+
"data.hvplot(x='time', y='value')"
|
|
786
|
+
|
|
787
|
+
Parameters
|
|
788
|
+
----------
|
|
789
|
+
var_name: string
|
|
790
|
+
Data variable name by which the returned string will start.
|
|
791
|
+
"""
|
|
792
|
+
settings = self.settings()
|
|
793
|
+
if 'legend' not in settings:
|
|
794
|
+
settings['legend'] = 'bottom_right'
|
|
795
|
+
settings['widget_location'] = 'bottom'
|
|
796
|
+
settings_args = ''
|
|
797
|
+
if settings:
|
|
798
|
+
settings_args = self._build_kwargs_string(settings)
|
|
799
|
+
snippet = f'{var_name or self._var_name}.hvplot(\n{settings_args}\n)'
|
|
800
|
+
opts = self.advanced.opts
|
|
801
|
+
if opts:
|
|
802
|
+
opts_args = self._build_kwargs_string(opts)
|
|
803
|
+
snippet += f'.opts(\n{opts_args}\n)'
|
|
804
|
+
return snippet
|
|
805
|
+
|
|
806
|
+
def _build_kwargs_string(self, kwargs):
|
|
807
|
+
args = ''
|
|
808
|
+
if kwargs:
|
|
809
|
+
for k, v in kwargs.items():
|
|
810
|
+
args += f' {k}={v!r},\n'
|
|
811
|
+
args = args[:-1]
|
|
812
|
+
return args
|
|
813
|
+
|
|
814
|
+
def save(self, filename, **kwargs):
|
|
815
|
+
"""Save the plot to file.
|
|
816
|
+
|
|
817
|
+
Calls the `holoviews.save` utility, refer to its documentation
|
|
818
|
+
for a full description of the available kwargs.
|
|
819
|
+
|
|
820
|
+
Parameters
|
|
821
|
+
----------
|
|
822
|
+
filename: string, pathlib.Path or IO object
|
|
823
|
+
The path or BytesIO/StringIO object to save to
|
|
824
|
+
"""
|
|
825
|
+
_hv.save(self._hvplot, filename, **kwargs)
|
|
826
|
+
|
|
827
|
+
def settings(self):
|
|
828
|
+
"""Return a dictionary of the customized settings.
|
|
829
|
+
|
|
830
|
+
This dictionary can be reused as an unpacked input to the explorer or
|
|
831
|
+
a call to the `.hvplot` accessor.
|
|
832
|
+
|
|
833
|
+
>>> hvplot.explorer(df, **settings)
|
|
834
|
+
>>> df.hvplot(**settings)
|
|
835
|
+
"""
|
|
836
|
+
settings = {}
|
|
837
|
+
for controller in self._controllers.values():
|
|
838
|
+
params = set(controller.param) - {'name', 'explorer'}
|
|
839
|
+
for p in params:
|
|
840
|
+
value = getattr(controller, p)
|
|
841
|
+
if value != controller.param[p].default:
|
|
842
|
+
settings[p] = value
|
|
843
|
+
for p in self._controls.parameters:
|
|
844
|
+
value = getattr(self, p)
|
|
845
|
+
if value != self.param[p].default or p == 'kind':
|
|
846
|
+
settings[p] = value
|
|
847
|
+
if 'y_multi' in settings:
|
|
848
|
+
settings['y'] = settings.pop('y_multi')
|
|
849
|
+
settings.pop('opts', None)
|
|
850
|
+
settings = {k: v for k, v in sorted(list(settings.items()))}
|
|
851
|
+
return settings
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
class hvGeomExplorer(hvPlotExplorer):
|
|
855
|
+
kind = param.Selector(default=None, objects=KINDS['all'])
|
|
856
|
+
|
|
857
|
+
@property
|
|
858
|
+
def _var_name(self):
|
|
859
|
+
return 'gdf'
|
|
860
|
+
|
|
861
|
+
@property
|
|
862
|
+
def _single_y(self):
|
|
863
|
+
return True
|
|
864
|
+
|
|
865
|
+
@property
|
|
866
|
+
def _x(self):
|
|
867
|
+
return None
|
|
868
|
+
|
|
869
|
+
@property
|
|
870
|
+
def _y(self):
|
|
871
|
+
return None
|
|
872
|
+
|
|
873
|
+
@param.depends('x')
|
|
874
|
+
def xlim(self):
|
|
875
|
+
pass
|
|
876
|
+
|
|
877
|
+
@param.depends('y')
|
|
878
|
+
def ylim(self):
|
|
879
|
+
pass
|
|
880
|
+
|
|
881
|
+
@property
|
|
882
|
+
def _groups(self):
|
|
883
|
+
return ['gridded', 'dataframe']
|
|
884
|
+
|
|
885
|
+
|
|
886
|
+
class hvGridExplorer(hvPlotExplorer):
|
|
887
|
+
kind = param.Selector(default='image', objects=KINDS['all'])
|
|
888
|
+
|
|
889
|
+
def __init__(self, ds, **params):
|
|
890
|
+
import xarray as xr
|
|
891
|
+
|
|
892
|
+
var_name_suffix = ''
|
|
893
|
+
if isinstance(ds, xr.Dataset):
|
|
894
|
+
data_vars = list(ds.data_vars)
|
|
895
|
+
if len(data_vars) == 1:
|
|
896
|
+
ds = ds[data_vars[0]]
|
|
897
|
+
var_name_suffix = f"['{data_vars[0]}']"
|
|
898
|
+
else:
|
|
899
|
+
ds = ds.to_array('variable').transpose(..., 'variable')
|
|
900
|
+
var_name_suffix = ".to_array('variable').transpose(..., 'variable')"
|
|
901
|
+
if 'kind' not in params:
|
|
902
|
+
params['kind'] = 'image'
|
|
903
|
+
self._var_name_suffix = var_name_suffix
|
|
904
|
+
super().__init__(ds, **params)
|
|
905
|
+
|
|
906
|
+
@property
|
|
907
|
+
def _var_name(self):
|
|
908
|
+
if self._var_name_suffix:
|
|
909
|
+
return f'ds{self._var_name_suffix}'
|
|
910
|
+
else:
|
|
911
|
+
return 'da'
|
|
912
|
+
|
|
913
|
+
@property
|
|
914
|
+
def _x(self):
|
|
915
|
+
return (self._converter.x or self._converter.indexes[0]) if self.x is None else self.x
|
|
916
|
+
|
|
917
|
+
@property
|
|
918
|
+
def _y(self):
|
|
919
|
+
return (self._converter.y or self._converter.indexes[1]) if self.y is None else self.y
|
|
920
|
+
|
|
921
|
+
@param.depends('x')
|
|
922
|
+
def xlim(self):
|
|
923
|
+
try:
|
|
924
|
+
values = self._data[self._x]
|
|
925
|
+
except Exception:
|
|
926
|
+
return 0, 1
|
|
927
|
+
if values.dtype.kind in 'OSU':
|
|
928
|
+
return None
|
|
929
|
+
return (np.nanmin(values), np.nanmax(values))
|
|
930
|
+
|
|
931
|
+
@param.depends('y', 'y_multi')
|
|
932
|
+
def ylim(self):
|
|
933
|
+
y = self._y
|
|
934
|
+
if not isinstance(y, list):
|
|
935
|
+
y = [y]
|
|
936
|
+
values = (self._data[y] for y in y)
|
|
937
|
+
return max_range([(np.nanmin(vs), np.nanmax(vs)) for vs in values])
|
|
938
|
+
|
|
939
|
+
@property
|
|
940
|
+
def _groups(self):
|
|
941
|
+
return ['gridded', 'dataframe', 'geom']
|
|
942
|
+
|
|
943
|
+
def _populate(self):
|
|
944
|
+
variables = self._converter.variables
|
|
945
|
+
indexes = getattr(self._converter, 'indexes', [])
|
|
946
|
+
variables_no_index = [v for v in variables if v not in indexes]
|
|
947
|
+
for pname in self.param:
|
|
948
|
+
if pname == 'kind':
|
|
949
|
+
continue
|
|
950
|
+
p = self.param[pname]
|
|
951
|
+
if isinstance(p, param.Selector):
|
|
952
|
+
if pname in ['x', 'y', 'groupby', 'by']:
|
|
953
|
+
p.objects = indexes
|
|
954
|
+
else:
|
|
955
|
+
p.objects = variables_no_index
|
|
956
|
+
|
|
957
|
+
# Setting the default value if not set
|
|
958
|
+
if pname == 'x' and getattr(self, pname, None) is None:
|
|
959
|
+
setattr(self, pname, p.objects[0])
|
|
960
|
+
elif pname == 'y' and getattr(self, pname, None) is None:
|
|
961
|
+
setattr(self, pname, p.objects[1])
|
|
962
|
+
elif (
|
|
963
|
+
pname == 'groupby'
|
|
964
|
+
and len(getattr(self, pname, [])) == 0
|
|
965
|
+
and len(p.objects) > 2
|
|
966
|
+
):
|
|
967
|
+
setattr(self, pname, p.objects[2:])
|
|
968
|
+
|
|
969
|
+
|
|
970
|
+
class hvDataFrameExplorer(hvPlotExplorer):
|
|
971
|
+
z = param.Selector()
|
|
972
|
+
|
|
973
|
+
kind = param.Selector(default='scatter', objects=KINDS['all'])
|
|
974
|
+
|
|
975
|
+
@property
|
|
976
|
+
def _var_name(self):
|
|
977
|
+
return 'df'
|
|
978
|
+
|
|
979
|
+
@property
|
|
980
|
+
def xcat(self):
|
|
981
|
+
if self.kind in ('bar', 'box', 'violin'):
|
|
982
|
+
return False
|
|
983
|
+
values = self._data[self.x]
|
|
984
|
+
return values.dtype.kind in 'OSU'
|
|
985
|
+
|
|
986
|
+
@property
|
|
987
|
+
def _x(self):
|
|
988
|
+
return (self._converter.x or self._converter.variables[0]) if self.x is None else self.x
|
|
989
|
+
|
|
990
|
+
@property
|
|
991
|
+
def _y(self):
|
|
992
|
+
if 'y_multi' in self._controls.parameters and self.y_multi:
|
|
993
|
+
y = self.y_multi
|
|
994
|
+
elif 'y_multi' not in self._controls.parameters and self.y:
|
|
995
|
+
y = self.y
|
|
996
|
+
else:
|
|
997
|
+
y = self._converter._process_chart_y(self._data, self._x, None, self._single_y)
|
|
998
|
+
if isinstance(y, list) and len(y) == 1:
|
|
999
|
+
y = y[0]
|
|
1000
|
+
return y
|
|
1001
|
+
|
|
1002
|
+
@property
|
|
1003
|
+
def _groups(self):
|
|
1004
|
+
return ['dataframe']
|
|
1005
|
+
|
|
1006
|
+
@param.depends('x')
|
|
1007
|
+
def xlim(self):
|
|
1008
|
+
if self._x == 'index':
|
|
1009
|
+
values = self._data.index.values
|
|
1010
|
+
else:
|
|
1011
|
+
try:
|
|
1012
|
+
values = self._data[self._x]
|
|
1013
|
+
except Exception:
|
|
1014
|
+
return 0, 1
|
|
1015
|
+
# for dask series; else it cannot get length
|
|
1016
|
+
if hasattr(values, 'compute_chunk_sizes'):
|
|
1017
|
+
values = values.compute_chunk_sizes()
|
|
1018
|
+
if values.dtype.kind in 'OSU':
|
|
1019
|
+
return None
|
|
1020
|
+
elif not len(values):
|
|
1021
|
+
return (np.nan, np.nan)
|
|
1022
|
+
return (np.nanmin(values), np.nanmax(values))
|
|
1023
|
+
|
|
1024
|
+
@param.depends('y', 'y_multi')
|
|
1025
|
+
def ylim(self):
|
|
1026
|
+
y = self._y
|
|
1027
|
+
if not isinstance(y, list):
|
|
1028
|
+
y = [y]
|
|
1029
|
+
values = [ys for ys in (self._data[y] for y in y) if len(ys)]
|
|
1030
|
+
if not len(values):
|
|
1031
|
+
return (np.nan, np.nan)
|
|
1032
|
+
return max_range([(np.nanmin(vs), np.nanmax(vs)) for vs in values])
|