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

我尝试创建一个交互式图表,绘制值 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)


翻过高山走不出你
浏览 37回答 4
4回答

慕沐林林

source.data更改滑块时需要创建一个新的。为此,您还需要一个不可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 valuessource = ColumnDataSource(data={'x': datesX, 'y': valuesY['A']}) source2 = ColumnDataSource(data={'x': datesX, 'y': valuesY['A']})# output to static HTML fileoutput_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 labelsp = 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 resultsshow(layout)

富国沪深

我发现上面的答案不起作用,因为数据的时间戳ref_source与来自散景滑块对象(cb_obj)的解析时间戳不同。例如,ref_source数据中的时间戳在使用 进行解析时会创建以下输出new Date(source.data.["x"]);:01/01/2020 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 toconsole.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 Sourcesconst data = source.data;const ref = ref_source.data;// Creating new Array and appending correctly parsed dateslet new_ref = []ref["x"].forEach(elem => {    elem = Date.parse(new Date(elem).toDateString());    new_ref.push(elem);    console.log(elem);})// Creating Indices with new Arrayconst 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();我希望它对你有一点帮助:)

鸿蒙传说

有趣的问题和讨论。添加以下两行(其中一行直接从文档中提取)允许滑块在不使用 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)

凤凰求蛊

使用 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)
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Python