fmfreeze
fmfreeze

Reputation: 197

Accessing bokeh (figure) parameters within holoviews

Primarly to use holoviews' decimate operation I stick to holoviews, but I ran into limitations in adapting the bokeh output within holoviews:

To realize "linked panning" in pure bokeh, one needs to share the e.g. x_range parameter in bokehs bokeh.plotting.figure.Figure object like

# create a new plot 
s1 = figure(plot_width=250, plot_height=250)

# create a new plot and share only one range 
s2 = figure(plot_width=250, plot_height=250, x_range=s1.x_range)

Full example on: https://docs.bokeh.org/en/latest/docs/user_guide/interaction/linking.html#userguide-interaction-linking

How can I access and manipulate the bokeh figures created by holoviews?

I would like to implement linked panning directly within holoviews. The reason for that is, to preserve interactivity in plots with different ranges.

I know, the holoviews option axiswise=True perfectly adjusts different ranges automatically but it seems this cannot be defined for a single axis only (e.g. y-axis), while perserving linked panning interactivity on the x-axis.

How can I accomplish this? Thank you very much!

Upvotes: 1

Views: 761

Answers (1)

philippjfr
philippjfr

Reputation: 4080

There are several different answer to this question. Let's start with the most immediate fix for what you are trying to do which is to link one axis but not another. This is most easily accomplished by giving the dimensions different names, e.g. simply name one of the dimensions something other than the default 'x' or 'y':

hv.Curve([1, 2, 3], 'x', 'y') + hv.Curve([1, 2, 3], 'x', 'y2')

To be very exact, the "identity" of an axis in this case is determined by the Dimension name, label and unit. This allows axes representing the same quantities to be easily shared. If you really need to you could even give them different names/labels/units and then override the displayed label with the xlabel/ylabel options.

Now to answer the more literal version of your answer, HoloViews generates bokeh models in the background and then renders them to screen. You can define hooks which can modify the bokeh plot representation before it is rendered. I certainly wouldn't recommend this approach but you can achieve the same thing as the previous solution like this:

ax_range = None
def hook(plot, element):
    global ax_range
    ax_range = plot.handles['x_range']

def hook2(plot, element):
    plot.state.x_range = ax_range

hv.Curve([1, 2, 3], 'x', 'y').opts(hooks=[hook]) + hv.Curve([1, 2, 3], 'x2', 'y').opts(hooks=[hook2], axiswise=True)

Lastly we could make the linking more explicit and if you like this approach I'd recommend you file an issue to request it be included. In HoloViews 1.11 so called Links were introduced, we could easily add a RangeLink like this:

import param
from holoviews.plotting.links import Link

class RangeLink(Link):

    x_range = param.Boolean(default=True)
    y_range = param.Boolean(default=True)

    _requires_target = True

from holoviews.plotting.bokeh.callbacks import LinkCallback

class RangeLinkCallback(LinkCallback):

    def __init__(self, root_model, link, source_plot, target_plot):
        if link.x_range:
            target_plot.handles['x_range'] = source_plot.handles['x_range']    
            target_plot.state.x_range = source_plot.state.x_range
        if link.y_range:
            target_plot.handles['y_range'] = source_plot.handles['y_range']    
            target_plot.state.y_range = source_plot.state.y_range

RangeLink.register_callback('bokeh', RangeLinkCallback)

# Now we can use it to link the axes
curve = hv.Curve([1, 2, 3])
curve2 = hv.Curve([1, 2, 3])

RangeLink(curve, curve2, y_range=False)

curve + curve2.opts(axiswise=True)

Upvotes: 2

Related Questions