I have been struggling trying to find a way to update graphs on a flask webserver. I stored the images in the static file of my directory and accessed them by {{url_for('static',filname=ph_plot.png) }} but everytime I would send a post request to fetch a new range of data the graph would not update on my webserver but on my filesystem it would. I know I can change the name of the file everytime I save it to make it appear but I dont know if that is an optimal way to display a dynamically changing photo.
Currently I have been using the send_from_directory method in flask but with it hasnt worked for me either. Below is my code.
I have been working on this for a while and would love some help! Thank you
Notes: all_plots.ph_plot() is calling a function from another python program.
FLASK CODE:
#app.route('/read_ph', methods=["GET", "POST"])
def ph_plot():
if request.method == "POST":
a = request.form['read_ph']
all_plots.ph_plot(a)
time.sleep(3)
ph_plot = os.path.join(os.getcwd(), "images/ph_plot.png")
return render_template('live_stream.html', image_name=ph_plot)
#app.route('/read_ph/<ph_plot>', methods=["GET", "POST"])
def send_ph(ph_plot):
return send_from_directory("images", ph_plot)
HTML:
<html>
<body>
<h1>Data Monitoring Station</h1>
<h2>PH</h2>
<form method="POST" action="read_ph" >
<input name="read_ph" placeholder="Instances" type="text">
</form>
<button type="button">PH Graph</button>
<img src="{{ url_for('send_ph',ph_plot=image_name) }}" id="plot" width ="220" height ="220">
<hr>
<h5> Return to main page RETURN</h5>
<hr>
</body>
</html>
send_from_directory is generally for files that have actually been uploaded into a directory from a user. This is not what you're actually trying to do; you're:
Generating the plot data
Creating a plot and spending time with matplotlib rendering it
Saving this plot image to disk
Loading that image back off disk and sending it to a user
Cut out the middleman here of disk storage: create the data and send it straight to the template. Here's a crude example using plotly.js in a single file to get the data rendered on the front end. You can keep refreshing the page to get different graphs. But note, each plot is interactive; you can zoom, for example, export etc. things with the menu in the top right, show/hide the plot (which would make more sense if there were multiple traces). You don't get any of that by rendering a plot image.
from flask import Flask, render_template_string
import random
app = Flask(__name__)
# Normally you'd have this in the templates directory but for simplicity of
# putting everything into one file for an example, I'm using a template string
template = """
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="plot_div" style="width: 100%; height: 100%"></div>
<script type="text/javascript">
var trace1 = {
x: {{ plot_data.x_axis }},
y: {{ plot_data.y_axis }},
type: 'scatter',
name: 'Example'
};
var layout = {
title: "Test",
titlefont: {
family: 'Poppins',
size: 18,
color: '#7f7f7f'
},
showlegend: true,
xaxis: {
title: 'Axis Unit',
},
yaxis: {
title: 'Other Axis Unit',
},
margin: {
l: 70,
r: 40,
b: 50,
t: 50,
pad: 4
}
};
var data = [trace1];
Plotly.newPlot("plot_div", data, layout);
</script>
"""
def get_plot_data():
x = list(range(10))
y = [random.randint(0, 100) for i in range(10)]
return {'x_axis': x, 'y_axis': y}
#app.route('/')
def home():
plot_data = get_plot_data()
return render_template_string(template,
plot_data=plot_data)
if __name__ == '__main__':
app.run()
A common issue here is in passing datetime strings because they will be escaped. In this case, you'll need to use x: {{ plot_data.x_axis | safe }}. See Passing HTML to template using Flask/Jinja2 and How to make html markup show up?
Related
I am working with pie charts using Canvasjs and I want to make it clickable so that if the specific label is clicked it will send the clicked label value to the flask so that I can process that value in the flask also redirect me to the other page
My Chart Code on the index.html page
{%extends "master.html"%}
{%block chart%}
<script>
window.onload = function () {
var chart = new CanvasJS.Chart("chartContainer", {
animationEnabled: true,
exportEnabled: false,
theme: "light1", // "light1", "light2", "dark1", "dark2"
backgroundColor: "transparent",
**data: [{
click: function (e) {
var dataPoint = e.dataPoint.label;
window.open({{url_for('jazz_index')}}, "_blank");
},**
type: "doughnut",
startAngle: 20,
//innerRadius: 10,
indexLabelFontSize: 17,
indexLabelFontColor: "white",
indexLabel: "#percent%",
toolTipContent: "<b>{label}:</b> {y} (#percent%)",
dataPoints: [
{%for outgoing_number,outgoing_call_type in outgoing_calls%}
{ y: {{outgoing_call_type}}, label: "{{outgoing_number}}" },
{%endfor%}
]
}]
});
chart.render();
}
</script>
{%endblock%}
{%block body%}
<div class="card-body" id="chartContainer" style="min-height: 250px; height: 250px; max-height: 250px; max-width: 90%;">
</div>
{%endblock%}
In this code, I am making a pie chart and I want to pass variable dataPoint to the flask route jazz_index when the specific bar is clicked on the pie chart and I also want to know how to access that variable value in the route jazz_index so that I can process that dataPoint value and redirect the chart to the new page (jazz_index.html) which contains the information related to that dataPoint.
This is my jazz_index route
#app.route('/jazz', methods=['GET', 'POST'])
def jazz_index():
return render_template('jazz_index.html')
Please help me out with some code.
Thanks in advance
You are using an approach that is not ideal and there are many ways to improve it.
Having the browser open up a new page just to send data to the back end is not a great user experience.
Also by the time that your Javascript finally runs, the code for url_for will have already executed and rendered out a url. It's not possible for url_for to dynamically include a value from Javascript like you are attempting to do.
Without diving too deep into the front end changes that will be required to make this production ready, you can get it "working" by taking an approach similar to below and then improve from there.
{
data: [{
click: function(e) {
var dataPoint = e.dataPoint.label;
var url = `{{url_for('jazz_index')}}`;
fetch(url + `?dataPoint=${dataPoint}`)
.then(response => response.json())
.then(data => console.log(data));
}
}]
}
Then in flask you need to get the data from the url parameters.
#app.route('/jazz', methods=['GET', 'POST'])
def jazz_index():
# import request from Flask
datapoint = request.args.get('dataPoint')
return render_template('jazz_index.html')
I have seen similar questions up before without answer.
The aim is to show the image on the Flask application but with the altered config.
How is the config setting on Plotly charts altered when converting the graph into JSON and not using the fig.show()? If the fig.show() was used then the config would be amended inside there.
Flask Code:
Of course, if this was just to show the graph fig.show would be used but this is passing it through to JSON.
#example flask application with graph
from flask import Flask, render_template, request
import plotly.express as px
import plotly
import numpy as np
import json
#Flask settings.
secret_key = 'verysecretkey123lol' #secret key
app = Flask(__name__)
#simple image
img_rgb = np.array([[[255, 0, 0], [0, 255, 0], [0, 0, 255]],
[[0, 255, 0], [0, 0, 255], [255, 0, 0]]
], dtype=np.uint8)
fig = px.imshow(img_rgb)
#Flask
#app.route("/", methods=['GET', 'POST'])
def create():
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
return render_template('example.html', image = image)
#Run
if __name__ == "__main__":
app.secret_key = secret_key
app.run(debug = False)
HTML Code (example.html):
I have added the Config as a javascript variable below.
<!DOCTYPE html>
<html>
<head lang="en">
<title>Dissertation</title>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
</head>
<body>
<div class="chart" id="image">
<script>
var graphs = {{image | safe}};
var config = {"displaylogo": FALSE, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']};
Plotly.plot('image', graphs, {});
</script>
</div>
</body>
</html>
Any help would be greatly appreciated, thanks
Andrew
I struggled with trying to pass config options to Plotly when embedding the plots in a Flask application. In particular, I wanted to remove modebar buttons in my application. I discovered the way to achieve this from this related question.
Specifically, in the HTML code where you call the JS to display the plot, you set the plot config using the command Plotly.setPlotConfig().
Using your code example, you can set the config and display the plot with
<body>
<div class="chart" id="image">
<script>
var graphs = {{image | safe}};
var config = {"displaylogo": FALSE, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']};
Plotly.setPlotConfig(config);
Plotly.plot('image', graphs, {});
</script>
</div>
</body>
If you have already included some config file in the HTML by using Plotly.setPlotConfig(config); and use Plotly.newPlot("image", graphs) to reload data, it won't work again.
You must embed the config in your graphs object
//... your ajax call
graphs.config = {'displayModeBar': false}
Plotly.newPlot('image', graphs);
//... your ajax call
You can get this to work for layout options and config options without any hacky extra code in the HTML.
The key is remembering what render_template accepts as parameters (valid json).
Convert the image, layout, and config dictionaries to json. Your "image" here could also be a "graph_object" or "figure".
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
layout = json.dumps({'margin': dict(l=0, r=0, t=50, b=0)})
config = json.dumps({'displaylogo': False, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']})
Pass those to render_template.
return render_template('example.html', image=image, layout=layout, config=config)
Use all three in your html template, following the safe convention if you trust the data.
<script>
Plotly.newPlot('image', {{ image | safe }}, {{ layout | safe }}, {{ config | safe }});
</script>
Put that all together, and your final code would look like this
#app.route("/", methods=['GET', 'POST'])
def create():
image = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder)
layout = json.dumps({'margin': dict(l=0, r=0, t=50, b=0)})
config = json.dumps({'displaylogo': False, 'modeBarButtonsToAdd': ['drawclosedpath', 'eraseshape']})
return render_template('example.html', image=image, layout=layout, config=config)
<body>
<div class="chart" id="image">
<script>
Plotly.newPlot('image', {{ image | safe }}, {{ layout | safe }}, {{ config |
safe }});
</script>
</div>
</body>
That's it! No need to set the config/layout somewhere other than the place where you set your figure.
Using Flask as a web framework, Jinja2 for templates, and Plotly for graphics. I can create bar or scatter plots and now want to set up an onClick event: click on a data point and call back a Python script with the x, y, index information of that data point. I thought, importing a function via the context processor would work. However, it works only for static information as in var pts = {{ utility(0) }}, but not for dynamic information as in var pts = {{ utility(data.points[0].pointNumber) }}. Error message: >jinja2.exceptions.UndefinedError: 'data' is undefined. I assume, the context processor works before the website is rendered, and so doesn't find that variable at that time. However, the standard alert function does work. So, what happens behind the scene, and how can I make my context function work similarily?
My Python script
from flask import Flask, render_template
import plotly, plotly.graph_objs as go
import json
app = Flask(__name__)
#app.context_processor
def utility_processor():
def utility(index):
with open('junk.txt', 'w') as file:
file.write('data index: %i' % index)
return str(index)
return dict(utility=utility)
def create_plot():
data = [go.Bar(x=[1,2],y=[3,4])]
graphJSON = json.dumps(data, cls=plotly.utils.PlotlyJSONEncoder)
return graphJSON
#app.route('/')
def junk():
plot=create_plot()
return render_template('junk.html', plot=plot)
if __name__ == '__main__':
app.run(debug=True)
My website:
<!doctype html>
<html>
<body>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<div id="plotDiv">
<script>
var myPlot = document.getElementById('plotDiv');
var graphs = {{plot | safe}};
Plotly.newPlot(myPlot, graphs, {}, {});
myPlot.on('plotly_click', function(data){
var pts = {{ utility(0) }};
var pts = {{ utility(data.points[0].pointNumber) }};
alert(
'alert: '+data.points[0].x+', '+data.points[0].y +
'\nutility: '+pts);
});
</script>
</div>
</body>
</html>
Problem
I am trying to display image-files using AJAX in Flask. More specifically, I want to display an image once a button is clicked and display the next image once the button is clicked again (like a slide-show). The filenames of the images are stored in my database. I query the database to get a list of filenames for the current user and combine each filename with the rest of the path (path to where the images are stored on disk) in order to display the image.
So far, I manage to get the first image of the current user. However, I can't figure out a way to keep track of which image is the next one to show.
I tried using a global variable as a counter (file_counter) which should serve as an index. I want to increase file_counter by 1 each time an ajax request is made in order to get the next file but the counter does not increase upon subsequent calls nor does it throw an error.
Question
How do I need to initialize the global variable (file_counter) in order for it to store its value across multiple calls? Furthermore, is the usage of global variables the correct way of doing this?
HTML
<div id="ajax-field"></div>
<button class="btn btn-block" id="next-button"><p>Next Image!</p></button>
AJAX:
$('#next-button').click(function(){
$("#ajax-field").text("");
$.ajax({
url: "/get_data",
type: "POST",
success: function(resp){
$('#ajax-field').append(resp.data);
}
});
});
Routing:
global filenames
global file_count
#app.route("/get_data", methods=['POST'])
def get_data():
try: # Is intended to fail on the first run in order for the global variables to be initialized. However it keeps failing on subsequent runs
display_img = filenames[file_count]
file_count +=1
except:
filenames = []
# current_user.uploads returns all file-objects of the current user
user_uploads = current_user.uploads
for file in user_uploads:
# file.filename returns the respective filename of the image
filenames.append(file.filename)
#filenames is now a list of filenames i.e. ['a.jpg','b.jpg','c.jpg'...]
display_img = filenames[0]
file_count = 1
path = "image_uploads/4_files/"+display_img
return jsonify({'data': render_template('ajax_template.html', mylist = path)})
ajax_template.html:
<ul>
{% block content %}
<li>
<img id="selected-image-ajax" src="{{url_for('static',filename=mylist)}}" class="img-thumbnail" style="display:block; margin:auto;"></img>
</li>
{% endblock content %}
</ul>
As #roganjosh pointed out, a session is the optimal way to store information across multiple requests. This solution presents an implementation of the photo display using flask.session to store the counter:
import flask, random, string
app = flask.Flask(__name__)
app.secret_key = ''.join(random.choice(string.printable) for _ in range(20))
#to use flask.session, a secret key must be passed to the app instance
#app.route('/display_page', methods=['GET'])
def display_page():
'''function to return the HTML page to display the images'''
flask.session['count'] = 0
_files = [i.filename for i in current_user.uploads]
return flask.render_template('photo_display.html', photo = _files[0])
#app.route('/get_photo', methods=['GET'])
def get_photo():
_direction = flask.request.args.get('direction')
flask.session['count'] = flask.session['count'] + (1 if _direction == 'f' else - 1)
_files = [i.filename for i in current_user.uploads]
return flask.jsonify({'photo':_files[flask.session['count']], 'forward':str(flask.session['count']+1 < len(_files)), 'back':str(bool(flask.session['count']))})
The display_page function will be called when a user accesses the /display_page route and will set the count to 0. get_photo is bound to the /get_photo route and will be called when the ajax request is sent.
photo_display.html:
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
</head>
<body>
<div class='image_display'>
<img src="{{photo}}" id='photo_display' height="100" width="100">
<table>
<tr>
<td class='back'></td>
<td class='forward'><button id='go_forward' class='navigate'>Forward</button></td>
</tr>
</table>
</div>
</body>
<script>
$(document).ready(function(){
$('.image_display').on('click', '.navigate', function(){
var direction = 'b';
if ($(this).prop('id') === 'go_forward'){
direction = 'f';
}
$.ajax({
url: "/get_photo",
type: "get",
data: {direction: direction},
success: function(response) {
$('#photo_display').attr('src', response.photo);
if (response.back === "True"){
$('.back').html("<button id='go_back' class='navigate'>Back</button>")
}
else{
$('#go_back').remove();
}
if (response.forward === "True"){
$('.forward').html("<button id='go_forward' class='navigate'>Forward</button>")
}
else{
$('#go_forward').remove();
}
},
});
});
});
</script>
</html>
The javascript in display_page.html communicates with the backend, and updates the img tag src accordingly. The script adds or removes the navigation buttons, depending on the current count value.
Demo:
To test the solution above, I created an image folder to store random photographs to display:
I have 1 very simple web application I am building right now but am very new to flask and jinja (and web development as a whole actually).
I have a watch folder, which will be getting an image sent to it via ftp on a pulse for ever. This wtch folder will only ever have one image in. Every 1 minute, the old image is replaced by a new image, with a new timestamp.
I would like to dynamically update the page, (and displayed timestamp) on a pulse as well, without having to reload any banners or static images that I will add later. I only want to update the following two lines out of the "Channels.Jinja" sample to follow.
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
Channels.Jinja
<!DOCTYPE HTML>
<html>
<head>
<title>Training</title>
</head>
<body bgcolor=white>
<div id=main>
<br>Date and Time of Screenshot <br/>
<br>{{screenshot_datetime}}<br/>
<img src={{screenshot_location}} width="100%"/>
</div>
<div id='test'>
<p>
<script>
var myVar=setInterval(function(){get_image()},1000);
function get_image() {
$.ajax({
type: 'GET',
cache: false,
url: 'get_data',
success: function({{data}}) {
$('img').attr('src', data);
}
});
}
</script>
</p>
</div>
</body>
</html>
Channels.py
def render_channel_route(cr):
static_folder = os.path.join('static',cr)
file_list = os.listdir(static_folder)
channel_files = [f for f in file_list if f.startswith(cr)]
if not channel_files :
logger.error('Could not find image file for Channel. File should start with {0}'.format(cr))
abort(404)
img = os.path.join(static_folder,file_list[0])
ts = get_time_from_filename(file_list[0],cr)
return render_template('Channels.jinja',screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
#app.route('/channel01-10')
def first_tab():
return render_channel_route('channel01-10')
#app.route('/get_data', methods=['GET'])
def get_data():
return render_template('Channels.jinja',
screenshot_datetime=time.strftime('%c',ts),screenshot_location=img)
Im at a loss, Ive been bumbling around for a while now. Any and all advice is welcome! I am seeing a 304 response upon refresh, but not even the timer i am trying to put on it is working. Pardon sloppy code, highly volatile code is getting changed often -_-
I don't know it there is a "special" way to deal with Ajax using some Flask extension, but in the "normal" Ajax flow first you need to use url_for to put the correct url in your Ajax call and return the data formatted in some way (in my example in JSON) and not to render the template again:
$.ajax({
type: 'GET',
cache: false,
url: "{{ url_for('get_data') }}",
success: function(resp){
$('img').attr('src', resp.url);
$('#sst').html(resp.time);
}
});
So, in your get_data function in your controller you have to get the time and the path again for your image an then return some like this (to fit in my example before):
from flask import json
#app.route('/get_data', methods=['GET'])
def get_data():
#get time and path
time=...
path=...
return json.dumps({time:time,url:path}), 200, {'Content-Type':'application/json'}
Look that I use $('#sst') so you have to put in your HTML:
<br><span id='sst'>{{screenshot_datetime}}</span><br/>