« Мои статьи на сайте javable.com | Графики, диаграммы, графы … и все это в веб? Часть 2 » |
Графики, диаграммы, графы … и все это в веб? Часть 1
Хотя в своей повседневной работе я часто сталкиваюсь с необходимостью программировать всевозможные отчеты, диаграммы, графики, но до не давних пор сфера моих интересов была смещена в сторону desktop standalone приложений. В редких случаях, когда возникала потребность в разработке программок работающих в браузере, я обходился встроенным в состав flex компонентом “графики”, исходные данные задавались в формате xml, визуализация была приятная и стильная – одним словом, меня все устраивало. И вот в очередном проекте мне пришлось углубится в детальное изучение различных подходов к созданию внедренных в веб-страницы графиков, диаграмм, оценить их плюсы, минусы. Так что, пока воспоминания свежи, спешу поделиться с вами.Как строить графики в веб? Вариант 1: в общем случае графики, диаграммы разных форм и видов - это все картинки, которые могут быть отрисованы с помощью: php, java или любого серверного языка программирования - благо универсальные библиотеки рисующие 2d изображения появились давным-давно. Вариант 2: отрисовка выполняется с помощью activex-компонентов или java-апплетов. Вариант 3: использование возможностей svg графики или тега canvas и, наконец, flash/flex. Очевидно, что самый плохой вариант - 2. Несмотря на потенциальную возможность реализовать сколь угодно сложное поведение внедренного в страницу компонента, добавить анимацию, интерактивность - все это теряется на фоне слов “не универсально”, “activex-только для windows”, “вирусы и activex – дружная семья”, “java плагин огромен по размеру и есть не у всех”. Первый подход (с генерацией статической картинки серверным скриптом) - самый универсальный и независящий от возможностей клиента. Вы не зависите от версии браузера, наличия в нем flash-плагина, настроек корпоративного proxy-сервера, который может резать часть трафика – те же flash-ролики. В минусы идет потеря интерактивности: так по нажатию на какой либо столбец в диаграмме достаточно сложно реализовать некоторый скрипт, который меняет изображение, переходит к новой странице, масштабирует картинку и т.д. Есть проблемы и с поддержкой анимации: необходимо использовать посторонние библиотеки. Например для php встроенный модуль gd2 не умеет работать с анимированными gif, но вы можете использовать gifsicle (http://www.lcdf.org/gifsicle/) или http://www.phpclasses.org/browse/package/3163.html. В минусы можно записать еще и неэкономное расходование сетевого трафика в некоторых случаях. В своей статье ([[Про тег html canvas - 1]]) я приводил пример, когда при отрисовке графика функции z=f(x,y), большая часть получившегося изображения будет залита одним цветом фона и лишь небольшая часть картинки будет отображать линии графика функции, оси, подписи. Для эксперимента я создавал картинку размером 1024*768 с нарисованной линией синусоиды. Ее размеры в разных форматах были таковы: gif - 8 кб, jpeg - 33 кб, png - зашкалил за 210 кб. В тот раз я обосновывал необходимость переноса логики построения графики на сторону клиента, а ajax поможет подгружать в страницу нужные данные. Если вы еще не сталкивались с тегом canvas - то это особый тег позволяющий выполнять рисование внутри веб-страницы: линии, дуги, кривые безье, работа с другими изображениями, операции масштабирования или вращения и т.д. на javascript. Формат изображений svg - основанный на xml и внедряется в дерево dom веб-страницы, поддерживается различные фигуры, заливки, текст, анимация - и все это доступно для управления через javascript. Увы, но в internet explorer для поддержки этих двух стандартов нужны сторонние плагины, о которых массовый пользователь ничего не знает, и устанавливать не собирается - в отличие от flash-плагина, распространенного, известного и легко обновляемого. Так значит flash - самое то? Увы, не совсем и не всегда. Что хочет сделать клиент после того как посмотрел страницу? Сохранить или распечатать. Увы, но самый замечательный браузер в мире - internet explorer что 6, что 7 не умеет сохранять flash-ролики. А это значит, что как бы хорошо не вела себя в плане сохранения flash-контента opera или firefox - решение увы не универсально. К тому же если flash-ролик не содержит внутри себя набор данных необходимых для построения графика, а загружает их динамически с сервера, то сохранить такую страницу в принципе не возможно (тут я немного лукавлю, решение есть - но технически очень сложное). Та же проблема с сохранением страницы на диске верна и для ajax-основанных сайтов. Таким образом идеального решения нет. И как вывод: если вам нужно решение работающее железно всегда и везде, страницы сохраняются и печатаются - на сервере генерируется статическая картинка. Важна возможность создания интерактивных графиков, диаграмм - flash.
Разумеется, что за я не первый кто озаботился проблемой построения красивых и функциональных графиков, на рынке есть и платные и бесплатные библиотеки позволяющие автоматизировать процесс разработки диаграмм. Сегодня я расскажу об трех интересных продуктах sparkline, libchart, graphpite - это основанные на php решения для динамической генерации изображения. Разумеется что подобных библиотек достаточно много, например по следующему адресу находится целый каталог подобных скриптов: http://www.hotscripts.com/PHP/Scripts_and_Programs/Graphs_and_Charts/. В следующий раз я закрою тему, рассказав об библиотеках для flash. Если будет достаточное количество откликов, то я также расскажу и о задаче визуализации в вебе графов. Если вы никогда не сталкивались с тем как работать в php с графикой и библиотекой gd2, то милости прошу познакомиться с http://www.php5.ru/articles/image. По правде говоря, есть еще один подход к построению диаграмм - имитация с помощью чистого html (например, столбчатая диаграмма будет представлена в виде таблицы - смотрится не так ужасно, как звучит). Если вас это заинтересует, то http://www.webguys.com/pdavis/Programs/html_graphs/, а мы переходим к sparkline.
Sparkline - написанная на php библиотека для построения маленьких, “wordlike” графиков внедряемых в текст страницы. Домашний сайт проекта http://sparkline.org/. В качестве технических требований к хостингу - только наличие модуля gd2 - есть везде и давно, даже бесплатных хостингах. В настоящее время есть несколько портов данной библиотеки на java, perl, ruby, javascript-версия (как основа используется canvas). Например, вот пример кода для использования javascript-основанной версии:
<html>
<style type="text/css">
.sparkline { display: none; }
</style>
<script language="javascript" src="jspark.js"></script>
<body>
Всю следующую неделю будет только холодать, только в конце недели выглянет солнце:
<span class="sparkline" style="width: 50px;">-2,-10,-8,-7,5,7</span>
</body>
</html>
Смотрится, откровенно, не очень: для восприятия не хватает осей и опорных подписей значений – но все же, как удобно применять sparkline, например, в основанных на mediawiki сайтах, подготовке документации и только из-за этого можно простить все.
Версия sparkline для php получше - прежде всего есть две версии sparkline: Sparkline_Bar и Sparkline_Line - диаграммы в виде набора столбиков (bar chart) и в виде линии соответственно. Я не привожу кода с примерами работы данной библиотеки поскольку ее php-версия достаточно сыра (я нашел пару багов практически не копая в глубину), и в терпеть ее недостатки по сравнению с libchart или graphpite просто не за что – данные уже не встраиваются внутрь hml-кода, а хранятся на сервере и вносятся внутрь объекта диаграммы с помощью функций setData (x, y) – схожий подход применяют и две следующие библиотеки, но качество кода там гораздо выше.
Теперь рассмотрим вторую библиотеку libchart (http://naku.dohcrew.com/libchart/pages/introduction/). Поддерживаются четыре вида диаграмм: VerticalBarChart, HorizontalBarChart, LineChart, PieChart. Методика работы традиционна: загружаете файлы библиотеки и подключаете их к своему проекту, затем создаете объект служащий для создания одной из тех самых четырех диаграмм. В качестве параметра конструктора следует указать размер будущего изображения и остается только наполнить диаграмму информацией. Для этого нам нужен еще один объект XYDataSet, метод addPoint которого как раз и служит для добавления очередной точки. XYDataSet привязывается к объекту диаграммы с помощью метода $chart->setDataSet($dataSet); и наконец мы задаем название диаграммы и говорим Render – создать изображение, как параметр задается имя файла, в который будет сохранено сформированное изображение, если же файл не задан, но результат в формате png будет отправлен сразу клиенту. Разве что вам придется явно указать тип отправляемого документа с помощью вызова header(что_мы_возвращаем). Интерес представляет также и возможность разместить на одной диаграмме несколько серий с данными (это не умеет делать sparkline). К сожалению совместить на одной картинке столбчатую диаграмму, линейную или круговую не возможно – столбчатые со столбчатыми, линейные с линейными, а круговые – вообще ни с чем. В примере ниже создаются диаграммы различных видов на основании случайных данных.
header ('Content-Type: image/png');
// задаем тип возвращемого документа
include "libchart/classes/libchart.php";
// подключаем библиотеку libchart
// создаем какую либо диаграмму
//$chart = new HorizontalBarChart(450, 250);
//$chart = new VerticalBarChart(450, 250);
//$chart = new VerticalBarChart(450, 250);
$chart = new LineChart(450, 250);
//создаем источник данных для диаграммы в виде набора серий
$dataSet = new XYSeriesDataSet();
// привязываем серии к диаграмме
$chart->setDataSet($dataSet);
// заполняем случайными данным диаграмму
for ($i = 0; $i < 3; $i++){
$serie1 = new XYDataSet();
for ($j = 0; $j < 5; $j++)
$serie1->addPoint(new Point("User_" . $j, $i * 20 + rand(1, 10)));
$dataSet->addSerie("Line # " . $i, $serie1);
}
// указываем название диаграммы
$chart->setTitle("Multi Series Demo");
// рисуем
$chart->render();
В следующем примере я создаю круговую диаграмму и заодно подключаю собственные шрифты:
header ('Content-Type: image/png');
include "libchart/classes/libchart.php";
$chart = new PieChart(500, 300);
$dataSet = new XYDataSet();
$dataSet->addPoint(new Point(iconv('windows-1251', 'utf-8', 'Дельфины'), 40));
$dataSet->addPoint(new Point(iconv('windows-1251', 'utf-8', 'Касатки'), 15));
$dataSet->addPoint(new Point(iconv('windows-1251', 'utf-8', 'Акулы'), 65));
$chart->setDataSet($dataSet);
$chart->setTitle(iconv('windows-1251', 'utf-8', 'Обитатели моря'));
$chart->render();
Код почти аналогичен прошлому примеру, разве что перед выводом текста я выполнил его преобразование из исходной кодировки windows-1251 в кодировку utf-8 с помощью функции iconv. Для отрисовки текста используются два файла шрифта размещенные в подкаталоге libchart/fonts – естественно, вы можете заменить их на другие.
Самой же интересной и сложной является библиотека graPHPite, ее домашний сайт (http://graphpite.sourceforge.net/). Кроме отрисовки уже знакомых нам столбчатых, круговых и линейных диаграмм возможно создавать комбинированные диаграммы, например: векторную диаграмму, лепестковую, логарифмическую, step, impulse, диаграммы с областями, столбчатые диаграммы с накоплением и много другое. На диаграмму можно выводить фоновое изображение, выполнять заливки градиентом, создавать линии со сглаживанием. Интересен и подход с специализированными генераторами данных привязываемых к диаграмме. Например, есть объект генератор случайных чисел по заданным параметрам, есть генератор-функция, генератор функция-вектор, генератор на основании содержимого некоторого массива. Придумана и функция фильтров позволяющих сгенерированные с помощью некоторого генератора данные перед использованием немного “подрихотовать”, например, если генератор создает значения 0,10,20… то фильтр может превратить эти значения перед выводом в 0%, 10%, 20%,… Далее я приведу пример кода создающего совмещенную диаграмму: области + столбчатую. Результат работы показан на рис. 4.
include("Image/Graph.php");
// создаем объект диаграммы с заданными размерами
$Graph =& new Image_Graph(400, 300);
// создается область рисования внутри диаграммы
$PlotArea =& $Graph->add(new Image_Graph_PlotArea());
// создается источник данных - случайный набор чисел в отрезке от 2 до 15 и числом 10
$DataSet =& new Image_Graph_Dataset_Random(10, 2, 15, true);
// И создаем собственно график на основании области рисования и набора данных
$Plot =& $PlotArea->addPlot(new Image_Graph_Plot_Area($DataSet));
// задаем цвет линии
$Plot->setLineColor(IMAGE_GRAPH_GRAY);
// выполняем заливку синим цветом области
$BLUE =& $Graph->newColor(IMAGE_GRAPH_BLUE, 100);
$Plot->setFillStyle($BLUE);
// Создаем источник данных для второй диаграммы - тоже случайные данные
$DataSet2 =& new Image_Graph_Dataset_Random(8, 30, 80, false);
// Создаем, собственно, диаграмму - столбчатую
$Plot2 =& $PlotArea->addPlot(new Image_Graph_Plot_Bar($DataSet2));
// Задаем параметры заливки цветом
$ORANGE =& $Graph->newColor(IMAGE_GRAPH_ORANGE, 100);
$Plot2->setFillStyle($ORANGE);
// Указываем - отображать подписи к осям
$AxisX =& $PlotArea->getAxis(IMAGE_GRAPH_AXIS_X);
$AxisY =& $PlotArea->getAxis(IMAGE_GRAPH_AXIS_Y);
// и рисовать стрелочки
$AxisX->showArrow();
$AxisY->showArrow();
// Теперь можно указать маркеры для значений графика
$Marker =& $Plot->add(new Image_Graph_Marker_Value(IMAGE_GRAPH_PCT_Y_MAX));
// фоновый цвет будет белым
$Marker->setFillColor(IMAGE_GRAPH_RED);
// черная граница
$Marker->setBorderColor(IMAGE_GRAPH_BLACK);
// задаем форму маркера
$PointingMarker =& $Plot->add(new Image_Graph_Marker_Pointing_Angular(20, $Marker));
// и привязываем маркер к графику # 1
$Plot->setMarker($PointingMarker);
// теперь форматируем значения текста для маркера
$Marker->setDataPreProcessor(new Image_Graph_DataPreprocessor_Formatted("%0.1f%%"));
// возможно указать легенду диаграммы с помощью пользовательских шрифтов
$Arial =& $Graph->addFont(new Image_Graph_Font_TTF("arial.ttf"));
// задаются размеры шрифта
$Arial->setSize(11);
// И выводим заданную надпись заданным шрифтом, незабываем также выполнить преобразование в формат utf-8
$Graph->add(new Image_Graph_Title(iconv("windows-1251", "utf-8", "Пример диаграммы с областями и столбиками"), $Arial));
// строим график и отправляем его клиенту
$Graph->done();
« Мои статьи на сайте javable.com | Графики, диаграммы, графы … и все это в веб? Часть 2 » |