import datetime as dt from collections import OrderedDict import pytest import numpy as np try: import holoviews as hv except Exception: hv = None from bokeh.models import (Row as BkRow, Column as BkColumn, GlyphRenderer, Scatter, Line, GridBox, Select as BkSelect, Slider as BkSlider, Spacer as BkSpacer, ColumnDataSource) from bokeh.plotting import Figure from panel.layout import Column, FlexBox, Row from panel.pane import Pane, PaneBase, HoloViews from panel.widgets import FloatSlider, DiscreteSlider, Select from panel.tests.util import hv_available, mpl_available @hv_available def test_get_holoviews_pane_type(): curve = hv.Curve([1, 2, 3]) assert PaneBase.get_pane_type(curve) is HoloViews @pytest.mark.usefixtures("hv_mpl") @mpl_available @hv_available def test_holoviews_pane_mpl_renderer(document, comm): curve = hv.Curve([1, 2, 3]) pane = Pane(curve) # Create pane row = pane.get_root(document, comm=comm) assert isinstance(row, BkRow) assert len(row.children) == 1 model = row.children[0] assert pane._models[row.ref['id']][0] is model assert model.text.startswith('<img src=') # Replace Pane.object scatter = hv.Scatter([1, 2, 3]) pane.object = scatter new_model = row.children[0] assert model.text != new_model.text # Cleanup pane._cleanup(row) assert pane._models == {} @pytest.mark.usefixtures("hv_mpl") @pytest.mark.usefixtures("hv_bokeh") @mpl_available @hv_available def test_holoviews_pane_switch_backend(document, comm): curve = hv.Curve([1, 2, 3]) pane = Pane(curve) # Create pane row = pane.get_root(document, comm=comm) assert isinstance(row, BkRow) assert len(row.children) == 1 model = row.children[0] assert pane._models[row.ref['id']][0] is model assert model.text.startswith('<img src=') # Replace Pane.object pane.backend = 'bokeh' model = row.children[0] assert isinstance(model, Figure) # Cleanup pane._cleanup(row) assert pane._models == {} @pytest.mark.usefixtures("hv_bokeh") @hv_available def test_holoviews_pane_bokeh_renderer(document, comm): curve = hv.Curve([1, 2, 3]) pane = Pane(curve) # Create pane row = pane.get_root(document, comm=comm) assert isinstance(row, BkRow) assert len(row.children) == 1 model = row.children[0] assert isinstance(model, Figure) assert pane._models[row.ref['id']][0] is model renderers = [r for r in model.renderers if isinstance(r, GlyphRenderer)] assert len(renderers) == 1 assert isinstance(renderers[0].glyph, Line) # Replace Pane.object scatter = hv.Scatter([1, 2, 3]) pane.object = scatter model = row.children[0] assert isinstance(model, Figure) renderers = [r for r in model.renderers if isinstance(r, GlyphRenderer)] assert len(renderers) == 1 assert isinstance(renderers[0].glyph, Scatter) assert pane._models[row.ref['id']][0] is model # Cleanup pane._cleanup(row) assert pane._models == {} @pytest.mark.usefixtures("hv_bokeh") @hv_available def test_holoviews_pane_initialize_empty(document, comm): pane = HoloViews() # Create pane row = pane.get_root(document, comm=comm) assert isinstance(row, BkRow) assert len(row.children) == 1 model = row.children[0] assert isinstance(model, BkSpacer) pane.object = hv.Curve([1, 2, 3]) model = row.children[0] assert isinstance(model, Figure) @hv_available def test_holoviews_widgets_from_dynamicmap(document, comm): range_dim = hv.Dimension('A', range=(0, 10.)) range_step_dim = hv.Dimension('B', range=(0, 10.), step=0.2) range_default_dim = hv.Dimension('C', range=(0, 10.), default=3) value_dim = hv.Dimension('D', values=['a', 'b', 'c']) value_default_dim = hv.Dimension('E', values=['a', 'b', 'c', 'd'], default='b') value_numeric_dim = hv.Dimension('F', values=[1, 3, 10], default=3) kdims = [range_dim, range_step_dim, range_default_dim, value_dim, value_default_dim, value_numeric_dim] dmap = hv.DynamicMap(lambda A, B, C, D, E, F: hv.Curve([]), kdims=kdims) widgets, _ = HoloViews.widgets_from_dimensions(dmap) assert len(widgets) == len(kdims) assert isinstance(widgets[0], FloatSlider) assert widgets[0].name == 'A' assert widgets[0].start == range_dim.range[0] assert widgets[0].end == range_dim.range[1] assert widgets[0].value == range_dim.range[0] assert widgets[0].step == 0.1 assert isinstance(widgets[1], FloatSlider) assert widgets[1].name == 'B' assert widgets[1].start == range_step_dim.range[0] assert widgets[1].end == range_step_dim.range[1] assert widgets[1].value == range_step_dim.range[0] assert widgets[1].step == range_step_dim.step assert isinstance(widgets[2], FloatSlider) assert widgets[2].name == 'C' assert widgets[2].start == range_default_dim.range[0] assert widgets[2].end == range_default_dim.range[1] assert widgets[2].value == range_default_dim.default assert widgets[2].step == 0.1 assert isinstance(widgets[3], Select) assert widgets[3].name == 'D' assert widgets[3].options == value_dim.values assert widgets[3].value == value_dim.values[0] assert isinstance(widgets[4], Select) assert widgets[4].name == 'E' assert widgets[4].options == value_default_dim.values assert widgets[4].value == value_default_dim.default assert isinstance(widgets[5], DiscreteSlider) assert widgets[5].name == 'F' assert widgets[5].options == OrderedDict([(str(v), v) for v in value_numeric_dim.values]) assert widgets[5].value == value_numeric_dim.default @hv_available def test_holoviews_with_widgets(document, comm): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) hv_pane = HoloViews(hmap) layout = hv_pane.get_root(document, comm) model = layout.children[0] assert len(hv_pane.widget_box.objects) == 2 assert hv_pane.widget_box.objects[0].name == 'X' assert hv_pane.widget_box.objects[1].name == 'Y' assert hv_pane._models[layout.ref['id']][0] is model hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['A', 'B']) hv_pane.object = hmap assert len(hv_pane.widget_box.objects) == 2 assert hv_pane.widget_box.objects[0].name == 'A' assert hv_pane.widget_box.objects[1].name == 'B' @hv_available def test_holoviews_updates_widgets(document, comm): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) hv_pane = HoloViews(hmap) layout = hv_pane.get_root(document, comm) hv_pane.widgets = {'X': Select} assert isinstance(hv_pane.widget_box[0], Select) assert isinstance(layout.children[1].children[0].children[0], BkSelect) hv_pane.widgets = {'X': DiscreteSlider} assert isinstance(hv_pane.widget_box[0], DiscreteSlider) assert isinstance(layout.children[1].children[0].children[0], BkColumn) assert isinstance(layout.children[1].children[0].children[0].children[1], BkSlider) @hv_available def test_holoviews_widgets_update_plot(document, comm): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) hv_pane = HoloViews(hmap, backend='bokeh') layout = hv_pane.get_root(document, comm) cds = layout.children[0].select_one(ColumnDataSource) assert cds.data['y'] == np.array([0]) hv_pane.widget_box[0].value = 1 hv_pane.widget_box[1].value = chr(65+1) assert cds.data['y'] == np.array([1]) @hv_available def test_holoviews_with_widgets_not_shown(document, comm): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) hv_pane = HoloViews(hmap, show_widgets=False) layout_obj = Column(hv_pane, hv_pane.widget_box) layout = layout_obj.get_root(document, comm) model = layout.children[0] assert len(hv_pane.widget_box.objects) == 2 assert hv_pane.widget_box.objects[0].name == 'X' assert hv_pane.widget_box.objects[1].name == 'Y' assert hv_pane._models[layout.ref['id']][0] is model hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['A', 'B']) hv_pane.object = hmap assert len(hv_pane.widget_box.objects) == 2 assert hv_pane.widget_box.objects[0].name == 'A' assert hv_pane.widget_box.objects[1].name == 'B' @hv_available def test_holoviews_layouts(document, comm): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) hv_pane = HoloViews(hmap, backend='bokeh') layout = hv_pane.layout model = layout.get_root(document, comm) for center in (True, False): for loc in HoloViews.param.widget_location.objects: hv_pane.param.update(center=center, widget_location=loc) if center: if loc.startswith('left'): assert len(layout) == 4 widgets, hv_obj = layout[0], layout[2] wmodel, hv_model = model.children[0], model.children[2] elif loc.startswith('right'): assert len(layout) == 4 hv_obj, widgets = layout[1], layout[3] wmodel, hv_model = model.children[3], model.children[1] elif loc.startswith('top'): assert len(layout) == 3 col = layout[1] cmodel = model.children[1] assert isinstance(col, Column) widgets, hv_col = col hv_obj = hv_col[1] wmodel, hv_model = cmodel.children[0], cmodel.children[1].children[1] elif loc.startswith('bottom'): col = layout[1] cmodel = model.children[1] assert isinstance(col, Column) hv_col, widgets = col hv_obj = hv_col[1] wmodel, hv_model = cmodel.children[1], cmodel.children[0].children[1] else: if loc.startswith('left'): assert len(layout) == 2 widgets, hv_obj = layout wmodel, hv_model = model.children elif loc.startswith('right'): assert len(layout) == 2 hv_obj, widgets = layout hv_model, wmodel = model.children elif loc.startswith('top'): assert len(layout) == 1 col = layout[0] cmodel = model.children[0] assert isinstance(col, Column) widgets, hv_obj = col wmodel, hv_model = cmodel.children elif loc.startswith('bottom'): assert len(layout) == 1 col = layout[0] cmodel = model.children[0] assert isinstance(col, Column) hv_obj, widgets = col hv_model, wmodel = cmodel.children assert hv_pane is hv_obj assert isinstance(hv_model, Figure) if loc in ('left', 'right', 'top', 'bottom', 'top_right', 'right_bottom', 'bottom_right', 'left_bottom'): box = widgets[1] boxmodel = wmodel.children[1] else: box = widgets[0] boxmodel = wmodel.children[0] assert hv_pane.widget_box is box assert isinstance(boxmodel, BkColumn) assert isinstance(boxmodel.children[0], BkColumn) assert isinstance(boxmodel.children[0].children[1], BkSlider) assert isinstance(boxmodel.children[1], BkSelect) @hv_available def test_holoviews_widgets_from_holomap(): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) widgets, _ = HoloViews.widgets_from_dimensions(hmap) assert isinstance(widgets[0], DiscreteSlider) assert widgets[0].name == 'X' assert widgets[0].options == OrderedDict([(str(i), i) for i in range(3)]) assert widgets[0].value == 0 assert isinstance(widgets[1], Select) assert widgets[1].name == 'Y' assert widgets[1].options == ['A', 'B', 'C'] assert widgets[1].value == 'A' @hv_available def test_holoviews_date_slider_widgets_from_holomap(): hmap = hv.HoloMap({dt.datetime(2016, 1, i+1): hv.Curve([i]) for i in range(3)}, kdims=['X']) widgets, _ = HoloViews.widgets_from_dimensions(hmap) assert isinstance(widgets[0], DiscreteSlider) assert widgets[0].name == 'X' assert widgets[0].options == OrderedDict([ ('2016-01-01 00:00:00', dt.datetime(2016, 1, 1)), ('2016-01-02 00:00:00', dt.datetime(2016, 1, 2)), ('2016-01-03 00:00:00', dt.datetime(2016, 1, 3))]) assert widgets[0].value == dt.datetime(2016, 1, 1) @hv_available def test_holoviews_widgets_explicit_widget_type_override(): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) widgets, _ = HoloViews.widgets_from_dimensions(hmap, widget_types={'X': Select}) assert isinstance(widgets[0], Select) assert widgets[0].name == 'X' assert widgets[0].options == OrderedDict([(str(i), i) for i in range(3)]) assert widgets[0].value == 0 @hv_available def test_holoviews_widgets_invalid_widget_type_override(): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) with pytest.raises(ValueError): HoloViews.widgets_from_dimensions(hmap, widget_types={'X': 1}) @hv_available def test_holoviews_widgets_explicit_widget_instance_override(): hmap = hv.HoloMap({(i, chr(65+i)): hv.Curve([i]) for i in range(3)}, kdims=['X', 'Y']) widget = Select(options=[1, 2, 3], value=3) widgets, _ = HoloViews.widgets_from_dimensions(hmap, widget_types={'X': widget}) assert widgets[0] is widget @hv_available def test_holoviews_linked_axes(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3]) layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is p2.x_range assert p1.y_range is p2.y_range @hv_available def test_holoviews_linked_axes_flexbox(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3]) layout = FlexBox(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is p2.x_range assert p1.y_range is p2.y_range @hv_available def test_holoviews_linked_axes_merged_ranges(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([0, 1, 2, 3, 4]) layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is p2.x_range assert p1.y_range is p2.y_range assert p1.y_range.start == -0.4 assert p1.y_range.end == 4.4 @hv_available def test_holoviews_linked_x_axis(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3], vdims='y2') layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is p2.x_range assert p1.y_range is not p2.y_range @hv_available def test_holoviews_axiswise_not_linked_axes(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3]).opts(axiswise=True, backend='bokeh') layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is not p2.x_range assert p1.y_range is not p2.y_range @hv_available def test_holoviews_shared_axes_opt_not_linked_axes(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3]).opts(shared_axes=False, backend='bokeh') layout = Row(HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh')) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is not p2.x_range assert p1.y_range is not p2.y_range @hv_available def test_holoviews_not_linked_axes(document, comm): c1 = hv.Curve([1, 2, 3]) c2 = hv.Curve([1, 2, 3]) layout = Row( HoloViews(c1, backend='bokeh'), HoloViews(c2, backend='bokeh', linked_axes=False) ) row_model = layout.get_root(document, comm=comm) p1, p2 = row_model.select({'type': Figure}) assert p1.x_range is not p2.x_range assert p1.y_range is not p2.y_range @hv_available def test_holoviews_link_across_panes(document, comm): from bokeh.models.tools import RangeTool from holoviews.plotting.links import RangeToolLink c1 = hv.Curve([]) c2 = hv.Curve([]) RangeToolLink(c1, c2) layout = Row(Pane(c1, backend='bokeh'), Pane(c2, backend='bokeh')) row = layout.get_root(document, comm=comm) assert len(row.children) == 2 p1, p2 = row.children assert isinstance(p1, Figure) assert isinstance(p2, Figure) range_tool = row.select_one({'type': RangeTool}) assert isinstance(range_tool, RangeTool) assert range_tool.x_range == p2.x_range @hv_available def test_holoviews_link_after_adding_item(document, comm): from bokeh.models.tools import RangeTool from holoviews.plotting.links import RangeToolLink c1 = hv.Curve([]) c2 = hv.Curve([]) RangeToolLink(c1, c2) layout = Row(Pane(c1, backend='bokeh')) row = layout.get_root(document, comm=comm) assert len(row.children) == 1 p1, = row.children assert isinstance(p1, Figure) range_tool = row.select_one({'type': RangeTool}) assert range_tool is None layout.append(Pane(c2, backend='bokeh')) _, p2 = row.children assert isinstance(p2, Figure) range_tool = row.select_one({'type': RangeTool}) assert isinstance(range_tool, RangeTool) assert range_tool.x_range == p2.x_range @hv_available def test_holoviews_link_within_pane(document, comm): from bokeh.models.tools import RangeTool from holoviews.plotting.links import RangeToolLink c1 = hv.Curve([]) c2 = hv.Curve([]) RangeToolLink(c1, c2) pane = Pane(Pane(hv.Layout([c1, c2]), backend='bokeh')) column = pane.get_root(document, comm=comm) assert len(column.children) == 1 subcolumn = column.children[0] assert isinstance(subcolumn, BkColumn) assert len(subcolumn.children) == 2 toolbar, subsubcolumn = subcolumn.children assert isinstance(subsubcolumn, GridBox) assert len(subsubcolumn.children) == 2 (p1, _, _), (p2, _, _) = subsubcolumn.children assert isinstance(p1, Figure) assert isinstance(p2, Figure) range_tool = subsubcolumn.select_one({'type': RangeTool}) assert isinstance(range_tool, RangeTool) assert range_tool.x_range == p2.x_range @hv_available def test_holoviews_property_override(document, comm): c1 = hv.Curve([]) pane = Pane(c1, backend='bokeh', background='red', css_classes=['test_class']) model = pane.get_root(document, comm=comm) assert model.background == 'red' assert model.css_classes == ['test_class']