График временного ряда с Flask, Bootstrap и Chart.js
Создайте тестовую страницу графика временного ряда с Flask, Bootstrap и Chart.js

У нас уже есть приложение, созданное с использованием Flask и Bootstrap , и мы хотим добавить несколько графиков. В этом коротком посте мы создадим одну веб-страницу с графиком временного ряда, используя Flask, Bootstrap и Chart.js. Это тестовая страница, которую мы можем вставить и настроить для нашего приложения.
Как обычно, я делаю это на Ubuntu 22.04 Desktop.
Обзор
Тестовая страница содержит график, отображающий демо-данные временного ряда:
- Данные временного ряда - это один синус в час, одна точка данных в минуту, повторяющиеся 24 часа.
- В данных временного ряда могут присутствовать некоторые пропущенные точки данных.
- Должна быть возможность увеличения и уменьшения масштаба.
- График должен быть отзывчивым, только по оси x.
- Используйте миллисекунды или ISO8601.
- Он должен быть многоязычным.
Почти все очень стандартно. Для типа оси x мы можем выбирать между:
- 'time': ось x соответствует времени, точки данных, которые отсутствуют, хорошо видны.
- 'timeseries': ось x сжата, где отсутствуют точки данных.
Здесь мы используем тип 'time' .
Мы генерируем синусоидальные данные, по одному синусу в час, со значениями от -10 до +10, и намеренно добавляем недостающие точки данных. Затем мы устанавливаем значение 'spanGaps' равным 2 минутам, чтобы гарантировать, что точки данных не связаны друг с другом.
spanGaps: 2 * 60 * 1000
График реагирует, но только в направлении x. Высота графика фиксирована, и мы создаем линейную ось y с минимальным значением (-10) и максимальным (10).
Чтобы увеличить масштаб, мы используем колесико мыши (колесо прокрутки) или выделяем область с помощью мыши. Мы добавили кнопку, чтобы сбросить масштаб и вернуться к исходному графику.
Для поддержки разных языков мы загружаем соответствующий модуль moment.js, например, для немецкого:
https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.30.1/locale/de.min.js
Код
Создайте virtual environment и установите Flask. Затем создайте каталог проекта со следующей структурой:
├── app
│ ├── factory.py
│ └── templates
│ ├── base.html
│ └── home.html
└── run.py
Как обычно, мы начинаем с 'run.py', обратите внимание, что здесь мы используем порт 5050:
# 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,
)
В 'factory.py' мы создаем точки данных и указываем язык:
# 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
У нас есть два файла шаблонов: шаблон базовой страницы и шаблон страницы диаграммы. Шаблон базовой страницы может использоваться всеми страницами, а шаблон страницы графика содержит страницу с нашим графиком.
{# 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 -%}
Для запуска перейдите в каталог проекта и введите:
python run.py
Затем наведите браузер на:
http://127.0.0.1:5050
Summary .
Мы создали одну веб-страницу, используя Flask, Bootstrap и Chart.js. На графике показана повторяющаяся синусоидальная волна. Хотя это не очень сложно, требуется время, чтобы пройтись по всем доступным опциям конфигурации. Теперь мы можем настроить тестовую страницу графика и добавить ее в наше приложение.
Недавний
- График временного ряда с Flask, Bootstrap и Chart.js
- Использование IPv6 с Microk8s
- Использование Ingress для доступа к RabbitMQ на кластере Microk8s
- Простая видеогалерея с Flask, Jinja, Bootstrap и JQuery
- Базовое планирование заданий с помощью APScheduler
- Коммутатор базы данных с HAProxy и HAProxy Runtime API
Большинство просмотренных
- Использование PyInstaller и Cython для создания исполняемого файла Python
- Уменьшение времени отклика на запросы на странице Flask SQLAlchemy веб-сайта
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Подключение к службе на хосте Docker из контейнера Docker
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов