I was finally able to put together an example that does everything I needed:
- lowlighting of unselected data
- dynamic brushing works regardless of which of the three plots was selected.
In order to get the lowlighting working, I ended up drawing each plot twice. Once with all the points drawn in lowlight color, and then an overlay with the just points selected drawn in normal color. I’m sure there must be a better way to do this, and I’d appreciate any advice. I’m really looking forward to when the team is able to fix bug 5066.
import numpy as np
import pandas as pd
import holoviews as hv
from holoviews import dim
from holoviews.operation import histogram
from holoviews import opts
from holoviews import streams
hv.extension('bokeh')
default_blue = '#30a2d9'
opts.defaults(opts.Points(tools=['box_select'],
active_tools=['box_select']),
opts.Histogram(tools=['box_select'],
active_tools=['box_select']))
# Declare some points
np.random.seed(1)
df = pd.DataFrame(data=np.random.randn(10,2 ),
columns=['x', 'y'])
points_all = hv.Points(df).opts(alpha=0.2,
tools=['box_select'],
active_tools=['box_select'])
# Declare points as source of selection stream
selection = streams.Selection1D(source=points_all)
reset = streams.PlotReset()
xhist_all = histogram(points_all,
bin_range=points_all.range('x'),
dimension='x').opts(alpha=0.2,
width=300,
height=100)
xhist_bounds = streams.BoundsX(source=xhist_all,
boundsx=(0,0))
yhist_all = histogram(points_all,
bin_range=points_all.range('y'),
dimension='y').opts(alpha=0.2,
width=100,
height=300)
# yhist_bounds = streams.BoundsX(source=yhist_all, boundsx=(0,0), rename={'boundsx': 'boundsy'}) # if not rotated
yhist_bounds = streams.BoundsY(source=yhist_all,
boundsy=(0,0))
def select_points(index, boundsx, boundsy, resetting):
selected = points_all.iloc[:]
if resetting:
selected = points_all.iloc[:]
xhist_bounds.reset()
yhist_bounds.reset()
elif index:
selected = points_all.iloc[index]
else:
if boundsx and (boundsx[0] or boundsx[1]):
selected = points_all.select(
selection_expr=((dim('x') >= boundsx[0])
&(dim('x') <= boundsx[1])))
if boundsy and (boundsy[0] or boundsy[1]):
selected = points_all.select(
selection_expr=((dim('y') >= boundsy[0])
&(dim('y') <= boundsy[1])))
return selected
# Declare DynamicMap to apply bounds selection
points_selected = hv.DynamicMap(select_points,
streams=[selection,
xhist_bounds,
yhist_bounds,
reset])
xhist_selected = histogram(points_selected,
bin_range=points_all.range('x'),
dimension='x').opts(color=default_blue,
alpha=1.0)
yhist_selected = histogram(points_selected,
bin_range=points_all.range('y'),
dimension='y').opts(color=default_blue,
alpha=1.0)
points_selected.opts(alpha=1.0, color=default_blue)
# Combine points and histograms
((points_all * points_selected)
<< (yhist_all * yhist_selected)
<< (xhist_all * xhist_selected))