Reload a static html plot

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

Hi,

I’m not sure, but updating standalone html is not posible.
You can do this using Bokeh Server, using for example add_next_tick_callback or add_periodic_callback moethods for Document calss.
https://docs.bokeh.org/en/latest/docs/reference/document.html

You can add a non-Bokeh, plain-JavaScript repeating setTimeout callback on your page by using a custom HTML template. A setTimeout callback could call reload() and if the content on the server has changed since the last reload, the possibly the content on the page might update. I say possibly because I can imagine that things like browser caching might complicate matters or make things not work as expected. So, I don’t really recommend such approaches because they seem fragile and kludgy.

As @smith666 mentions, this kind of use-case is the reason that the Bokeh server exists.

Now, it is very recently possible to “compile” Python to the browser using tools like PyScript, and this even includes demonstrations of “running” Bokeh server apps purely in a browser. But I am not up to date on those developments, I am only passingly aware they exist, so I can’t offer any more information about that possibility. cc @James_A_Bednar1

thanks for the reply. I decided I will not need this functionality at the moment as I can just add a button to refresh the plot and or refresh the browser and itll have the most up to date chart.

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.