Bokeh:在 CustomJS 回调后,major_label_overrides 没有更新

问题描述 投票:0回答:1

我编写了下面的代码来制作一个图,当用户对数据应用一些过滤器时可以更新该图。从下拉列表中选择一个值后,回调会按预期更新所有内容,但 yaxis.major_label_overrides 除外(它将名称(字符串)写在 y 轴滚动条(整数)顶部。

当我调查控制台时,我注意到 y_axis.major_label_overrides 中存在以下异常: Exception in browser console 以下是完整的异常: Exception: TypeError: 'caller', 'callee', and 'arguments' 属性可能无法在严格模式函数上访问,或者无法在 Function.invokeGetter 调用它们的参数对象上访问 (< anonymous > :3:28 ).

经过一些研究,我不确定如何在这种特定情况下解决此异常(以及是否是问题的原因)。

应用过滤器之前的样子: Plot before filter is applied

应用过滤器

之后的样子: Plot after filter is applied

控制台中没有出现任何错误,但应用过滤器后的

预期输出是y轴,如下所示:

    “Joe_updated”而不是“Bob”,
  • “Guy_updated”而不是“Joe”。
我正在使用

Google Chrome 打开输出文件。 Bokeh 版本是 3.2.2,Python 是3.11.4

下面是我的代码:

### Packages ### from math import pi import bokeh.events as bev import bokeh.layouts as bla import bokeh.models as bmo import numpy as np import pandas as pd from bokeh.plotting import figure, output_file, show from bokeh.transform import factor_cmap ### Data ### data = [ ['medium', 'F', 'F', 108, 'Bob', 767.02, 'medium'], ['bad_medium_bad', 'F', 'DB_D_F', 202, 'Joe', 542.9, 'bad'], ['bad_medium_bad', 'DB', 'DB_D_F', 202, 'Joe', 5.8, 'bad'], ['bad_medium_bad', 'D', 'DB_D_F', 202, 'Joey', 0.0, 'medium'], ['medium', 'F', 'F', 810, 'Francis', 679.7, 'medium'], ['medium', 'F', 'F', 355, 'James', 354.6, 'medium'], ['medium', 'F', 'F', 288, 'Suze', 23.1, 'medium'], ['medium', 'F', 'F', 281, 'Anna', 36.5, 'medium'], ['medium_medium_medium', 'F', 'DB_D_F', 249, 'Guy', 673.1, 'medium'], ['medium_medium_medium', 'DB', 'DB_D_F', 249, 'Guy', 13.2, 'medium'], ['medium_medium_medium', 'D', 'DB_D_F', 249, 'Guy B', 99.1, 'medium'] ] data = pd.DataFrame(data, columns=['overall_status', 'group', 'overall_group', 'id', 'name', 'amount', 'status']) data.insert(loc=0, column="y_ticker", value=0, allow_duplicates=True) # replace id by a shorter number that can be used to place data on y_axis in a neat way. for i in list(data.index.values): if i == 0: data.loc[i, "y_ticker"] = 0 else: if data.loc[i-1, "id"] == data.loc[i, "id"]: data.loc[i, "y_ticker"] = data.loc[i-1, "y_ticker"] else: data.loc[i, "y_ticker"] = data.loc[i-1, "y_ticker"] + 1 ### Setup ### output_file('test_dashboard.html') source = bmo.ColumnDataSource(data.to_dict(orient='list')) filtered_data = bmo.ColumnDataSource(data.to_dict(orient='list')) ### Plot ### cmap = { 'good': '#006B3D', 'medium': '#FF980E', 'bad': '#D3212C', } TOOLS = "hover,save,ypan,reset,wheel_zoom" p = figure(title='Dashboard for testing', x_range=bmo.FactorRange(), x_axis_label = "Group", y_range=bmo.Range1d(), y_axis_label = "Name", x_axis_location="above", width=600, height=500, tools=TOOLS, toolbar_location='below', tooltips=[('Name', '@name'), ('Amount', '@amount')] ) # create rectangles p.rect(x="group", y="y_ticker", width=1, height=1, source=filtered_data, legend_field='status', color=factor_cmap("status", palette=list(cmap.values()), factors=list(cmap.keys())), line_color=None) # create initial label_overrides p.yaxis.major_label_overrides = dict(zip(filtered_data.data['y_ticker'], filtered_data.data['name'])) p.yaxis.ticker = list(range(0, len(p.yaxis.major_label_overrides))) # create inital ranges p.x_range.factors = list(np.unique(filtered_data.data['group'])) p.y_range.start = 0 # adapt y_range.end to the length of p.yaxis.major_label_overrides # (I have thousands of rows in the real dataset) max_y_range = 20 if(len(p.yaxis.major_label_overrides) < 20): max_y_range = len(p.yaxis.major_label_overrides) p.y_range.end = max_y_range p.xgrid.grid_line_color = '#FFFFFF' p.axis.axis_line_color = None p.axis.major_tick_line_color = None p.axis.major_label_text_font_size = "15px" p.axis.major_label_standoff = 0 p.xaxis.major_label_orientation = pi / 3 p.x_range.range_padding = 1 ### Define dropdown options ### dropdown_group_options = [ 'All' ] + [ cat for i, cat in enumerate(sorted(data['group'].unique()), 2) # à quoi sert le 2 ici ? ] ### Generate dropdown widget ### dropdown_group = bmo.Select(title='group', value=dropdown_group_options[0], options=dropdown_group_options) ### Callback ### callback = bmo.CustomJS( args=dict(unfiltered_data=source, filtered_data=filtered_data, p=p, y_axis=p.yaxis[0], dropdown_group=dropdown_group), code=""" // utils function getKeyByValue(object, value) { return Object.keys(object).find(key => object[key] === value); } var source = unfiltered_data.data; // create a variable for each column of the unfiltered_data var group_or = source['group'] ; var overall_group_or = source['overall_group'] ; var id_or = source['id'] ; var name_or = source['name'] ; var amount_or = source['amount'] ; var status_or = source['status'] ; var overall_status_or = source['overall_status'] ; var y_ticker_or = source['y_ticker'] ; // init filtered_data filtered_data.data['group'] = [] ; filtered_data.data['overall_group'] = [] ; filtered_data.data['id'] = [] ; filtered_data.data['name'] = [] ; filtered_data.data['amount'] = [] ; filtered_data.data['overall_status'] = [] ; filtered_data.data['status'] = [] ; filtered_data.data['y_ticker'] = [] ; // get value from dropdown var f_sec = dropdown_group.value; // push matching rows in filtered_data var match = 0 for(var i=0; i < overall_status_or.length; i++){ if((overall_group_or[i].search(f_sec) != -1 || f_sec == 'All')){ filtered_data.data['group'].push(group_or[i]) ; filtered_data.data['overall_group'].push(overall_group_or[i]) ; filtered_data.data['id'].push(id_or[i]) ; filtered_data.data['name'].push(name_or[i]) ; filtered_data.data['amount'].push(amount_or[i]) ; filtered_data.data['overall_status'].push(overall_status_or[i]) ; filtered_data.data['status'].push(status_or[i]) ; // I use the below if statement to 'reset' y_tickers value (start=0, by=1) // it allows me to keep equally spread values on the y_axis if(match == 0){ filtered_data.data['y_ticker'].push(match); } else if(id_or[i] == id_or[i-1]){ filtered_data.data['y_ticker'].push(filtered_data.data['y_ticker'].at(-1)); } else{ var new_y_ticker = filtered_data.data['y_ticker'].at(-1) +1 filtered_data.data['y_ticker'].push(new_y_ticker) } ++match } } console.log(filtered_data.data) // delete the initial mapping y_axis.major_label_overrides.clear() ; // create new y_tickers const y_tickers = [...new Set(filtered_data.data['y_ticker'])] console.log(y_tickers) y_axis.ticker.ticks.length = y_tickers.length ; y_axis.ticker.ticks = y_tickers ; // update mapping in major_label_overrides for(const element of y_tickers){ var key_name = getKeyByValue(filtered_data.data['y_ticker'], element) ; y_axis.major_label_overrides.set(element, filtered_data.data['name'][key_name] + '_updated') ; } // get new x_range var new_x_range = [...new Set(filtered_data.data['group'])]; // update existing x_range with new factors p.x_range.factors.length = new_x_range.length for(var i=0; i < new_x_range.length; i++){ p.x_range.factors[i] = new_x_range[i]; } // adapt y_range to y_tickers length var max_y_range = 20 if(y_tickers.length < 20){ max_y_range = y_tickers.length } p.y_range._reset_start = 0 ; p.y_range._reset_end = max_y_range ; console.log(y_axis.major_label_overrides) // update everything filtered_data.change.emit(); p.reset.emit(); p.change.emit(); y_axis.change.emit(); """ ) ### Link actions ### dropdown_group.js_on_change('value', callback) show(bla.column(dropdown_group, p))
提前感谢您在这个问题上花费的时间:)

javascript python callback bokeh strict-mode
1个回答
0
投票
Jonas_Grave_Kristens 在 Bokeh 社区支持中提供了解决方案 (

https://discourse.bokeh.org/t/customjs-major-label-overrides-isnt-updated-on-new-plot/10990/2) .

技巧是用一个新对象更新整个major_label_overrides,而不是先清除然后设置。

const map1 = new Map(); for(const element of y_tickers){ var key_name = getKeyByValue(filtered_data.data['y_ticker'], element); map1.set(element, filtered_data.data['name'][key_name] + '_updated'); // y_axis.major_label_overrides.set(element, filtered_data.data['name'][key_name] + '_updated'); } y_axis.major_label_overrides = map1;
    
© www.soinside.com 2019 - 2024. All rights reserved.