Campbell D
Campbell D

Reputation: 105

How to embed a DataTable widget in a Python Flask Web App

Apologies if this question is answered elsewhere on StackOverflow, I did my best to search for an answer before posting this question.

I am working to create a Flask-based web app using Bokeh for data visualization at work. I have had no problem embedding scatter/line plots using bokeh.embed.components and bokeh.plotting.figure.

I would like to display the data which is being used to make the plot in a DataTable widget below the plot. Unfortunately, using components to generate a script and div does not seem to work for widgets. Components also seems to fail when using a column, row, or layout created with bokeh.layouts.

The general layout of my code is a Python file, which contains my Flask app, and an HTML file which contains the layout of my webpage. The general layout of the Python file is below:

from flask import Flask, render_template
import pandas as pd
import numpy as np
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.plotting import figure
from bokeh.layouts import widgetbox, column
from bokeh.embed import components

app = Flask(__name__)
x = np.linspace(0, 2, 1000)
y = np.sin(x)
df = pd.DataFrame({"x": x, "y":y}) # Not sure how to provide sample data

@app.route("/")
def index():
    p = figure()
    p.scatter(df['x'], df['y'])

    data_source = ColumnDataSource(df)
    columns = [
        TableColumn(field="field1", title="Field 1"),
        TableColumn(field="field2", title="Field 2"),
        TableColumn(field="field3", title="Field 3"),
    ]

    data_table = DataTable(source=data_source, columns=columns)
    script, div = components(column(p, widgetbox(data_table)))
    return render_template('sample.html', script=script, div=div)

The HTML template ("sample.html") would look something like the following:

<html>
<head>
<link
    href="http://cdn.bokeh.org/bokeh/release/bokeh-1.0.4.min.css"
    rel="stylesheet" type="text/css">
<link
    href="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.0.4.min.css"
    rel="stylesheet" type="text/css">

<script src="http://cdn.bokeh.org/bokeh/release/bokeh-1.0.4.min.js"></script>
<script src="http://cdn.bokeh.org/bokeh/release/bokeh-widgets-1.0.4.min.js"></script>

</head>
<body>
{{ script|safe }}
{{ div|safe }}
</body>
</html>

I've elected to use a simple sine wave as the sample data for this code, but in real life I am using an Excel file (pd.read_excel) to test the code before connecting the web app to a database.

In the Python code, if I replace

script, div = components(column(p, widgetbox(data_table))

with

script, div = components(p)

the code works perfectly. So, that leads me to believe that the problem is with embedding a widget or a layout of plots and widgets. Thanks in advance for any help that you might be able to provide.

Upvotes: 1

Views: 1455

Answers (2)

Campbell D
Campbell D

Reputation: 105

For what it's worth I realized what was wrong with my original code. Apparently, one needs to link to the CSS and JS for bokeh-tables when using DataTables with components. Below are the relevant link and script tags.

<link
    href="http://cdn.bokeh.org/bokeh/release/bokeh-tables-1.0.4.min.css"
    rel="stylesheet" type="text/css">

<script src="http://cdn.bokeh.org/bokeh/release/bokeh-tables-1.0.4.min.js"></script>

Upvotes: 1

Tony
Tony

Reputation: 8297

The code below renders the plot and table correctly for Bokeh v1.0.4

import numpy as np
import pandas as pd
import webbrowser
from flask import Flask, render_template
from tornado.ioloop import IOLoop
from bokeh.application import Application
from bokeh.application.handlers import FunctionHandler
from bokeh.embed import server_document
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.server.server import Server
from bokeh.layouts import column

app = Flask(__name__)
port = 5001

def get_plot(doc):
    x = np.linspace(0, 2, 1000)
    y = np.sin(x)
    df = pd.DataFrame({"x": x, "y":y})  # Not sure how to provide sample data

    def get_plot():
        p = figure()
        p.scatter(df['x'], df['y'])
        data_source = ColumnDataSource(df)
        columns = [ TableColumn(field = "field1", title = "Field 1"),
                    TableColumn(field = "field2", title = "Field 2"),
                    TableColumn(field = "field3", title = "Field 3"), ]
        data_table = DataTable(source = data_source, columns = columns)

        return column(p, data_table)

    doc.add_root(get_plot())
    doc.title = "Flask App Plot"

bokeh_app = Application(FunctionHandler(get_plot))

@app.route('/', methods = ['GET'])
def index():
    script = server_document('http://localhost:5006/bkapp')
    return render_template("index.html", script = script)

def bk_worker():
    server = Server({'/bkapp': bokeh_app}, io_loop = IOLoop(), allow_websocket_origin = ["localhost:{}".format(port)], port = port)
    server.start()
    server.io_loop.start()

from threading import Thread
Thread(target = bk_worker).start()

if __name__ == '__main__':
    print('Opening single process Flask app with embedded Bokeh application on http://localhost:{}/'.format(port))
    webbrowser.open_new("http://localhost:{}/".format(port))
    app.run(port = port, debug = False)

Result:

enter image description here

Upvotes: 0

Related Questions