1

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>

        <a href="read_ph"><button type="button">PH Graph</button></a>

        <img src="{{ url_for('send_ph',ph_plot=image_name) }}" id="plot" width ="220" height ="220">

    <hr>
    <h5> Return to main page <a href="/"class="button">RETURN</a></h5>
    <hr>

  </body>
</html>
  • Simple answer: use a JS plotting library rather than serve varying static content. Possibly not what you wanted to hear but did you try it? Is there a reason you don't want to do this? – roganjosh Nov 21 '19 at 21:41
  • sweet! Im just knew to using flask and writing html. I just found this video https://www.youtube.com/watch?v=9Ic79kOBj_M which seems relevant – Mike Sandstrom Nov 21 '19 at 21:46
  • would you mind summarizing what I would have to do? Would I write javascript inside my html code? – Mike Sandstrom Nov 21 '19 at 21:48
  • I use plotly.js which I believe is a wrapper around D3 (plotly.js is tough to search for in documentation because there is also plotly (without the `.js` in Python and I'm not suggesting the latter version). The rendering is quick; pretty much equivalent to matplotlib. Other than that, I haven't used `send_from_directory` but it doesn't look related to template rendering – roganjosh Nov 21 '19 at 21:48
  • ok cool. Ive been using matplotlib to make my graphs. So im assuming then plotly.js is using a javascript version of matplotlib to display graphs on webservers like flask. I dont know any javascript so this could be a good chance to learn some of the language – Mike Sandstrom Nov 21 '19 at 21:49
  • I can give you a demo but it doesn't strictly answer your question so I don't think it's appropriate on its own. Let me research `send_from_directory` for a bit first because I'd like to know how that works myself – roganjosh Nov 21 '19 at 21:49
  • cool man, big thanks from los angeles – Mike Sandstrom Nov 21 '19 at 21:51
  • https://www.youtube.com/watch?v=Y2fMCxLz6wM&t=1030s this is where i found information on the send_from_directory method – Mike Sandstrom Nov 21 '19 at 21:51
  • I see, so I would have to write javascript code in a seperate file and store it in my static file. then I would reference the javascript that can manipulate my graphs from my html code, correct? – Mike Sandstrom Nov 21 '19 at 22:01

1 Answers1

1

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:

  1. Generating the plot data
  2. Creating a plot and spending time with matplotlib rendering it
  3. Saving this plot image to disk
  4. 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?

roganjosh
  • 12,594
  • 4
  • 29
  • 46
  • I cant thank you enough. You just gave me so much new knowledge,man. Turned me on to a whole new world now hahaha. Thanks bro – Mike Sandstrom Nov 21 '19 at 22:38
  • @MikeSandstrom no problem. If you think this has answered your question, please consider marking it accepted. I notice you also have an AJAX question left open but it's also a pretty decent self-contained example. See [accepting an answer](https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work) – roganjosh Nov 21 '19 at 22:40
  • yea I feel using the method you mentioned will be able to solve a lot of my problems with making this webserver. So basically i'll just have more javascript within my html code? In that case I should obviously learn javascript haha correct? – Mike Sandstrom Nov 21 '19 at 22:49
  • @MikeSandstrom I've never really sat down to learn JS but the more I do learn, the better the outcome. I've given the bare minimum of JS here, you'll spend more time faffing with HTML I imagine to make this fit into the site. But, generally, the answer is yes; you'll need to learn more JS. Combine this approach with your other AJAX question and you'll have a decent foundation in user experience (there's nothing stopping this plot being updated by AJAX rather than refreshing the whole page other than my desire to keep it simple) – roganjosh Nov 21 '19 at 22:53
  • hey man! Worked on this for a bit and got it to work with my little project. However, I am running into one problem. I have my dates on the x axis and they are converted to numbers 0 and up on a scale on 1. How do I keep my original date formatting in plotly? i saw for the python version they accept pandas datetime object but it didnt mention anything like that for javascript. I am already retrieving my dates from an sql table using pandas read_from_sql function. they are in string form currently. (HH:MM:SS) – Mike Sandstrom Nov 22 '19 at 05:33
  • 1
    @mikesandstrom I anticipated this and should have put it in the answer. You need `x: {{ plot_data.x_axis|safe }}` and the same for y data. I'll edit later, on a phone atm but it's a common problem – roganjosh Nov 22 '19 at 08:48
  • ""x: '[Timestamp('2019-11-22 16:50:32')"" thats an example of what a row from my x-column looks like when I inspect the page. However, only numbers incrementing by one appear. Notes::when I use the "safe" method within my html a graph does not appear. Even when converted to datetime in pandas or as a string the x axis dates dont seem to appear on the screen. – Mike Sandstrom Nov 22 '19 at 17:34
  • Format as strings before you pass them to the template @MikeSandstrom – roganjosh Nov 22 '19 at 17:38
  • Yea i dont know Ive tried many different things now and nothing has worked. I used plotly's timeseries example in javascript and formatted my dates exactly as they did in string format and it hasnt worked. I tried turning the y axis from a float to a string and that didnt work either. Tried using datetime variables and it didnt work. I keep getting "" characters on the webpage before my data. I dont know if its a discrepency between python and javascript or something, or if I have to use this " x: unpack(rows, 'Date')" unpack function...but I think thats only to retrieve csv data. – Mike Sandstrom Nov 22 '19 at 18:57
  • Sorry to keep commenting, but I ended up realizing I was forgetting a ":" when formatting my date to send, then with the "safe" method my data looks normal when inspecting it on the webpage. HOWEVER LOL, now it still wont show on the page when I use the "safe" method even though my data is in the appropriate format. Any help would be huge man, thank you – Mike Sandstrom Nov 22 '19 at 21:56
  • FIGURED IT OUT, sorry if I bugged you with my questions hombre. Genuinely appreciate your help. thanks – Mike Sandstrom Nov 22 '19 at 22:03
  • @MikeSandstrom you haven't bugged me; do I need to edit my answer in response or was it a separate issue? – roganjosh Nov 22 '19 at 22:14
  • No your answer was fine, it was a separate issue on my end. By the way, is it possible to encase this code in the script tag into a .js file and store it in my static files folder? and then run it whenever I receive a post request from a form with a certain id or name tag? – Mike Sandstrom Nov 23 '19 at 06:21