Lets say I am creating a plot from OHLC data and adding some customizations. I am also adding other figures like Volume and creating a gridplot with the plots
I am then running show(fig) and all is well. The static html page pops up with my candlestick chart no problem. This plot is being invoked from a plot function which is called from an outside module. that is collecting data every 5 minutes for new candles. Once a new candle arrive I update the dataFrame and would then like to automatically update the static html plot with the new data from that dataframe. I am not adding a new bar but replacing the whole dataFrame
Here is my code implementation:
import numpy as np
import os
from functools import partial
from itertools import cycle
from typing import Dict
# from data_objects import Indicator
from bokeh.palettes import Category10
from bokeh.layouts import gridplot
from bokeh.transform import factor_cmap
from bokeh.models import (
Range1d,
ColumnDataSource,
CustomJSTickFormatter,
DatetimeTickFormatter,
HoverTool,
NumeralTickFormatter,
CrosshairTool,
WheelZoomTool,
Span,
CustomJS
)
from bokeh.plotting import figure, show, curdoc, save
from bokeh.io.state import curstate
from bokeh.io import output_file
from bokeh.colors.named import (
lime as BULL_COLOR,
tomato as BEAR_COLOR
)
OHLCV_LIST = ['Open', 'High', 'Low', 'Close', 'Volume']
def _bokeh_reset(filename=None):
curstate().reset()
if filename:
if not filename.endswith('.html'):
filename += '.html'
output_file(filename, title=filename)
else:
output_file('default.html', title='')
def plot(df: pd.DataFrame, indicators: Dict[str, str]=None, update_plot=False):
# reset the current state and create an html output
_bokeh_reset('test_plot')
COLORS = [BEAR_COLOR, BULL_COLOR]
BAR_WIDTH = .8
is_datetime_index = isinstance(df.index, pd.DatetimeIndex)
# copy dataframe without indicators
plot_df = df[list(OHLCV_LIST)].copy(deep=False)
# get indicators for plot
indicator_columns = df.columns.difference(plot_df.columns)
plot_df.index.name = None # Provides source name @index
plot_df['datetime'] = plot_df.index # Save original, maybe datetime index
plot_df = plot_df.reset_index(drop=True)
index = plot_df.index
# Initialize Bokeh figure function
new_bokeh_figure = partial(
figure,
x_axis_type='linear',
width=500,
height=400,
tools="xpan,xwheel_zoom,box_zoom,undo,redo,reset,save",
active_drag='xpan',
sizing_mode='stretch_both',
active_scroll='xwheel_zoom')
pad = (index[-1] - index[0]) / 20
_kwargs = dict(x_range=Range1d(index[0], index[-1],
min_interval=10,
bounds=(index[0] - pad,
index[-1] + pad))) if index.size > 1 else {}
# create bokeh figure instance
fig_ohlc = new_bokeh_figure(**_kwargs)
# used for volume and can be extended in the future
figs_below_ohlc = []
# create source object
source = ColumnDataSource(plot_df)
source.add((plot_df.Close >= plot_df.Open).values.astype(np.uint8).astype(str), 'inc')
# map colors
inc_cmap = factor_cmap('inc', COLORS, ['0', '1'])
def new_indicator_figure(**kwargs):
kwargs.setdefault('height', 150)
fig = new_bokeh_figure(x_range=fig_ohlc.x_range,
active_scroll='xwheel_zoom',
active_drag='xpan',
sizing_mode='stretch_width',
**kwargs)
fig.xaxis.visible = False
fig.yaxis.minor_tick_line_color = None
return fig
# create function to add candlesticks for OHLC
def _plot_ohlc():
"""Main OHLC bars"""
fig_ohlc.segment('index', 'High', 'index', 'Low', source=source, color="black")
fig_ohlc.vbar('index', BAR_WIDTH, 'Open', 'Close', source=source,
line_color="black", fill_color=inc_cmap)
def _plot_volume_section():
"""Volume section"""
fig = new_indicator_figure(y_axis_label="Volume")
fig.xaxis.formatter = fig_ohlc.xaxis[0].formatter
fig.xaxis.visible = True
fig_ohlc.xaxis.visible = False # Show only Volume's xaxis
fig.vbar('index', BAR_WIDTH, 'Volume', source=source, color='red')
return fig
fig_volume = _plot_volume_section()
figs_below_ohlc.append(fig_volume)
ohlc_bars = _plot_ohlc()
plots = [fig_ohlc] + figs_below_ohlc # where figs_below holds the volume figure
fig = gridplot(
plots,
ncols=1,
toolbar_location='right',
toolbar_options=dict(logo=None),
merge_tools=True,
sizing_mode='stretch_both'
)
show(fig)
return fig
Now once a new bar of data comes in I would like to reload the plot instead of opening a new browser instance. I am not sure how but i believe itll be something with js_on_change function for a source change. and then calling some callback like so
def refresh_plot():
# This will execute once when the document changes
print("Document changed, triggering reload.")
return """
window.location.reload(); // Reload the page
"""
EDIT: When calling this function again and saving the fig instead of showing the fig after an update, I can just refresh the browser window myself and the new chart populates. At the end of my file I added this
if (update_plot):
save(fig)
else:
show(fig)
return fig
But I am asking how to automatically refresh that browser window instead of manually doing it