Update a bokeh plot using ajax Update a bokeh plot using ajax flask flask

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.