rendering plt.savefig() to HTML page with flask - python

I want to render an html page that contains an image, produced by some matplotlib functions. So I figured that I have to:
perform all the nescessary matplotlib plotting
output it to an PNG file in /static folder
render the resulting PNG file to html
So in my main.py I do:
plt.imshow(a)
for i in range(len(obj)):
x = obj[i][u'faceRectangle'][u'left']
y = obj[i][u'faceRectangle'][u'top']
w = obj[i][u'faceRectangle'][u'width']
h = obj[i][u'faceRectangle'][u'height']
plt.plot([x,x+w],[y,y], color='r')
plt.plot([x,x+w],[y+h,y+h], color='r')
plt.plot([x,x],[y,y+h], color='r')
plt.plot([x+w,x+w],[y,y+h], color='r')
plt.savefig('static/1.png')
plt.clf()
That produces a "1.PNG" file in my /static folder like this one:
Than I renderan html template that has the following line:
<img src="{{url_for('static', filename='1.png')}}" >
This code works perfectly and the image gets included in the resulting html page.
Than something strangehappens, when I run the same code with another image it works well in the sence that it replaces the static/1.png file with the new one, but it keeps displaying the first image in the html page. Even thow there is no such image in the static/ folder anymore.
If it is the browser caching the file how can I avoid the same to happen to other users, do I need to make random filenames somehow?

Consider tacking a cache-busting nonce on to the end of the URL:
>>> '1.png?%d' % time.time()
'1.png?1505929617'
Or use e.g. app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 60

Related

How to convert matplotlib animation to an HTML5 <video> tag

Below there is the code of a matplolib animation graph and here is how to save it.
from IPython.display import HTML
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
t = np.linspace(0,2*np.pi)
x = np.sin(t)
fig, ax = plt.subplots()
ax.axis([0,2*np.pi,-1,1])
l, = ax.plot([],[])
def animate(i):
l.set_data(t[:i], x[:i]);
ani = matplotlib.animation.FuncAnimation(fig, animate, frames=len(t));
#HTML(ani.to_jshtml())
ani.to_html5_video()
What I basically do is copying the generated code into a basic html script like this one:
<html>
<head>
<title>Page Title</title>
</head>
<body>
<h1>My First Heading</h1>
<video width="432" height="288" controls autoplay loop> BLABLA </video>
</body>
</html>
Graph is not showing but the heading and the video menu is there. Why? How can I fix it?
Just stating ani.to_html5_video() will not give you the correct string out.
If you look closely at what you have copied, you will see linebreaks (\n) all over the output. (I marked them in the screenshot...)
Use
print(ani.to_html5_video())
to obtain the HTML code of the video. Or, to directly save to a file
with open("myvideo.html", "w") as f:
print(ani.to_html5_video(), file=f)
Explanation
As an explanatory example, take
string = "Hello\nWorld"
If you state string in the cell it will produce
'Hello\nWorld'
But if you print(string), it will give you the interpreted string
Hello
World
The to_html5 function has a default embed_limit of 20 MB.
If the embed_limit is exceeded, this returns the string "Video too large to embed."
My guess would be that your animation exceeds this limit. As you define the <video> tag in the HTML, the heading and controls get generated nevertheless.
You could take a look on the generated document to see if the video body just says "Video too large to embed." If this is the case, you can try to increase the embed_limit.

Error with Folium iframe and Flask

I've experimented quite a bit / searched through solutions here on the site, but so far am having trouble displaying a folium map within an iframe when the holding page is served from Flask.
wiki
app
__init.py__
routes.py
static
map_test.html
templates
map.html
Basic overview: on map.html, I have an iframe where I am trying to source the map_test.html file, a sample Folium map.
Does anyone have any thoughts on what to rearrange or fix? My thinking is the static folder, the map.html, and map_test.html files are all in the right location, but my routing is wrong. Any help is appreciated!
Here are the various code bits.
init.py
app = Flask(__name__, static_folder='static')
routes.py
#app.route('/map/')
def map():
map = folium.Map(
location = [37.852995, -120.999986],
tiles = 'Stamen Terrain',
detect_retina = True,
prefer_canvas = True,
zoom_start = 4
)
startLat_1 = 34.286565
startLong_1 = -118.561021
clusterData_1 = [retLatLong(startLat_1, startLong_1) for i in range(100)]
plugins.FastMarkerCluster(clusterData_1).add_to(map)
map_path = app.root_path + '/' + 'static/map_test.html'
map.save(map_path)
return render_template('map.html')
#app.route('/static/map_test.html')
def map_data():
return send_file(url_for('static', filename = 'map_test.html'))
map.html
<div>
<iframe src="{{url_for('static', filename = 'map_test.html')}}" height="600" width="800">
</iframe>
</div>
Through the code above, I'm able to successfully generate map_test.html in the static folder, but cannot figure how/why to route it properly into the iframe.
The error from the current code configuration above is:
IOError: [Errno 2] No such file or directory: '/static/map_test.html'
127.0.0.1 - - [27/Mar/2018 13:01:50] "GET /static/map_test.html HTTP 1.1" 500

save plotly plot to local file and insert into html

I am using python and plotly to product interactive html report. This post gives a nice framework.
If I produce the plot(via plotly) online, and insert the url into the html file, it works but refreshing the charts takes a long time. I wonder if I could produce the chart offline and have it embedded in the html report, so that loading speed is not a problem.
I find plot offline would generate a html for the chart, but I don't know how to embed it in another html. Anyone could help?
There is a better alternative as of right now, that is to do offline plotting into a div, rather than a full html.
This solution does not involve any hacks.
If you call:
plotly.offline.plot(data, filename='file.html')
It creates a file named file.html and opens it up in your web browser. However, if you do:
plotly.offline.plot(data, include_plotlyjs=False, output_type='div')
the call will return a string with only the div required to create the data, for example:
<div id="82072c0d-ba8d-4e86-b000-0892be065ca8" style="height: 100%; width: 100%;" class="plotly-graph-div"></div>
<script type="text/javascript">window.PLOTLYENV=window.PLOTLYENV || {};window.PLOTLYENV.BASE_URL="https://plot.ly";Plotly.newPlot("82072c0d-ba8d-4e86-b000-0892be065ca8",
[{"y": ..bunch of data..., "x": ..lots of data.., {"showlegend": true, "title": "the title", "xaxis": {"zeroline": true, "showline": true},
"yaxis": {"zeroline": true, "showline": true, "range": [0, 22.63852380952382]}}, {"linkText": "Export to plot.ly", "showLink": true})</script>
Notice how its just a tiny portion of an html that you are supposed to embed in a bigger page. For that I use a standard template engine like Jinga2.
With this you can create one html page with several charts arranged the way you want, and even return it as a server response to an ajax call, pretty sweet.
Update:
Remember that you'll need to include the plotly js file for all these charts to work.
You could include
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
just before putting the div you got. If you put this js at the bottom of the page, the charts won't work.
Option 1: Use plotly's offline functionality in your Jupyter Notebook (I suppose you are using a Jupyter Notebook from the link you are providing). You can simply save the whole notebook as a HTML file. When I do this, the only external reference is to JQuery; plotly.js will be inlined in the HTML source.
Option 2: The best way is probably to code directly against plotly's JavaScript library. Documentation for this can be found here: https://plot.ly/javascript/
Update: Calling an internal function has never been a good idea. I recommend to use the approach given by #Fermin Silva. In newer versions, there now is also a dedicated function for this: plotly.io.to_html (see https://plotly.com/python-api-reference/generated/plotly.io.to_html.html)
Hacky Option 3 (original version for reference only): If you really want to continue using Python, you can use some hack to extract the HTML it generates. You need some recent version of plotly (I tested it with plotly.__version__ == '1.9.6'). Now, you can use an internal function to get the generated HTML:
from plotly.offline.offline import _plot_html
data_or_figure = [{"x": [1, 2, 3], "y": [3, 1, 6]}]
plot_html, plotdivid, width, height = _plot_html(
data_or_figure, False, "", True, '100%', 525)
print(plot_html)
You can simply paste the output somewhere in the body of your HTML document. Just make sure that you include a reference to plotly in the head:
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
Alternatively, you can also reference the exact plotly version you used to generate the HTML or inline the JavaScript source (which removes any external dependencies; be aware of the legal aspects however).
You end up with some HTML code like this:
<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<!-- Output from the Python script above: -->
<div id="7979e646-13e6-4f44-8d32-d8effc3816df" style="height: 525; width: 100%;" class="plotly-graph-div"></div><script type="text/javascript">window.PLOTLYENV=window.PLOTLYENV || {};window.PLOTLYENV.BASE_URL="https://plot.ly";Plotly.newPlot("7979e646-13e6-4f44-8d32-d8effc3816df", [{"x": [1, 2, 3], "y": [3, 1, 6]}], {}, {"showLink": false, "linkText": ""})</script>
</body>
</html>
Note: The underscore at the beginning of the function's name suggests that _plot_html is not meant to be called from external code. So it is likely that this code will break with future versions of plotly.
In addition to the other answers, another possible solution is to use to_json() on the plotly figure.
No need for custom JSON serializer or use of internal solutions.
import plotly
# create a simple plot
bar = plotly.graph_objs.Bar(x=['giraffes', 'orangutans', 'monkeys'],
y=[20, 14, 23])
layout = plotly.graph_objs.Layout()
fig = plotly.graph_objs.Figure([bar], layout)
# convert it to JSON
fig_json = fig.to_json()
# a simple HTML template
template = """<html>
<head>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
<div id='divPlotly'></div>
<script>
var plotly_data = {}
Plotly.react('divPlotly', plotly_data.data, plotly_data.layout);
</script>
</body>
</html>"""
# write the JSON to the HTML template
with open('new_plot.html', 'w') as f:
f.write(template.format(fig_json))
Thought the answer needs an update based on recent version. (plotly==4.9.0)
fig.write_html("path/to/file.html")
reference: plotly documentation
I wasn't able to get any of these solutions to work. My goal was to generate plots in one notebook and publish them in another, so persisting the plot HTML wasn't as important for me as simply having some method for serializing the plot to disk to be rebuilt somewhere else.
The solution I came up with is to serialize the fig object to JSON, and then use plotly's "json chart schema" to build the plot from JSON. This demo is all python, but it should be trivial to build a plot in HTML using this JSON-serialization strategy and invoking the plotly javascript library directly, if that's what you need.
import numpy as np
import json
from plotly.utils import PlotlyJSONEncoder
from plotly.offline import download_plotlyjs, init_notebook_mode, iplot
import plotly.graph_objs as go
init_notebook_mode()
def plotlyfig2json(fig, fpath=None):
"""
Serialize a plotly figure object to JSON so it can be persisted to disk.
Figures persisted as JSON can be rebuilt using the plotly JSON chart API:
http://help.plot.ly/json-chart-schema/
If `fpath` is provided, JSON is written to file.
Modified from https://github.com/nteract/nteract/issues/1229
"""
redata = json.loads(json.dumps(fig.data, cls=PlotlyJSONEncoder))
relayout = json.loads(json.dumps(fig.layout, cls=PlotlyJSONEncoder))
fig_json=json.dumps({'data': redata,'layout': relayout})
if fpath:
with open(fpath, 'wb') as f:
f.write(fig_json)
else:
return fig_json
def plotlyfromjson(fpath):
"""Render a plotly figure from a json file"""
with open(fpath, 'r') as f:
v = json.loads(f.read())
fig = go.Figure(data=v['data'], layout=v['layout'])
iplot(fig, show_link=False)
## Minimial demo ##
n = 1000
trace = go.Scatter(
x = np.random.randn(n),
y = np.random.randn(n),
mode = 'markers')
fig = go.Figure(data=[trace])
#iplot(fig)
plotlyfig2json(fig, 'myfile.json')
plotlyfromjson('myfile.json')
EDIT: Per discussion on the associated github issue, this is probably the current best approach.
Easiest way is to use pio from plotly
import plotly.io as pio
pio.write_html(fig, file='Name.html', auto_open=True)
I have used it a ton of lots to store my graphs before important meetings
You can also use an iframe according to an answer to the question how to embed html into ipython output?
plotly.offline.plot(fig, filename='figure.html',validate=False)
from IPython.display import IFrame
IFrame(src='./figure.html', width=1000, height=600)
To improvise the below code you could just call, to_plotly_json() for ex:,
def plotlyfig2json(fig, fpath=None):
"""
Serialize a plotly figure object to JSON so it can be persisted to disk.
Figures persisted as JSON can be rebuilt using the plotly JSON chart API:
http://help.plot.ly/json-chart-schema/
If `fpath` is provided, JSON is written to file.
Modified from https://github.com/nteract/nteract/issues/1229
"""
redata = json.loads(json.dumps(fig.data, cls=PlotlyJSONEncoder))
relayout = json.loads(json.dumps(fig.layout, cls=PlotlyJSONEncoder))
fig_json=json.dumps({'data': redata,'layout': relayout})
if fpath:
with open(fpath, 'wb') as f:
f.write(fig_json)
else:
return fig_json
--------------------------------------------
Simple way:
fig = go.Figure(data=['data'], layout=['layout'])
fig.to_plotly_json()
In Plotly 5 (and likely in 4 as well), BaseFigure.to_html has include_plotlyjs argument that is True by default.
Specifies how the plotly.js library is included/loaded in the output div string.
If True, a script tag containing the plotly.js source code (~3MB) is included in the output. HTML files generated with this option are fully self-contained and can be used offline.
Funnily enough Plotly figure HTML embedding can be illustrated with Pyodide right here.
const plotlyScript = `
import plotly.express as px
fig = px.bar(y=[0, 1, 1, 2, 3, 5, 8, 13, 21, 34])
fig.to_html(full_html=False)
`
async function main() {
const pyodideUrl = 'https://cdn.jsdelivr.net/pyodide/v0.17.0/full/'
const pyodide = await loadPyodide({'indexURL': pyodideUrl})
await pyodide.loadPackage(['pandas']) // needed for plotly
const html = await pyodide.runPythonAsync(plotlyScript)
const fragment = document.createRange().createContextualFragment(html)
const output = document.getElementById('plotly-output')
output.textContent = ''
output.append(fragment)
}
main()
<script type="text/javascript" src="https://cdn.jsdelivr.net/pyodide/v0.17.0/full/pyodide.js"></script>
<div id="plotly-output">Loading pyodide... It may take a minute.</div>
to embed it in another page just use
<iframe width="560" height="315" title="Plot1" src="./scatter1.html"></iframe>
But why go with the trouble when you can not save it to a file and instead it, embed it directly
import plotly.offline as pyo
import plotly.graph_objs as go
import numpy as np
#generate the Plot
np.random.seed(42)
random_x = np.random.randint(1,101,100)
random_y = np.random.randint(1,101,100)
data = [go.Scatter(
x = random_x,
y = random_y,
mode = 'markers',
)]
the_plot= pyo.plot(data, include_plotlyjs=False, output_type='div')
print(the_plot)
num=11
strTable = '<html><head><title>Plots and Reports</title><script src="https://cdn.plot.ly/plotly-latest.min.js"></script></head><body><table style="width:100%"><tr><th>Plot</th><th>Something else</th></tr>'
strRW = "<tr><td>"+the_plot+ "</td><td>"+str(num)+"</td></tr>"
strTable = strTable+strRW
strTable = strTable+"</table></body></html>"
hs = open("theReport.html", 'w')
hs.write(strTable)
I have recently needed to export Plotly Chart to HTML files.
Here is the simple proper way to do it in 2019.
import plotly.offline
plot(figure, "file.html")
If you want to insert fig (go.Figure) in HTML, maybe you should try use it.
Note: where the fig is one instance of go.Figure and fig contain your plot.
import json
import plotly.graph_objects as go
# step 1 convert fig to json
data = fig.to_json() #return one string
# step 2, save data in the file
filename = 'name_file.json'
with open ('' + filename + 'w') as file:
json.dump(data, file, indent=4)
After you will be able to insert the json file in HTML code, but you should insert dependencies.
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script src="https://code.jquery.com/jquery-2.1.4.min.js"></script>
<!-- Plotly.js -->
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
Just adding to this thread for anyone who is looking to render a plotly figure into a HTML template as a HTML readable string. I recently had to do this and wanted to share...
Basically, if you have a HTML file where you want to place a plotly figure as a static image, one method is to insert an img class and add a string representation of the figure to the src kwarg. For example:
<img
class="max-width"
border="0"
style="display:block; padding:0px; line-height: 0px; font-size: 0px; border:0px; color:#000000;text-decoration:none;max-width:100% !important; width:100%;"
align="top"
height="350"
width="1000"
alt=""
src="" <---- this is where you can add a string rep of figure
>
To generate the string rep of any figure from plotly you can use the following function:
def render_base64_image_from_plotly_fig(fig):
"""Render a base64 encoded image from a plotly figure.
Args:
fig (dict): Plotly figure information (data and layout).
Returns:
str: base64 encoded string of plotly graph.
"""
# render figure to .png image
img_bytes = fig.to_image(format="png")
# encode into b64
encoding = b64encode(img_bytes).decode()
# create b64 image placeholder
img_b64 = "data:image/png;base64," + encoding
return img_b64
The output string can be inserting into the src="" and you will have a HTML rendered version of your plotly graph.
NOTE: watch out for truncation in the output string if you are copying straight from an IDE... this caught me out and will cause a headache. Basically a truncated base64 string will truncate with a '.' but '.' is a forbidden char in a base64 string. If you want to guaranteed that this doesn't happen, just write the output string to a .txt file on your machine and then copy and paste from it. i.e.
with open('html.txt', 'w') as f:
f.write(img_b64)
f.close()
I hope that this helps someone!
As alternative to Plotly, you can use utilmy which save HTML
on disk without extra config:
!pip install utilmy
from utilmy.viz import vizhtml as vi
import pandas as pd
url='https://raw.githubusercontent.com/AlexAdvent/high_charts/main/data/stock_data.csv'
df = pd.read_csv(url)
doc.h2('Stock Market Analysis')
doc.h4('plot Data in table format')
doc.table(df, table_id="test", custom_css_class='intro',use_datatable=True)
doc.hr()
doc.h4('Stock tseries graph')
doc.plot_tseries(
df,coldate = 'Date',
date_format = '%m/%d/%Y',
coly1 = ['Open', 'High', 'Low', 'Close'],
coly2 = ['Turnover (Lacs)'],title = "Stock",
)
doc.save('stock market analysis.html')

Embedding an html page inside Flask

Ok, I have managed to create a flask page that shows a bokeh image -- now I have to somehow put that into a template
https://gist.github.com/cloudformdesign/a0c5f2e8558ea3b60f0a
What I would like is to create a webpage with a few text boxes where the user can type the data they want to graph and it will be graphed below the text boxes. The user can select new data they want graphed and the graph will update.
I am very bad at coding in html, so I am very bad in creating templates. How would I do something like this?
I created my own example, thanks to #elyase
https://github.com/bokeh/bokeh/tree/master/examples/embed/simple
This is a very simple example of embedding bokeh pages in an html page. The example giving by #elyase was helpful, but didn't actually work with python3 (I could not import or install pyaudio). It was also overly complicated for what I was trying to ask. The gist above gives a very simple answer with only two files, all in the same directory. Thanks a ton!
Basically you need to create a view which will serve as static image, and you get the image url from that route.
I haven't got your included library so I'm going to use matplotlib and numpy to simulate what you are trying to attempt. This isn't a FULL solution for you (it's in the simplest working way using 2 views and 1 simple template), but you should be able to understand all the ESSENTIAL techniques which let you finish off your page.
I have a few comments and guidelines, and I think the code itself is pretty much self explanatory.
OK, here is the view main.py:
from flask import Flask, render_template, request, send_file
import matplotlib.pyplot as plt
import numpy as np
from StringIO import StringIO
app = Flask(__name__)
#app.route('/plot/')
def plot():
try:
# try to get the query string like ?width=xxx&height=xxx
height = int(request.args.get('height'))
width = int(request.args.get('width'))
except ValueError:
# if height, width not available, set a default value
height, width = 100, 100
# I randomly generate the plot which just based on H x W
# you should be able to generate yours from your library
to_draw = np.random.randint(0, 255, (height, width))
img = plt.imshow(to_draw)
# you can convert the graph to arrays and save to memory
imgIO = StringIO()
img.write_png(imgIO, noscale=True) # save to memory
imgIO.seek(0)
# and send that image as file as static file of url
# like... /plot/?width=100&height=100
return send_file(imgIO, mimetype='image/png')
# this is the main page with the form and user input
#app.route('/', methods=['GET', 'POST'])
def index():
# set the default values
height, width = 100, 100
# handle whenever user make a form POST (click the Plot button)
if request.method == 'POST':
try:
# use this to get the values of user input from the form
height = int(request.form['height'])
width = int(request.form['width'])
except ValueError:
pass
# and pass the context back to the template
# if no form is POST, default values will be sent to template
return render_template('index.html', height=height, width=width)
if __name__ == '__main__':
app.debug = True
app.run()
And the template at templates/index.html:
<html>
<head>
<title>Micro Plot!</title>
</head>
<body>
<h1>Interactive Plot</h1>
<form action="/" name="plot" method="POST">
<p><input type="text" name='height' value="{{ height }}" /></p>
<p><input type="text" name='width' value="{{ width }}" /></p>
<p><input type="submit" value="Plot Now!"></p>
</form>
<img src="{{ url_for('plot', height=height, width=width) }}" />
</body>
</html>
The trick is to set the image src to send a GET request to the url, and then Flask's /plot/ view to render an in-memory image and feed back as static.
Key Notes:
The url_for will then dynamically generate the src as like /plot/?width=xxx&height=xxx.
To get the querystring from the url in the view, use request.args.get('KeyName').
Once your plot is ready, save it in the memory with Python StringIO module and use Flask's send_file to serve as static content.
You should read and learn more about Flask and HTML, you cannot build something really amazing without having a good understanding of how these things work together.
I hope this helps you understand the underlying techniques, and Good Luck!
I ended up creating my own answer with the help of elyase, and the code was pulled into an example folder in the bokeh project. Check it out here:
https://github.com/bokeh/bokeh/tree/master/examples/embed/simple

Edit response logo in web2py

I am trying to edit the default logo to my own in my MODEL(menu.py).
The default code for the logo is:
response.logo = A(B('web',SPAN(2),'py'),XML('™ '), _class="brand", _href="http://www.web2py.com/")
How would I change this line to display my own logo? For instance, the url for the logo might be something like "http://www.web2py.com/init/static/images/logo_lb.png"
Just change the content of the anchor tag to include the image:
response.logo = A(IMG(_src=URL('static', 'images/logo_lb.png'),
_href=URL('default', 'index'))
Note, you don't really need to use response.logo at all. You can instead just put the relevant HTML directly in the layout.html view.

Categories