Dynamisch in Python generierte Grafiken in HTML anzeigen

Django, Flask, Bottle, WSGI, CGI…
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Hallo,
ich nutze Flask um einen Web-Server zu betreiben. Mit seaborn/matplotlib erstelle ich dann dynamisch Grafiken in Python und weise diese der Variablen img_buffer vom Typ IOBytes zu. Dann transformiere ich diese Daten in einen str img_tag wie folgt:

Code: Alles auswählen

...
import base64
str_equivalent_image = base64.b64encode(img_buffer.getvalue()).decode()
img_tag = "<img src='data:image/png;base64," + str_equivalent_image + "'/>"
Jetzt versuche ich dieses img_tag in der index.html so einzubinden, dass dieses Bild angezeigt wird. Folgender Versuch war nicht erfolgreich:

Code: Alles auswählen

...
<body>
  <h1>Graph</h1>
  {img_tag}
</body>
</html>
Kann mir da jemand weiterhelfen? Kann ja nicht so schwer sein:-) Aber ich finde einfach keine Lösung.

VG
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Lessi: Du musst dafür sorgen, dass `img_tag` da 1:1 so ins HTML übernommen wird und nicht spezielle Zeichen wie "<" so kodiert werden, dass sie als "<" in der Seite als Text angezeigt werden können. Jinja2 hat da einen Filter für: `safe()` (das Gegenteil vom `escape()`-Filter). Also:

Code: Alles auswählen

...
<body>
  <h1>Graph</h1>
  {img_tag|safe}
</body>
</html>
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Leider funktioniert das so nicht. Im Browser bekomme ich dann den kryptischen Inhalt der Variable angezeigt:

Graph
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAYAAAC+ZpjcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dpAAAhxElEQVR4nO3df2zV9b348Rco9Ae17a6wWvgyZ3ReuahUsMqFbo4t8zr9w4hm5t7c2C1kE8ONGu9VL0u23d47h4uCMyHcbOGm2d1dconsurgxssQhZqBFQNcpZAsb0YuGauCGVgrthfTz.....

Ich möchte ja, dass es als HTML interpretiert wird. Noch eine andere Idee?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

Was hast Du jetzt genau versucht? Mit dem safe-Filter funktioniert es.
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Bitte ignorier meinen letzte Post. Da hatte ich noch einen print Ausgabe für das img_tag drin.

Im Endeffekt habe ich es in der html-Datei so gemacht wie du vorgeschlagen hast mit "safe":

Code: Alles auswählen

...
<body>
  <h1>Graph</h1>
  {img_tag|safe}
</body>
</html>
Wenn ich den Server dann ausführe und über den Browser darauf zugreife gibt er mir folgendes aus:

Graph
{img_tag|safe}
Der entsprechene Quelltext im Browser ist dann genau wie oben.

Bei doppelten geschweiften Klammern {{img_tag|safe}} gibt er mir übrigens gar nichts aus. Im Quelltext des Browsers ist dort dann einfach eine leere Zeile
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Dann hast Du für `img_tag` nichts an das Template übergeben. Den Teil vom Code zeigst Du ja nicht. (Die fehlenden geschweiften Klammern hatte ich aus Deinem ersten Beitrag übernommen.)
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
DeaD_EyE
User
Beiträge: 1020
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Ein Beispiel:

Code: Alles auswählen

from base64 import b64decode, b64encode
from io import BufferedReader, BytesIO

from flask import Flask, render_template

# diesen Import brauchst du später nicht
# da render_template von Flask verwendet werden sollte
from jinja2 import Template


def inline_image(img: bytes | BytesIO | BufferedReader, format: str = "png"):
    """
    BytesIO und geöffnete Dateien (im Raw Modus) werden automatisch in Bytes umgewandelt
    und dann als <img> tag ausgegeben.
    """
    if isinstance(img, BytesIO):
        img = img.getvalue()
    elif isinstance(img, BufferedReader):
        img = img.read()

    return f'<img src="data:image/{format};base64,{b64encode(img).decode()}" alt="Red dot" />'


# Beispielbild hardcoded
my_image = b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAFcAAABXAQMAAABLBksvAAAABlBMVEUAAAD///+l2Z/dAAAAAnRS"
    "TlP//8i138cAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAC2SURBVDiNzdOxEcMgDAVQ+VzQZQLdaQ06"
    "r2QWsJMJvJI61shdFoCOgosiu7AbIspE1TsKIf4JkKvgL5wAFo8AZDhLXaCuYppx9ji7jhdfu14j"
    "dix6KZ4zNK3zh3i9pWmt5OnMpOnkAOC5RstvNyZXh0KGc8QQxztYlqhtJRcxnCa8TeNDLGtlfglb"
    "3nNgeTAZ1jxnT5uYZgxFT8S27snAHYc9CrIsde955PDNx/y0FTL8q7/W9gcCUcBHF+7vZQAAAABJ"
    "RU5ErkJggg=="
)


# Bitte jinja2.Template nicht direkt verwenden,
# da sonst nichts ausgeschlossen (escaped) wird.
# dafür die Funktion flask.render_template verwenden
template = Template(
    """
<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Test QR</title>
  </head>

  <body>
    {{ body_image }}
  </body>
</html>
"""
)

app = Flask(__name__)


@app.route("/")
def index():
    # render_template("blabla.tpl", body_image=inline_image(my_image))
    return template.render(body_image=inline_image(my_image))
Anstatt nur data einzufügen, erstellt die Funktion gleich den gesamten Tag. Das alt Attribut ist auch nützlich, vor allem wenn blinde Menschen die Seite besuchen sollen. Das könnte man z.B. auch mit der Funktion übergeben. Momentan ist es statusch. Nur das mit dem |safe verstehe ich noch nicht so ganz. Was ist an einem Base64-String für HTML problematisch? In meinem Beispiel verwende ich den Filter nicht.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@DeaD_EyE: Du schreibst doch selbst, dass Du Template falsch benutzt, weil Du kein Environment mit auto-escape hast. Warum wunderst Du Dich dann, dass kein safe-Filter nötig ist?

Konstanten schreibt man komplett GROSS, my_-Präfixe sind nicht sinnvoll, weil sie keine relevante Information enthalten.
Typ-Annotations sollten zutreffend sein. Warum kann `img` nur bytes, BytesIO oder BufferedReader sein? Warum nicht auch bytearray oder irgendein anderes Objekt, das eine `read´-Methode bereitstellt? Ein schönes Beispiel, wie beschränkt Typ-Annotationen sind.

Code: Alles auswählen

from base64 import b64decode, b64encode
from flask import Flask, render_template_string


# Beispielbild hardcoded
IMAGE = b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAFcAAABXAQMAAABLBksvAAAABlBMVEUAAAD///+l2Z/dAAAAAnRS"
    "TlP//8i138cAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAC2SURBVDiNzdOxEcMgDAVQ+VzQZQLdaQ06"
    "r2QWsJMJvJI61shdFoCOgosiu7AbIspE1TsKIf4JkKvgL5wAFo8AZDhLXaCuYppx9ji7jhdfu14j"
    "dix6KZ4zNK3zh3i9pWmt5OnMpOnkAOC5RstvNyZXh0KGc8QQxztYlqhtJRcxnCa8TeNDLGtlfglb"
    "3nNgeTAZ1jxnT5uYZgxFT8S27snAHYc9CrIsde955PDNx/y0FTL8q7/W9gcCUcBHF+7vZQAAAABJ"
    "RU5ErkJggg=="
)


TEMPLATE = """<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Test QR</title>
  </head>

  <body>
    {{body_image|safe}}
  </body>
</html>
"""

def inline_image(image, format="png"):
    """
    BytesIO und geöffnete Dateien (im Raw Modus) werden automatisch in Bytes umgewandelt
    und dann als <img> tag ausgegeben.
    """
    if hasattr(image, 'read'):
        image = image.read()
    return f'<img src="data:image/{format};base64,{b64encode(image).decode()}" alt="Red dot" />'


app = Flask(__name__)

@app.route("/")
def index():
    return flask.render_template_string(TEMPLATE, body_image=inline_image(IMAGE))

if __name__ == "__main__":
    app.run()
Aber warum sollte man überhaupt das gesamte <img>-Tag einfügen wollen, warum nicht nur die Daten?

Code: Alles auswählen

from base64 import b64decode, b64encode
from flask import Flask, render_template_string


# Beispielbild hardcoded
IMAGE = b64decode(
    "iVBORw0KGgoAAAANSUhEUgAAAFcAAABXAQMAAABLBksvAAAABlBMVEUAAAD///+l2Z/dAAAAAnRS"
    "TlP//8i138cAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAC2SURBVDiNzdOxEcMgDAVQ+VzQZQLdaQ06"
    "r2QWsJMJvJI61shdFoCOgosiu7AbIspE1TsKIf4JkKvgL5wAFo8AZDhLXaCuYppx9ji7jhdfu14j"
    "dix6KZ4zNK3zh3i9pWmt5OnMpOnkAOC5RstvNyZXh0KGc8QQxztYlqhtJRcxnCa8TeNDLGtlfglb"
    "3nNgeTAZ1jxnT5uYZgxFT8S27snAHYc9CrIsde955PDNx/y0FTL8q7/W9gcCUcBHF+7vZQAAAABJ"
    "RU5ErkJggg=="
)


TEMPLATE = """<!DOCTYPE html>
<html lang="de">
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Test QR</title>
  </head>

  <body>
    <img src="data:image/{{format}};base64,{{image_data}}" alt="{{alt}}" />
  </body>
</html>
"""

app = Flask(__name__)

@app.route("/")
def index():
    return flask.render_template_string(TEMPLATE,
        image_data=b64encode(IMAGE).decode(),
        format="png",
        alt="Hello World")

if __name__ == "__main__":
    app.run()
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

musst oder willst du das so machen? Grundsätzlich kannst du ein BytesIO Objekt ja auch wie eine Datei ausliefern bzw. von der Webseite nachladen lassen. Dann brauchst du den Umweg über das Base64 Encoding nicht.

Gruß, noisefloor
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Einmal mein etwas reduzierter aber lauffähiger Code. Später soll der Webserver eine Seite zur Verfügung stellen, welche regelmäßig Daten von einer URL ausliest und diese in einem Diagram visualisiert. In dem aktuellen Code geschieht die Aktualisierung alle 0,5 Sekunden. Das Bild mit dem Graphen wird aber (noch) nicht erneuert. Mein aktuelles Problem ist es halt die Grafik in dem Browser (über HTML) vom Server aus (!!!) in einem festen Intervall zu erneuern. Es soll nicht erst vom Client ein Aufforderung dazu erfolgen müssen! Das ist wohl der große Unterschied zu den Beispielen.
(Wenn ich das schaffe, geht es zum nächsten Schritt, die Grafik immer neu zu erzeugen.)

server.py

Code: Alles auswählen

from gevent import monkey; monkey.patch_all()
from flask import Flask, Response, render_template, stream_with_context, url_for, send_file, render_template
from gevent.pywsgi import WSGIServer
import json
import time
import io
import base64
from requests.exceptions import HTTPError
import matplotlib as mpl
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import matplotlib.pyplot as plt
import seaborn as sns

fig,ax=plt.subplots(figsize=(6,6))
ax=sns.set(style="darkgrid")

x=[i for i in range(100)]
y=[i for i in range(100)]

app = Flask(__name__)
counter = 100

@app.route("/")
def render_index():
    return render_template("index.html")

@app.route('/getVisualizeData')
def getVisualizeData():
    sns.lineplot(x,y)
    canvas=FigureCanvas(fig)
    img = io.BytesIO()
    fig.savefig(img)
    img.seek(0)
    return img

@app.route("/listen")
def listen():

    def respond_to_client():
        while True:
            img_buffer=getVisualizeData()
            str_equivalent_image = base64.b64encode(img_buffer.getvalue()).decode()
            img_tag = "<img src='data:image/png;base64," + str_equivalent_image + "'/>"
            _data = json.dumps({"img_tag":img_tag})
            yield f"id: 1\ndata: {_data}\nevent: online\n\n"
            time.sleep(0.5)
        return Response(respond_to_client(), mimetype='text/event-stream')

if __name__ == "__main__":
    http_server = WSGIServer(("localhost", 8080), app)
    http_server.serve_forever()

index.html

Code: Alles auswählen

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>APP</title>
</head>
<body>
  <h1>Graph</h1>
  {img_tag}
  <script>
    var eventSource = new EventSource("/listen")
    eventSource.addEventListener("message", function(e) {
      console.log(e.data)
    }, false)

    eventSource.addEventListener("online", function(e) {
      data = JSON.parse(e.data)
      document.querySelector("#img_tag").innerText = data.img_tag
    }, true)
  </script>
</body>
</html>
Zuletzt geändert von Lessi am Donnerstag 25. August 2022, 22:43, insgesamt 1-mal geändert.
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

__blackjack__ hat geschrieben: Donnerstag 25. August 2022, 16:32 Dann hast Du für `img_tag` nichts an das Template übergeben. Den Teil vom Code zeigst Du ja nicht. (Die fehlenden geschweiften Klammern hatte ich aus Deinem ersten Beitrag übernommen.)
Doch, das habe ich. Es wird img_tag (mit Inhalt) an das Template übergeben. Es wird nur nicht korrekt angezeigt. Denn wenn ich in der HTML Datei das img_tag folgendermaßen einbinde, erhalte ich die entsprechenden Daten; aber halt nur als Text

index.html Ausschnitt:

Code: Alles auswählen

 <h1>Graph</h1>
  <div id="img_tag"></div>
Ausgabe im Browser:
Graph
<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAAJYCAYAAAC+ZpjcAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMywgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/NK7nSAAAACXBIWXMAAA9hAAAPYQGoP6dp...
Zuletzt geändert von Lessi am Donnerstag 25. August 2022, 22:45, insgesamt 2-mal geändert.
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

noisefloor hat geschrieben: Donnerstag 25. August 2022, 19:44 Hallo,

musst oder willst du das so machen? Grundsätzlich kannst du ein BytesIO Objekt ja auch wie eine Datei ausliefern bzw. von der Webseite nachladen lassen. Dann brauchst du den Umweg über das Base64 Encoding nicht.

Gruß, noisefloor
Mein Ziel ist es die Grafik, welche auf sich ständig ändernden Daten basiert und in Python kalkuliert wird, in einem festen Intervall im Browser vom Server (!!!!!) zu aktualisieren. Ich schaffe das mit einem einfachen Counter. Aber halt nicht mit der Grafik. Wenn es einen einfacheren Weg gibt, bin ich dafür offen. Meinen Code habe ich gepostet
Benutzeravatar
__blackjack__
User
Beiträge: 13100
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Lessi: Der Code den Du gepostet hast ist nicht lauffähig weil die Einrückung kaputt ist, und da wo Du schreibst Du hast das wie folgt eingebunden, ist ein Template(ausschnitt) wo *gar kein Platzhalter* drin ist, das kann so natürlich nicht funktionieren.

Im JavaScript machst Du einen äquivalenten Fehler zum Template ohne `safe`-Filter. `innerText` ist der Inhalt als Text so wie er in der Zeichenkette steht. Wenn da "<b>Achtung</b>" steht, dann wird das genau *so* *angezeigt* und nicht etwa <b> und </b> als HTML interpretiert und "Achtung" in Fettdruck angezeigt. Das wäre `innerHTML` wenn Du HTML setzen willst das auch als HTML gesehen/interpretiert wird.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
DeaD_EyE
User
Beiträge: 1020
Registriert: Sonntag 19. September 2010, 13:45
Wohnort: Hagen
Kontaktdaten:

Bei dieser Zeile die Einrückung um eine Ebene verringern.

Code: Alles auswählen

return Response(respond_to_client(), mimetype='text/event-stream')
Ansonsten passiert gar nichts, da in der Funktion listen die Funktion respond_to_client definiert wird, aber nie aufgerufen.
Die Reduzierung der Einrückung bewirkt dann, dass das Response-Objekt mit dem Generator respond_to_client aufgerufen und dann auch zurückgegeben wird.

Dann musst du dir noch mal die Syntax ansehen, um Strings zu formatieren. Es gibt auch f-strings. Bin zwar kein JS-Experte, aber etwas einfacher wäre das sicherlich, wenn du eine Datei zurücklieferst und diese kannst du ja alle x-Sekunden durch Javascript abholen lassen. Das geht auch mit bytes und BytesIO. Wobei schon allein durch die Nutzung von Matplotlib Schreibzugriffe auf das Dateisystem stattfinden (hauptsächlich Cache).

Dateien kann man übrigens mit flask.send_file zurückgeben. Wenn man Binärdaten (bytes, BytesIO) als Quelle angibt, kann der Mimetype nicht ermittelt werden. Man kann aber einen Dateinamen angeben, wodurch dann auch der Mimetype automatisch gesetzt wird.

Erweiterung des Beispielcodes.

Code: Alles auswählen

# zusätzlicher import
from flask import send_file

# vorheriger code

# zusätzliche Route
@app.route("/statistik.png")
def stat():
    return send_file(BytesIO(my_image), download_name="statistik.png")
Die Route Statistik liefert das Bild als statistik.png aus.
Ganz wichtig, niemals den User den Pfad bzw. den Dateinamen bestimmen lassen.
Das gilt auch, wenn z.B. durch JavaScript ein get-Request gemacht wird, um irgendwas abzohlen.
Wenn dann der Query in der URL oder Inhalt im Body (json z.B.) blind von der Webanwendung übernommen wird, kann der Angreifer auch andere Dateien öffnen.

Dann muss ich noch anmerken, dass der Filter safe im vorherigen Beispiel verwendet worden ist. Das soll davor schützen, dass HTML-Quelltext von der Interpretation ausgeschlossen wird. Das ist besonders dann wichtig, wenn man sich den HTML-Tag im Programm erstellt. Mit Beispiel wo jinja2.Template direkt verwendet wird, geht das, weil dort nicht automatisch Filter angewandt werden. D.h. der String wird nicht verändert. Macht man es hingegen richtig mit fllask.render_template, wird der Filter immer angewandt, es seiden man verwendet den Filter safe. D.h. wenn du weiterhin den gesammten HTML-Tag als string ausliefern willst, wird safe benötigt.
sourceserver.info - sourceserver.info/wiki/ - ausgestorbener Support für HL2-Server
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Lessi: so langsam kommt heraus, was Du eigentlich vorhast. Mit dieser Information sieht die Lösung auch ganz anders aus.

Funktionen werden komplett klein geschrieben. Benutze keine globalen Variablen. Der `FigureCanvas`, den Du da erzeugst, wird gar nicht benutzt. Die List-Comprehensions für `x` und `y` könnte man durch einen einfachen list-Aufruf ersetzen. Du malst ständig in den selben Plot, der wir also mit der Zeit ziemlich voll.

Wenn Du einen Film abspielen willst, dann ist Motion-JPEG, bzw. geht das auch mit Motion-PNG, das richtige, denn das unterstützt jeder Browser schon von sich aus, ganz ohne Javascript.
Hier werden die Daten auch Binär übertragen, so dass man die Konvertierung in base64 nicht braucht, was Bandbreite, Speicher und CPU spart.

Code: Alles auswählen

import time
import io
import numpy as np
import matplotlib.pyplot as plt
from flask import Flask, Response
import matplotlib
matplotlib.use('agg')

def get_visualize_data():
    x = range(100)
    y = np.random.random(100)
    fig, axis = plt.subplots(figsize=(6,6))
    axis.plot(x, y)
    img = io.BytesIO()
    fig.savefig(img)
    plt.close(fig)
    return img.getvalue()

app = Flask(__name__)
@app.route("/")
def index():
    return """
    <body>
        <h1>Visualize</h1>
        <img src="/visualize" />
    </body>
    """

def visualize_loop():
    while True:
        frame = get_visualize_data()
        yield b'--frame\r\nContent-Type: image/png\r\n\r\n' + frame + b'\r\n'
        time.sleep(1)

@app.route("/visualize")
def visualize():
    return Response(visualize_loop(), mimetype='multipart/x-mixed-replace; boundary=frame')


if __name__ == "__main__":
    app.run(threaded=True)
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Ich habe den Source Code nochmal um einen Counter erweitert, um klar zu machen wie der Code funktioniert. Der Counter wird in Python alle 0,5s inkrementiert und automatisch im Browser angezeigt. Das img_tag wird später dann auch dynamisch verändert werden. (Doch da bin ich noch nicht). Jetzt versuche ich erstmal nur diese Grafik auch wirklich darzustellen. Die Daten werden an HTML übergeben aber offensichtlich nicht richtig dargestellt (ob mit oder ohne "safe").

server.py:

Code: Alles auswählen

from gevent import monkey; monkey.patch_all()
from flask import Flask, Response, render_template, stream_with_context, url_for, send_file, render_template
from gevent.pywsgi import WSGIServer
import json
import time
from requests.exceptions import HTTPError
import matplotlib as mpl
import io
import base64
from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
import matplotlib.pyplot as plt
import seaborn as sns

fig,ax=plt.subplots(figsize=(6,6))
ax=sns.set(style="darkgrid")

x=[i for i in range(100)]
y=[i for i in range(100)]

app = Flask(__name__)
counter = 100

@app.route("/")
def render_index():
  return render_template("index.html")

@app.route('/getVisualizeData')
def getVisualizeData():
  sns.lineplot(x,y)
  canvas=FigureCanvas(fig)
  img = io.BytesIO()
  fig.savefig(img)
  img.seek(0)
  return img

@app.route("/listen")
def listen():

  def respond_to_client():
    while True:
      global counter
      counter+=1
      img_buffer=getVisualizeData()
      str_equivalent_image = base64.b64encode(img_buffer.getvalue()).decode()
      img_tag = "<img src='data:image/png;base64," + str_equivalent_image + "'/>"
      _data = json.dumps({"counter":counter, "img_tag":img_tag})
      yield f"id: 1\ndata: {_data}\nevent: online\n\n"
      time.sleep(0.5)
  return Response(respond_to_client(), mimetype='text/event-stream')

if __name__ == "__main__":
  http_server = WSGIServer(("localhost", 8080), app)
  http_server.serve_forever()
index.html

Code: Alles auswählen

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>APP</title>
</head>
<body>
  <h1>Counter</h1>
  <div id="counter"></div>
  <h1>Graph</h1>
  {img_tag|safe}
  <script>
    var eventSource = new EventSource("/listen")
    eventSource.addEventListener("message", function(e) {
      console.log(e.data)
    }, false)

    eventSource.addEventListener("online", function(e) {
      data = JSON.parse(e.data)
      document.querySelector("#counter").innerText = data.counter
      document.querySelector("#img_tag").innerText = data.img_tag
    }, true)
  </script>
</body>
</html>
__deets__
User
Beiträge: 14536
Registriert: Mittwoch 14. Oktober 2015, 14:29

Gibt es einen Grund nicht einfach bokeh zu benutzen, was den Browser als Anzeige für Grafen benutzt, die man auch dynamisch aktualisieren kann?
Sirius3
User
Beiträge: 17747
Registriert: Sonntag 21. Oktober 2012, 17:20

@Lessi: wie es in Deinem Fall funktioniert, habe ich Dir gezeigt. Ich weiß jetzt nicht, ob das bei Dir angekommen ist.
Das mit `innerText` ist bei Dir anscheinend auch nicht angekommen.
Neben den ganzen anderen Hinweisen zu Python-Konventionen noch eine: eingerückt wird immer mit vier Leerzeichen pro Ebene, nicht 2.
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

Sirius3 hat geschrieben: Freitag 26. August 2022, 08:47 @Lessi: wie es in Deinem Fall funktioniert, habe ich Dir gezeigt. Ich weiß jetzt nicht, ob das bei Dir angekommen ist.
Das mit `innerText` ist bei Dir anscheinend auch nicht angekommen.
Neben den ganzen anderen Hinweisen zu Python-Konventionen noch eine: eingerückt wird immer mit vier Leerzeichen pro Ebene, nicht 2.
Auch wenn ich in meinem Code {img_tag} durch {{img_tag|safe}} ersetze funktioniert es nicht. Es wird für die Zeile nichts ausgegeben und im Quellcode steht eine leere Zeile. Kann es daran liegen, dass bei mir im Gegensatz zu deinem Code, img_tag erst später durch Listener gesetzt wird?
Lessi
User
Beiträge: 15
Registriert: Donnerstag 25. August 2022, 14:27

__deets__ hat geschrieben: Freitag 26. August 2022, 08:21 Gibt es einen Grund nicht einfach bokeh zu benutzen, was den Browser als Anzeige für Grafen benutzt, die man auch dynamisch aktualisieren kann?
Kenne ich nicht. Würde ich im Notfall auch
Antworten