angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

График временного ряда с Flask, Bootstrap и Chart.js

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

16 декабря 2024
post main image

У нас уже есть приложение, созданное с использованием 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. На графике показана повторяющаяся синусоидальная волна. Хотя это не очень сложно, требуется время, чтобы пройтись по всем доступным опциям конфигурации. Теперь мы можем настроить тестовую страницу графика и добавить ее в наше приложение.

Ссылки / кредиты

Bootstrap
https://getbootstrap.com

Chart.js
https://www.chartjs.org/docs/latest

Flask
https://flask.palletsprojects.com/en/stable

Подробнее

Bootstrap Flask

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.