Skip to content

Commit

Permalink
Add link_selections support
Browse files Browse the repository at this point in the history
  • Loading branch information
jonmmease committed Sep 26, 2019
1 parent 91259b9 commit 317b5fb
Show file tree
Hide file tree
Showing 12 changed files with 927 additions and 2 deletions.
14 changes: 14 additions & 0 deletions holoviews/plotting/bokeh/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from bokeh.models.tools import BoxSelectTool
from bokeh.transform import jitter

from ...plotting.bokeh.selection import BokehOverlaySelectionDisplay
from ...selection import NoOpSelectionDisplay
from ...core.data import Dataset
from ...core.dimension import dimension_name
from ...core.util import (
Expand Down Expand Up @@ -58,6 +60,8 @@ class PointPlot(LegendPlot, ColorbarPlot):
_plot_methods = dict(single='scatter', batched='scatter')
_batched_style_opts = line_properties + fill_properties + ['size', 'marker', 'angle']

selection_display = BokehOverlaySelectionDisplay()

def _get_size_data(self, element, ranges, style):
data, mapping = {}, {}
sdim = element.get_dimension(self.size_index)
Expand Down Expand Up @@ -396,6 +400,8 @@ class HistogramPlot(ColorbarPlot):

_nonvectorized_styles = ['line_dash', 'visible']

selection_display = BokehOverlaySelectionDisplay()

def get_data(self, element, ranges, style):
if self.invert_axes:
mapping = dict(top='right', bottom='left', left=0, right='top')
Expand Down Expand Up @@ -505,6 +511,10 @@ class ErrorPlot(ColorbarPlot):

_plot_methods = dict(single=Whisker)

# selection_display should be changed to BokehOverlaySelectionDisplay
# when #3950 is fixed
selection_display = NoOpSelectionDisplay()

def get_data(self, element, ranges, style):
mapping = dict(self._mapping)
if self.static_source:
Expand Down Expand Up @@ -664,6 +674,8 @@ class SpikesPlot(ColorbarPlot):

_plot_methods = dict(single='segment')

selection_display = BokehOverlaySelectionDisplay()

def get_extents(self, element, ranges, range_type='combined'):
if len(element.dimensions()) > 1:
ydim = element.get_dimension(1)
Expand Down Expand Up @@ -779,6 +791,8 @@ class BarPlot(ColorbarPlot, LegendPlot):
# Declare that y-range should auto-range if not bounded
_y_range_type = Range1d

selection_display = BokehOverlaySelectionDisplay()

def get_extents(self, element, ranges, range_type='combined'):
"""
Make adjustments to plot extents by computing
Expand Down
3 changes: 3 additions & 0 deletions holoviews/plotting/bokeh/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from bokeh.models import (ColumnDataSource, Column, Row, Div)
from bokeh.models.widgets import Panel, Tabs

from ...selection import NoOpSelectionDisplay
from ...core import (
OrderedDict, Store, AdjointLayout, NdLayout, Layout, Empty,
GridSpace, HoloMap, Element
Expand Down Expand Up @@ -77,6 +78,8 @@ class BokehPlot(DimensionedPlot, CallbackPlot):

backend = 'bokeh'

selection_display = NoOpSelectionDisplay()

@property
def id(self):
return self.root.ref['id'] if self.root else None
Expand Down
33 changes: 33 additions & 0 deletions holoviews/plotting/bokeh/selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from ...selection import OverlaySelectionDisplay
from ...core.options import Store


class BokehOverlaySelectionDisplay(OverlaySelectionDisplay):
"""
Overlay selection display subclass for use with bokeh backend
"""
def _build_element_layer(
self, element, layer_color, selection_expr=True
):
element, visible = self._select(element, selection_expr)

backend_options = Store.options(backend='bokeh')
style_options = backend_options[(type(element).name,)]['style']

def alpha_opts(alpha):
options = dict()

for opt_name in style_options.allowed_keywords:
if 'alpha' in opt_name:
options[opt_name] = alpha

return options

layer_alpha = 1.0 if visible else 0.0
layer_element = element.options(
tools=['box_select'],
**self._get_color_kwarg(layer_color),
**alpha_opts(layer_alpha)
)

return layer_element
9 changes: 8 additions & 1 deletion holoviews/plotting/bokeh/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

from bokeh.models import FactorRange, Circle, VBar, HBar

from .selection import BokehOverlaySelectionDisplay
from ...core.dimension import Dimension, Dimensioned
from ...core.ndmapping import sorted_context
from ...core.util import (basestring, dimension_sanitizer, wrap_tuple,
Expand All @@ -34,6 +35,8 @@ class DistributionPlot(AreaPlot):
filled = param.Boolean(default=True, doc="""
Whether the bivariate contours should be filled.""")

selection_display = BokehOverlaySelectionDisplay()


class BivariatePlot(PolygonPlot):
"""
Expand All @@ -55,7 +58,7 @@ class BivariatePlot(PolygonPlot):
levels = param.ClassSelector(default=10, class_=(list, int), doc="""
A list of scalar values used to specify the contour levels.""")


selection_display = BokehOverlaySelectionDisplay(color_prop='cmap', is_cmap=True)


class BoxWhiskerPlot(CompositeElementPlot, ColorbarPlot, LegendPlot):
Expand Down Expand Up @@ -84,6 +87,8 @@ class BoxWhiskerPlot(CompositeElementPlot, ColorbarPlot, LegendPlot):

_stream_data = False # Plot does not support streaming data

selection_display = BokehOverlaySelectionDisplay()

def get_extents(self, element, ranges, range_type='combined'):
return super(BoxWhiskerPlot, self).get_extents(
element, ranges, range_type, 'categorical', element.vdims[0]
Expand Down Expand Up @@ -332,6 +337,8 @@ class ViolinPlot(BoxWhiskerPlot):

_stat_fns = [partial(np.percentile, q=q) for q in [25, 50, 75]]

selection_display = BokehOverlaySelectionDisplay(color_prop='violin_fill_color')

def _kde_data(self, el, key, **kwargs):
vdim = el.vdims[0]
values = el.dimension_values(vdim)
Expand Down
3 changes: 3 additions & 0 deletions holoviews/plotting/plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from panel.io.notebook import push
from panel.io.state import state

from ..selection import NoOpSelectionDisplay
from ..core import OrderedDict
from ..core import util, traversal
from ..core.element import Element, Element3D
Expand Down Expand Up @@ -947,6 +948,8 @@ class GenericElementPlot(DimensionedPlot):
_propagate_options = []
v17_option_propagation = True

_selection_display = NoOpSelectionDisplay()

def __init__(self, element, keys=None, ranges=None, dimensions=None,
batched=False, overlaid=0, cyclic_index=0, zorder=0, style=None,
overlay_dims={}, stream_sources=[], streams=None, **params):
Expand Down
13 changes: 12 additions & 1 deletion holoviews/plotting/plotly/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import param
import numpy as np

from .selection import PlotlyOverlaySelectionDisplay
from ...core.data import Dataset
from ...core import util
from ...element import Bars
Expand Down Expand Up @@ -44,6 +45,8 @@ class ScatterPlot(ChartPlot, ColorbarPlot):

_style_key = 'marker'

selection_display = PlotlyOverlaySelectionDisplay()

def graph_options(self, element, ranges, style):
opts = super(ScatterPlot, self).graph_options(element, ranges, style)
cdim = element.get_dimension(self.color_index)
Expand Down Expand Up @@ -148,6 +151,8 @@ class ErrorBarsPlot(ChartPlot, ColorbarPlot):

_style_key = 'error_y'

selection_display = PlotlyOverlaySelectionDisplay()

def get_data(self, element, ranges, style):
x, y = ('y', 'x') if self.invert_axes else ('x', 'y')
error_k = 'error_' + x if element.horizontal else 'error_' + y
Expand Down Expand Up @@ -188,6 +193,8 @@ class BarPlot(ElementPlot):

trace_kwargs = {'type': 'bar'}

selection_display = PlotlyOverlaySelectionDisplay()

def get_extents(self, element, ranges, range_type='combined'):
"""
Make adjustments to plot extents by computing
Expand Down Expand Up @@ -297,10 +304,14 @@ class HistogramPlot(ElementPlot):

trace_kwargs = {'type': 'bar'}

style_opts = ['visible', 'color', 'line_color', 'line_width', 'opacity']
style_opts = [
'visible', 'color', 'line_color', 'line_width', 'opacity', 'selectedpoints'
]

_style_key = 'marker'

selection_display = PlotlyOverlaySelectionDisplay()

def get_data(self, element, ranges, style):
xdim = element.kdims[0]
ydim = element.vdims[0]
Expand Down
7 changes: 7 additions & 0 deletions holoviews/plotting/plotly/chart3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from plotly import colors
from plotly.figure_factory._trisurf import trisurf as trisurface

from .selection import PlotlyOverlaySelectionDisplay
from ...core.options import SkipRendering
from .element import ElementPlot, ColorbarPlot
from .chart import ScatterPlot, CurvePlot
Expand Down Expand Up @@ -58,6 +59,12 @@ class Scatter3DPlot(Chart3DPlot, ScatterPlot):

trace_kwargs = {'type': 'scatter3d', 'mode': 'markers'}

style_opts = [
'visible', 'marker', 'color', 'cmap', 'alpha', 'opacity', 'size', 'sizemin'
]

selection_display = PlotlyOverlaySelectionDisplay()


class Path3DPlot(Chart3DPlot, CurvePlot):

Expand Down
29 changes: 29 additions & 0 deletions holoviews/plotting/plotly/selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import absolute_import
from ...selection import OverlaySelectionDisplay
from ...core.options import Store


class PlotlyOverlaySelectionDisplay(OverlaySelectionDisplay):
"""
Overlay selection display subclass for use with plotly backend
"""
def _build_element_layer(
self, element, layer_color, selection_expr=True
):
element, visible = self._select(element, selection_expr)

backend_options = Store.options(backend='plotly')
style_options = backend_options[(type(element).name,)]['style']

if 'selectedpoints' in style_options.allowed_keywords:
shared_opts = dict(selectedpoints=False)
else:
shared_opts = dict()

layer_element = element.options(
visible=visible,
**self._get_color_kwarg(layer_color),
**shared_opts
)

return layer_element
10 changes: 10 additions & 0 deletions holoviews/plotting/plotly/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import param

from .selection import PlotlyOverlaySelectionDisplay
from .chart import ChartPlot
from .element import ElementPlot, ColorbarPlot

Expand All @@ -18,6 +19,8 @@ class BivariatePlot(ChartPlot, ColorbarPlot):

_style_key = 'contours'

selection_display = PlotlyOverlaySelectionDisplay()

def graph_options(self, element, ranges, style):
opts = super(BivariatePlot, self).graph_options(element, ranges, style)
copts = self.get_color_opts(element.vdims[0], element, ranges, style)
Expand All @@ -44,6 +47,9 @@ def graph_options(self, element, ranges, style):

opts['showscale'] = copts.get('showscale', False)

# Add visible
opts['visible'] = style.get('visible', True)

return opts


Expand All @@ -64,6 +70,8 @@ class DistributionPlot(ElementPlot):

_style_key = 'line'

selection_display = PlotlyOverlaySelectionDisplay()


class MultiDistributionPlot(ElementPlot):

Expand Down Expand Up @@ -118,6 +126,8 @@ class BoxWhiskerPlot(MultiDistributionPlot):

_style_key = 'marker'

selection_display = PlotlyOverlaySelectionDisplay()

def graph_options(self, element, ranges, style):
options = super(BoxWhiskerPlot, self).graph_options(element, ranges, style)
options['boxmean'] = self.mean
Expand Down
15 changes: 15 additions & 0 deletions holoviews/plotting/plotly/tabular.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import param

from ...selection import ColorListSelectionDisplay
from .element import ElementPlot


Expand All @@ -17,12 +18,26 @@ class TablePlot(ElementPlot):

_style_key = 'cells'

selection_display = ColorListSelectionDisplay(color_prop='fill')

def get_data(self, element, ranges, style):
header = dict(values=[d.pprint_label for d in element.dimensions()])
cells = dict(values=[[d.pprint_value(v) for v in element.dimension_values(d)]
for d in element.dimensions()])
return [{'header': header, 'cells': cells}]

def graph_options(self, element, ranges, style):
opts = super(TablePlot, self).graph_options(element, ranges, style)

# Transpose fill_color array so values apply by rows not column
if 'fill' in opts.get('cells', {}):
opts['cells']['fill_color'] = [opts['cells'].pop('fill')]

if 'line' in opts.get('cells', {}):
opts['cells']['line_color'] = [opts['cells']['line']]

return opts

def init_layout(self, key, element, ranges):
return dict(width=self.width, height=self.height,
title=self._format_title(key, separator=' '),
Expand Down
Loading

0 comments on commit 317b5fb

Please sign in to comment.