Zeitreihenchart mit Flask, Bootstrap und Chart.js
Erstellen einer Testseite für ein Zeitreihen-Diagramm mit Flask, Bootstrap und Chart.js
Wir haben bereits eine Anwendung mit Flask und Bootstrap erstellt und möchten nun einige Diagramme hinzufügen. In diesem kurzen Beitrag erstellen wir eine einzelne Webseite mit einem Zeitreihendiagramm mit Flask, Bootstrap und Chart.js. Diese Seite ist eine Testseite, die wir einfügen und für unsere Anwendung anpassen können.
Wie immer mache ich dies auf Ubuntu 22.04 Desktop.
Übersicht
Die Testseite enthält ein Diagramm mit Zeitreihen-Demodaten:
- Bei den Zeitreihendaten handelt es sich um einen Sinus pro Stunde, einen Datenpunkt pro Minute, der 24 Stunden lang wiederholt wird.
- Die Zeitreihendaten können einige fehlende Datenpunkte enthalten.
- Es muss möglich sein, das Diagramm zu vergrößern und zu verkleinern.
- Das Diagramm sollte reaktionsfähig sein, nur die x-Achse.
- Verwenden Sie Millisekunden oder ISO8601.
- Es sollte mehrsprachig sein.
Fast alles ist sehr standardisiert. Für die Art der x-Achse, können wir wählen zwischen:
- 'time': die x-Achse ist mit der Zeit konsistent, fehlende Datenpunkte sind deutlich sichtbar.
- 'timeseries': Die x-Achse ist an den Stellen, an denen Datenpunkte fehlen, gestaucht.
Hier verwenden wir den Typ 'time' .
Wir erzeugen Sinusdaten, einen Sinus pro Stunde, mit Werten zwischen -10 und +10, und fügen absichtlich fehlende Datenpunkte hinzu. Dann setzen wir 'spanGaps' auf einen Wert, 2 Minuten, um sicherzustellen, dass die Datenpunkte nicht miteinander verbunden sind.
spanGaps: 2 * 60 * 1000
Das Diagramm ist reaktionsfähig, allerdings nur in x-Richtung. Die Höhe des Diagramms ist festgelegt und wir erstellen eine lineare y-Achse mit einem Mindestwert (-10) und einem Höchstwert (10).
Zum Vergrößern verwenden wir das Mausrad (Scrollrad) oder wählen einen Bereich mit der Maus aus. Wir fügen eine Schaltfläche hinzu, um den Zoom zurückzusetzen und zum ursprünglichen Diagramm zurückzukehren.
Um verschiedene Sprachen zu unterstützen, laden wir das entsprechende Modul von moment.js, zum Beispiel für Deutsch:
https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/locale/de.min.js
Der Code
Erstellen Sie ein virtual environment und installieren Sie Flask. Erstellen Sie dann ein Projektverzeichnis mit der folgenden Struktur:
├── app
│ ├── factory.py
│ └── templates
│ ├── base.html
│ └── home.html
└── run.py
Wie immer beginnen wir mit 'run.py', beachten Sie, dass wir hier den Port 5050 verwenden:
# run.py
from app.factory import create_app
app = create_app()
if __name__ == '__main__':
app.run(
host='127.0.0.1',
port=5050,
use_debugger=True,
use_reloader=True,
)
In 'factory.py' legen wir die Datenpunkte an und geben die Sprache an:
# app/factory.py
import datetime
import logging
import math
from flask import current_app, Flask, render_template
def create_app():
app = Flask(__name__)
app.jinja_env.auto_reload = True
app.config['TEMPLATES_AUTO_RELOAD'] = True
app.logger.setLevel(logging.DEBUG)
@app.route('/', methods=['GET', 'POST'])
def chart():
current_app.logger.debug('()')
# select locale
locale = 'de-DE'
#locale = 'en-US'
#locale = 'nl-NL'
chart_locales = {
'de-DE': {'html': 'de', 'chartjs': 'de-DE', 'momentjs': 'de'},
'en-US': {'html': 'en', 'chartjs': 'en-US', 'momentjs': None},
'nl-NL': {'html': 'nl', 'chartjs': 'nl-NL', 'momentjs': 'nl'},
}
# 1 sine per hour, for 24 hours, starting now
dt = datetime.datetime.utcnow()
ts = int(dt.timestamp())
chart_data = []
dt_format_milliseconds = False
for h in range(24):
for minute in range(60):
# x: in milliseconds or iso8601
dt += datetime.timedelta(minutes=1)
if dt_format_milliseconds:
x = int(dt.timestamp() * 1000)
else:
x = dt.isoformat()
# y: -10 to 10
rad = 6 * minute
y = 10 * (math.sin(math.radians(rad)))
# insert missing data points
if rad > 120 and rad < 160:
continue
chart_data.append({'x': x, 'y': y})
return render_template(
'/chart.html',
page_title='Chart',
page_contains_charts=True,
chart_locale=chart_locales[locale],
chart_data=chart_data,
)
return app
Wir haben zwei Vorlagendateien, die Basisseitenvorlage und die Diagrammseitenvorlage. Die Basisseitenvorlage kann von allen Seiten verwendet werden, die Diagrammseitenvorlage enthält die Seite mit unserem Diagramm.
{# app/templates/base.html #}
<!DOCTYPE html>
<html lang="{{ chart_locale['html'] }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>{{ page_title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
</head>
<body>
<main id="main" class="container -fluid">
<div class="row">
<div class="col">
{% block main -%}{% endblock -%}
</div>
</div>
</main>
<!-- bootstrap + jquery -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
{%- if page_contains_charts -%}
<!-- chartjs -->
<script src=" https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js "></script>
<!-- moment -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/moment.min.js"></script>
<!-- moment with locale -->
{%- if chart_locale['momentjs'] is not none -%}
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/locale/{{ chart_locale['momentjs'] }}.min.js"></script>
{%- endif -%}
<!-- chartjs moment adapter -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-adapter-moment/1.0.1/chartjs-adapter-moment.min.js" integrity="sha512-hVy4KxCKgnXi2ok7rlnlPma4JHXI1VPQeempoaclV1GwRHrDeaiuS1pI6DVldaj5oh6Opy2XJ2CTljQLPkaMrQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<!-- chartjs zoom -->
<script src="https://cdn.jsdelivr.net/npm/hammerjs@2.0.8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-zoom/2.2.0/chartjs-plugin-zoom.min.js" integrity="sha512-FRGbE3pigbYamZnw4+uT4t63+QJOfg4MXSgzPn2t8AWg9ofmFvZ/0Z37ZpCawjfXLBSVX2p2CncsmUH2hzsgJg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
$(document).ready(function() {
moment.locale(chartLocale);
const ctx = document.getElementById('chart-canvas');
const chart = new Chart(ctx, {
type: chartType,
data: chartData,
options: chartOptions,
});
window.chart = chart;
function reset_zoom() {
window.chart.resetZoom();
}
$('#reset-zoom-button').click(function(){
reset_zoom();
});
});
</script>
{%- endif -%}
</body>
</html>
{# app/templates/chart.html #}
{% extends "base.html" %}
{% block main %}
<h2 class="mb-3">
{{ page_title }}
</h2>
<div class="row border-top border-bottom border-2 border-tertiary">
<div class="col my-4" style="height: 300px; ">
<canvas id="chart-canvas"></canvas>
</div>
</div>
<div class="row">
<div class="col">
<button id="reset-zoom-button" type="button" class="btn btn-outline-secondary btn-sm my-2 px-1 py-0">
Reset Zoom
</button>
</div>
</div>
<script>
const chartLocale = "{{ chart_locale['chartjs'] }}";
// colors from:
// https://github.com/chartjs/Chart.js/blob/master/docs/scripts/utils.js
const CHART_COLORS = {
red: 'rgb(255, 99, 132)',
orange: 'rgb(255, 159, 64)',
yellow: 'rgb(255, 205, 86)',
green: 'rgb(75, 192, 192)',
blue: 'rgb(54, 162, 235)',
purple: 'rgb(153, 102, 255)',
grey: 'rgb(201, 203, 207)'
};
const chartType = 'line';
const dataSet1 = {
label: 'dataSet1',
data: {{ chart_data|safe }},
spanGaps: 2 * 60 * 1000,
// line & point color & width
borderColor: CHART_COLORS.blue,
borderWidth: 1,
pointBackgroundColor: CHART_COLORS.red,
pointBorderColor: CHART_COLORS.red,
pointStyle: 'circle',
pointRadius: 1,
pointHoverRadius: 10
};
const chartData = {
datasets: [
dataSet1
]
};
const xScaleDisplayFormat = 'MMM DD HH:mm:ss';
const chartScales = {
x: {
type: 'time',
ticks: {
autoSkip: true,
autoSkipPadding: 10,
},
time: {
tooltipFormat: xScaleDisplayFormat,
displayFormats: {
millisecond: xScaleDisplayFormat,
second: xScaleDisplayFormat,
minute: xScaleDisplayFormat,
hour: xScaleDisplayFormat,
}
},
},
y: {
type: 'linear',
min: -10,
max: 10,
}
};
const chartZoom = {
pan: {
enabled: true,
mode: 'x',
modifierKey: 'ctrl',
},
zoom: {
wheel: {
enabled: true,
},
drag: {
enabled: true
},
mode: 'x',
}
};
const chartOptions = {
locale: chartLocale,
responsive: true,
maintainAspectRatio: false,
scales: chartScales,
plugins: {
zoom: chartZoom,
legend: {
display: false
}
}
};
</script>
{%- endblock -%}
Zur Ausführung gehen Sie in das Projektverzeichnis und geben Sie ein:
python run.py
Rufen Sie dann Ihren Browser auf:
http://127.0.0.1:5050
Zusammenfassung
Wir haben eine einzelne Webseite mit Flask, Bootstrap und Chart.js erstellt. Das Diagramm zeigt eine wiederholte Sinuswelle. Obwohl es nicht sehr schwierig ist, braucht man Zeit, um alle verfügbaren Konfigurationsoptionen durchzugehen. Jetzt können wir unsere Diagramm-Testseite anpassen und zu unserer Anwendung hinzufügen.
Neueste
- Zeitreihenchart mit Flask, Bootstrap und Chart.js
- Verwendung von IPv6 mit Microk8s
- Verwendung von Ingress für den Zugriff auf RabbitMQ auf einem Microk8s -Cluster
- Einfache Videogalerie mit Flask, Jinja, Bootstrap und JQuery
- Grundlegende Auftragsplanung mit APScheduler
- Ein Datenbankschalter mit HAProxy und der HAProxy Runtime API
Meistgesehen
- Grundlegende Auftragsplanung mit APScheduler
- Verwendung von Pythons pyOpenSSL zur Überprüfung von SSL-Zertifikaten, die von einem Host heruntergeladen wurden
- Verhinderung des Versands doppelter Nachrichten an ein entferntes System
- LSTM mehrstufige hyperparameter Optimierung mit Keras Tuner
- Dokumentieren einer Flask RESTful API mit OpenAPI (Swagger) mit APISpec
- Testen der Veröffentlichungsbeispiele RabbitMQ Pika