簡體   English   中英

如何將散景中的 CrossHairTool 鏈接到多個圖?

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

在一個圖中移動十字准線(尺寸=寬度)時,我想在其他圖中看到相同的位置。 我的圖共享相同的 x 軸。

這是繪圖設置和示例:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from bokeh.plotting import figure, ColumnDataSource, output_file, save
from bokeh.models import Span, CrosshairTool, HoverTool, ResetTool, PanTool, WheelZoomTool
from datetime import datetime



def timeline_figure(title=None, x_range=None, y_range=None):

    # TODO: align x-axis

    # TOOLS = "resize,crosshair,pan,wheel_zoom,box_zoom,reset,box_select,lasso_select,save"
    # TOOLS = "resize,crosshair,xpan,xwheel_zoom,box_zoom,reset,save"
    TOOLS = [CrosshairTool(dimensions=['height']),
             PanTool(dimensions=['width']),
             HoverTool(tooltips=[("Dato", "@Date")]),
             WheelZoomTool(dimensions=['width']),
             ResetTool()]

    # Setting up the bokeh figure
    fig = figure(width=800, height=250, title=title, x_axis_type="datetime",
                 x_range=x_range, y_range=y_range, tools=TOOLS)

    # make the outline "invisible"
    fig.outline_line_color = 'white'

    # change just some things about the x-grid
    fig.xgrid.grid_line_color = None
    fig.ygrid.grid_line_color = None
    # change just some things about the y-grid

    fig.yaxis.minor_tick_line_color = None

    year = 2016
    dec = Span(location=datetime(year-1, 12, 1, 0, 0, 0).timestamp() * 1000,
                    dimension='height', line_color='grey', line_dash='dashed', line_width=1)
    jan = Span(location=datetime(year, 1, 1, 0, 0, 0).timestamp() * 1000,
               dimension='height', line_color='grey', line_dash='dashed', line_width=1)
    feb = Span(location=datetime(year, 2, 1, 0, 0, 0).timestamp() * 1000,
               dimension='height', line_color='grey', line_dash='dashed', line_width=1)
    mar = Span(location=datetime(year, 3, 1, 0, 0, 0).timestamp() * 1000,
               dimension='height', line_color='grey', line_dash='dashed', line_width=1)
    apr = Span(location=datetime(year, 4, 1, 0, 0, 0).timestamp() * 1000,
               dimension='height', line_color='grey', line_dash='dashed', line_width=1)
    may = Span(location=datetime(year, 5, 1, 0, 0, 0).timestamp() * 1000,
               dimension='height', line_color='grey', line_dash='dashed', line_width=1)

    fig.renderers.extend([dec, jan, feb, mar, apr, may])

    return fig


def usage():
    import numpy as np
    from datetime import timedelta
    from bokeh.io import gridplot

    output_file("test.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()
    p2.square(x='t', y='s2', source=source, size=10, color="red")

    p = gridplot([[p1], [p2]])
    save(p)

if __name__ == "__main__":
    usage()

感謝任何建議。

卡斯滕

Bokeh 目前沒有對此的內置支持。 我想出了如何使用 javascript 回調來做到這一點。 以下函數適用於 Bokeh 0.13 上的兩個垂直對齊的圖:

from bokeh.models import CustomJS, CrosshairTool

def add_vlinked_crosshairs(fig1, fig2):
    cross1 = CrosshairTool()
    cross2 = CrosshairTool()
    fig1.add_tools(cross1)
    fig2.add_tools(cross2)
    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'
    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))

這個想法是為每個繪圖添加一個鼠標移動回調,觸發另一個繪圖上十字准線的垂直部分。 這是通過使用鼠標回調 ( cb_obj.sx ) 提供的屏幕位置更新十字准線的spans.height.computed_location成員來完成的。

鼠標移動事件在繪圖的整個區域上觸發,包括軸、邊界等。添加檢查以確保鼠標位於數據空間內( cb_obj.xcb_obj.y是軸坐標),如果不是。 還添加了一個mouseleave事件,因為在情節外的快速移動可能不會在邊界區域觸發事件。

如果圖垂直對齊,則此方法有效。 對於水平對齊(根據 OP)只需更改cross.spans.height.computed_location -> cross.spans.width.computed_locationcb_obj.sx -> cb_obj.sy

這僅在繪圖大小相同時才有效,如果不是,則需要進一步檢查。

從 bokeh v2.2.1 開始,解決方案得到簡化。 我不確定在以前的版本中是否已經可以這樣了。 這是在散景 v2.2.1 中的網格圖中的 9 個圖之間共享兩個維度的十字准線的示例:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.layouts import gridplot
from bokeh.models import CrosshairTool

plots = [figure() for i in range(6)]
[plot.line(np.arange(10), np.random.random(10)) for plot in plots]
crosshair = CrosshairTool(dimensions="both")
for plot in plots:
    plot.add_tools(crosshair)

show(gridplot(children=[plot for plot in plots], ncols=3))

對於任意數量的圖和兩個 CrossHair 尺寸的更緊湊的示例(針對 Bokeh v1.0.4 更新):

from bokeh.models import CustomJS, CrosshairTool
from bokeh.plotting import figure, show, curdoc
from bokeh.layouts import gridplot
import numpy as np

def addLinkedCrosshairs(plots):
    js_move = '''   start = fig.x_range.start, end = fig.x_range.end
                    if(cb_obj.x>=start && cb_obj.x<=end && cb_obj.y>=start && cb_obj.y<=end)
                        { cross.spans.height.computed_location=cb_obj.sx }
                    else { cross.spans.height.computed_location = null }
                    if(cb_obj.y>=start && cb_obj.y<=end && cb_obj.x>=start && cb_obj.x<=end)
                        { cross.spans.width.computed_location=cb_obj.sy  }
                    else { cross.spans.width.computed_location=null }'''
    js_leave = '''cross.spans.height.computed_location=null; cross.spans.width.computed_location=null'''

    figures = plots[:]
    for plot in plots:
        crosshair = CrosshairTool(dimensions = 'both')
        plot.add_tools(crosshair)
        for figure in figures:
            if figure != plot:
                args = {'cross': crosshair, 'fig': figure}
                figure.js_on_event('mousemove', CustomJS(args = args, code = js_move))
                figure.js_on_event('mouseleave', CustomJS(args = args, code = js_leave))

plots = [figure(plot_width = 200, plot_height = 200, tools = '') for i in range(9)]
[plot.line(np.arange(10), np.random.random(10)) for plot in plots]
addLinkedCrosshairs(plots)
show(gridplot(children = [plot for plot in plots], ncols = 3))

要減少到只有一維(垂直或水平),請刪除回調的相應“if / else”部分

結果:

在此處輸入圖片說明

Marged 解決方案(針對 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()

結果:

在此處輸入圖片說明

這個答案適用於像我一樣喜歡 Graeme 解決方案的人,但需要像我一樣將其應用於兩個以上的數字:

from bokeh.models import CustomJS, CrosshairTool

def add_vlinked_crosshairs(figs):
    js_leave = ''
    js_move = 'if(cb_obj.x >= fig.x_range.start && cb_obj.x <= fig.x_range.end &&\n'
    js_move += 'cb_obj.y >= fig.y_range.start && cb_obj.y <= fig.y_range.end){\n'
    for i in range(len(figs)-1):
        js_move += '\t\t\tother%d.spans.height.computed_location = cb_obj.sx\n' % i
    js_move += '}else{\n'
    for i in range(len(figs)-1):
        js_move += '\t\t\tother%d.spans.height.computed_location = null\n' % i
        js_leave += '\t\t\tother%d.spans.height.computed_location = null\n' % i
    js_move += '}'
    crosses = [CrosshairTool() for fig in figs]
    for i, fig in enumerate(figs):
        fig.add_tools(crosses[i])
        args = {'fig': fig}
        k = 0
        for j in range(len(figs)):
            if i != j:
                args['other%d'%k] = crosses[j]
                k += 1
        fig.js_on_event('mousemove', CustomJS(args=args, code=js_move))
        fig.js_on_event('mouseleave', CustomJS(args=args, code=js_leave))

bchusdt 圖上的十字准線

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM