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
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
from unittest import TestCase, SkipTest
|
|
2
|
+
import sys
|
|
3
|
+
|
|
4
|
+
from parameterized import parameterized
|
|
5
|
+
import numpy as np
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from holoviews.core import GridMatrix, NdOverlay
|
|
9
|
+
from holoviews.element import (
|
|
10
|
+
Bivariate,
|
|
11
|
+
Distribution,
|
|
12
|
+
HexTiles,
|
|
13
|
+
Histogram,
|
|
14
|
+
Scatter,
|
|
15
|
+
)
|
|
16
|
+
from hvplot import scatter_matrix
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestScatterMatrix(TestCase):
|
|
20
|
+
def setUp(self):
|
|
21
|
+
self.df = pd.DataFrame(np.random.randn(1000, 4), columns=['a', 'b', 'c', 'd'])
|
|
22
|
+
|
|
23
|
+
def test_returns_gridmatrix(self):
|
|
24
|
+
sm = scatter_matrix(self.df)
|
|
25
|
+
self.assertIsInstance(sm, GridMatrix)
|
|
26
|
+
|
|
27
|
+
def test_wrong_diagonal(self):
|
|
28
|
+
with self.assertRaises(ValueError):
|
|
29
|
+
scatter_matrix(self.df, diagonal='wrong')
|
|
30
|
+
|
|
31
|
+
def test_wrong_chart(self):
|
|
32
|
+
with self.assertRaises(ValueError):
|
|
33
|
+
scatter_matrix(self.df, chart='wrong')
|
|
34
|
+
|
|
35
|
+
def test_diagonal_default(self):
|
|
36
|
+
sm = scatter_matrix(self.df)
|
|
37
|
+
self.assertIsInstance(sm['a', 'a'], Histogram)
|
|
38
|
+
|
|
39
|
+
def test_offdiagonal_default(self):
|
|
40
|
+
sm = scatter_matrix(self.df)
|
|
41
|
+
self.assertIsInstance(sm['a', 'b'], Scatter)
|
|
42
|
+
|
|
43
|
+
def test_diagonal_kde(self):
|
|
44
|
+
sm = scatter_matrix(self.df, diagonal='kde')
|
|
45
|
+
self.assertIsInstance(sm['a', 'a'], Distribution)
|
|
46
|
+
|
|
47
|
+
def test_offdiagonal_bivariate(self):
|
|
48
|
+
sm = scatter_matrix(self.df, chart='bivariate')
|
|
49
|
+
self.assertIsInstance(sm['a', 'b'], Bivariate)
|
|
50
|
+
|
|
51
|
+
def test_offdiagonal_hexbin(self):
|
|
52
|
+
sm = scatter_matrix(self.df, chart='hexbin')
|
|
53
|
+
self.assertIsInstance(sm['a', 'b'], HexTiles)
|
|
54
|
+
|
|
55
|
+
def test_diagonal_kwargs_mutually_exclusive(self):
|
|
56
|
+
with self.assertRaises(TypeError):
|
|
57
|
+
scatter_matrix(self.df, diagonal_kwds=dict(a=1), hist_kwds=dict(a=1))
|
|
58
|
+
with self.assertRaises(TypeError):
|
|
59
|
+
scatter_matrix(self.df, diagonal_kwds=dict(a=1), density_kwds=dict(a=1))
|
|
60
|
+
with self.assertRaises(TypeError):
|
|
61
|
+
scatter_matrix(self.df, density_kwds=dict(a=1), hist_kwds=dict(a=1))
|
|
62
|
+
|
|
63
|
+
def test_diagonal_kwargs(self):
|
|
64
|
+
sm = scatter_matrix(self.df, diagonal_kwds=dict(line_color='red'))
|
|
65
|
+
self.assertEqual(sm['a', 'a'].opts.get().kwargs['line_color'], 'red')
|
|
66
|
+
|
|
67
|
+
def test_c(self):
|
|
68
|
+
df = self.df.copy(deep=True)
|
|
69
|
+
df['e'] = np.random.choice(list('xyz'), size=len(df))
|
|
70
|
+
sm = scatter_matrix(df, c='e')
|
|
71
|
+
|
|
72
|
+
self.assertIsInstance(sm['a', 'a'], NdOverlay)
|
|
73
|
+
diag_kdims = sm['a', 'a'].kdims
|
|
74
|
+
self.assertEqual(len(diag_kdims), 1)
|
|
75
|
+
self.assertEqual(diag_kdims[0].name, 'e')
|
|
76
|
+
|
|
77
|
+
self.assertIsInstance(sm['a', 'b'], Scatter)
|
|
78
|
+
offdiag_vdims = sm['a', 'b'].vdims
|
|
79
|
+
self.assertTrue('e' in (d.name for d in offdiag_vdims))
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TestDatashader(TestCase):
|
|
83
|
+
def setUp(self):
|
|
84
|
+
try:
|
|
85
|
+
import datashader # noqa
|
|
86
|
+
except ImportError:
|
|
87
|
+
raise SkipTest('Datashader not available')
|
|
88
|
+
if sys.maxsize < 2**32:
|
|
89
|
+
raise SkipTest('Datashader does not support 32-bit systems')
|
|
90
|
+
self.df = pd.DataFrame(np.random.randn(1000, 3), columns=['a', 'b', 'c'])
|
|
91
|
+
|
|
92
|
+
def test_rasterize_datashade_mutually_exclusive(self):
|
|
93
|
+
with self.assertRaises(ValueError):
|
|
94
|
+
scatter_matrix(self.df, rasterize=True, datashade=True)
|
|
95
|
+
|
|
96
|
+
def test_spread_but_no_rasterize_or_datashade(self):
|
|
97
|
+
with self.assertRaises(ValueError):
|
|
98
|
+
scatter_matrix(self.df, dynspread=True)
|
|
99
|
+
with self.assertRaises(ValueError):
|
|
100
|
+
scatter_matrix(self.df, spread=True)
|
|
101
|
+
with self.assertRaises(ValueError):
|
|
102
|
+
scatter_matrix(self.df, dynspread=True, spread=True)
|
|
103
|
+
|
|
104
|
+
@parameterized.expand([('rasterize',), ('datashade',)])
|
|
105
|
+
def test_rasterization(self, operation):
|
|
106
|
+
sm = scatter_matrix(self.df, **{operation: True})
|
|
107
|
+
dm = sm['a', 'b']
|
|
108
|
+
self.assertEqual(dm.callback.operation.name, operation)
|
|
109
|
+
dm[()]
|
|
110
|
+
self.assertEqual(len(dm.last.pipeline.operations), 3)
|
|
111
|
+
|
|
112
|
+
@parameterized.expand([('rasterize',), ('datashade',)])
|
|
113
|
+
def test_datashade_aggregator(self, operation):
|
|
114
|
+
sm = scatter_matrix(self.df, aggregator='mean', **{operation: True})
|
|
115
|
+
dm = sm['a', 'b']
|
|
116
|
+
dm[()]
|
|
117
|
+
self.assertEqual(dm.last.pipeline.operations[-1].aggregator, 'mean')
|
|
118
|
+
|
|
119
|
+
@parameterized.expand([('spread',), ('dynspread',)])
|
|
120
|
+
def test_spread_rasterize(self, operation):
|
|
121
|
+
sm = scatter_matrix(self.df, rasterize=True, **{operation: True})
|
|
122
|
+
dm = sm['a', 'b']
|
|
123
|
+
dm[()]
|
|
124
|
+
self.assertEqual(len(dm.last.pipeline.operations), 4)
|
|
125
|
+
|
|
126
|
+
@parameterized.expand([('spread',), ('dynspread',)])
|
|
127
|
+
def test_spread_datashade(self, operation):
|
|
128
|
+
sm = scatter_matrix(self.df, datashade=True, **{operation: True})
|
|
129
|
+
dm = sm['a', 'b']
|
|
130
|
+
dm[()]
|
|
131
|
+
self.assertEqual(len(dm.last.pipeline.operations), 4)
|
|
132
|
+
|
|
133
|
+
@parameterized.expand([('spread',), ('dynspread',)])
|
|
134
|
+
def test_spread_kwargs(self, operation):
|
|
135
|
+
sm = scatter_matrix(self.df, datashade=True, **{operation: True, 'shape': 'circle'})
|
|
136
|
+
dm = sm['a', 'b']
|
|
137
|
+
dm[()]
|
|
138
|
+
self.assertEqual(dm.last.pipeline.operations[-1].args[0].keywords['shape'], 'circle')
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Urls in docstrings etc. should be valid and secure, i.e.
|
|
2
|
+
|
|
3
|
+
- exist, i.e. provide a 200 response
|
|
4
|
+
- use https:// instead of http:// unless
|
|
5
|
+
- https:// is not supported by the web site
|
|
6
|
+
- https:// cannot be used. For example in SVGs.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import pathlib
|
|
10
|
+
import re
|
|
11
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
12
|
+
from urllib.request import urlopen
|
|
13
|
+
import glob
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
|
|
17
|
+
# Note: The regex will find urls from code cells in notebooks ending with '\\' because the are really inside \"some_url\"
|
|
18
|
+
URL_REGEX = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))" # pylint: disable=line-too-long
|
|
19
|
+
ROOT = pathlib.Path(__file__).parent
|
|
20
|
+
PACKAGE_ROOT = ROOT.parent
|
|
21
|
+
MAX_WORKERS = 10
|
|
22
|
+
POST_FIXES = ['.py', '.ipynb', '.md', '.yaml']
|
|
23
|
+
SKIP_URLS = [
|
|
24
|
+
'https://anaconda.org/anaconda/hvplot',
|
|
25
|
+
'https://anaconda.org/conda-forge/hvplot',
|
|
26
|
+
'https://anaconda.org/pyviz/hvplot',
|
|
27
|
+
'https://creativecommons.org/publicdomain/zero/1.0/',
|
|
28
|
+
'https://github.com/rasterio/rasterio',
|
|
29
|
+
'https://www.dask.org',
|
|
30
|
+
'pyproject.toml/equivalent',
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_files_to_check():
|
|
35
|
+
for post_fix in POST_FIXES:
|
|
36
|
+
for file in glob.glob('**/*' + post_fix, recursive=True):
|
|
37
|
+
yield pathlib.Path(file)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
FIXTURES = [pytest.param(file, id=str(file)) for file in _get_files_to_check()]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _skip_url(url: str):
|
|
44
|
+
if url in SKIP_URLS:
|
|
45
|
+
return True
|
|
46
|
+
if url.startswith('https://github.com/holoviz/hvplot/pull/'):
|
|
47
|
+
return True
|
|
48
|
+
if url.startswith('https://img.shields.io'):
|
|
49
|
+
return True
|
|
50
|
+
if url.startswith('assets.holoviews.org/data/'):
|
|
51
|
+
return True
|
|
52
|
+
if url.startswith('Math.PI'):
|
|
53
|
+
return True
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _clean_url(url: str):
|
|
58
|
+
if url.endswith('\\'):
|
|
59
|
+
return url[0:-1]
|
|
60
|
+
return url
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _find_urls(text):
|
|
64
|
+
url = re.findall(URL_REGEX, text)
|
|
65
|
+
return {_clean_url(x[0]) for x in url if not _skip_url(x[0])}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _request_a_response(url):
|
|
69
|
+
return urlopen(url)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _verify_urls(urls):
|
|
73
|
+
"""Returns True if all urls are valid"""
|
|
74
|
+
futures = {}
|
|
75
|
+
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
|
|
76
|
+
futures = {}
|
|
77
|
+
for url in urls:
|
|
78
|
+
futures[executor.submit(_request_a_response, url)] = url
|
|
79
|
+
|
|
80
|
+
for future in as_completed(futures):
|
|
81
|
+
url = futures[future]
|
|
82
|
+
try:
|
|
83
|
+
result = future.result()
|
|
84
|
+
except Exception as ex:
|
|
85
|
+
raise ValueError(f'The url {url} raised an exception') from ex
|
|
86
|
+
if not result.status == 200:
|
|
87
|
+
raise ValueError(f'The url {url} responded with status {result.status}, not 200.')
|
|
88
|
+
|
|
89
|
+
return True
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# @pytest.mark.parametrize(["file"], FIXTURES)
|
|
93
|
+
# def test_urls(file: pathlib.Path):
|
|
94
|
+
# """The urls is docstring should be valid"""
|
|
95
|
+
# # Given
|
|
96
|
+
# text = file.read_text()
|
|
97
|
+
# urls = _find_urls(text)
|
|
98
|
+
# # When/ Then
|
|
99
|
+
# assert _verify_urls(urls)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import holoviews
|
|
2
|
+
import pytest
|
|
3
|
+
from holoviews.core import Store
|
|
4
|
+
from holoviews.element import Area, Curve
|
|
5
|
+
|
|
6
|
+
from hvplot.backend_transforms import (
|
|
7
|
+
_transfer_opts,
|
|
8
|
+
_transform_size_to_mpl,
|
|
9
|
+
_is_interactive_opt,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.mark.parametrize(
|
|
14
|
+
('width', 'height', 'aspect', 'opts'),
|
|
15
|
+
(
|
|
16
|
+
(300, 100, None, {'aspect': 3.0, 'fig_size': 100.0}),
|
|
17
|
+
(300, None, 3, {'aspect': 3, 'fig_size': 100.0}),
|
|
18
|
+
(None, 300, 3, {'aspect': 3, 'fig_size': 100.0}),
|
|
19
|
+
(300, None, None, {'fig_size': 100.0}),
|
|
20
|
+
(None, 300, None, {'fig_size': 100.0}),
|
|
21
|
+
),
|
|
22
|
+
)
|
|
23
|
+
def test_transform_size_to_mpl(width, height, aspect, opts):
|
|
24
|
+
assert _transform_size_to_mpl(width, height, aspect) == opts
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@pytest.mark.parametrize(
|
|
28
|
+
('element', 'opt', 'val', 'backend', 'opt_kind', 'transf_opt', 'transf_val'),
|
|
29
|
+
(
|
|
30
|
+
(Curve([]), 'line_dash', 'dashed', 'matplotlib', 'style', 'linestyle', 'dashed'),
|
|
31
|
+
(Curve([]), 'line_alpha', 0.123, 'matplotlib', 'style', None, None),
|
|
32
|
+
(Area([]), 'line_cap', 'square', 'matplotlib', 'style', 'capstyle', 'projecting'),
|
|
33
|
+
(Curve([]), 'line_dash', 'dashed', 'plotly', 'style', 'dash', 'dash'),
|
|
34
|
+
),
|
|
35
|
+
)
|
|
36
|
+
def test_transfer_opts(element, opt, val, backend, opt_kind, transf_opt, transf_val):
|
|
37
|
+
current_backend = Store.current_backend
|
|
38
|
+
if backend not in Store.registry:
|
|
39
|
+
holoviews.extension(backend)
|
|
40
|
+
Store.set_current_backend(backend)
|
|
41
|
+
try:
|
|
42
|
+
element = element.opts(backend='bokeh', **{opt: val})
|
|
43
|
+
new_element = element.apply(_transfer_opts, backend=backend)
|
|
44
|
+
new_opts = new_element.opts.get(opt_kind).kwargs
|
|
45
|
+
if transf_opt is None:
|
|
46
|
+
assert val not in new_opts.values()
|
|
47
|
+
else:
|
|
48
|
+
assert transf_opt in new_opts
|
|
49
|
+
assert new_opts[transf_opt] == transf_val
|
|
50
|
+
finally:
|
|
51
|
+
Store.set_current_backend(current_backend)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@pytest.mark.parametrize(
|
|
55
|
+
('opt', 'val', 'backend', 'opt_kind', 'transf_opt', 'transf_val'),
|
|
56
|
+
(
|
|
57
|
+
('line_dash', 'dashed', 'matplotlib', 'style', 'linestyle', 'dashed'),
|
|
58
|
+
('line_dash', 'dashed', 'plotly', 'style', 'dash', 'dash'),
|
|
59
|
+
),
|
|
60
|
+
)
|
|
61
|
+
def test_transfer_opts_compositeoverlay(opt, val, backend, opt_kind, transf_opt, transf_val):
|
|
62
|
+
current_backend = Store.current_backend
|
|
63
|
+
if backend not in Store.registry:
|
|
64
|
+
holoviews.extension(backend)
|
|
65
|
+
Store.set_current_backend(backend)
|
|
66
|
+
try:
|
|
67
|
+
element = Area([]) * Curve([]).opts(backend='bokeh', **{opt: val})
|
|
68
|
+
new_element = element.apply(_transfer_opts, backend=backend)
|
|
69
|
+
transformed_element = new_element.Curve.I
|
|
70
|
+
new_opts = transformed_element.opts.get(opt_kind).kwargs
|
|
71
|
+
assert transf_opt in new_opts
|
|
72
|
+
assert new_opts[transf_opt] == transf_val
|
|
73
|
+
finally:
|
|
74
|
+
Store.set_current_backend(current_backend)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.mark.parametrize(
|
|
78
|
+
('bk_option', 'expected'),
|
|
79
|
+
(
|
|
80
|
+
('height', False),
|
|
81
|
+
('hover_line_alpha', True),
|
|
82
|
+
('nonselection_line_alpha', True),
|
|
83
|
+
('muted_line_alpha', True),
|
|
84
|
+
('selection_line_alpha', True),
|
|
85
|
+
('annular_muted_alpha', True),
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
def test_is_interactive_opt(bk_option, expected):
|
|
89
|
+
assert _is_interactive_opt(bk_option) == expected
|