首页 > 解决方案 > 散景:如何单击并拖动以显示点之间的位移

问题描述

我希望能够在图表上的两点之间单击并拖动,并显示点之间的位置变化并显示连接两点的线。我该如何解决这个问题?

在此处输入图像描述

标签: pythonbokeh

解决方案


您可以使用这样的 JS 回调来实现它(Bokeh v1.3.0):

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, Div, Row
from bokeh.events import *

source = ColumnDataSource({'x': [], 'y': []})

p = figure(plot_width = 900)
lines = [p.line(np.arange(10), np.random.random(10)) for i in range(3)]
lilne = p.line('x', 'y', line_color = 'red', line_dash = 'dashed', source = source)
div = Div(text='')

callback_tap = '''
if (true === Bokeh.drawing) {
    Bokeh.drawing = false
}
else {
    if (!Bokeh.drawing) {
        src.data = {'x':[], 'y':[]}        
        src.change.emit()
    }

    src.data['x'].push(cb_obj.x)
    src.data['y'].push(cb_obj.y)

    Bokeh.drawing = true
    Bokeh.sx_start = cb_obj.sx
    Bokeh.x_start = cb_obj.x
}'''

callback_mousemove = '''
if (Bokeh.drawing) {  
    if (src.data['x'].length > 1) {
        src.data['x'].pop()
        src.data['y'].pop()   
    }

    src.data['x'].push(cb_obj.x)
    src.data['y'].push(cb_obj.y)
    src.change.emit()

    div.text = 'Distance: ' + Math.round(cb_obj.sx - Bokeh.sx_start) + ' px' + ' (' + (Math.round((cb_obj.x - Bokeh.x_start) * 100) / 100) + ' units)'
}'''

p.js_on_event('tap', CustomJS(args = {'src': source}, code = callback_tap))
p.js_on_event('mousemove', CustomJS(args = {'src': source, 'div': div}, code = callback_mousemove))

show(Row(p, div))

第一次单击绘图开始线条,下一次单击结束它。

在此处输入图像描述

或者,如果您更喜欢类似工具提示的 div 外观,您可以使用这个稍微复杂一点的代码,它会动态添加工具提示 div:

import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS
from bokeh.events import *

source = ColumnDataSource({'x': [], 'y': []})

p = figure(plot_width = 900)
lines = [p.line(np.arange(10), np.random.random(10)) for i in range(3)]
line = p.line('x', 'y', line_color = 'red', line_dash = 'dashed', source = source)

callback_tap = '''

if (typeof custom_tooltip == 'undefined') {
        custom_tooltip = document.createElement('div');
        custom_tooltip.setAttribute('id','tooltip_div');
        custom_tooltip.style = 'position: absolute; left: 0px; top: 0px; z-index: 9999; border:1px solid black; padding: 10px; background: white; font-family: arial; font-size: 12px'
        document.body.prepend(custom_tooltip);  
}        

if (true === Bokeh.drawing) {
    Bokeh.drawing = false
}
else {
    if (!Bokeh.drawing) {
        src.data = {'x':[], 'y':[]}        
        src.change.emit()
    }

    src.data['x'].push(cb_obj.x)
    src.data['y'].push(cb_obj.y)

    Bokeh.drawing = true
    Bokeh.sx_start = cb_obj.sx
    Bokeh.x_start = cb_obj.x
    Bokeh.sy_start = cb_obj.sy
    Bokeh.y_start = cb_obj.y  
}'''

callback_mousemove = '''
function print(...args) {
    for (i in args) {
        console.log(args[i])
    }
}

if (Bokeh.drawing) {  
    if (src.data['x'].length > 1) {
        src.data['x'].pop()
        src.data['y'].pop()   
    }

    src.data['x'].push(cb_obj.x)
    src.data['y'].push(cb_obj.y)
    src.change.emit()

    tooltip = document.getElementById('tooltip_div')
    tooltip.style.left = cb_obj.sx + 30 + 'px'
    tooltip.style.top = cb_obj.sy + 10 + 'px'
    tooltip.innerHTML = 'Distance X: ' + Math.round(cb_obj.sx - Bokeh.sx_start) + ' px' + ' (units): ' + (Math.round((cb_obj.x - Bokeh.x_start) * 100) / 100) + ' units' + 
                        '<br />' +
                        'Distance Y: ' + Math.round(cb_obj.sy - Bokeh.sy_start) + ' px' + ' (units): ' + (Math.round((cb_obj.y - Bokeh.y_start) * 100) / 100) + ' units' 

}'''

p.js_on_event('tap', CustomJS(args = {'src': source, }, code = callback_tap))
p.js_on_event('mousemove', CustomJS(args = {'src': source, }, code = callback_mousemove))

show(p)

在此处输入图像描述


推荐阅读