Update a bokeh plot using ajax
I have deleted the table from your example, and just focused on updating the bokeh plot. Important to note in this example the plot is being re-created each time you press calculate.
There are now three files - server code (app.py), base template (index.html) and a template to render only bokeh plot (update_content.html). Both the templates need to be in the templates folder as usual.
The '_get_table' endpoint now returns rendered html containing the bokeh plot. Clicking the calculate button will trigger a callback, which in turn submits a post request to this endpoint. app.py:
from flask import Flask, render_template, request, jsonifyimport pandas as pdimport numpy as npimport jsonfrom bokeh.plotting import figure, show, savefrom bokeh.embed import components# Initialize the Flask applicationapp = Flask(__name__)@app.route('/')def index(): # just an initial figure p = figure(plot_width=150, plot_height=100) p.line([1, 2, 3], [1, 2, 3]) # save(p, 'testing.html') script_bok, div_bok = components(p) return render_template('index.html', div_bok=div_bok, script_bok=script_bok)@app.route('/_get_table', methods=['GET','POST'])def get_table(): # extract nrow, ncol via ajax post - contained in request.form nrow = request.form.get('nrow', type=int) ncol = request.form.get('ncol', type=int) # the updated/new plot p = figure(plot_width=150, plot_height=100) p.line(list(range(nrow)), list(range(nrow))) script_bok, div_bok = components(p) #return rendered html to the browser return render_template('update_content.html', div_bok=div_bok, script_bok=script_bok)if __name__ == '__main__': app.run(debug=True, threaded=True)
Index.html :
<!DOCTYPE html><html lang="en"> <head> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet"> <link href="http://cdn.bokeh.org/bokeh/release/bokeh-0.12.15.min.css" rel="stylesheet" type="text/css"> <link href="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.15.min.css" rel="stylesheet" type="text/css"> </head> <body> <div class="container"> <div class="header"> <h3 class="text-muted">some stuff</h3> </div> <hr class="mb-4"> <div class="row"> <div class="col-md-8"> <form id="input_data_form" class="needs-validation" novalidate> <div class="row"> <div class="col-md-6 mb-3"> <label for="nrow">rows</label> <input type="number" class="form-control" id="nrow" min="0" step="1" placeholder="" value="2" required> <div class="invalid-feedback"> please provide an integer </div> </div> <div class="col-md-6 mb-3"> <label for="ncol">columns</label> <input type="number" class="form-control" id="ncol" min="0" step="1" placeholder="" value="2" required> <div class="invalid-feedback"> please provide an integer </div> </div> </div> <div class="form-row text-center"> <div class="col-12"> <button id="calculate" type="submit" class="btn btn-primary">Calculate!</button> </div> </div> </form> </div> <div class="col-md-4"> <div class="header"> <h5 class="text-muted">Plot results</h5> </div> <div id="plot-content"> {{div_bok|safe}} {{script_bok|safe}} </div> </div> </div> </div> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.12.15.min.js"></script> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.15.min.js"></script> <script type="text/javascript"> $(document).ready(function(){ $('#calculate').on('click', function(e){ // prevent page being reset, we are going to update only // one part of the page. e.preventDefault() $.ajax({ url:'./_get_table', type:'post', data:{'nrow':$("#nrow").val(), 'ncol':$("#ncol").val()}, success : function(data){ // server returns rendered "update_content.html" // which is just pure html, use this to replace the existing // html within the "plot content" div $('#plot-content').html(data) } }) }); }); </script> </body>
update_content.html
{{div_bok|safe}}{{script_bok|safe}}
@Anthonydouc's excellent answer got me on the right track, so all the credit should go to him; this is just an extension on how one can also update the table at the same time.
The key is to create an additional file, here update_content.html
that will contain the required HTML for the plot which should be updated everytime the user provides some input. It contains only:
{{div_bok|safe}}{{script_bok|safe}}
Given this, one can then use render_template
to produce the HTML string that represents the figure and passes it also via jsonify
. The updated flask script therefore looks as follows:
from flask import Flask, render_template, request, jsonifyimport pandas as pdimport numpy as npimport jsonfrom bokeh.plotting import figure, show, savefrom bokeh.embed import components# Initialize the Flask applicationapp = Flask(__name__)@app.route('/')def index(): return render_template('index.html')@app.route('/_get_table')def get_table(): nrow = request.args.get('nrow', type=int) ncol = request.args.get('ncol', type=int) # the table we want to display df = pd.DataFrame(np.random.randint(0, 10, size=(nrow, ncol))) # the updated/new plot p = figure(plot_width=150, plot_height=100) p.line(list(range(nrow + 1)), list(range(nrow + 1))) # save(p, 'testing.html') script_bok, div_bok = components(p) # pass the div and script to render_template return jsonify(my_table=json.loads(df.to_json(orient="split"))["data"], columns=[{"title": str(col)} for col in json.loads(df.to_json(orient="split"))["columns"]], html_plot=render_template('update_content.html', div_bok=div_bok, script_bok=script_bok))if __name__ == '__main__': app.run(debug=True, threaded=True)
and the index.html
file looks like this (make sure to use the correct bokeh
version, here it is 0.12.15
):
<!DOCTYPE html><html lang="en"> <head> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link href="https://cdn.datatables.net/1.10.16/css/jquery.dataTables.min.css" rel="stylesheet"> <link href="http://cdn.bokeh.org/bokeh/release/bokeh-0.12.15.min.css" rel="stylesheet" type="text/css"> <link href="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.15.min.css" rel="stylesheet" type="text/css"> </head> <body> <div class="container"> <div class="header"> <h3 class="text-muted">some stuff</h3> </div> <hr class="mb-4"> <div class="row"> <div class="col-md-8"> <form id="input_data_form" class="needs-validation" novalidate> <div class="row"> <div class="col-md-6 mb-3"> <label for="nrow">rows</label> <input type="number" class="form-control" id="nrow" min="0" step="1" placeholder="" value="2" required> <div class="invalid-feedback"> please provide an integer </div> </div> <div class="col-md-6 mb-3"> <label for="ncol">columns</label> <input type="number" class="form-control" id="ncol" min="0" step="1" placeholder="" value="2" required> <div class="invalid-feedback"> please provide an integer </div> </div> </div> <div class="form-row text-center"> <div class="col-12"> <button id="calculate" type="submit" class="btn btn-primary">Calculate!</button> </div> </div> </form> </div> <div class="col-md-4"> <div class="header"> <h5 class="text-muted">Plot results</h5> </div> <div id="plot_content"> </div> </div> </div> <hr class="mb-4"> <table id="a_nice_table" class="table table-striped"></table> </div> <script src="https://code.jquery.com/jquery-3.2.1.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script> <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js" type="text/javascript"></script> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-0.12.15.min.js"></script> <script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-0.12.15.min.js"></script> <script type="text/javascript"> $(document).ready(function() { var table = null; $('#input_data_form').submit(function (event) { event.preventDefault(); if ($('#input_data_form')[0].checkValidity() === false) { event.stopPropagation(); if (table !== null) { table.destroy(); table = null; $("#a_nice_table").empty(); } } else { $.getJSON('/_get_table', { nrow: $("#nrow").val(), ncol: $("#ncol").val() }, function(data) { $('#plot_content').html(data.html_plot); if (table !== null) { table.destroy(); table = null; $("#a_nice_table").empty(); } table = $("#a_nice_table").DataTable({ data: data.my_table, columns: data.columns }); }); return false; } $('#input_data_form').addClass('was-validated'); }); }); </script> </body>
There might be better options where the plot is really just updated instead of redrawn completely as in this solution, but that gets the job done for now.