20 января 2015 в 23:52

Gnuplot на домашней страничке из песочницы

Зачем?


При разработке доступной онлайн базы данных для хранения результатов расчётов возникло непреодолимое желание представлять информацию не только в табличном виде, но и в виде графиков. Можно пойти различными путями, например, рисовать кривые в PHP, но правильнее (в смысле UNIX-way) будет использовать внешнюю программу, уже умеющую строить графики, такую как Gnuplot.

Особенно интригует возможность вывода графиков в виде набора JS комманд для рисования на HTML5-холсте (canvas), чем мы и займёмся.

О Gnuplot много писали, в том числе и на Хабре (1, 2), поэтому нет необходимости в подробном описании его синтаксиса.

К своему удивлению, я не обнаружил хорошего туториала о том, как связать вывод gnuplot с динамически формируемой страничкой. Возможно, это связано с недостатком знаний о Java Script и на самом деле всё очевидно, но тем не менее, есть надежда, что эта статья окажется полезной.

Вывод Gnuplot в HTML5 Canvas


Чтобы получить представления о возможностях такого режима, составим минимальный скрипт, рисующий кусок синусоиды в файле output.html:

#!/usr/bin/gnuplot
set terminal canvas
set output "output.html"
plot sin(x) with lines

Здесь первая строка задает интерпретатор команд, вторая — устанавливает формат вывода canvas. Для особо удачных графиков здесь можно было бы заменить canvas на pdf и сразу вставить результат в статью для, скажем, Nature. Третья строка указывает имя файла для записи, если ее не будет — то поток будет направлен в stdout, чем мы воспользуемся при генерации html-странички. Последняя строка содержит собственно команду для построения графика синуса.

Открыв сгенерированный файл output.html, можно увидеть график:



Хорошо, но такой результат вполне можно было бы получить, вставляя рисунки, полученные и терминалом png, а хотелось бы интерактивности! Для этого нужно всего лишь указать параметры терминала enhanced mousing:

set terminal canvas enhanced mousing 
set output "output.html"
set xlabel 'Time'
set ylabel 'Energy'
plot sin(4*x)/x with lines linewidth 2

В результате появится панелька с управляющими кнопками, возможность ставить точки и приближать интересующую область:



Уже больше похоже на примеры с сайта разработчиков, но с удаленного компьютера работать не будет, поскольку за вспомогательными JS-скриптами идет обращение в локальную файловую систему, так как сгенерированный файл содержит строки типа:

<script src="/usr/share/gnuplot/gnuplot/4.6/js/gnuplot_common.js"></script>

То есть, нужно разместить файлы из /usr/share/gnuplot/gnuplot/4.6/js на своем сайте, скопировав их или создав ссылку. Для работы достаточно иметь файлы canvastext.js, gnuplot_common.js, gnuplot_mouse.js, gnuplot_mouse.css и png-иконки. Также необходимо указать путь к этим файлам в параметрах терминала:

set terminal canvas enhanced mousing jsdir 'js'


Взаимодействие PHP и gnuplot


Итак, есть путь к папке расчётов, нужно построить график согласно выбранным пунктам на html-страничке.
Разобьем задачу на несколько частей, как показано на диаграмме:



Такая схема, возможно, избыточна, и всю функциональность можно реализовать на php, но такое разделение разбивает код по его функциям и правам доступа:
  • shell-скрипт имеет доступ к файлам на диске и запуску gnuplot, но не должен быть доступен снаружи,
  • php и html — наоборот, доступны снаружи и не должны иметь доступа к файлам за пределами сайта,
  • отдельный .gp файл позволяет повторить построение графика, но в более удобном для публикации формате выбором pdf или png терминалов.


PHP/HTML


html-форма и php-скрипт объединены в один файл plot_calc.php:

<?php
// получаем и устанавливаем управляющие переменные:
$path = $_GET['path'];                        // user-friendly путь,
$fullpath = "/srv/calculations/".$path."/";   // полный путь в локальной ФС
$nSites = $_GET['nsites'];                    // параметр расчета - число 'сайтов'

$fSite = 1;                                   // текущий 'сайт'
if (isset($_POST["fSite"])) { $fSite = $_POST["fSite"]; }

// опущено описание заголовка и стилей
echo '<a href=index.html>Back</a>';
echo '<h1>Plot Calculation Data</h1>';
echo '<form id="form_plots" method="post" action=plot_calc.php?path='.$path.'&nsites='.$nSites.'>';
echo '    <input id="plotProb"  type="submit" name="plotProb" value="Prob" />';
echo '    <input id="plotEnergy" type="submit" name="plotEnergy" value="Energy" />';
// ...
echo '    <label>Site number to plot</label>';
echo '    <input id="fSite" name="fSite"  type="text" value='.$fSite.' />';
echo '</form>';

if (isset($_POST["plotProb"])) {   // если нажата кнопка plotProb
        $input='./plot_calc.sh'.' '.'p '.$nSites.' '.$fullpath.' '.$fSite;
        echo "Probability plot for Site #".$fSite;
    }
if (isset($_POST["plotEnergy"])) { // если нажата кнопка plotEnergy
        $input='./plot_calc.sh'.' '.'E '.$nSites.' '.$fullpath.' '.$fSite;
        echo "Energy plot for Site #".$fSite;
    }
// ... 

$output = shell_exec($input); // запуск скрипта оболочки
echo $output;                 // вывод результата работы скрипта

echo '</body>';
echo '</html>';
?>

Как заметили в комментариях, вызов shell_exec в таком виде является опасной затеей, так что следует, как минимум, провести фильтрацию данных с помощью функции filter_input.

SHELL


Задача скрипта plot_calc.sh — формирование команд для gnuplot согласно заданным внешним параметрам. Скрипт создает файл во временной директории /tmp, содержимое которого передается gnuplot, а результат выполнения возвращается обратно, где его уже ждет php-скрипт.

#!/bin/bash
# параметры:
# 1 - тип
# 2 - число сайтов
# 3 - путь
# 4 - сайт для рисования (требуется для p)
#  --------------
# тип   значение   
# E	energy
# p     probaility
# ....
#------------------------------#

# создадим новый файл во временном каталоге:
TFILE="/tmp/$(basename $0).$$.gp"
# запишем в него команды gnuplot:
echo "# Automatically generated Gnuplot script " > $TFILE
echo "set terminal canvas enhanced mousing jsdir 'js'" >> $TFILE

### Probability ###
if [ $1 == 'p' ]
then
    echo "set xlabel 'Time'">>$TFILE
    echo "set ylabel 'Probability'">>$TFILE
    let icol=$4+1
    echo "plot '$3prob.res' u 1:$icol wi li">>$TFILE
fi

# ...

### Energy ###
if [ $1 == 'E' ]
then
    let col=$4*2
    let col1=$4*2+1
    echo "set xlabel 'Time'">>$TFILE
    echo "set ylabel 'Energy'">>$TFILE
    echo "plot '$3Energ.res' u 1:$col:$col1 wi err">>$TFILE
fi

/usr/bin/gnuplot $TFILE

Этому файлу потребуются права на исполнение:
 chmod +x plot_calc.sh 

Gnuplot


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

# Automatically generated Gnuplot script 
set terminal canvas enhanced mousing jsdir 'js'
set xlabel 'Time'
set ylabel 'Probability'
plot '/srv/calculations/GoodCalc/prob.res' u 1:6 wi li

Проверить работу можно направив браузер по адресу http://servername/plot_calc.php?path=GoodCalc&nsites=10.
В результате появится возможность построить графики по данным файлов prob.res и Energy.res, и должна получиться страничка, похожая на:



Генерируемые файлы в /tmp надо время от времени подчищать, для этого сгодится задание cron (от root'а):

crontab -e

0 */1 * * * rm -v /tmp/plot*.gp >> /var/log/rmplotgp.log


Заключение


  1. Тестирование и демонстрация проводились на Debian Jessie, но должно работать и на прочих системах;
  2. JS-файлы gnuplot должны быть доступны из внешней сети;
  3. Есть возможность формирования не только html-страничек, но и js-скриптов, управляемых пользовательским кодом.
  4. Вопросы нагрузки и безопасности здесь не рассматривались и потребуют дополнительного внимания...
Leon @klurik
карма
8,0
рейтинг 0,0
Пользователь
Самое читаемое Разработка

Комментарии (11)

  • +9
    А вы рассматривали вариант с отрисовкой графиков с помощью js библиотек? Данные то вы все равно на клинт передаете (для табличек и т.д.)
    • 0
      Но тогда всё будет совсем просто:(
    • 0
      Могу попытаться придумать необходимость для данного решения: бывает, что данных МНОГО. Я ещё помню, как в youtube-аналитике страница подвисала на минуты, если попытаться просмотреть данные за период в 1 год или более. Потом они видимо оптимизировали алгоритм, и теперь в такой ситуации не повторяются. И тем не менее, иногда лучше на клиенте графики всё-таки не рисовать :)
      • +2
        бывает, что данных МНОГО

        Я как раз такими ситуациями по работе занимаюсь. В этом случае на сервере делается «прореживание» данных, они «огрубляются», подгружаются кусками при зуммировании и т.д.
        В общем, делается весь комплекс мер, который делался бы для отображения данных в табличном виде, если их МНОГО ;)

        Описаный метод данную проблему не решит, а скорее усугубит.

        И тем не менее, иногда лучше на клиенте графики всё-таки не рисовать :)

        Наверное. Но могу придумать очень мало ситуаций, когда это лучше. Конечно, при условии, что клиент поддерживает «рисование».
        Например, когда нужно отобразить 100500 мелких простых графичков, их, наверное, лучше отдать растром, а не греть процессор пользователя. Или, когда один и тот же график, которому нафиг не нужна интерактивность будет отдаваться куче клиентов. Но в этих случаях, не нужен интерактив, предложеный автором.

        А вот если интерактив нужен, мое мнение, лучше график рисовать на клиенте. Автор молодец (без сарказма), но, на мой взгляд, получившийся график выглядит некрасиво, взаимодействие пользователя с ним неестественное и неудобное и сам способ получения и доставки пользователю этого графика, уж простите, похож на удаление гланд через задний проход. Я ничего плохого не хочу сказать про гнуплот, но конкретно для веба существуют более функциональные, красивые и удобные клиентские библиотеки для построения графиков.
        Причем, если данных не МНОГО, чтобы юзать тот же jQplot, js знать вообще не обязательно. Примеров вагон и маленькая тележка, все «программирование» по сути сводится к написанию конфига. Короче, порог вхождения нулевой.
        Как-то так :)
        • +1
          А вы не знаете js-библиотек поддерживающих логарифмический масштаб, позволяющий сохранять графики в векторном формате и PNG?
    • 0
      Увы, до js библиотек дело не дошло. Дело в том, что впервые эту идею я увидел в программе Wien2k, в веб-интерфейсе которой используется gnuplot для построения графиков и решил повторить для себя. Ну а потом уже встретился терминал canvas и пришлось смотреть в сторону html5… Данные для табличек не скачиваются, а основной интерес представляют параметры расчетов и качественное поведение кривых. Файлы не то чтобы очень большие — несколько тысяч строк и десятки столбцов.
  • +1
    ещё можно попробовать shiny от r-studio прикрутить
  • +2
    Простите, но по моему передавать переменные от пользователей в shell_exec плохая идея.
  • +1
    Вместо $_GET и $_POST лучше использовать filter_input.
  • 0
    gnuplot> set terminal canvas
    Terminal type set to 'canvas'
    Options are ' solid butt size 600,400 fsize 10 lw 1 fontscale 1 standalone'
    
    gnuplot> plot sin(x) width lines
                         ^
             ';' expected
    


    gnuplot — либо херня полная. либо в статье ошибки исправить надо.
    • 0
      Бл*, ну вы поняли, я ошибся в одной лишней букве,

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