КИРИЛЛ СУХОВ
JpGraph
Динамическая графика – это просто
Ещё каких-то лет пять назад человек, занимающийся разработкой сайтов, мог гордо именовать себя веб-мастером, не вызывая при этом иронических усмешек у коллег. В это понятие входили дизайн, вёрстка, веб-программирование, и в объявлениях о приёме на работу часто мирно уживались требования по знанию Perl и Photoshop. Теперь, разумеется, многое изменилось, и в резюме веб-программиста считается дурным тоном упоминать навыки работы с графическими пакетами, даже если таковые и присутствуют. Тем не менее если вы работаете в небольшом проекте, в бюджете которого не предусмотрен многочисленный штат сотрудников, то время от времени у вас может возникнуть необходимость в самостоятельном создании кое-какой графики – кнопочек, маркеров, пиктограмм, фоновых изображений, которые нужны для любого веб-интерфейса. В общем случае такие задачи решаются использованием готовой библиотеки изображений или приглашением знакомого дизайнера (как правило, за солидный гонорар – две, а то и три бутылки пива). Проблема, решение которой изложено ниже, не так стандартна и немного сложнее, хотя встречается не так уж редко.
Итак, представим, что вы разрабатываете/ведёте биллинг, или складскую систему, или… в общем, приложение, использующее базу данных и несущее достаточно сложную логику. Представим также (хотя это и сложнее), что никаких особых проблем у вас нет – приложение работает корректно, новая функциональность добавляется относительно легко, небольшие корректировки много времени не отнимают. В целом вы довольны, но кроме вас в офисе трудятся менеджеры, бухгалтеры, начальство, и вот у последнего возникают довольно своеобразные требования. В частности, шефа не устраивают ваши отчёты, генерирующиеся в виде аккуратных HTML-таблиц, ему нужны графики, причём графики красивые и ежедневно (ежечасно?!) обновляющиеся. Не рисовать же их руками?
К счастью, PHP-разработчик может воспользоваться таким мощным инструментом, как графическая библиотека GD, включаемая по умолчанию в дистрибутив, начиная с версии 4.0.1. Возможность попиксельной прорисовки и обработки изображения, манипуляции с размером картинки и текстом, наличие примитивов позволяют генерировать достаточно сложную графику. Проблема заключается в том, что на построение качественного графика или диаграммы уйдет довольно много времени, а наша задача в данном случае – справиться с поручением в сжатые сроки и с минимальным количеством кода. Существует некоторое количество готовых библиотек для разработки графиков, например, автор этих строк в своё время использовал довольно удачную разработку – phplot (http://www.phplot.com), но безусловным лидером в этом отношении является объектно-ориентированная библиотека JpGraph.
Среди её преимуществ можно выделить возможность построения множества видов графиков, неограниченного числа графиков одного типа в одном изображении, сглаживание линий, заполнение фонов диаграмм и гистограмм градиентной заливкой, генерация карт-изображений и кэширование рисунков. Стандартная версия библиотеки бесплатна (существует и коммерческая версия pro, в которой доступны некоторые дополнительные возможности, в частности, построение штрих-кодов). В статье речь пойдет о стандартной версии, о возможностях коммерческой читатель может ознакомиться на сайте производителя – http://www.aditus.nu/jpgraph/jpg_proversion.php. Ещё одним достоинством этого продукта является прекрасная документация, идущая вместе с дистрибутивом. На самом деле подробность документации ставит под сомнение необходимость чтения этой статьи, но автор этих строк, как наверняка и кто-нибудь из вас, попадал в ситуацию, когда времени на обстоятельное изучение руководства просто нет, и я попытался изложить этот материал для того, чтобы программист по его прочтении мог немедленно приступить к работе.
Постановка задачи
Для примера возьмём вполне реальную проблему из практики небольшого интернет-провайдера. Необходимо написать скрипт, строящий график распределения входящего трафика по дням за определённый период (скажем, за последние десять дней). Естественно, график должен строиться динамически и генерироваться по запросу на внутреннем веб-сайте компании. Будем считать, что веб-сервер с поддержкой PHP уже установлен.
Все нижеописанные действия проводились на веб-сервере Apache версии 1.3.31 (Win32), работающем под управлением Windows 2000 Server. Версия интерпретатора PHP – 4.3.9 с поддержкой библиотеки GD2, версии 2.0.28. PHP был установлен как модуль.
Инсталляция
В целом установка библиотеки JpGraph никаких сложностей не вызовет. Нужно просто скачать архив с библиотекой, расположенный по адресу http://www.aditus.nu/jpgraph.php, и распаковать его в папку документов вашего веб-сервера. До установки следует убедиться, что PHP у вас версии не ниже 4.01 (рекомендуется 4.3х) и скомпилирован с поддержкой библиотеки GD (для доступа к последним возможностям JpGraph, GD2).
Графические форматы, с которыми работает библиотека, определяются возможностями установленной GD и автоматически выбираются JpGraph в следующем порядке: «PNG», «GIF», «JPG».
Для проверки установки GD руководство по JpGraph рекомендует следующий простой сценарий:
$im = ImageCreate (50, 100)
or die ("Cannot create a new GD image.");
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$text_color = ImageColorAllocate ($im, 233, 14, 91);
ImageString ($im, 1, 5, 5, "A Simple Text String", $text_color);
header ("Content-type: image/png");
ImagePng ($im);
Если тест прошёл удачно и изображение было создано, можно устанавливать библиотеку и начинать работу, но для реализации поставленной задачи необходимо выполнить ещё несколько действий. Прежде всего вы должны обеспечить поддержку TTF-шрифтов (они нам обязательно понадобятся, об этом далее). Для этого PHP должен быть собран с поддержкой TTF, а также с FreeType 1 или FreeType 2 библиотеками, и несколько (как минимум два) шрифтов установлены в системе.
Следующий этап – настройка нелатинских шрифтов. Понятно, что в данном случае необходимости в китайских символах у нас нет, а для использования кириллицы необходимо отредактировать файл jpg-config.inc. В нём имеются следующие настройки:
$im = ImageCreate (50, 100)
or die ("Cannot create a new GD image.");
$background_color = ImageColorAllocate ($im, 255, 255, 255);
$text_color = ImageColorAllocate ($im, 233, 14, 91);
ImageString ($im, 1, 5, 5, "A Simple Text String", $text_color);
header ("Content-type: image/png");
ImagePng ($im);
После установки значений обоих параметров в true необходимо убедиться, что на сервере имеются стандартные юникодные шрифты (обычно они лежат в директории типа /usr/X11R6/lib/X11/fonts/TTF/) и проверить, поддерживают ли они кириллицу. В случае их отсутствия шрифты следует установить, например с Windows-машины (должен заметить, что при установке под Windows проблем с кириллицей вообще не возникает). После этого в том же файле jpg-config.inc следует прописать путь к шрифтам:
DEFINE("TTF_DIR",…);
Теперь всё готово. Для проверки и ознакомления с возможностями инструмента раскроем в браузере файл src/examples/testsuit.php из дистрибутива библиотеки. Мы увидим около трёхсот графиков диаграмм и рисунков, кликнув по каждому из которых, можно увидеть исходный код примера. Самый лёгкий способ начать работу с библиотекой – взять наиболее подходящий пример и методом «научного тыка» подогнать его под свои нужды. Когда передо мной встала необходимость быстро построить график, я так и сделал, но, разумеется, для полноценной работы с библиотекой необходимо разобраться в её свойствах и методах, поэтому далее мы будем строить график «с нуля».
Приступаем к работе (простой график)
Прежде всего определимся с исходными данными. Разумеется, в реальных условиях они будут извлекаться из базы данных, но чтобы не усложнять задачу, мы просто создадим два массива, первый из которых будет состоять из чисел месяца, а второй – из соответствующих им показателей входящего трафика.
<?
$traff = array(350, 280, 450, 615, 588,755, 547, 320, 380, 810); // трафик (Мб)
$days = array (9, 10, 11, 12, 13, 14, 15, 16, 17, 18); // даты
?>
Для построения графика используем следующий код:
include ("../jpgraph.php");
include ("../jpgraph_line.php");
$graph = new Graph(450, 200,"auto");
$graph->SetScale( "textlin");
$lineplot =new LinePlot($traff);
$graph->Add( $lineplot);
$graph->Stroke();
Если всё установлено правильно, в результате должна получиться картинка, показанная на рис. 1 (пример дан в предположении, что ваш скрипт test.php расположен в папке src/examples/, если это не так, отредактируйте пути к библиотеке).
Рисунок 1
Конечно, это ещё довольно далеко от желаемого результата, но давайте сначала разберёмся в том, что мы уже сделали.
В первых двух строчках кода мы подключаем базовую библиотеку jpgraph.php и один из модулей (plot extensions), в данном случае это jpgraph_line.php, необходимый для построения линейных графиков. Затем создаём объект класса Graph, задав его размеры, и выбираем масштаб оси X (метод SetScale()). Потом создается объект класса plot (график), параметры которого задаются созданным нами ранее массивом, сохранённым в переменной $traff. В последних двух строчках график добавляется в объект Graph и методом Stroke() Graph выводится в браузер.
Данные по оси X должны соответствовать реальным датам. Для этого добавим строчку:
$graph->xaxis->SetTickLabels($days);
Теперь добавим заголовок и подписи к осям (для чего нам понадобится устанавливать шрифты).
$graph->title->SetFont(FF_VERDANA,FS_NORMAL,12);
$graph->xaxis->title->SetFont(FF_VERDANA,FS_NORMAL,8);
$graph->yaxis->title->SetFont(FF_VERDANA,FS_NORMAL,8);
$graph->title->Set("Трафик");
$graph->xaxis->title->Set("Период");
$graph->yaxis->title->Set("Трафик (Mb)");
Задача в первом приближении выполнена.
Рисунок 2
Теперь осталось добавить удобочитаемость. Выполняем следующие действия.
Устанавливаем отступы и поля для подписи к оси Y (поскольку с ней проблемы):
$graph->SetMargin(60,40,40,40);
$graph->yaxis->SetTitlemargin(40);
Добавляем маркеры и подписи к ним:
$lineplot->mark->SetType(MARK_IMG_DIAMOND, "blue", 0.3);
$lineplot->value->Show() ;
$lineplot->value->SetColor("darkgray");
$lineplot->value->SetFont(FF_FONT0,FS_BOLD);
$lineplot->value->SetFormat(" %01.0f Mb");
«Раскрашиваем» график, добавляем фон и тень:
$lineplot ->SetColor("blue");
$graph->SetColor("cadetblue1");
$graph->SetMarginColor("lightblue3");
$graph->SetShadow();
По-моему, в результате получился вполне симпатичный график.
Рисунок 3
Полный код, генерирующий эту картинку, приведен на сайте журнала http://www.samag.ru/source.
Всё, что осталось сделать, – это сохранить весь код в файле (например, my_graph.php) и в нужном месте веб-страницы вставить тег рисунка с указанием на этот файл (<img src=”my_graph.php”).
Что внутри?
Теперь давайте разберёмся с внутренним устройством библиотеки. Как уже было упомянуто, для построения любого изображения необходимо включить в сценарий основной модуль – файл jpgraph.php и один или несколько модулей, ответственных за построение изображения требуемого типа. Все они содержатся в папке src, хотя могут быть перенесены оттуда в любое удобное для вас место. (При этом, разумеется надо соблюдать некоторые зависимости. Так, внутренний модуль jpgraph_gradient.php никогда явно не подключается к сценарию, но используется другими модулями и без него не обойтись при генерации любого изображения, использующего градиентную заливку.) Ниже перечислены основные модули.
- jpgraph_line.php – уже знакомое нам расширение для построения линейных графиков.
- jpgraph_bar.php – модуль для построения баров (гистограмм).
- jpgraph_log.php – модуль для отображения логарифмических зависимостей.
- jpgraph_error.php – модуль для вывода графических сообщений об ошибках (для чего это нужно – далее).
- jpgraph_pie.php – модуль для построения плоских диаграмм.
- jpgraph_pie3d.php – модуль для построения 3D-диаграмм.
- jpgraph_gantt.php – модуль для построения графиков Ганта.
- jpgraph_radar.php, jpgraph_polar.php – модули позволяют строить «центростремительную графику».
- jpgraph_regstat.php – модуль для построения сглаженных графиков.
- jpgraph_scatter.php – модуль для отображения распределенных по плоскости элементов и векторов.
- jpgraph_iconplot.php – модуль для работы с иконками, которые могут быть добавлены к графикам.
- jpgraph_plotband.php – модуль для работы с группами данных на графиках.
- jpgraph_canvas.php – модуль для возможности построения произвольных графиков.
- jpgraph_canvtools.php – модуль для возможности построения произвольных форм.
С полным списком подключаемых модулей вы можете ознакомиться в соответствующем разделе документации.
В коммерческую версию библиотеки входят ещё несколько модулей, а именно:
- jpgraph_windrose.php – модуль для построения розы ветров.
- jpgraph_odo.php – модуль для отображения одометрических данных.
- jpgraph_barcode.php – модуль для построения линейных штрих-кодов.
- jpgraph_pdf417.php – модуль для построения штрих-кодов PDF417 2D.
Усложняем задачу
Теперь, используя полученную информацию, посмотрим, на что ещё способна библиотека JpGraph.
Сглаженный график
В вышеприведенном примере необходимость сглаживать линии графика отсутствует, но для того, чтобы просто продемонстрировать эту возможность, мы сделаем это, немного расширив диапазон значений.
Сначала представим исходные данные:
$traff = array(810,480, 550, 715, 688, 785, 847,902, 350, 280, 450, 615, 588,755, 547,320, 380); // трафик (Мб)
$days = array (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17); // даты
Теперь строим сглаженный график, подключив для этого ещё один модуль:
include "../jpgraph.php";
include "../jpgraph_line.php";
include "../jpgraph_regstat.php";
$spline = new Spline($days,$traff);
list($newx,$newy) = $spline->Get(100);
$g = new Graph(300,200);
$g->SetMargin(30,20,40,30);
$g->SetScale('linlin');
$g->xaxis->SetLabelFormat('%0.0f');
$lplot = new LinePlot($newy,$newx);
$g->Add($lplot);
$g->Stroke();
Общая схема та же, что и в случае с простым графиком, но в данном случае мы создаём объект Spline(), описанный в модуле jpgraph_regstat.php. Быть может, результат не очень впечатляет, но довести его до приличного вида большого труда не составит.
Рисунок 4
Добавим на график маркеры, соответствующие точным значениям трафика, и нарисуем координатную сетку. Сначала подключим ещё один модуль:
include "../jpgraph_scatter.php";
Теперь расставляем маркеры:
$splot = new ScatterPlot($traff, $days);
$splot->mark->SetFillColor("blue@0.3");
$splot->mark->SetColor("green@0.5");
$g->Add($splot);
И координатную сетку:
$g->xgrid->Show();
Результат:
Рисунок 5
Бары (гистограммы)
Указанное в заголовке представление данных было бы как раз наиболее уместным в предыдущем случае, но мы, следуя наказам менеджмента, используем эту визуализацию несколько для другого – для отображения количества подключённых клиентов. Опять берём два массива:
$pepl = array(8, 16, 10, 7, 4,12, 7,15, 11, 22); // количество подключений
$days = array (9, 10, 11, 12, 13, 14, 15, 16, 17, 18); // даты
И строим простую гистограмму:
$width=480;
$height=280;
$graph = new Graph($width,$height,"auto");
$graph->SetScale("textlin");
$graph->SetBox();
$graph->xaxis->SetTickLabels($days);
$bplot = new BarPlot($pepl);
$bplot->SetWidth(0.5);
$graph->Add($bplot);
$graph->Stroke();
Собственно, это и всё. Немногим отличается от предыдущих примеров. Всего несколько строк кода, а результат уже почти годен к употреблению. Сделаем его пригодным окончательно.
Рисунок 6
Сначала добавим подписи к столбцам и заголовки:
$graph->title->SetFont(FF_VERDANA,FS_BOLD,11);
$graph->title->Set("Подключения");
$graph->subtitle->SetFont(FF_VERDANA,FS_NORMAL,9);
$graph->subtitle->Set("(Декабрь 2004)");
$bplot->value->Show();
$bplot->value->SetFont(FF_ARIAL,FS_BOLD,8);
$bplot->value->SetAlign("left","center");
$bplot->value->SetColor("white");
$bplot->value->SetFormat("%.0f");
$bplot->SetValuePos("max");
Теперь сделаем картинку немножко привлекательней:
$graph->SetMarginColor("white");
$graph->SetBackgroundGradient("white","lightblue", GRAD_HOR,BGRAD_PLOT);
$graph->SetFrame(false);
$graph->xaxis->SetFont(FF_VERDANA,FS_NORMAL,8);
$graph->xaxis->SetLabelMargin(10);
$graph->xaxis->SetLabelAlign("right","center");
$graph->yaxis->scale->SetGrace(20);
$bplot->SetShadow();
$bplot->SetFillGradient("green","blue",GRAD_HOR);
Я не думаю, что данный код требует каких-либо объяснений. Всё вполне интуитивно понятно.
Результат, конечно не является шедевром делового дизайна, но вполне приемлем.
Рисунок 7
Диаграммы
Для демонстрации этой возможности также не будем далеко уходить от реальности. Пусть на круговой диаграмме необходимо отобразить распределение потребляемого трафика. Допустим, что трафик, израсходованный физическими лицами за текущий месяц, составил 54.36 Гб, юридическими – 64.57 Гб, и, наконец, 6.75 Гб ушли на внутренние нужды провайдера. Следующими несколькими строчками кода (обратившись к расширению PieGraph) рисуем простую круговую диаграмму:
<?
include ("../jpgraph.php");
include ("../jpgraph_pie.php");
$data = array(54.36, 64.57, 6.75);
$graph = new PieGraph(300,200);
$p1 = new PiePlot($data);
$graph->Add($p1);
$graph->Stroke();
?
Результат показан на рис. 8. Как видите, проценты реализации библиотека вычисляет сама (мелочь, а приятно).
Добавляем заголовок и для пояснения создаем легенду (пояснения к диаграмме или карте, иначе говоря «условные обозначения»). Легенду можно добавить практически ко всем объектам JpGraph, она представляет собой самостоятельный объект:
$graph->title->SetFont(FF FF_VERDANA,FS_BOLD,8);
$graph->title->Set("Распределение трафика");
$graph->legend->SetColor("navy");
$graph->legend->SetFillColor("lightgreen");
$graph->legend->SetLineWeight(1);
$graph->legend->SetFont(FF_ARIAL,FS_NORMAL,8);
$graph->legend->SetShadow("gray@0.4",3);
$graph->legend->SetAbsPos(10,100,"right","bottom");
$legends = array("Физ лица","Юр Лица","Служебный");
$p1->SetLegends($legends);
Результат вполне информативен (рис. 9), но, как и в предыдущих примерах, для красоты придадим диаграмме объем и выделим служебный трафик.
Рисунок 8
Рисунок 9
Нужно заметить, что для работы следующего кода необходима вторая версия библиотеки GD, но так как в дистрибутив PHP в настоящее время входит расширение GD2, это обстоятельство проблем создать не должно.
Для начала подключаем ещё один модуль:
include ("../jpgraph_pie3d.php");
Заменяем вид диаграммы:
//$p1 = new PiePlot($data);
$p1 = new PiePlot3D($data);
Выделяем служебный трафик:
$p1->ExplodeSlice(2);
Изменяем цвета по умолчанию:
$p1->SetSliceColors(array("skyblue3","yellow3","darkred"));
Наконец, убираем совершенно неуместную в данном случае рамку:
$graph->SetFrame(false);
Полученный результат, думается, годится для представления.
Рисунок 10
Дополнительные возможности
Кэширование
Любой программист, работавший с библиотекой GD, знает, как сильно генерация изображений потребляет системные ресурсы. Если подобный график требуется создавать раз в сутки – ничего страшного, но если в вашем офисе десяткам менеджеров требуются эти данные ежеминутно, создание графиков на лету порождает серьёзные проблемы с производительностью. Поэтому нельзя переоценить ещё одну впечатляющую возможность JpGraph – управляемое кэширование изображений. Под этим термином здесь подразумевается сохранение созданного изображения на сервере, на заранее заданный период времени. В самом деле, данные по ежедневному трафику обновляются раз в сутки, соответственно более одного раза за 24 часа создавать изображение не имеет смысла. Для этого в объявлении нового класса указываем (кроме размеров графа) имя файла, в котором будет сохранено изображение, и период актуальности данного кэша в минутах:
$graph = new Graph(450, 200, "traf”, 1464, true);
Теперь при повторном запросе графика в течение 24 часов клиенту будет возвращено сохранённое в файле изображение, а выполнение скрипта прервется.
Сообщения об ошибках
Если при обращении к какому-либо объекту или методу библиотеки была допущена ошибка, в браузер вместо изображения отправляется графическое сообщение об ошибке, аналогичное показанному на рис. 10.
Для чего это нужно? Всё просто. Основной способ передачи изображений, созданных JpGraph, – использование тега . Если бы не этот механизм, в случае возникновения вышеописанной ошибки в браузер выводилось бы не привычное для разработчика сообщение, а рамка отсутствующего рисунка, которая, разумеется, жизнь программисту никак не облегчает. Более того, при работе с библиотекой имеет смысл писать свои обработчики ошибок с графическим выводом (благо её возможности это вполне позволяют) к таким распространённым сбоям, как неудачный запрос к базе данных или неполадки при работе с файловой системой.
Рисунок 11
Что в перспективе
Библиотека JpGraph живёт и развивается, обрастая новыми модулями. Неожиданное препятствие на пути её использования возникло в связи с переходом разработчиков на пятую версию языка PHP с совершенно другой объектной моделью. Это общая проблема любых ОО-библиотек, заточенных под PHP 4. Язык обрёл новые возможности, но, как это нередко бывает, потерял частично обратную совместимость.
Так, уже первый пример из скрипта tessuit.php на новом движке выдаёт ошибку. Что делать? Можно, конечно, подправить код библиотеки (причём, скорее всего, отказавшись от некоторых возможностей), но можно и немного подождать. Создатели JpGraph держат руку на пульсе прогресса, и уже доступна альфа-версия библиотеки JpGraph 2, ориентированная на пятую версию PHP (вернее, на новую объектную модель). Мне кажется, что внесенные изменения не будут препятствовать разработчикам, а напротив, помогут освоить новые горизонты.
Заключение
Конечно, в этой статье я осветил лишь небольшую часть воз-можностей JpGraph. За кадром остались использование карт-изображений (image-map), генерация текста, работа с готовой графикой, с фоновыми изображениями и многое другое. Изучить их несложно.
Как уже упоминалось, JpGraph – не единственное средство для работы с графикой на PHP. Широко распространены такие инструменты, как мощнейший по возможностям, но, к сожалению, коммерческий пакет для работы с деловой графикой ChartDirector, или библиотека по работе с готовыми изображениями ImageMagic.
Наличие таких средств обработки графики, конечно, ещё не повод исключить из процесса «сайтостроительства» дизайнера, но возможность иногда обойтись кодом без участия дорогостоящих графических пакетов весьма приятна.
При написании данного материала были использованы статья Александра Шиляева «JpGraph и русский язык» (http://detail.phpclub.ru/article/jpgraph_ru) и перевод руководства по JpGraph, выполненный Екатериной Жемойтук.