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>