Bokeh 自定义 JS 回调日期范围滑块

Bokeh custom JS callback date range slider

我尝试创建一个交互式图表,绘制值 Y 与日期 X。到目前为止一切顺利。现在我想通过 DateRangeSlider 调整我的 x 轴的限制 xmin 和 xmax 但我不明白 js 回调函数(我想在最后有一个独立的 html 文件)并且因为我不我什至不知道如何从函数内部打印值并且不会产生任何错误,我现在不知道该怎么做。

这里是一个 运行 代码示例:

import numpy as np
import pandas as pd
from datetime import datetime
from bokeh.models import ColumnDataSource, DatetimeTickFormatter, HoverTool
from bokeh.models.widgets import DateRangeSlider
from bokeh.layouts import layout, column
from bokeh.models.callbacks import CustomJS
from bokeh.plotting import figure, output_file, show, save

datesX = pd.date_range(start='1/1/2018', periods=100)
valuesY = pd.DataFrame(np.random.randint(0,25,size=(100, 1)), columns=list('A'))

source = ColumnDataSource(data={'x': datesX, 'y': valuesY['A']}) 

# output to static HTML file
output_file('file.html')

hover = HoverTool(tooltips=[('Timestamp', '@x{%Y-%m-%d %H:%M:%S}'), ('Value', '@y')],
                           formatters={'x': 'datetime'},)
    
date_range_slider = DateRangeSlider(title="Zeitrahmen", start=datesX[0], end=datesX[99], \
                                        value=(datesX[0], datesX[99]), step=1, width=300)

# create a new plot with a title and axis labels
p = figure(title='file1', x_axis_label='Date', y_axis_label='yValue',  x_axis_type='datetime', 
               tools="pan, wheel_zoom, box_zoom, reset", plot_width=300, plot_height=200)

# add a line renderer with legend and line thickness
    
p.line(x='x', y='y', source=source, line_width=2)
p.add_tools(hover)
       
callback = CustomJS(args=dict(source=source), code="""

    ##### what to do???

    source.change.emit();
    """)
    
date_range_slider.js_on_change('value', callback)
layout = column(p, date_range_slider)

# show the results
show(layout)

我试图调整和改编 Whosebug 和散景演示中的类似示例,但我没能生成 运行 代码。希望一切都清楚,你可以提供帮助。

您需要在更改滑块时创建一个新的 source.data。为此,您还需要一个您 更改的“back-up”source,作为要包含哪些数据的参考。将两者作为参数传递给回调函数使它们可用于 Javascript 代码。

datesX = pd.date_range(start='1/1/2018', periods=100)
valuesY = pd.DataFrame(np.random.randint(0,25,size=(100, 1)), columns=list('A'))

# keep track of the unchanged, y-axis values
source = ColumnDataSource(data={'x': datesX, 'y': valuesY['A']}) 
source2 = ColumnDataSource(data={'x': datesX, 'y': valuesY['A']})

# output to static HTML file
output_file('file.html')

hover = HoverTool(
    tooltips=[('Timestamp', '@x{%Y-%m-%d %H:%M:%S}'), ('Value', '@y')],
    formatters={'x': 'datetime'},)
    
date_range_slider = DateRangeSlider(
    title="Zeitrahmen", start=datesX[0], end=datesX[99],
    value=(datesX[0], datesX[99]), step=1, width=300)

# create a new plot with a title and axis labels
p = figure(
    title='file1', x_axis_label='Date', y_axis_label='yValue',
    y_range=(0, 30), x_axis_type='datetime',
    tools="pan, wheel_zoom, box_zoom, reset",
    plot_width=600, plot_height=200)

# add a line renderer with legend and line thickness
    
p.line(x='x', y='y', source=source, line_width=2)
p.add_tools(hover)

callback = CustomJS(args=dict(source=source, ref_source=source2), code="""
    
    // print out array of date from, date to
    console.log(cb_obj.value); 
    
    // dates returned from slider are not at round intervals and include time;
    const date_from = Date.parse(new Date(cb_obj.value[0]).toDateString());
    const date_to = Date.parse(new Date(cb_obj.value[1]).toDateString());
    
    const data = source.data;
    const ref = ref_source.data;
    
    const from_pos = ref["x"].indexOf(date_from);
    // add + 1 if you want inclusive end date
    const to_pos = ref["x"].indexOf(date_to);
        
    // re-create the source data from "reference"
    data["y"] = ref["y"].slice(from_pos, to_pos);
    data["x"] = ref["x"].slice(from_pos, to_pos);
    
    source.change.emit();
    """)
    
date_range_slider.js_on_change('value', callback)
layout = column(p, date_range_slider)

# show the results
show(layout)

我发现上面的答案不起作用,因为 ref_source 数据的时间戳与来自散景滑块对象 (cb_obj) 的解析时间戳不同。

例如,来自 ref_source 数据的时间戳在使用 new Date(source.data.["x"]); 解析时创建以下输出:

2020 年 1 月 1 日 02:00:00

来自散景滑块对象 cb_obj 的时间戳始终具有 00:00:00 的时间。因此使用 const from_pos = ref["date"].indexOf(date_from);.

时找不到时间戳

为了正确解析 ref_source 中的日期,我创建了一个新数组 new_ref 并将正确解析的日期添加到该数组中。但是,我必须在这里强调,我不是 JavaScript 专家,我很确定在这里可以更有效地编写代码。

这是我的工作示例:

// print out array of date from, date to
console.log(cb_obj.value); 

// dates returned from slider are not at round intervals and include time;
const date_from = Date.parse(new Date(cb_obj.value[0]).toDateString());
const date_to = Date.parse(new Date(cb_obj.value[1]).toDateString());
console.log(date_from, date_to)

// Creating the Data Sources
const data = source.data;
const ref = ref_source.data;

// Creating new Array and appending correctly parsed dates
let new_ref = []
ref["x"].forEach(elem => {
    elem = Date.parse(new Date(elem).toDateString());
    new_ref.push(elem);
    console.log(elem);
})

// Creating Indices with new Array
const from_pos = new_ref.indexOf(date_from);
const to_pos = new_ref.indexOf(date_to) + 1;


// re-create the source data from "reference"
data["y"] = ref["y"].slice(from_pos, to_pos);
data["x"] = ref["x"].slice(from_pos, to_pos);

source.change.emit();

希望对您有所帮助:)

我取得了与@Jan Burger 类似的成功,但使用 CustonJS 直接更改绘图 x_range 而不是过滤数据源。

callback = CustomJS(args=dict(p=p), code="""
    p.x_range.start = cb_obj.value[0]
    p.x_range.end = cb_obj.value[1]
    p.x_range.change.emit()
    """)

date_range_slider.js_on_change('value_throttled', callback)

有趣的问题和讨论。添加以下两行(其中一行直接从文档中提取)允许滑块在不使用 CustomJS 和 js_on_change 函数的情况下工作 - 使用 js_link 函数代替:

date_range_slider.js_link('value', p.x_range, 'start', attr_selector=0)
date_range_slider.js_link('value', p.x_range, 'end', attr_selector=1)