Home > OS >  How do I embed a Bokeh Interactive Plot in a Flask application?
How do I embed a Bokeh Interactive Plot in a Flask application?

Time:11-03

I am currently trying to create a very simple app that displays a recipe and a network diagram.

I have gotten most of the way there, but I am receiving the following error: Uncaught ReferenceError: Bokeh is not defined at HTMLDocument.fn (?description=blueberry pancakes:29:5)

I have tried implementing the solutions at the following locations:
Python Flask App with Interactive Bokeh plots
Embedding a bokeh plot in Flask
Embedding bokeh plot and datatable in flask
Bokeh plot not visible in flask application

but nothing is really changing.

Below is my current version of index.html.

<html>
<head>
    <title>Recipes</title>
    <style>
        .row {
            display: flex;
        }

        .column {
            flex: 50%;
        }
    </style>
    <script scr="https://cdn.pydata.org/bokeh/release/bokeh-3.0.0.min.js"></script>
    <script scr="https://cdn.pydata.org/bokeh/release/bokeh-widgets-3.0.0.min.js"></script>
    <script scr="https://cdn.pydata.org/bokeh/release/bokeh-tables-3.0.0.min.js"></script>
    <script scr="https://cdn.pydata.org/bokeh/release/bokeh-gl-3.0.0.min.js"></script>
    <link
        href="https://cdn.pydata.org/bokeh/release/bokeh-0.13.0.min.css"
        rel="stylesheet" type="text/css">
    <link
        href="https://cdn.pydata.org/bokeh/release/bokeh-widgets-0.13.0.min.css"
        rel="stylesheet" type="text/css">
    <link
        href="https://cdn.pydata.org/bokeh/release/bokeh-tables-0.13.0.min.css"
        rel="stylesheet" type="text/css">
    {{script | safe}}
</head>
<!-- <header>
    {{ script|safe }}
</header> -->
<body>

<form action="" method="get">
    Recipe Description: <input type="text" name="description">
    <label for="diets">Choose a Diet:</label>
    <select id="diets" name="diet_plan" size="2" multiple>
        <option value="Vegan">Vegan</option>
        <option value="Vegetarian">Vegetarian</option>
        <option value="Nut-Free">Nut-Free</option>
        <option value="Gluten-Free">Gluten-Free</option>
    </select><br><br>
    <input type="submit" value="Generate a recipe">
</form>

<h1>{{description}}</h1>
<h2>{{diet_plan}}</h2>
<div >
    <div >
        <h2>Ingredients</h2>
        <ul>
            {%for i in range(0, len)%}
                <li>{{ingredients[i]}}</li>
            {%endfor%}
        </ul>
    </div>
    <div >
        <h2>Instructions</h2>
        <ul>
            {%for i in range(0, len)%}
                <li>{{instructions[i]}}</li>
            {%endfor%}
        </ul> 
    </div>
</div>

<h1>{{div}}</h1>

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

And here is the main portion of main.py.

from flask import Flask
from flask import request, render_template, send_file
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from bokeh.io import output_file, show
from bokeh.models import (BoxZoomTool, Circle, HoverTool, MultiLine, Plot, Range1d, ResetTool)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx, save
from bokeh.embed import components
from bokeh.resources import CDN
from io import BytesIO
import re

app = Flask(__name__)

@app.route("/")
def index():
    description = request.args.get("description", "")
    # diet_plan = request.form.getlist("diets", "")
    diet_plan = request.args.get("diet_plan", "")
    if description:
        recipe, steps = get_ai_recipe()
        ingredients = recipe["ingredient"]   " ("   recipe["qty"]   ")"
        instructions = steps["instruction"]
        script, div = generate_graph(recipe)
    else:
        recipe = None
        steps = None
        ingredients = pd.DataFrame({"ingredient" : []})
        instructions = pd.DataFrame({"instruction" : []})
        script, div = ("", "")
    return render_template("index.html", 
                            len = len(ingredients), 
                            ingredients = ingredients, 
                            instructions = instructions,
                            description = description,
                            diet_plan = diet_plan,
                            script = script,
                            div = div,
                            resources = CDN.render())

I didn't include the get_ai_recipe() or generate_graph() functions to save some space, but basically get_ai_recipe() returns 2 pandas dataframes, and generate_graph() returns a script and a div from components.

script, div = components(plot)
return script, div

So the "plot" in that code is an actual "Plot" class from bokeh.models. Pretty much every online example I saw when trying to debug this uses figure() instead of Plot(). I'm not sure if that has anything to do with my issue, but if it does, I would appreciate help in figuring out how to convert the Plot to a figure.

Additionally, I am very new to flask and html (this is my very first interaction with them, really), so I'm not sure what all of the scripts/links in the head section are doing. I also do not know if those are the most recent versions of those links. I used the original version from the examples online and then updated them as far as I could, but I'm not sure if they go any further. Some examples used all of those, and some used just one. I assume this is where my issue lies, but I am not sure.

The code is definitely generating the div, as I have it printed as a title on the page, but it's unable to find bokeh.

I would appreciate any help. Thanks!

CodePudding user response:

You are already passing the div with the plot, but cannot tell how, try using bokeh.plotting.Figure and bokeh.embed.components;

import bokeh
# ...
empty_boxplot = bokeh.plotting.Figure(
            plot_width=500,
            plot_height=450
        )
script, div = bokeh.embed.components(empty_boxplot)

you can then render it with Jinja in your index.html

<div id="plot">
   {{ div }}
</div>

CodePudding user response:

OK I figured it out.

The solution comes from here
Bokeh plot not visible in flask application
Which is a solution I tried to implement but failed to do so correctly. Below is the correct solution.

main.py:

from flask import Flask
from flask import request, render_template, send_file
import pandas as pd
import networkx as nx
import matplotlib.pyplot as plt
from bokeh.io import output_file, show
from bokeh.models import (BoxZoomTool, Circle, HoverTool, MultiLine, Plot, Range1d, ResetTool)
from bokeh.palettes import Spectral4
from bokeh.plotting import from_networkx, save, figure
from bokeh.embed import components
from bokeh.resources import CDN
from io import BytesIO
import re

app = Flask(__name__)

@app.route("/")
def index():
    description = request.args.get("description", "")
    # diet_plan = request.form.getlist("diets", "")
    diet_plan = request.args.get("diet_plan", "")
    if description:
        recipe, steps = get_ai_recipe()
        ingredients = recipe["ingredient"]   " ("   recipe["qty"]   ")"
        instructions = steps["instruction"]
        script, div = generate_graph(recipe)
    else:
        recipe = None
        steps = None
        ingredients = pd.DataFrame({"ingredient" : []})
        instructions = pd.DataFrame({"instruction" : []})
        script, div = ("", "")
    return render_template("index.html", 
                            len = len(ingredients), 
                            ingredients = ingredients, 
                            instructions = instructions,
                            description = description,
                            diet_plan = diet_plan,
                            script = script,
                            div = div,
                            resources = CDN.render())

index.html:

<html>
<head>
    <title>Recipes</title>
    <style>
        .row {
            display: flex;
        }

        .column {
            flex: 50%;
        }
    </style>
    {{ resources|safe }}
</head>
<header>
    {{ script|safe }}
</header>
<body>

<form action="" method="get">
    Recipe Description: <input type="text" name="description">
    <label for="diets">Choose a Diet:</label>
    <select id="diets" name="diet_plan" size="2" multiple>
        <option value="Vegan">Vegan</option>
        <option value="Vegetarian">Vegetarian</option>
        <option value="Nut-Free">Nut-Free</option>
        <option value="Gluten-Free">Gluten-Free</option>
    </select><br><br>
    <input type="submit" value="Generate a recipe">
</form>

<h1>{{description}}</h1>
<h2>{{diet_plan}}</h2>
<div >
    <div >
        <h2>Ingredients</h2>
        <ul>
            {%for i in range(0, len)%}
                <li>{{ingredients[i]}}</li>
            {%endfor%}
        </ul>
    </div>
    <div >
        <h2>Instructions</h2>
        <ul>
            {%for i in range(0, len)%}
                <li>{{instructions[i]}}</li>
            {%endfor%}
        </ul> 
    </div>
</div>

<h1>{{div}}</h1>

<div>{{div|safe }}</div>
  
</body>
</html>
  • Related