javascript 散景:major_label_overrides在CustomJS回调后未更新

im9ewurl  于 12个月前  发布在  Java
关注(0)|答案(1)|浏览(89)

我写了下面的代码来制作一个图,当用户对数据应用一些过滤器时,这个图可以被更新。在选择了一个值之后,回调函数会按照预期更新所有内容,除了yaxis.major_label_overrides(它将名称(字符串)写在y轴代码(整数)的顶部)。
当我研究控制台时,我注意到y_axis.major_label_overrides中有以下异常:Exception in browser console以下是完整的异常:Exception:TypeError:'caller','callee',and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them at Function.invokeGetter(:3:28)< anonymous >。
经过一些研究,我不知道如何解决这个特殊情况下的异常(以及它是否是问题的原因)。
应用过滤器之前的外观:Plot before filter is applied
应用过滤器后的外观:Plot after filter is applied
控制台中没有任何错误,但应用过滤器后的预期输出是一个y轴,如下所示:

  • “Joe_updated”而不是“Bob”,
  • “Guy_updated”而不是“Joe”

我使用Google Chrome打开输出文件,散景版本为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))

字符串
提前感谢您在这个问题上花费的时间:)

drnojrws

drnojrws1#

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;

字符串

相关问题