How do I link the CrossHairTool in bokeh over several plots?

后端 未结 5 1274
逝去的感伤
逝去的感伤 2020-12-30 07:34

When moving the crosshair (dimensions=width) in one plot I want to see the same position in the other plot(s). My plots share the same x-axis.

Here is the plot setup

5条回答
  •  渐次进展
    2020-12-30 08:12

    Marged solution (updated for Bokeh v1.0.4)

    from bokeh.layouts import gridplot
    from bokeh.models import CustomJS, CrosshairTool
    from bokeh.plotting import figure, ColumnDataSource, output_file, save, show
    from bokeh.models import Span, CrosshairTool, HoverTool, ResetTool, PanTool, WheelZoomTool
    from datetime import datetime
    from datetime import timedelta
    import numpy as np
    import time
    
    def add_vlinked_crosshairs(fig1, fig2):
        js_move = '''if(cb_obj.x >= fig.x_range.start && cb_obj.x <= fig.x_range.end && cb_obj.y >= fig.y_range.start && cb_obj.y <= fig.y_range.end)
                        { cross.spans.height.computed_location = cb_obj.sx }
                     else 
                        { cross.spans.height.computed_location = null }'''
        js_leave = 'cross.spans.height.computed_location = null'
    
        cross1 = CrosshairTool()
        cross2 = CrosshairTool()
        fig1.add_tools(cross1)
        fig2.add_tools(cross2)
        args = {'cross': cross2, 'fig': fig1}
        fig1.js_on_event('mousemove', CustomJS(args = args, code = js_move))
        fig1.js_on_event('mouseleave', CustomJS(args = args, code = js_leave))
        args = {'cross': cross1, 'fig': fig2}
        fig2.js_on_event('mousemove', CustomJS(args = args, code = js_move))
        fig2.js_on_event('mouseleave', CustomJS(args = args, code = js_leave))
    
    def to_seconds(date):
        return time.mktime(date.timetuple())
    
    def timeline_figure(title = None, x_range = None, y_range = None):
    
        TOOLS = [CrosshairTool(dimensions = 'height'), PanTool(dimensions = 'width'), HoverTool(tooltips = [("Date", "@t")]), WheelZoomTool(dimensions = 'width'), ResetTool()]
    
        fig = figure(width = 800, height = 250, title = title, x_axis_type = "datetime", x_range = x_range, y_range = y_range, tools = TOOLS)
        fig.outline_line_color = 'white'
        fig.xgrid.grid_line_color = None
        fig.ygrid.grid_line_color = None
        fig.yaxis.minor_tick_line_color = None
    
        year = 2016
        dec = Span(location = to_seconds(datetime(year - 1, 12, 1, 0, 0, 0)))
        jan = Span(location = to_seconds(datetime(year, 1, 1, 0, 0, 0)))
        feb = Span(location = to_seconds(datetime(year, 2, 1, 0, 0, 0)))
        mar = Span(location = to_seconds(datetime(year, 3, 1, 0, 0, 0)))
        apr = Span(location = to_seconds(datetime(year, 4, 1, 0, 0, 0)))
        may = Span(location = to_seconds(datetime(year, 5, 1, 0, 0, 0)))
    
        fig.renderers.extend([dec, jan, feb, mar, apr, may])
    
        return fig
    
    def usage():
        output_file("test_linked_crosshair.html", mode = "cdn")
    
        d_start = datetime(2016, 6, 1)
        d_step = timedelta(days = 1)
    
        t = [d_start + (i * d_step) for i in range(0, 12)]
        s1 = np.random. randint(2, 10, 12)
        s2 = np.random.randint(2, 10, 12)
        source = ColumnDataSource({'t': t, 's1': s1, 's2': s2})
    
        p1 = timeline_figure()
        p1.triangle(x = 't', y = 's1', source = source, size = 10, color = "blue")
        p2 = timeline_figure(x_range = p1.x_range)
        p2.square(x = 't', y = 's2', source = source, size = 10, color = "red")
    
        add_vlinked_crosshairs(p1, p2)
    
        p = gridplot([[p1], [p2]])
        show(p)
    
    if __name__ == "__main__":
        usage()
    

    Result:

提交回复
热议问题