Is it possible to programmatically update the range when using a RangeToolLink?

I have two plots linked via a RangeToolLink. How would I programmatically update the RangeTool which is added to the source plot so that the new range is reflected on the target plot?

I’m hoping to let a user play with the range tool to establish an appropriate range for the data and then save the range so it can be re-applied when looking at the data at a later time.

This is how I am setting up the RangeToolLink and listening to the current range of the target plot in order to save it. That seems to work but I haven’t been able to figure out how to update the range at a later time.

data = np.random.randn(1000).cumsum()

def gen_target():        
    return hv.Curve(data).opts(width=800, labelled=['y'], toolbar=None)

target_dmap = hv.DynamicMap(gen_target)

def subscribe(x_range=(0,0), y_range=(0,0)):
    print('x_range: ', x_range)

range_xy_stream = hv.streams.RangeXY(source=target_dmap)    
range_xy_stream.add_subscriber(subscribe)

source = hv.Curve(data).opts(width=800, height=125, axiswise=True, default_tools=[])

def gen_source(x_range=(0,0), y_range=(0,0)):
    return source

source_dmap = hv.DynamicMap(gen_source, streams=[range_xy_stream])

rtlink = RangeToolLink(source_dmap, target_dmap)
layout = (target_dmap + source_dmap).opts(merge_tools=False).cols(1)
layout

By going through the renderer I think I can get a handle to the RangeTool, but not sure how to update the the range.

from bokeh.models import RangeTool
plot = renderer.get_plot(layout)
src_plot = plot.subplots[(1, 0)].subplots['main']
range_tool = src_plot.state.select_one({'type': RangeTool})
print(range_tool) 

# Outputs:  RangeTool(id='126543', ...)

# this has no effect
range_tool.x_range.update(start=10, end=20)

Any ideas on how to update the range when using a RangeToolLink would be most appreciated!

I haven’t used the RangeToolLink, but I have used hv.stream.RangXY. Have you tried to just change the values in the RangeXY tool directly? e.g. range_xy_stream.x_range = (0, 0)?

I’m doing something similar here (https://github.com/Quansight/lsst_dashboard/blob/master/lsst_dashboard/overview.py#L160) to get the current range before rerendering the plot. I’m recreating the stream but I’m pretty sure that’s not necessary now that I’m looking at it again.

Hi @kcpevey, thanks for the reply!

I tried setting the x_range directly on the hv.stream.RangeXY as you suggested. It did not seem to have any effect. However I took a look at the lsst_dashboard code you linked and loosely followed what you did there and if I initialize a stream with my desired x_range values and pass that stream into my target plot when the plot is created my values are respected and the RangeToolLink is updated as well.

This is a solution that I can work with. Previously I was trying to update the RangeToolLink programmatically after the plot was created but I can adjust my code to pass in the values at the time I am constructing the plot.

Here is my code snippet adjusted to pass some x range values in via a stream when I create my plot.

data = np.random.randn(1000).cumsum()

MyRange = hv.streams.Stream.define('MyRange', xmin=param.Number(default=None), xmax=param.Number(default=None))

# using test values (50, 500) as an example range...
my_range = MyRange(xmin=50, xmax=500)

def gen_target(xmin, xmax):
    if xmin is not None and xmax is not None:
        return hv.Curve(data).opts(width=800, labelled=['y'], toolbar=None).opts(xlim=(xmin, xmax))
    else:
        return hv.Curve(data).opts(width=800, labelled=['y'], toolbar=None)

target_dmap = hv.DynamicMap(gen_target, streams=[my_range])

def subscribe(x_range=(0,0), y_range=(0,0)):
    print('x_range: ', x_range)

range_xy_stream = hv.streams.RangeXY(source=target_dmap)    
range_xy_stream.add_subscriber(subscribe)

source = hv.Curve(data).opts(width=800, height=125, axiswise=True, default_tools=[])

def gen_source(x_range=(0,0), y_range=(0,0)):
    return source

source_dmap = hv.DynamicMap(gen_source, streams=[range_xy_stream])

rtlink = hv.plotting.links.RangeToolLink(source_dmap, target_dmap)
layout = (target_dmap + source_dmap).opts(merge_tools=False).cols(1)
layout
1 Like