Если информация была полезной для вас, вы можете поблагодарить за труды Юmoney: 41001164449086

Рисуем графики Highcharts (Highstock) по данным из MySQL

В этой статье я расскажу о том, как рисовать графики при помощи библиотеки Highcharts, а точнее её подвида Highstock. Данные при этом мы будем получать из MySQL при помощи PHP и передавать их в JSON формате.
Написать статью меня побудило полное отсутствие документации на русском языке. Кроме того, на момент написания в рунете, детально расписанного примера использования Highstock мне найти так и не удалось.

Итак, существует несколько подвидов библиотеки Highcharts, далее позволю себе вольный перевод:

  • Highcharts – простое создание интерактивных диаграмм для web проектов
  • Highstock – создание временных графиков на чистом JavaScript
  • Highmaps – развёрнутые интерактивные карты с поддержкой касания

Поскольку мне было необходимо рисовать временные графики, я использовал Highstock.

Теперь о том почему именно Highcharts и коротко о том, как это работает.

В классическом подходе, когда мы говорим слово график мы подразумеваем картинку (jpeg, png и т.д.). Эту самую картинку кто-то должен подготовить по имеющимся данным. Делать статичную картинку не вариант. А если делать динамически (автоматически обновляемую), то необходимо при каждом обращении создавать новое изображение, а это довольно ресурсоёмко. И вот тут можно применить более прогрессивный подход.

Если описать процесс очень коротко, то Highstock получает данные от источника в JSON формате и затем отрисовывает (рендерит) их в окне браузера используя JavaScript. Тем самым мы не нагружаем каждый раз сервер, а всю работу по отрисовки переносим на сторону клиента. Кроме того, Highcharts библиотеки бесплатны для не коммерческого использования, в общем то что, надо.

В качестве источника данных, для Highcharts, может выступать web сервер с MySQL, микроконтроллер или что-то ещё, суть не в этом. Главное, это получить данные от источника в формате JSON. Если вы не знаете, что это такое, то это просто ещё один формат представления данных, как например XML. Если надо подробнее – гугл в помощь.

Библиотека Highstock обрабатывает полученные данные используя JavaScript в браузере и рисует их в виде графика.
Таким образом мы не нагружаем источник, он просто отдаёт данные, а вся отрисовка происходит на стороне клиента, будь то телефон, планшет или обычный компьютер.
Теперь перейдём к практике, разбив её на этапы.

1.    MySQL

Предположим у нас уже есть данные представленные в виде одной таблицы “t_power” в MySQL (на всякий случай вот архив с дампом БД: t_power.sql.zip).


Рис.1


Рис.2.

В таблице записаны данные от вольтметра-амперметра Peacefair pzem-004t. Тут всё очевидно, единственное на чём следует заострить внимание это на столбец date, он имеет тип datetime и предназначен для хранения информации о времени измерения.

В моём случае поля current и active, с данными для тока и мощности, были MySQL заполнены случайными значениями. А вот ‘Напряжение’ это вполне реальные данные.


2.    JSON

Теперь нам необходимо получить данные из БД, и представить их в JSON формате. Для этого я буду использовать незамысловатый PHP скрипт jsonp.php (который положим в корень), листинг которого чуть ниже:

<?php
date_default_timezone_set( 'UTC' );
//echo date('Y-m-d H:i:s');
$mysqli = new mysqli('localhost', 'power', 'power', 'power');
 
$query = "SELECT `datetime`, `voltage`, `current`,`active` FROM `t_power`;";
$result = $mysqli->query($query);
 
while ($record = $result->fetch_row()){
    $all[] =  array(strtotime($record[0]), (float)$record[1], (float)$record[2], (float)$record[3]);
}
echo json_encode($all);
 
$mysqli->close();
?>

В качестве разнообразия я буду использовать ООП подход.

Чуть более дробно о том, что здесь происходит.

Мы подключаемся к серверу localhost, используя имя пользователя и пароль power. Четвёртый параметр - имя БД, в моём примере также power.
Получать мы будем столбцы `datetime`, `voltage`, `current`, `active`, тут всё очевидно.

$all[] =  array(strtotime($record[0]), (float)$record[1], (float)$record[2], (float)$record[3]);

Строка формирует массив, первый элемент которого это метка времени Unix (тут используется функция преобразования времени strtotime()). Остальные элементы соответственно это данные о напряжении, токе, и потребляемой мощности. (float) перед знаком производит преобразование типов данных к числу.

Выполнив всё это в цикле while мы сформировали двумерный массив $all.
После чего выводим  его, предварительно преобразовав выходные данные в JSON.

echo json_encode($all);

При обращении к этому файлу мы будем получать данные вида:

[
[1515029601,206.4,3.82,2985],
[1515029606,205.8,8.42,518],
[1515029633,210.6,10.65,1634],
[1515029666,219.8,7.98,2617],

[1515095928,209.4,0,0],
[1515095961,198.5,0,0]
]

Значения в квадратных скобках это: метка времени Unix, напряжение, ток и мощность соответственно.

Важно понимать, что для корректного выполнения скрипта необходима поддержка PHP.

Теперь у нас есть подготовленные данные, и можно приступать к настройке самого Highstock.

3.    Highstock

Прежде всего нам потребуется библиотека jquery, которую скачаем jquery.com/download, и также расположим файл в корне.

Разумеется нам потребуется и сам Highstock, который надо скачать www.highcharts.com/download.

На момент написания последняя версия 6.0.4.
После распаковки архива сразу переименуем файл index.htm например в index_old.html и создадим файл energy.html следующего содержания:

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>Highstock Example</title>

        <style type="text/css">

        </style>
    </head>
    <body>
<script src="/jquery-3.2.1.min.js"></script>
<script src="/code/highstock.js"></script>
<script src="/code/themes/grid-light.js"></script>
<script src="/code/modules/exporting.js"></script>
<a href="https://unboxit.ru" target="_blank">Это и многое другое на unboxIT.ru</a>

<div id="container2" style="height: 500px; min-width: 310px"></div>
<script>
    Highcharts.setOptions({
                lang: {
                    loading: 'Загрузка...',
                    months: ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'],
                    weekdays: ['Воскресенье', 'Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота'],
                    shortMonths: ['Янв', 'Фев', 'Март', 'Апр', 'Май', 'Июнь', 'Июль', 'Авг', 'Сент', 'Окт', 'Нояб', 'Дек'],
                    exportButtonTitle: "Экспорт",
                    printButtonTitle: "Печать",
                    rangeSelectorFrom: "С",
                    rangeSelectorTo: "По",
                    rangeSelectorZoom: "Период",
                    downloadPNG: 'Скачать PNG',
                    downloadJPEG: 'Скачать JPEG',
                    downloadPDF: 'Скачать PDF',
                    downloadSVG: 'Скачать SVG',
                    printChart: 'Напечатать график'
                }
        });
</script>

        <script type="text/javascript">
            $.getJSON('jsonp.php', function (data) {

                // split the data set into voltage and current
                var voltage = [],
                    current = [],
                    active = [],
                    dataLength = data.length,

                i = 0;
                for (i; i < dataLength; i += 1) {
                    voltage.push([
                        data[i][0] * 1000, // the date
                        data[i][1], // voltage
                    ]);

                    current.push([
                        data[i][0] * 1000, // the date
                        data[i][2] // the current
                    ]);
                    
                    active.push([
                        data[i][0] * 1000, // the date
                        data[i][3] // the active power
                    ]);
                }


                // create the chart
                Highcharts.stockChart('container2', {

                    rangeSelector: {
                        selected: 1,
                        buttons: [{
                            type: 'minute',
                            count: 10,
                            text: '10м'
                        }, {
                            type: 'hour',
                            count: 1,
                            text: '1час'
                        }, {
                            type: 'hour',
                            count: 6,
                            text: '6час'
                        }, {
                            type: 'day',
                            count: 1,
                            text: '1дн'
                        }, {
                            type: 'week',
                            count: 1,
                            text: 'неделя'
                        }, {
                            type: 'month',
                            count: 1,
                            text: 'мес'
                        }, {
                            type: 'year',
                            count: 1,
                            text: 'год'
                        }, {
                            type: 'all',
                            text: 'Всё'
                        }]
                    },

                    title: {
                        text: 'Электросеть'
                    },

                    yAxis: [{
                        labels: {
                            align: 'right',
                            x: -3
                        },
                        title: {
                            text: 'Напряжение'
                        },
                        height: '50%',
                        lineWidth: 1,
                        resize: {
                            enabled: true
                        }
                    }, {
                        labels: {
                            align: 'right',
                            x: -3
                        },
                        title: {
                            text: 'Ток'
                        },
                        top: '52%',
                        height: '20%',
                        offset: 0,
                        lineWidth: 1
                    }, {
                        labels: {
                            align: 'right',
                            x: -3
                        },
                        title: {
                            text: 'Мощьность'
                        },
                        top: '75%',
                        height: '20%',
                        offset: 0,
                        lineWidth: 1
                    }],

                    tooltip: {
                        split: true
                    },

                    series: [{
                        type: 'spline',
                        name: 'Вольт',
                        data: voltage,
                        yAxis: 0
                    }, {
                        type: 'spline',
                        name: 'Ампер',
                        data: current,
                        yAxis: 1
                    }, {
                        type: 'spline',
                        name: 'Ватт',
                        data: active,
                        yAxis: 2
                    }]
                });
            });
        </script>
    </body>
</html>


Это обычный HTML файл с JavaScript.

Теперь пройдёмся по самым значимым строкам, чуть подробнее.

<script src="/jquery-3.2.1.min.js"></script>
<script src="/code/highstock.js"></script>

Подключаем библиотеки jquery и highstock.

$.getJSON('jsonp.php', function (data)

Откуда будем получать данные, это рассмотренный ранее файл 'jsonp.php', который лежит в корне.

voltage = [],
current = [],
active = [],

Я буду рендерить сразу 3 графика за раз, для чего подготавливаю соответствующие массивы.

data[i][0] * 1000

Умножаем на 1000 поскольку в принятых JSON данных метка времени Unix передана в секундах, а нам нужны данные в микросекундах. В принципе можно было передавать JSON данные уже в микросекундах, но тогда у нас во времени всегда были 3 лишних нуля, увеличивая объём передаваемых данных, что не очень хорошо.


Теперь у нас есть все необходимые данные и можно заняться рендерингом.

Highcharts.stockChart('container2' …

'container2' это id блока div куда мы будем отрисовывать.

title: {
text: 'Электросеть'
        },

Название графика, которое будет показано сверху.

Далее:

    yAxis: [{
        labels: {
            align: 'right',
            x: -3
        },
        title: {
            text: 'Напряжение'
        },
        height: '50%',
        lineWidth: 1,
        resize: {
            enabled: true
        }
    },

Шкала Y(ноль) Которая будет расположена справа, иметь подпись 'Напряжение', занимать она будет 50% от размера всего графика (поля рендеринга). Чуть далее идёт тоже самое только для 'Тока' и 'Потребляемой мощности'.

Отличия для Шкалы Y(1) буду в следующем:

top: '52%', -  
height: '20%',

График тока будет отступать от верха 52% и при этом занимать 20%. Аналогично для шкалы Y(2).

Далее:

series: [{
    type: 'spline',
    name: 'Вольт',
    data: voltage,
    yAxis: 0
    },

Серия данных type: 'spline' -  тип графика - огибающая (плавная) линия по нашим точкам. name: 'Вольт' – единица измерения которая будет показана при наведении на график, data: voltage – данные в виде массива подготовленные нами ранее и самый главный фокус yAxis: 0 – говорим что график будет отрисовываться для 0 (нулевой) оси. Тоже самое происходит с остальными значениями, только они отрисовываются относительно оси 1 – Ток, 2 – Мощность. Помним, что чуть ранее мы подготовили 3 оси Y (yAxis).

Теперь несколько слов о менее значимых параметрах.

<script src="/code/themes/grid-light.js"></script>

Мы подключили тему, для отображения графиков с линейкой.

<script src="/code/modules/exporting.js"></script>

Подключили модуль для возможности экспорта графиков.
На самом деле список модулей довольно обширен, и вы можете подключить их самостоятельно, благо теперь понятно, как это делать.

Ну и напоследок “десерт”:

Highcharts.setOptions({ lang: {...

Здесь я задаю языковые параметры, для русификации интерфейса.

Далее кнопки:

rangeSelector: {
    selected: 1,
    buttons: [{
    type: 'minute',
    count: 10,
    text: '10м'
},..

Для переключения отображаемого диапазона данных, selected: 1 – она выбрана по умолчанию, type: 'minute', - тип минуты, count: 10 – значение 10, text: '10м' -  что будет написано на самой кнопке.
Таким образом при щелчке по кнопке '10м', график начнёт отображать временной интервал в 10 минут. Остальные кнопки настраиваются аналогично.

Ну и собственно при обращении к energy.html мы увидим следующий график:


Рис.3.

Надеюсь моя статья помогла немного разобраться с Highcharts. Теперь нарисовать, например, 1 график вообще не составит труда.

Полностью готовый пример включая Highstock-6.0.4 и jquery:
highstock-6.0.4rel.zip
 

Комментарии   

# Вячеслав 18.06.2018 21:33
Добрый день, решил попробовать данный пример но получаю ошибку(((
Backend fatal error: PHP Fatal error: Call to a member function fetch_row() on boolean in /var/www/**/jso np.php on line 14\n, referer:

14-я строка у меня: while ($record = $result->fetch_row()){

не могу сообразить, подскажите что не так? Спасибо
Ответить | Ответить с цитатой | Цитировать
# admin 18.06.2018 22:12
Скорее всего нет доступа к таблице t_power, либо она не создана, либо создана как-то не так.
Вот архив дампа с БД unboxit.ru/storage/files/t_power.sql.zip
Создайте пользователя и БД - power, после чего будучи в БД power импортируйте данные из архива (проще всего для этого использовать phpMyAdmin).
Ответить | Ответить с цитатой | Цитировать
# Вячеслав 19.06.2018 19:45
ИмпоЦитирую admin:
Скорее всего нет доступа к таблице t_power, либо она не создана, либо создана как-то не так.
Вот архив дампа с БД http://unboxit.ru/storage/files/t_power.sql.zip
Создайте пользователя и БД - power, после чего будучи в БД power импортируйте данные из архива (проще всего для этого использовать phpMyAdmin).

Все сделал и все работает))) странно почему с моей таблицей не работало(( в файле jsonp.php все указал верно((
В любом случае Спасибо большое)
Ответить | Ответить с цитатой | Цитировать
# Melan 22.08.2018 14:43
Здравствуйте, я импортировал вашу таблицу к себе в бд, поменял соответственно хост, пользователя, пароль и название бд в php-файле. Скачал вашу папку с сайтом, но получаю белый лист, на котором есть только ссылка. Подскажите из-за чего это может быть?
Ответить | Ответить с цитатой | Цитировать
# admin 22.08.2018 19:43
Мало данных.
Для начала следует проверить соединение с БД.
После строки $mysqli = new mysqli(...
вставьте код вроде такого:
if ($mysqli->conne ct_error) {
die('Ошибка подключения (' . $mysqli->connec t_errno . ') '
. $mysqli->connect_error);
}
Ответить | Ответить с цитатой | Цитировать
# Melan 23.08.2018 10:04
Спасибо, я уже разобрался, все заработало как надо. Есть еще вопрос, можно ли сделать так, чтобы график обновлялся динамически , то есть после поступления новых данных в бд достраивался, без обновления страницы?
Ответить | Ответить с цитатой | Цитировать
# admin 23.08.2018 12:32
Можно, но код меняется довольно сильно.

Если статически, то работа выглядит сл. образом:
Получить данные JSON->Построить график по готовым данным.

То динамически, будет примерно так:
Подготовить график.
Получить данные JSON->вставить их в график->перерисовать.
Регулярно обновлять данные->добавля ть их к существующим данным->перерисовывать.
Тут уже идёт асинхронная работа, как только появляются новые данные идёт обновление и перерисовка.

Частично я реализовал это сдесь: https://unboxit.ru/blog/68-sistema-monitoringa-dlya-doma-na-raspberry-pi-zero-w.html

Но вообще лучше почитать офф. API.
Ответить | Ответить с цитатой | Цитировать
# Melan 23.08.2018 13:54
Да у меня как раз малинка с датчиком температуры DHT22, спасибо за статью попробую разобраться.
Ответить | Ответить с цитатой | Цитировать
# Сергей 19.02.2019 17:10
Привет, подскажите пожалуйста, что Вы делали? Создал базу, пользователя, добавил таблицу и белый лист, только ссылка на сайт автора. Уже все перерыл, не отрисовывает по образцу.
Ответить | Ответить с цитатой | Цитировать
# Сергей 19.02.2019 23:27
Цитирую Сергей:
Привет, подскажите пожалуйста, что Вы делали? Создал базу, пользователя, добавил таблицу и белый лист, только ссылка на сайт автора. Уже все перерыл, не отрисовывает по образцу.


В общем и я разобрался. Что бы это все работало, нужен сервер, например опенсервер, на котором все это запускается. На компе пхп не выполняется просто так) извиняюсь за нубство, но если входишь в это с нуля, сложно представить все нужное для работы.
Ответить | Ответить с цитатой | Цитировать
# bitreid 15.03.2019 09:42
У многих не будет работать без вашей базы, потому, что в одной строчке не верно написали обяснение значения полей и плюс в создании файла вы говорите написать json.php, а в energy.html используется файл jsonp.php

в файле jsonp.php строчку подключения к БД нужно было пояснить так:
$mysqli = new mysqli('localho st', 'ИМЯ ПОЛЬЗОВАТЕЛЯ', 'ПАРОЛЬ ', 'ИМЯ БАЗЫ ДАННЫХ');
Ответить | Ответить с цитатой | Цитировать
# admin 16.03.2019 12:17
Исправил.
Ответить | Ответить с цитатой | Цитировать
# рост 20.03.2019 00:32
Просто нереальный пример.вы очень щедрый человек. Спасибо вам! Долго правда разбирался.на локальных серверах так и не заработало. Но на хостинге все сразу запустилось.воз можно загружать данные не из localhost, а из другого ip?
Ответить | Ответить с цитатой | Цитировать
# Ночник 24.10.2019 04:02
Здраствуйте!
Уже некоторое время борюсь с массивами: у меня не получается вызвать массив voltage, так как он является локальным массивом функции $.getJSON и поэтому не может быть вызван позже в series
Так как график не получает данных, он не отрисовывается на странице
Может я чего-то не понимаю?
Ответить | Ответить с цитатой | Цитировать
# Алексей 03.02.2020 04:12
Добрый день!
Борюсь с построением графика три дня, всё равно не строит. Код изменил под себя, проверил jquery и скрипт, который даёт данные - как будто всё работает, в тестовом режиме jquery работает. Не вижу никаких ошибок, а график всё равно не строится. Что может быть с этим? Вы решили свой вопрос?
Ответить | Ответить с цитатой | Цитировать
# Николай 27.11.2019 12:09
Добрый день!
Заработало на коленке, просто изменив параметры в файле json!!!
Искал такое готовое решение около месяца, т.к. своих знаний не достаточно! Огромное Вам человеческое СПАСИБО!
Ответить | Ответить с цитатой | Цитировать

Добавить комментарий


Если информация была полезной для вас, вы можете поблагодарить за труды Юmoney: 41001164449086