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
sourceproperty, 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();
"""