Skip to content

Commit

Permalink
Merge 6d6c34a into 2961d7d
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Mar 22, 2017
2 parents 2961d7d + 6d6c34a commit 6276131
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 58 deletions.
8 changes: 4 additions & 4 deletions doc/Tutorials/Bokeh_Backend.ipynb
Expand Up @@ -226,7 +226,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Both the matplotlib and the bokeh backends allow plotting datetime data, if you ensure the dates array is of a datetime dtype."
"Both the matplotlib and the bokeh backends allow plotting datetime data, if you ensure the dates array is of a datetime dtype. Note also that the legends are interactive and can be toggled by clicking on its entries. Additionally the style of unselected curve can be controlled by setting the ``muted_alpha`` and ``muted_color`` style options."
]
},
{
Expand All @@ -239,7 +239,7 @@
},
"outputs": [],
"source": [
"%%opts Overlay [width=600 legend_position='top_left']\n",
"%%opts Overlay [width=600 legend_position='top_left'] Curve (muted_alpha=0.5 muted_color='black')\n",
"try:\n",
" import bokeh.sampledata.stocks\n",
"except:\n",
Expand Down Expand Up @@ -544,7 +544,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"Bokeh provides a number of tools for selecting data points including ``box_select``, ``lasso_select`` and ``poly_select``. To distinguish between selected and unselected data points we can also set the ``unselected_color``. You can try out any of these selection tools and see how the plot is affected:"
"Bokeh provides a number of tools for selecting data points including ``tap``, ``box_select``, ``lasso_select`` and ``poly_select``. To distinguish between selected and unselected data points we can also control the color and alpha of the ``selection`` and ``nonselection`` points. You can try out any of these selection tools and see how the plot is affected:"
]
},
{
Expand All @@ -558,7 +558,7 @@
},
"outputs": [],
"source": [
"%%opts Points [tools=['box_select', 'lasso_select', 'poly_select']] (size=10 unselected_color='red' color='blue')\n",
"%%opts Points [tools=['box_select', 'lasso_select', 'tap']] (size=10 nonselection_color='red' color='blue')\n",
"hv.Points(error)"
]
},
Expand Down
20 changes: 16 additions & 4 deletions holoviews/plotting/bokeh/__init__.py
Expand Up @@ -29,6 +29,8 @@
HSVPlot, QuadMeshPlot)
from .renderer import BokehRenderer
from .tabular import TablePlot
from .util import bokeh_version


Store.renderers['bokeh'] = BokehRenderer.instance()

Expand Down Expand Up @@ -127,11 +129,11 @@
options.Points = Options('style', color=Cycle(), size=point_size, cmap='hot')
options.Histogram = Options('style', line_color='black', fill_color=Cycle())
options.ErrorBars = Options('style', color='black')
options.Spread = Options('style', fill_color=Cycle(), fill_alpha=0.6, line_color='black')
options.Spread = Options('style', color=Cycle(), alpha=0.6, line_color='black')

options.Spikes = Options('style', color='black')
options.Area = Options('style', color=Cycle(), line_color='black')
options.VectorField = Options('style', line_color='black')
options.VectorField = Options('style', color='black')

# Paths
options.Contours = Options('style', color=Cycle())
Expand All @@ -149,9 +151,19 @@
options.HeatMap = Options('style', cmap='RdYlBu_r', line_alpha=0)

# Annotations
options.HLine = Options('style', line_color=Cycle(), line_width=3, line_alpha=1)
options.VLine = Options('style', line_color=Cycle(), line_width=3, line_alpha=1)
options.HLine = Options('style', color=Cycle(), line_width=3, alpha=1)
options.VLine = Options('style', color=Cycle(), line_width=3, alpha=1)

# Define composite defaults
options.GridMatrix = Options('plot', shared_xaxis=True, shared_yaxis=True,
xaxis=None, yaxis=None)

if bokeh_version >= '0.12.5':
options.Overlay = Options('style', click_policy='mute')
options.NdOverlay = Options('style', click_policy='mute')
options.Curve = Options('style', muted_alpha=0.2)
options.Path = Options('style', muted_alpha=0.2)
options.Scatter = Options('style', muted_alpha=0.2)
options.Points = Options('style', muted_alpha=0.2)
options.Polygons = Options('style', muted_alpha=0.2)

4 changes: 1 addition & 3 deletions holoviews/plotting/bokeh/annotation.py
Expand Up @@ -65,9 +65,7 @@ def _init_glyph(self, plot, mapping, properties):
"""
Returns a Bokeh glyph object.
"""
properties = {p: v for p, v in properties.items()
if p not in ['source', 'legend']}
box = Span(level='overlay', **dict(mapping, **properties))
box = Span(level='overlay', **mapping)
plot.renderers.append(box)
return None, box

Expand Down
42 changes: 10 additions & 32 deletions holoviews/plotting/bokeh/chart.py
Expand Up @@ -16,7 +16,8 @@
from ...operation import interpolate_curve
from ..util import (compute_sizes, get_sideplot_ranges, match_spec,
map_colors, get_min_distance)
from .element import ElementPlot, ColorbarPlot, LegendPlot, line_properties, fill_properties
from .element import (ElementPlot, ColorbarPlot, LegendPlot, line_properties,
fill_properties)
from .path import PathPlot, PolygonPlot
from .util import get_cmap, mpl_to_bokeh, update_plot, rgb2hex, bokeh_version

Expand Down Expand Up @@ -45,7 +46,7 @@ class PointPlot(LegendPlot, ColorbarPlot):
Function applied to size values before applying scaling,
to remove values lower than zero.""")

style_opts = (['cmap', 'palette', 'marker', 'size', 's', 'alpha', 'color',
style_opts = (['cmap', 'palette', 'marker', 'size', 's', 'color',
'unselected_color'] +
line_properties + fill_properties)

Expand Down Expand Up @@ -111,7 +112,7 @@ def get_batched_data(self, element, ranges=None, empty=False):
nvals = len(data[k][-1])
if 'color' not in elmapping:
val = styles[zorder].get('color')
elmapping['color'] = 'color'
elmapping['color'] = {'field': 'color'}
if isinstance(val, tuple):
val = rgb2hex(val)
data['color'].append([val]*nvals)
Expand All @@ -124,29 +125,6 @@ def get_batched_data(self, element, ranges=None, empty=False):
return data, elmapping


def _init_glyph(self, plot, mapping, properties):
"""
Returns a Bokeh glyph object.
"""
properties = mpl_to_bokeh(properties)
unselect_color = properties.pop('unselected_color', None)
if (any(t in self.tools for t in ['box_select', 'lasso_select'])
and unselect_color is not None):
source = properties.pop('source')
color = properties.pop('color', None)
color = mapping.pop('color', color)
properties.pop('legend', None)
unselected = Circle(**dict(properties, fill_color=unselect_color, **mapping))
selected = Circle(**dict(properties, fill_color=color, **mapping))
renderer = plot.add_glyph(source, selected, selection_glyph=selected,
nonselection_glyph=unselected)
else:
plot_method = self._plot_methods.get('batched' if self.batched else 'single')
renderer = getattr(plot, plot_method)(**dict(properties, **mapping))
if self.colorbar and 'color_mapper' in self.handles:
self._draw_colorbar(plot, self.handles['color_mapper'])
return renderer, renderer.glyph


class VectorFieldPlot(ColorbarPlot):

Expand All @@ -170,7 +148,7 @@ class VectorFieldPlot(ColorbarPlot):
Whether the lengths will be rescaled to take into account the
smallest non-zero distance between two vectors.""")

style_opts = ['color'] + line_properties
style_opts = line_properties
_plot_methods = dict(single='segment')

def _get_lengths(self, element, ranges):
Expand Down Expand Up @@ -244,7 +222,7 @@ class CurvePlot(ElementPlot):
default is 'linear', other options include 'steps-mid',
'steps-pre' and 'steps-post'.""")

style_opts = ['color'] + line_properties
style_opts = line_properties
_plot_methods = dict(single='line', batched='multi_line')
_mapping = {p: p for p in ['xs', 'ys', 'color', 'line_alpha']}

Expand Down Expand Up @@ -334,7 +312,7 @@ def get_data(self, element, ranges=None, empty=False):

class SpreadPlot(PolygonPlot):

style_opts = ['color'] + line_properties + fill_properties
style_opts = line_properties + fill_properties

def get_data(self, element, ranges=None, empty=None):
if empty:
Expand All @@ -359,7 +337,7 @@ def get_data(self, element, ranges=None, empty=None):

class HistogramPlot(ElementPlot):

style_opts = ['color'] + line_properties + fill_properties
style_opts = line_properties + fill_properties
_plot_methods = dict(single='quad')

def get_data(self, element, ranges=None, empty=None):
Expand Down Expand Up @@ -459,7 +437,7 @@ class ErrorPlot(PathPlot):

horizontal = param.Boolean(default=False)

style_opts = ['color'] + line_properties
style_opts = line_properties

def get_data(self, element, ranges=None, empty=False):
if empty:
Expand Down Expand Up @@ -666,7 +644,7 @@ class BoxPlot(ChartPlot):
percentiles.
"""

style_opts = ['color', 'whisker_color', 'marker'] + line_properties
style_opts = ['whisker_color', 'marker'] + line_properties

def _init_chart(self, element, ranges):
properties = self.style[self.cyclic_index]
Expand Down
47 changes: 32 additions & 15 deletions holoviews/plotting/bokeh/element.py
Expand Up @@ -42,18 +42,23 @@
else:
FuncTickFormatter = None

property_prefixes = ['selection', 'nonselection', 'muted']

# Define shared style properties for bokeh plots
line_properties = ['line_width', 'line_color', 'line_alpha',
line_properties = ['line_color', 'line_alpha', 'color', 'alpha', 'line_width',
'line_join', 'line_cap', 'line_dash']
line_properties += ['_'.join([prefix, prop]) for prop in line_properties[:4]
for prefix in property_prefixes]

fill_properties = ['fill_color', 'fill_alpha']
fill_properties += ['_'.join([prefix, prop]) for prop in fill_properties
for prefix in property_prefixes]

text_properties = ['text_font', 'text_font_size', 'text_font_style', 'text_color',
'text_alpha', 'text_align', 'text_baseline']

legend_dimensions = ['label_standoff', 'label_width', 'label_height', 'glyph_width',
'glyph_height', 'legend_padding', 'legend_spacing']
'glyph_height', 'legend_padding', 'legend_spacing', 'click_policy']



Expand Down Expand Up @@ -611,13 +616,31 @@ def _glyph_properties(self, plot, element, source, ranges):
properties['source'] = source
return properties


def _update_glyph(self, glyph, properties, mapping):
def _update_glyphs(self, renderer, properties, mapping, glyph):
allowed_properties = glyph.properties()
properties = mpl_to_bokeh(properties)
merged = dict(properties, **mapping)
glyph.update(**{k: v for k, v in merged.items()
if k in allowed_properties})
for glyph_type in ('', 'selection_', 'nonselection_'):
if renderer:
glyph = getattr(renderer, glyph_type+'glyph', None)
if not glyph or (not renderer and glyph_type):
continue
glyph_props = dict(merged)

for gtype in ((glyph_type, '') if glyph_type else ('',)):
for prop in ('color', 'alpha'):
glyph_prop = merged.get(gtype+prop)
if glyph_prop and ('line_'+prop not in glyph_props or gtype):
glyph_props['line_'+prop] = glyph_prop
if glyph_prop and ('fill_'+prop not in glyph_props or gtype):
glyph_props['fill_'+prop] = glyph_prop

glyph_props.update({k[len(gtype):]: v for k, v in glyph_props.items()
if k.startswith(gtype)})
filtered = {k: v for k, v in glyph_props.items()
if k in allowed_properties}
glyph.update(**filtered)


def _execute_hooks(self, element):
"""
Expand Down Expand Up @@ -677,7 +700,7 @@ def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):

# Update plot, source and glyph
with abbreviated_exception():
self._update_glyph(glyph, properties, mapping)
self._update_glyphs(renderer, properties, mapping, glyph)
if not self.overlaid:
self._update_plot(key, plot, style_element)
self._update_ranges(style_element, ranges)
Expand Down Expand Up @@ -745,7 +768,8 @@ def update_frame(self, key, ranges=None, plot=None, element=None, empty=False):
if glyph:
properties = self._glyph_properties(plot, element, source, ranges)
with abbreviated_exception():
self._update_glyph(self.handles['glyph'], properties, mapping)
self._update_glyphs(self.handles['glyph_renderer'], properties, mapping,
glyph)
if not self.overlaid:
self._update_ranges(style_element, ranges)
self._update_plot(key, plot, style_element)
Expand Down Expand Up @@ -984,13 +1008,6 @@ def _init_glyph(self, plot, mapping, properties):
return ret


def _update_glyph(self, glyph, properties, mapping):
allowed_properties = glyph.properties()
merged = dict(properties, **mapping)
glyph.update(**{k: v for k, v in merged.items()
if k in allowed_properties})


class LegendPlot(ElementPlot):

legend_position = param.ObjectSelector(objects=["top_right",
Expand Down
43 changes: 43 additions & 0 deletions tests/testplotinstantiation.py
Expand Up @@ -385,6 +385,12 @@ def test_points_colormapping(self):
points = Points(np.random.rand(10, 4), vdims=['a', 'b'])(plot=dict(color_index=3))
self._test_colormapping(points, 3)

def test_points_colormapping_with_nonselection(self):
opts = dict(plot=dict(color_index=3),
style=dict(nonselection_color='red'))
points = Points(np.random.rand(10, 4), vdims=['a', 'b'])(**opts)
self._test_colormapping(points, 3)

def test_points_colormapping_categorical(self):
points = Points([(i, i*2, i*3, chr(65+i)) for i in range(10)],
vdims=['a', 'b'])(plot=dict(color_index='b'))
Expand All @@ -395,6 +401,43 @@ def test_points_colormapping_categorical(self):
self.assertIsInstance(cmapper, CategoricalColorMapper)
self.assertEqual(cmapper.factors, list(points['b']))

def test_points_color_selection_nonselection(self):
opts = dict(color='green', selection_color='red', nonselection_color='blue')
points = Points([(i, i*2, i*3, chr(65+i)) for i in range(10)],
vdims=['a', 'b'])(style=opts)
plot = bokeh_renderer.get_plot(points)
glyph_renderer = plot.handles['glyph_renderer']
self.assertEqual(glyph_renderer.glyph.fill_color, 'green')
self.assertEqual(glyph_renderer.glyph.line_color, 'green')
self.assertEqual(glyph_renderer.selection_glyph.fill_color, 'red')
self.assertEqual(glyph_renderer.selection_glyph.line_color, 'red')
self.assertEqual(glyph_renderer.nonselection_glyph.fill_color, 'blue')
self.assertEqual(glyph_renderer.nonselection_glyph.line_color, 'blue')

def test_points_alpha_selection_nonselection(self):
opts = dict(alpha=0.8, selection_alpha=1.0, nonselection_alpha=0.2)
points = Points([(i, i*2, i*3, chr(65+i)) for i in range(10)],
vdims=['a', 'b'])(style=opts)
plot = bokeh_renderer.get_plot(points)
glyph_renderer = plot.handles['glyph_renderer']
self.assertEqual(glyph_renderer.glyph.fill_alpha, 0.8)
self.assertEqual(glyph_renderer.glyph.line_alpha, 0.8)
self.assertEqual(glyph_renderer.selection_glyph.fill_alpha, 1)
self.assertEqual(glyph_renderer.selection_glyph.line_alpha, 1)
self.assertEqual(glyph_renderer.nonselection_glyph.fill_alpha, 0.2)
self.assertEqual(glyph_renderer.nonselection_glyph.line_alpha, 0.2)

def test_points_alpha_selection_partial(self):
opts = dict(selection_alpha=1.0, selection_fill_alpha=0.2)
points = Points([(i, i*2, i*3, chr(65+i)) for i in range(10)],
vdims=['a', 'b'])(style=opts)
plot = bokeh_renderer.get_plot(points)
glyph_renderer = plot.handles['glyph_renderer']
self.assertEqual(glyph_renderer.glyph.fill_alpha, 1.0)
self.assertEqual(glyph_renderer.glyph.line_alpha, 1.0)
self.assertEqual(glyph_renderer.selection_glyph.fill_alpha, 0.2)
self.assertEqual(glyph_renderer.selection_glyph.line_alpha, 1)

def test_image_colormapping(self):
img = Image(np.random.rand(10, 10))(plot=dict(logz=True))
self._test_colormapping(img, 2, True)
Expand Down

0 comments on commit 6276131

Please sign in to comment.