Skip to content

Commit

Permalink
Merge 4c46d73 into 27da6a3
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Feb 7, 2022
2 parents 27da6a3 + 4c46d73 commit bea0ea1
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 88 deletions.
14 changes: 11 additions & 3 deletions holoviews/core/data/xarray.py
Expand Up @@ -217,9 +217,17 @@ def retrieve_unit_and_label(dim):
# not need to be canonicalized
if any(len(da.coords[c].shape) > 1 for c in da.coords):
continue
undeclared = [
c for c in da.coords if c not in kdims and len(da[c].shape) == 1 and
da[c].shape[0] > 1]
undeclared = []
for c in da.coords:
if c in kdims or len(da[c].shape) != 1 or da[c].shape[0] <= 1:
# Skip if coord is declared, represents irregular coordinates or is constant
continue
elif all(d in kdims for d in da[c].dims):
continue # Skip if coord is alias for another dimension
elif any(all(d in da[kd.name].dims for d in da[c].dims) for kd in kdims):
# Skip if all the dims on the coord are present on another coord
continue
undeclared.append(c)
if undeclared:
raise DataError(
'The coordinates on the %r DataArray do not match the '
Expand Down
45 changes: 41 additions & 4 deletions holoviews/core/util.py
Expand Up @@ -1569,17 +1569,27 @@ def is_param_method(obj, has_deps=False):
def resolve_dependent_value(value):
"""Resolves parameter dependencies on the supplied value
Resolves parameter values, Parameterized instance methods and
parameterized functions with dependencies on the supplied value.
Resolves parameter values, Parameterized instance methods,
parameterized functions with dependencies on the supplied value,
including such parameters embedded in a list or tuple.
Args:
value: A value which will be resolved
Returns:
A new dictionary where any parameter dependencies have been
A new value where any parameter dependencies have been
resolved.
"""
range_widget = False
if isinstance(value, list):
value = [resolve_dependent_value(v) for v in value]
elif isinstance(value, tuple):
value = tuple(resolve_dependent_value(v) for v in value)
elif isinstance(value, dict):
value = {
resolve_dependent_value(k): resolve_dependent_value(v) for k, v in value.items()
}

if 'panel' in sys.modules:
from panel.widgets import RangeSlider, Widget
range_widget = isinstance(value, RangeSlider)
Expand Down Expand Up @@ -1614,7 +1624,7 @@ def resolve_dependent_kwargs(kwargs):
kwargs (dict): A dictionary of keyword arguments
Returns:
A new dictionary with where any parameter dependencies have been
A new dictionary where any parameter dependencies have been
resolved.
"""
return {k: resolve_dependent_value(v) for k, v in kwargs.items()}
Expand Down Expand Up @@ -2294,3 +2304,30 @@ def cast_array_to_int64(array):
category=FutureWarning,
)
return array.astype('int64')


def flatten(line):
"""
Flatten an arbitrarily nested sequence.
Inspired by: pd.core.common.flatten
Parameters
----------
line : sequence
The sequence to flatten
Notes
-----
This only flattens list, tuple, and dict sequences.
Returns
-------
flattened : generator
"""

for element in line:
if any(isinstance(element, tp) for tp in (list, tuple, dict)):
yield from flatten(element)
else:
yield element
2 changes: 1 addition & 1 deletion holoviews/operation/datashader.py
Expand Up @@ -2,7 +2,7 @@

import warnings

from collections import Callable
from collections.abc import Callable
from functools import partial

import param
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/mpl/element.py
Expand Up @@ -174,7 +174,7 @@ def _finalize_axis(self, key, element=None, title=None, dimensions=None, ranges=
if self.logy:
axis.set_yscale('log')

if not isinstance(self.projection, str) and self.projection == '3d':
if not (isinstance(self.projection, str) and self.projection == '3d'):
self._set_axis_position(axis, 'x', self.xaxis)
self._set_axis_position(axis, 'y', self.yaxis)

Expand Down Expand Up @@ -330,7 +330,7 @@ def _set_axis_limits(self, axis, view, subplots, ranges):
coords = [coord if isinstance(coord, np.datetime64) or np.isreal(coord) else np.NaN for coord in extents]
coords = [date2num(util.dt64_to_dt(c)) if isinstance(c, np.datetime64) else c
for c in coords]
if isinstance(self.projection, str) and self.projection == '3d' or len(extents) == 6:
if (isinstance(self.projection, str) and self.projection == '3d') or len(extents) == 6:
l, b, zmin, r, t, zmax = coords
if self.invert_zaxis or any(p.invert_zaxis for p in subplots):
zmin, zmax = zmax, zmin
Expand Down
2 changes: 1 addition & 1 deletion holoviews/streams.py
Expand Up @@ -770,7 +770,7 @@ def hashkey(self):
for p in self.parameters:
pkey = (p.owner, p.name)
pname = self._rename.get(pkey, p.name)
key = ' '.join([p.owner.name, pname])
key = ' '.join([str(id(p.owner)), pname])
if self._rename.get(pkey, True) is not None:
hashkey[key] = getattr(p.owner, p.name)
hashkey['_memoize_key'] = self._memoize_counter
Expand Down
8 changes: 8 additions & 0 deletions holoviews/tests/core/data/testxarrayinterface.py
Expand Up @@ -60,6 +60,14 @@ def get_multi_dim_irregular_dataset(self):
'time': pd.date_range('2014-09-06', periods=3),
'reference_time': pd.Timestamp('2014-09-05')})

def test_ignore_dependent_dimensions_if_not_specified(self):
coords = OrderedDict([('time', [0, 1]), ('lat', [0, 1]), ('lon', [0, 1])])
da = xr.DataArray(np.arange(8).reshape((2, 2, 2)),
coords, ['time', 'lat', 'lon']).assign_coords(
lat1=xr.DataArray([2,3], dims=['lat']))
assert Dataset(da, ['time', 'lat', 'lon'], vdims='value').kdims == ['time', 'lat', 'lon']
assert Dataset(da, ['time', 'lat1', 'lon'], vdims='value').kdims == ['time', 'lat1', 'lon']

def test_xarray_dataset_irregular_shape(self):
ds = Dataset(self.get_multi_dim_irregular_dataset())
shape = ds.interface.shape(ds, gridded=True)
Expand Down
23 changes: 20 additions & 3 deletions holoviews/tests/core/testapply.py
@@ -1,12 +1,14 @@
import numpy as np
import pandas as pd
import param

from panel.widgets import TextInput
from panel.widgets import RadioButtonGroup, TextInput

from holoviews import Dataset, util
from holoviews.core.spaces import DynamicMap, HoloMap
from holoviews.element import Image, Curve
from holoviews.element import Curve, Image
from holoviews.element.comparison import ComparisonTestCase
from holoviews.streams import Params, ParamMethod
from holoviews.streams import ParamMethod, Params


class ParamClass(param.Parameterized):
Expand Down Expand Up @@ -275,3 +277,18 @@ def test_dmap_apply_dynamic_with_param_method(self):
self.assertEqual(applied[1], self.dmap[1].relabel('Test!'))
pinst.label = 'Another label'
self.assertEqual(applied[1], self.dmap[1].relabel('Another label!'))


def test_nested_widgets():
df = pd._testing.makeDataFrame()
column = RadioButtonGroup(value="A", options=list("ABC"))
ds = Dataset(df)
transform = util.transform.df_dim("*").groupby(["D", column]).mean()

params = list(transform.params.values())
assert len(params) == 1
assert params[0] == column.param.value

df1 = transform.apply(ds, keep_index=True, compute=False)
df2 = df.groupby(["D", "A"]).mean()
pd.testing.assert_frame_equal(df1, df2)
95 changes: 27 additions & 68 deletions holoviews/tests/plotting/bokeh/testserver.py
@@ -1,8 +1,6 @@
import time
import threading

from unittest import SkipTest
from threading import Event

import param

Expand All @@ -14,21 +12,18 @@
from holoviews.streams import Stream, RangeXY, PlotReset

try:
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
from bokeh.client import pull_session
from bokeh.document import Document
from bokeh.io.doc import curdoc, set_curdoc
from bokeh.models import ColumnDataSource
from bokeh.server.server import Server

from holoviews.plotting.bokeh.callbacks import (
Callback, RangeXYCallback, ResetCallback
)
from holoviews.plotting.bokeh.renderer import BokehRenderer
from panel.widgets import DiscreteSlider, FloatSlider
from panel.io.server import StoppableThread
from panel.io.state import state
from panel import serve
bokeh_renderer = BokehRenderer.instance(mode='server')
except:
bokeh_renderer = None
Expand Down Expand Up @@ -107,78 +102,39 @@ def setUp(self):
if not bokeh_renderer:
raise SkipTest("Bokeh required to test plot instantiation")
Store.current_backend = 'bokeh'
self._loaded = Event()
self._port = None
self._thread = None
self._server = None

def tearDown(self):
Store.current_backend = self.previous_backend
Callback._callbacks = {}
if self._thread is not None:
try:
self._thread.stop()
except:
pass
state._thread_id = None
if self._server is not None:
try:
self._server.stop()
except:
pass
state.kill_all_servers()
time.sleep(1)

def _launcher(self, obj, threaded=False, io_loop=None):
if io_loop:
io_loop.make_current()
launched = []
def modify_doc(doc):
bokeh_renderer(obj, doc=doc)
launched.append(True)
handler = FunctionHandler(modify_doc)
app = Application(handler)
server = Server({'/': app}, port=0, io_loop=io_loop)
server.start()
self._port = server.port
self._server = server
if threaded:
server.io_loop.add_callback(self._loaded.set)
thread = threading.current_thread()
state._thread_id = thread.ident if thread else None
io_loop.start()
else:
url = "http://localhost:" + str(server.port) + "/"
session = pull_session(session_id='Test', url=url, io_loop=server.io_loop)
self.assertTrue(len(launched)==1)
return session, server
return None, server

def _threaded_launcher(self, obj):
from tornado.ioloop import IOLoop
io_loop = IOLoop()
thread = StoppableThread(target=self._launcher, io_loop=io_loop,
args=(obj, True, io_loop))
thread.setDaemon(True)
thread.start()
self._loaded.wait()
self._thread = thread
return self.session
def _launcher(self, obj, threaded=True, port=6001):
self._port = port
server = serve(obj, threaded=threaded, show=False, port=port)
time.sleep(0.5)
return server, self.session

@property
def session(self):
url = "http://localhost:" + str(self._port) + "/"
return pull_session(session_id='Test', url=url)

def test_launch_simple_server(self):
obj = Curve([])
self._launcher(obj)
server, _ = self._launcher(obj, port=6001)
server.stop()

def test_launch_server_with_stream(self):
obj = Curve([])
stream = RangeXY(source=obj)
el = Curve([])
stream = RangeXY(source=el)

_, server = self._launcher(obj)
cb = bokeh_renderer.last_plot.callbacks[0]
obj, _ = bokeh_renderer._validate(el, None)
server, _ = self._launcher(obj, port=6002)
[(plot, _)] = obj._plots.values()

cb = plot.callbacks[0]
self.assertIsInstance(cb, RangeXYCallback)
self.assertEqual(cb.streams, [stream])
x_range = bokeh_renderer.last_plot.handles['x_range']
Expand All @@ -195,15 +151,15 @@ def test_launch_server_with_complex_plot(self):
static = Polygons([]) * Path([]) * Curve([])
layout = overlay + static

_, server = self._launcher(layout)
server, _ = self._launcher(layout, port=6003)
server.stop()

def test_server_dynamicmap_with_dims(self):
dmap = DynamicMap(lambda y: Curve([1, 2, y]), kdims=['y']).redim.range(y=(0.1, 5))
obj, _ = bokeh_renderer._validate(dmap, None)
session = self._threaded_launcher(obj)
server, session = self._launcher(obj, port=6004)
[(plot, _)] = obj._plots.values()
[(doc, _)] = obj.layout._documents.items()
[(doc, _)] = obj._documents.items()

cds = session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(cds.data['y'][2], 0.1)
Expand All @@ -214,13 +170,14 @@ def run():
time.sleep(1)
cds = self.session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(cds.data['y'][2], 3.1)
server.stop()

def test_server_dynamicmap_with_stream(self):
stream = Stream.define('Custom', y=2)()
dmap = DynamicMap(lambda y: Curve([1, 2, y]), kdims=['y'], streams=[stream])
obj, _ = bokeh_renderer._validate(dmap, None)
session = self._threaded_launcher(obj)
[(doc, _)] = obj.layout._documents.items()
server, session = self._launcher(obj, port=6005)
[(doc, _)] = obj._documents.items()

cds = session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(cds.data['y'][2], 2)
Expand All @@ -230,14 +187,15 @@ def run():
time.sleep(1)
cds = self.session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(cds.data['y'][2], 3)
server.stop()

def test_server_dynamicmap_with_stream_dims(self):
stream = Stream.define('Custom', y=2)()
dmap = DynamicMap(lambda x, y: Curve([x, 1, y]), kdims=['x', 'y'],
streams=[stream]).redim.values(x=[1, 2, 3])
obj, _ = bokeh_renderer._validate(dmap, None)
session = self._threaded_launcher(obj)
[(doc, _)] = obj.layout._documents.items()
server, session = self._launcher(obj, port=6006)
[(doc, _)] = obj._documents.items()

orig_cds = session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(orig_cds.data['y'][2], 2)
Expand All @@ -256,3 +214,4 @@ def run():
time.sleep(1)
cds = self.session.document.roots[0].select_one({'type': ColumnDataSource})
self.assertEqual(cds.data['y'][0], 3)
server.stop()
8 changes: 8 additions & 0 deletions holoviews/tests/plotting/matplotlib/testelementplot.py
Expand Up @@ -59,6 +59,14 @@ def test_element_font_scaling_fontsize_override_specific(self):
self.assertEqual(ax.xaxis._major_tick_kw['labelsize'], 24)
self.assertEqual(ax.yaxis._major_tick_kw['labelsize'], 20)

def test_element_no_xaxis_yaxis(self):
element = Curve(range(10)).options(xaxis=None, yaxis=None)
axes = mpl_renderer.get_plot(element).handles['axis']
xaxis = axes.get_xaxis()
yaxis = axes.get_yaxis()
self.assertEqual(xaxis.get_visible(), False)
self.assertEqual(yaxis.get_visible(), False)

def test_element_xlabel(self):
element = Curve(range(10)).options(xlabel='custom x-label')
axes = mpl_renderer.get_plot(element).handles['axis']
Expand Down

0 comments on commit bea0ea1

Please sign in to comment.