我有很多样地,每个样地有很多样本。我需要在所有图中缩放和平移。此外,所有范围都必须实时同步。如果我共享范围,则可以在一些地块上很好地工作,但是在许多地块上,它会变得迟钝。然后,为解决此问题,我想在平移或缩放操作完成时触发同步。
有一个PanEnd
事件,该事件在用户停止平移时触发。但是我无法对轮式缩放做同样的事情,因为没有MouseWheelEnd
事件,只有MouseWheel
事件,所以我无法检测到用户何时停止。最后,我添加了一个定期回调以不时更新范围。但是我不喜欢这种解决方案。
我还尝试了LODStart
和LODEnd事件(与下采样有关),我不得不强制执行lod_threshold=1
。但是有时LODEnd
不会被触发,只有LODStart
总是被触发。
from bokeh.plotting import figure
from bokeh.models.sources import ColumnDataSource, CDSView
from bokeh.models.filters import IndexFilter
from bokeh.models.markers import Scatter, Circle
from bokeh.models.tools import LassoSelectTool
from bokeh.models.ranges import DataRange1d
from bokeh.plotting import curdoc, gridplot
from bokeh.events import MouseWheel, PanEnd
import numpy as np
N = 3500
x = np.random.random(size=N) * 200
y = np.random.random(size=N) * 200
source = ColumnDataSource(data=dict(x=x, y=y))
plots = []
x_ranges = []
y_ranges = []
p_last_modified = -1
def render_plot(i, p_last_modified):
range_padding = 0.25
x_range = DataRange1d(
range_padding=range_padding,
renderers=[]
)
y_range = DataRange1d(
range_padding=range_padding,
renderers=[]
)
plot = figure(
width=500,
height=500,
x_range=x_range,
y_range=y_range,
toolbar_location='left',
tools='pan,wheel_zoom,tap,lasso_select',
output_backend='webgl',
)
c = plot.scatter(
x='x',
y='y',
size=3,
fill_color='blue',
line_color=None,
line_alpha=1.0,
source=source,
nonselection_fill_color='blue',
nonselection_line_color=None,
nonselection_fill_alpha=1.0,
)
c.selection_glyph = Scatter(
fill_color='yellow',
line_color='red',
line_alpha=1.0,
)
def mouse_wheel_event(event):
print('>> MOUSE WHEEL EVENT: PLOT NUMBER: {}'.format(i))
global p_last_modified
p_last_modified = i
plot.on_event(MouseWheel, mouse_wheel_event)
def pan_end_event(event):
print('>> PAN END: {}'.format(i))
for p in range(len(plots)):
if p != i:
plots[p].x_range.end = plots[i].x_range.end
plots[p].x_range.start = plots[i].x_range.start
plots[p].y_range.end = plots[i].y_range.end
plots[p].y_range.start = plots[i].y_range.start
plot.on_event(PanEnd, pan_end_event)
plots.append(plot)
x_ranges.append(x_range)
y_ranges.append(y_range)
for i in range(12):
render_plot(i, p_last_modified)
gp = gridplot(
children=plots,
ncols=4,
plot_width=300,
plot_height=300,
toolbar_location='left',
)
def callback():
global p_last_modified
print('-- CALLBACK: last_modified: {}'.format(p_last_modified))
if p_last_modified != -1:
for p in range(len(plots)):
if p != p_last_modified:
plots[p].x_range.end = plots[p_last_modified].x_range.end
plots[p].x_range.start = plots[p_last_modified].x_range.start
plots[p].y_range.end = plots[p_last_modified].y_range.end
plots[p].y_range.start = plots[p_last_modified].y_range.start
p_last_modified = -1
curdoc().add_periodic_callback(callback, 3000)
curdoc().add_root(gp)
还有其他建议吗?
答案 0 :(得分:1)
虽然我不太喜欢它,但我还是可以使用。 它涉及一些JS和3个“虚拟”小部件,我希望有一种更简单的方法,但是无论如何这是一种方法。
dum_txt_timer
是一个文本输入,将用作计时器,其值以秒为单位,并以所需的时间步长进行更新。当值达到所需阈值时,将触发范围更新。当该值低于阈值时,它将不执行任何操作
dum_button
是一个有两个功能的按钮,第一次单击将在dum_txt_timer
中启动计时器,第二次单击将停止计时器。
dum_txt_trigger
是另一个文本输入,用于单击dum_button
并启动/停止计时器。
mouse_wheel_event
函数在每次鼠标滚轮迭代时触发。将鼠标存储在其中的图的值存储在mod_source
中,dum_txt_timer
是传递到dum_txt_timer
回调的数据源。
它检查dum_txt_trigger
的值是否为0,是否更新dum_txt_timer
中的值,单击该按钮并启动计时器,并更新dum_txt_timer,以便其他转轮事件在更新之前不起作用。如果它不同于0,则不执行任何操作。
dum_txt_trigger
的回调需要mod_source
,dum_txt_timer
数据源,该数据源存储图ID和所有图范围。
直到超时功能结束时dum_txt_trigger
的值被更新,回调才起作用。否则,它首先更新dum_button
的值,第二次单击from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, CDSView, IndexFilter, Scatter, Circle, LassoSelectTool, DataRange1d, CustomJS, TextInput, Button
from bokeh.events import MouseWheel, PanEnd
from bokeh.layouts import widgetbox, gridplot
import numpy as np
N = 3500
x = np.random.random(size=N) * 200
y = np.random.random(size=N) * 200
source = ColumnDataSource(data=dict(x=x, y=y))
dum_txt_timer = TextInput(value='0',visible=False)
# javascript code for a dummy (invisible) button, it starts and stops a timer that will be written in dum_txt_timer
dum_button_code = """
if (cb_obj.button_type.includes('success')){
// start a timer in dum_txt by updating its value with a fixed timestep
var start = new Date();
var intervalID = setInterval(function(){var current = new Date(); var diff=((current-start)/1000.0).toFixed(4); dum_txt_timer.value=diff.toString(); }, 500)
cb_obj.button_type = 'warning';
} else {
// stop the timer and set the dum_txt_timer value back to 0
var noIntervals = setInterval(function(){});
for (var i = 0; i<noIntervals; i++) { window.clearInterval(i);}
dum_txt_timer.value='0';
cb_obj.button_type = 'success';
}
"""
dum_button = Button(label='dummy_button',button_type='success',visible=False) # the dummy button itself
dum_button.callback = CustomJS(args={'dum_txt_timer':dum_txt_timer},code=dum_button_code) # the callback of the button
# dummy textinput to click the dummy button
dum_txt_trigger = TextInput(value='0',visible=False)
dum_txt_trigger_code = """
// click the dummy button
var button_list = document.getElementsByTagName('button');
for(var i=0;i<button_list.length;i++){
if(button_list[i].textContent==="dummy_button"){button_list[i].click()}
}
"""
dum_txt_trigger.js_on_change('value',CustomJS(code=dum_txt_trigger_code))
dum_box = widgetbox(dum_txt_timer,dum_txt_trigger,dum_button,visible=False)
plots = []
x_ranges = []
y_ranges = []
mod_source = ColumnDataSource(data={'x':[]})
reference = None
def render_plot(i):
range_padding = 0.25
x_range = DataRange1d(range_padding=range_padding,renderers=[])
y_range = DataRange1d(range_padding=range_padding,renderers=[])
plot = figure(width=500,height=500,x_range=x_range,y_range=y_range,toolbar_location='left',tools='pan,wheel_zoom,tap,lasso_select',output_backend='webgl',)
c = plot.scatter(x='x',y='y',size=3,fill_color='blue',line_color=None,line_alpha=1.0,source=source,nonselection_fill_color='blue',nonselection_line_color=None,nonselection_fill_alpha=1.0,)
c.selection_glyph = Scatter(fill_color='yellow',line_color='red',line_alpha=1.0,)
def mouse_wheel_event(event):
if dum_txt_timer.value != '0':
return
# if the timer value is 0, start the timer
dum_txt_trigger.value = str(int(dum_txt_trigger.value)+1)
dum_txt_timer.value = '0.0001' # immediatly update the timer value for the check on 0 in the python callback to work immediatly
mod_source.data.update({'x':[i]})
plot.on_event(MouseWheel, mouse_wheel_event)
def pan_end_event(event):
print('>> PAN END: {}'.format(i))
for p in range(len(plots)):
if p != i:
plots[p].x_range.end = plots[i].x_range.end
plots[p].x_range.start = plots[i].x_range.start
plots[p].y_range.end = plots[i].y_range.end
plots[p].y_range.start = plots[i].y_range.start
plot.on_event(PanEnd, pan_end_event)
plots.append(plot)
x_ranges.append(x_range)
y_ranges.append(y_range)
for i in range(12):
render_plot(i)
dum_txt_timer_args = {'dum_txt_trigger':dum_txt_trigger,'mod_source':mod_source}
dum_txt_timer_args.update( {'xrange{}'.format(i):plot.x_range for i,plot in enumerate(plots)} )
dum_txt_timer_args.update( {'yrange{}'.format(i):plot.y_range for i,plot in enumerate(plots)} )
set_arg_list = "var xrange_list = [{}];".format(','.join(['xrange{}'.format(i) for i in range(len(plots))]))
set_arg_list += "var yrange_list = [{}];".format(','.join(['yrange{}'.format(i) for i in range(len(plots))]))
# code that triggers when the dum_txt_timer value is changed, so every 100 ms, but only clicks dum_button when the value is greater than 2 (seconds)
dum_txt_timer_code = set_arg_list + """
var timer = Number(cb_obj.value);
var trigger_val = Number(dum_txt_trigger.value);
// only do something when the value is greater than 2 (seconds)
if (timer>0.0001) {
trigger_val = trigger_val + 1;
dum_txt_trigger.value = trigger_val.toString(); // click button again to stop the timer
// update the plot ranges
var p_last_modified = mod_source.data['x'][0];
var nplots = xrange_list.length;
for (var i=0; i<nplots; i++){
if (i!=p_last_modified){
xrange_list[i].start = xrange_list[p_last_modified].start;
xrange_list[i].end = xrange_list[p_last_modified].end;
yrange_list[i].start = yrange_list[p_last_modified].start;
yrange_list[i].end = yrange_list[p_last_modified].end;
}
}
}
"""
dum_txt_timer.js_on_change('value',CustomJS(args=dum_txt_timer_args,code=dum_txt_timer_code))
gp = gridplot(children=plots,ncols=4,plot_width=300,plot_height=300,toolbar_location='left',)
grid = gridplot([[gp],[dum_box]],toolbar_location=None)
curdoc().add_root(grid)
并停止计时器(将其重置为0。然后更新所有图的范围。
在此示例中,通过按钮回调中的超时功能设置更新之前的时间。
dum_txt_trigger
一件好事是,相同的虚拟小部件可用于设置来自不同事件的范围更新的延迟,事件回调只需要像mouse_wheel_event
中一样更新subjects