Bokeh多时间框架图表步进逻辑实现中的数据模型重复及HTML体积优化问询
Bokeh多时间框架图表步进逻辑实现中的数据模型重复及HTML体积优化问询
我想要用Bokeh库实现一个多时间框架图表的步进逻辑,需要导出包含所有数据的静态HTML文件,所以HTML文件的体积是关键考量因素。
在这个方案中,多个时间框架可以同时可视化,不同时间框架的图表会叠加绘制,但只显示到某个特定时间点(我们称之为trigger_date)。我用ColumnDataSource存储每个时间框架的数据,并且需要为每个时间框架至少生成两个按钮(后退步进、前进步进)。每个按钮都需要传递所有数据,也就是所有时间框架的数据源和数据框,来实现步进逻辑(只显示到指定时间点的图表,并正确emit()变更)。
关键限制
重要提示:时间框架的数量和取值(聚合单位)不是固定的,也无法提前预知,完全由用户输入决定,可能是任意值。绝对不能用固定列表来枚举,比如:30s, 1m, 5m, 15m, 30m, 1h, 4h, 1d, ...
所以像下面这样的写法根本不可行:
step_buttons[timeframe]['prev'].js_on_click(CustomJS( args = dict( time_tracker = time_tracker, direction = -1, min_dt = min_dt, max_dt = max_dt, # 所有时间框架无法在这里枚举,因为是未知的 datasource_5m = ..., datasource_15m = ..., datasource_30m = ..., datasource_1h = ..., datasource_4h = ..., ?????????????? = ..., ), code = JS_CODE_STEP_LOGIC, ))
当前尝试与问题
因此我必须把所有时间框架的数据源包装成一个更动态、更复杂的数据结构(用时间框架作为键,数量任意),作为单个参数传递给按钮:
js_data[timeframe] = { 'data_source' : ColumnDataSource(dataframes[timeframe]), 'candle_data' : dataframes[timeframe].to_dict(orient="list"), } # 或者 # data_sources = {} # candle_data = {} # for timeframe in dataframes.keys(): # data_sources[timeframe] = ColumnDataSource(dataframes[timeframe]) # candle_data[timeframe] = dataframes[timeframe].to_dict(orient="list") # ... for tf in timeframes: # 我有很多这样的按钮 # 参数列表不能固定 # 我必须把数据源放到带时间框架键的层级结构中(candle_data_and_sources) # 并在JS端实现动态逻辑 # 时间框架无法提前预知 # 这会导致数据重复 # 进而增大生成的HTML文件体积 step_buttons[tf]['prev'].js_on_click(CustomJS( args = dict( candle_data_and_sources = js_data, # 需要在这里使用复杂结构 # 或者 # data_sources = data_sources # candle_data = candle_data time_tracker = time_tracker, direction = -1, timeframe = tf, min_dt = min_dt, max_dt = max_dt, ), code = JS_CODE_STEP_LOGIC, )) step_buttons[tf]['next'] = ...
但问题来了:这样做会导致数据模型明显重复,生成的文件体积远超出需求,会在可视化时引发性能问题,甚至浏览器无法打开文件。
核心问题
- 如何在不重复数据模型的前提下,给每个按钮只传递一次所有可用数据?
- 我觉得把所有可能的时间框架硬编码到按钮参数里不是好的实现方式(而且在我的场景里甚至不可能),这个感觉对吗?
补充信息
补充信息1
我曾尝试通过在JS端设置全局变量来绕过这个限制,但没能找到可行的解决方案。
补充信息2
我使用的步进逻辑大致如下:
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 * get_tf_value(timeframe)); 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(); // 我需要对每个时间框架执行以下逻辑 // 时间框架的数量/取值是动态的 // 因此需要通过层级结构访问数据源 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(); """
备注:内容来源于stack exchange,提问作者elaspog
相关产品推荐
相关产品推荐

