散景:当轴更改时,带有 Select 小部件的图不会更新散景服务器中的图

Bokeh: Plots with Select widgets isn't updating the plots in bokeh server when the axes are changed

编辑:添加了一个最小的工作示例代码来重现该问题。

df 有 trip、driverName、carRegNo、totalDistanceTravelled、totalTimeTaken 等列。我想在更改 x and/or y 轴时绘制 'Drivers vs Distance Travelled'、'Drivers vs Time taken'。 'Car Reg No.' 与距离和所用时间的对比也是如此。

import pandas as pd

from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Select
from bokeh.io import curdoc

trip_data = [{"trip": '1', "totalDistanceTravelled": "4.66", "totalTimeTaken": "765083","time": "1504189219256", "carRegNo": "ABC123", "driverName": "Anne"},
             {"trip": '2', "totalDistanceTravelled": "14.63", "totalTimeTaken": "1282369","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '3', "totalDistanceTravelled": "3.66", "totalTimeTaken": "602713","time": "1504189219256", "carRegNo": "XYZ890", "driverName": "Ron"},
             {"trip": '4', "totalDistanceTravelled": "7.11", "totalTimeTaken": "2234282","time": "1504189219256", "carRegNo": "ABC123", "driverName": "Anne"},
             {"trip": '5', "totalDistanceTravelled": "14.14", "totalTimeTaken": "1282369","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '6', "totalDistanceTravelled": "4.33", "totalTimeTaken": "748446","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '7', "totalDistanceTravelled": "10.66", "totalTimeTaken": "960362","time": "1504189219256", "carRegNo": "XYZ890", "driverName": "Ron"}]

df = pd.DataFrame(trip_data)
drivers = df['driverName'].str.strip()
vehicles = df['carRegNo'].str.strip()
time_stamp = df['time'].astype(float)
total_distance_travelled = df['totalDistanceTravelled'].astype(float)
df['totalTimeTaken'] = df['totalTimeTaken'].astype(float)
df['totalTimeTaken'] /= 1000 * 3600

# Create Input controls
x_axis = Select(title="X Axis", options=sorted(["Drivers", "Vehicle Reg. Number"]), value="Drivers")
y_axis = Select(title="Y Axis", options=sorted(["Distance Travelled (kms)", "Time Taken (hours)"]), value="Time Taken (hours)")
source = ColumnDataSource(data=dict(x=[], y=[]))


def update():
    col_key_values = {
        "Time Taken (hours)": 'totalTimeTaken',
        "Distance Travelled (kms)": 'totalDistanceTravelled'
    }

    x_map = {
        "Drivers": drivers,
        "Vehicle Reg. Number": vehicles
    }

    x_name = x_map[x_axis.value]
    x_y_values = {}
    for x in x_name.unique():
        x_y_values[x] = round(df.loc[x_name == x, col_key_values[y_axis.value]].astype(float).sum(), 2)

    source.data = dict(
        x=x_y_values.keys(),
        y=x_y_values.values()
    )
    print source.data

update()  # initial load of the data
p = figure(x_range=source.data['x'], plot_height=600, plot_width=700, title="", toolbar_location=None)
p.xaxis.axis_label = x_axis.value
p.yaxis.axis_label = y_axis.value
p.line(x="x", y="y", source=source)
controls = [x_axis, y_axis]
for control in controls:
    print 'control: ', control.value
    control.on_change('value', lambda attr, old, new: update())

sizing_mode = 'fixed'
inputs = widgetbox(*controls, sizing_mode=sizing_mode)

l = layout(children=[[inputs, p]],sizing_mode='fixed')
curdoc().add_root(l)
curdoc().title = "Travel Data"

此代码使用默认设置渲染绘图,即,Drivers vs Time Taken,但在将 x 轴更改为 "Vehicle Reg. Number" 后,绘图不会更新。我错过了什么吗?

我没有注意到您正在为 X 轴使用分类范围。分类范围由列出的因素定义,并且仅由这些因素定义。因此,如果您想更新数据以使用来自 不同 一组因素的坐标,那么您还需要更新范围。否则,上面代码的情况是您试图将数据值绘制到不存在的 x 坐标,就范围和绘图而言。

这是您的脚本的修改版本,我相信它的行为符合您的预期(请注意,我使用 py3,因此它也已为此更新):

import pandas as pd

from bokeh.plotting import figure
from bokeh.layouts import layout, widgetbox
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import Select
from bokeh.io import curdoc

trip_data = [{"trip": '1', "totalDistanceTravelled": "4.66", "totalTimeTaken": "765083","time": "1504189219256", "carRegNo": "ABC123", "driverName": "Anne"},
             {"trip": '2', "totalDistanceTravelled": "14.63", "totalTimeTaken": "1282369","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '3', "totalDistanceTravelled": "3.66", "totalTimeTaken": "602713","time": "1504189219256", "carRegNo": "XYZ890", "driverName": "Ron"},
             {"trip": '4', "totalDistanceTravelled": "7.11", "totalTimeTaken": "2234282","time": "1504189219256", "carRegNo": "ABC123", "driverName": "Anne"},
             {"trip": '5', "totalDistanceTravelled": "14.14", "totalTimeTaken": "1282369","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '6', "totalDistanceTravelled": "4.33", "totalTimeTaken": "748446","time": "1504189219256", "carRegNo": "DEF345", "driverName": "Harry"},
             {"trip": '7', "totalDistanceTravelled": "10.66", "totalTimeTaken": "960362","time": "1504189219256", "carRegNo": "XYZ890", "driverName": "Ron"}]

df = pd.DataFrame(trip_data)
drivers = df['driverName'].str.strip()
vehicles = df['carRegNo'].str.strip()
time_stamp = df['time'].astype(float)
total_distance_travelled = df['totalDistanceTravelled'].astype(float)
df['totalTimeTaken'] = df['totalTimeTaken'].astype(float)
df['totalTimeTaken'] /= 1000 * 3600

# Create Input controls
x_axis = Select(title="X Axis", options=sorted(["Drivers", "Vehicle Reg. Number"]), value="Drivers")
y_axis = Select(title="Y Axis", options=sorted(["Distance Travelled (kms)", "Time Taken (hours)"]), value="Time Taken (hours)")
source = ColumnDataSource(data=dict(x=[], y=[]))

p = figure(x_range=source.data['x'], plot_height=600, plot_width=700, title="", toolbar_location=None)

def update():
    col_key_values = {
        "Time Taken (hours)": 'totalTimeTaken',
        "Distance Travelled (kms)": 'totalDistanceTravelled'
    }

    x_map = {
        "Drivers": drivers,
        "Vehicle Reg. Number": vehicles
    }

    x_name = x_map[x_axis.value]
    x_y_values = {}
    for x in x_name.unique():
        x_y_values[x] = round(df.loc[x_name == x, col_key_values[y_axis.value]].astype(float).sum(), 2)

    p.x_range.factors = list(x_y_values.keys())
    source.data = dict(
        x=list(x_y_values.keys()),
        y=list(x_y_values.values())
    )
    print(source.data)
    print(p.x_range.factors)

update()  # initial load of the data
p.xaxis.axis_label = x_axis.value
p.yaxis.axis_label = y_axis.value
p.line(x="x", y="y", source=source)
controls = [x_axis, y_axis]
for control in controls:
    print('control: ', control.value)
    control.on_change('value', lambda attr, old, new: update())

sizing_mode = 'fixed'
inputs = widgetbox(*controls, sizing_mode=sizing_mode)

l = layout(children=[[inputs, p]],sizing_mode='fixed')
curdoc().add_root(l)
curdoc().title = "Travel Data"

这是添加的重要行:

p.x_range.factors = list(x_y_values.keys())

这告诉 x 轴范围新的有效因子名称是什么,因此当您更改为使用这些新因子的数据时,它知道它们应该去哪里。我应该补充一点,我认为 Bokeh 对此处的更新顺序有点敏感:首先更新因子有效,但首先更新数据似乎无效。