admin管理员组

文章数量:1122832

What I want to achieve here:

  • I want to generate static HTML and do the data initialization exactly once
  • I want to pass a complex data structure to the document and use it by multiple buttons / UI elements
  • I don't want to pass the same complex data structure to each button / UI element as source property, because it will generate larger HTML file
from bokeh.models import Div, CustomJS
from bokeh.layouts import column
from bokeh.plotting import show
from bokeh.io import curdoc

dummy_div = Div(text="")
init_code = CustomJS(code="""
    window.sharedData = { initialized: true };
    console.log("Data initialized in Div change");
""")
#dummy_div.js_on_change("text", init_code)

button1 = Button(label="Log Complex Data", button_type="success")
button1.js_on_click(CustomJS(code="""
    console.log("Current shared data:", window.sharedData);
"""))

# button_N = ...

layout = column(dummy_div, button1)

curdoc().add_root(layout)
curdoc().on_event('document_ready', lambda event: init_code.execute(curdoc()))

show(layout)

Is it possible to implement something like this with this library?

Context:
This part is not needed for answering the above question, I've just wanted to show the use-case because some people wish not to give a simple answer without this

I've a complex hierarchy of ColumnDataSource-es and other data for a very specific step logic stored in form of a dict, what I need to use on JS side. I can not pass the ColumnDataSource objects separately, because the number of ColumnDataSource-s to be used is unknown in advance. There is a dynamic logic how the buttons should be generated and how they should read this hierarchy, the logic is dependent on a number of timeframe keys within the dict. I need to pass this dict to each generated button. Since the DataSource is wrapped, duplication occurs.

This is how I need to organize the data for the step logic:

js_data[aggr_interval] = {
    'data_source' : ColumnDataSource(dataframe),
    'candle_data' : dataframe.to_dict(orient="list"),
}

This is the step logic:

    time_tracker = ColumnDataSource(data=dict(trigger_date=[max_dt]))

    # I'VE A LOT OF THESE BUTTONS
    # THE ARGUMENT LIST CAN NOT BE FIXED HERE
    # I'VE TO PUT DATA SOURCES INTO A HIERARCHY WITH TIMEFRAME KEYS (candle_data_and_sources )
    # AND IMPLEMENT A DYNAMIC LOGIC ON JS SIDE
    # THE TIMEFRAMES ARE NOT KNOWN IN ADVANCE
    # THIS IS WHAT DUPLICATES THE DATA
    # AND INCREASES THE SIZE OF THE GENERATED HTML

    step_buttons['prev'].js_on_click(CustomJS(
        args = dict(
            candle_data_and_sources = candle_data_and_sources,
            time_tracker            = time_tracker,
            direction               = -1,
            min_dt                  = min_dt,
            max_dt                  = max_dt,
        ),
        code = JS_CODE_STEP_LOGIC,
    ))
JS_CODE_STEP_LOGIC = """
    const trigger_date = new Date(time_tracker.data['trigger_date'][0]);
    let new_date       = new Date(trigger_date);
    new_date.setDate(new_date.getDate() + 1 * direction);
    if (direction < 0){
        new_date = new Date(Math.max(min_dt, new_date));
    } else if (direction > 0){
        new_date = new Date(Math.min(max_dt, new_date));
    }
    time_tracker.data['trigger_date'][0] = new_date.toISOString();

    // I NEED TO DO THE FOLLOWING LOGIC FOR EACH TIMEFRAME
    // THE NUMBER/VALUE OF TIMEFRAMES HERE ARE DYNAMIC
    // THEREFORE THEY ARE ADDRESSING THE DATASOURCE IN THE HIERARCHY 

    for (const [timeframe, data] of Object.entries(candle_data_and_sources)) {

        const filtererd_obejcts = {};
        for (const [key, value] of Object.entries(data['candle_data'])) {
            if(!filtererd_obejcts[key]){
                filtererd_obejcts[key] = [];
            }
        }

        for (let i = 0; i < data['candle_data'].trigger_dt.length; i++) {
            if (new Date(data['candle_data'].trigger_dt[i]) <= new_date) {
                for (const [key, value] of Object.entries(data['candle_data'])) {
                    filtererd_obejcts[key].push(value[i]);
                }
            }
        }

        data['data_source'].data = filtererd_obejcts;
        data['data_source'].change.emit();
    }
    time_tracker.change.emit();
"""

What I want to achieve here:

  • I want to generate static HTML and do the data initialization exactly once
  • I want to pass a complex data structure to the document and use it by multiple buttons / UI elements
  • I don't want to pass the same complex data structure to each button / UI element as source property, because it will generate larger HTML file
from bokeh.models import Div, CustomJS
from bokeh.layouts import column
from bokeh.plotting import show
from bokeh.io import curdoc

dummy_div = Div(text="")
init_code = CustomJS(code="""
    window.sharedData = { initialized: true };
    console.log("Data initialized in Div change");
""")
#dummy_div.js_on_change("text", init_code)

button1 = Button(label="Log Complex Data", button_type="success")
button1.js_on_click(CustomJS(code="""
    console.log("Current shared data:", window.sharedData);
"""))

# button_N = ...

layout = column(dummy_div, button1)

curdoc().add_root(layout)
curdoc().on_event('document_ready', lambda event: init_code.execute(curdoc()))

show(layout)

Is it possible to implement something like this with this library?

Context:
This part is not needed for answering the above question, I've just wanted to show the use-case because some people wish not to give a simple answer without this

I've a complex hierarchy of ColumnDataSource-es and other data for a very specific step logic stored in form of a dict, what I need to use on JS side. I can not pass the ColumnDataSource objects separately, because the number of ColumnDataSource-s to be used is unknown in advance. There is a dynamic logic how the buttons should be generated and how they should read this hierarchy, the logic is dependent on a number of timeframe keys within the dict. I need to pass this dict to each generated button. Since the DataSource is wrapped, duplication occurs.

This is how I need to organize the data for the step logic:

js_data[aggr_interval] = {
    'data_source' : ColumnDataSource(dataframe),
    'candle_data' : dataframe.to_dict(orient="list"),
}

This is the step logic:

    time_tracker = ColumnDataSource(data=dict(trigger_date=[max_dt]))

    # I'VE A LOT OF THESE BUTTONS
    # THE ARGUMENT LIST CAN NOT BE FIXED HERE
    # I'VE TO PUT DATA SOURCES INTO A HIERARCHY WITH TIMEFRAME KEYS (candle_data_and_sources )
    # AND IMPLEMENT A DYNAMIC LOGIC ON JS SIDE
    # THE TIMEFRAMES ARE NOT KNOWN IN ADVANCE
    # THIS IS WHAT DUPLICATES THE DATA
    # AND INCREASES THE SIZE OF THE GENERATED HTML

    step_buttons['prev'].js_on_click(CustomJS(
        args = dict(
            candle_data_and_sources = candle_data_and_sources,
            time_tracker            = time_tracker,
            direction               = -1,
            min_dt                  = min_dt,
            max_dt                  = max_dt,
        ),
        code = JS_CODE_STEP_LOGIC,
    ))
JS_CODE_STEP_LOGIC = """
    const trigger_date = new Date(time_tracker.data['trigger_date'][0]);
    let new_date       = new Date(trigger_date);
    new_date.setDate(new_date.getDate() + 1 * direction);
    if (direction < 0){
        new_date = new Date(Math.max(min_dt, new_date));
    } else if (direction > 0){
        new_date = new Date(Math.min(max_dt, new_date));
    }
    time_tracker.data['trigger_date'][0] = new_date.toISOString();

    // I NEED TO DO THE FOLLOWING LOGIC FOR EACH TIMEFRAME
    // THE NUMBER/VALUE OF TIMEFRAMES HERE ARE DYNAMIC
    // THEREFORE THEY ARE ADDRESSING THE DATASOURCE IN THE HIERARCHY 

    for (const [timeframe, data] of Object.entries(candle_data_and_sources)) {

        const filtererd_obejcts = {};
        for (const [key, value] of Object.entries(data['candle_data'])) {
            if(!filtererd_obejcts[key]){
                filtererd_obejcts[key] = [];
            }
        }

        for (let i = 0; i < data['candle_data'].trigger_dt.length; i++) {
            if (new Date(data['candle_data'].trigger_dt[i]) <= new_date) {
                for (const [key, value] of Object.entries(data['candle_data'])) {
                    filtererd_obejcts[key].push(value[i]);
                }
            }
        }

        data['data_source'].data = filtererd_obejcts;
        data['data_source'].change.emit();
    }
    time_tracker.change.emit();
"""

Share Improve this question edited yesterday elaspog asked yesterday elaspogelaspog 1,6994 gold badges28 silver badges58 bronze badges 2
  • “because it will generate larger HTML file” Why do you think this is the case? Bokeh documents have a single registry with every model stored only once, and every where else only use a unique index id into that registry as a reference. You need to provide an MRE that actually shows the real problem rather than the problem with a supposed solution. – bigreddot Commented yesterday
  • @bigreddot I've a complex hierarchy of ColumnDataSource-es and other data for a very specific step logic stored in form of a dict, what I need to use on JS side. I can not pass the ColumnDataSource objects separately, because the number of ColumnDataSource-s to be used is unknown in advance. There is a dynamic logic how the buttons should be generated and how they should read this hierarchy, the logic is dependent on a number of timeframe keys within the dict. I need to pass this dict to each generated button. Since the DataSource is wrapped, duplication occurs. Is this a real problem now? – elaspog Commented yesterday
Add a comment  | 

1 Answer 1

Reset to default 1

on_event is for registering actual Python callbacks, which are only possible when you are deploying a Bokeh server application (the running Bokeh server, connected to the page via websocket, is the Python process that would run your Python callback code).

When you are generating static HTML output, it is only possible to use JavaScript callbacks, e.g. with js_on_event, since there is no Python process around to run any Python code at the time the page HTML is loaded. In this case, you want:

curdoc().js_on_event('document_ready', init_code)

Edit: I will add, for completeness: there are some folks working on making "Python" callbacks possible in the browser without a running Bokeh server, by using things like PyScript and Web Assembly. I don't know any more than to comment about their existence, though.

本文标签: javascriptInitialize global variable in bokeh and use it in handler codeStack Overflow