Дополнительные возможности JpGraph
Владимир Жульев
2004-04-13
Оригинал статьи доступен по адресу http://phparch.com/issuedata/articles/article_40.pdf
JpGraph (http://www.aditus.nu/jpgraph/) - объектно-ориентированная PHP-библиотека, позволяющая достаточно просто создавать графику профессионального качества, используя минимум кода. Данная статья представляет собой учебный пример, иллюстрирующий некоторые дополнительные возможности библиотеки JpGraph, а именно:
- общая методика разработки скриптов с использованием JpGraph;
- последовательный процесс разработки графиков (в отличие от простой демонстрации конечного результата);
- использование механизмов кэширования JpGraph для увеличения производительности;
- использование карт-изображений на стороне клиента (далее - CSIM, т.е Client Side Image Map. Прим. перев.) для реализации быстрой навигации.
Инсталляция и необходимое программное окружение
Для начала работы с JpGraph необходимо скачать исходный код, доступный по адресу: http://www.aditus.nu/jpgraph/jpdownload.php.
Далее распакуйте содержимое архива в одну из директорий, перечисленных в конфигурационной опции PHP include_path. Теперь вы можете изменить пути к директориям установленных в вашей системе шрифтов, а также директории кэша изображений. Эти настройки находятся в файле jpgraph.php.
Для проверки правильности инсталляции просмотрите страницу /Examples/testsuit.php. Эта страница генерирует более 200 демонстрационных графиков, используя библиотеку JpGraph, и позволяет просмотреть исходный код, использованный для построения каждого из них.
Если вы только изучаете JpGraph и исследуете его возможности, вам потребуется справочное руководство. Оно доступно по адресу http://www.aditus.nu/jpgraph/jpdownload.php и содержит как последовательное описание, так и превосходный справочник по классам. Вы также можете посетить форум поддержки JpGraph - http://jpgraph.fan-atics.com.
Все скрипты в этой статье были разработаны и протестированы на PHP 4.3.0 (с включенной поддержкой библиотеки GD2), установленном как модуль к Apache 1.3.27 под управлением операционной системы RedHat Linux 7.2. Скрипты используют СУБД MySQL (http://www.mysql.com) и ADOdb (http://php.weblogs.com/adodb) как абстрактный уровень доступа к базе данных. По этой причине все скрипты включают в себя файл phpa_db.inc.php, показанный в листинге 1.
Примечание переводчика: в английском варианте статьи нет информации об используемой версии библиотеки JpGraph. Здесь и далее информация о путях, конфигурационных опциях, URI приведена для версии 1.14, имеющейся в распоряжении переводчика. Код примеров сохранен оригинальный. Примеры тестировались на Windows 2000 Server SP4/Apache 1.3.27/PHP 4.3.3 (уст. как модуль к Apache)/JpGraph 1.14/MySQL 4.1-alpha.
Листинг 1: phpa_db.inc.php
<?php
error_reporting(E_ALL);
require_once 'adodb/adodb.inc.php';
define('MYSQL_DT_FMT', '%Y-%m-%d');
$conn = &ADONewConnection('mysql');
//$conn->debug=true;
$conn->Connect('localhost', 'phpa', 'phpapass', 'phpa');
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
?>
Данный включаемый файл создает объект-соединение ADOdb - $conn, который используется для доступа к БД в остальных скриптах. Вам необходимо изменить параметры вызова метода Connect() в соответствии с вашими установками, в том числе и имя тестовой базы данных.
Примечание переводчика: Пример, более понятный для русскоязычного читателя:
$conn->Connect('localhost', 'username', 'passwd', 'dbname');
ADOdb должна быть установлена в одну из директорий, перечисленных в конфигурационной опции PHP include_path. Для читателей, не знакомых с ADOdb, я дам очень краткий обзор некоторых базовых возможностей. В ADOdb вы можете получить результат выполнения SQL-запроса используя следующий вызов:
$rs = $conn->Execute($sql_statement, $bind_array);
Если запрос был выполнен удачно, метод вернет объект - набор записей ADOdb, в противном случае метод вернет FALSE. Затем используются два метода этого объекта: GetArray() и GetAssoc(). Метод GetArray() возвращает вектор записей, где каждая запись представлена ассоциативным массивом вида 'COLUMN' => 'VALUE'. Метод GetAssoc() вместо вектора возвращает ассоциативный массив, индексы которого являются значениями первого столбца для каждой записи.
Приведенные ниже примеры предполагают, что у вас в системе установлен TrueType шрифт Arial, в противном случае JpGraph будет выдавать ошибку. Самый простой способ обойти эту проблему - заменить все ссылки на константу FF_ARIAL на FF_FONT1. FF_FONT1 - встроенный системный шрифт, он выглядит не так приятно как FF_ARIAL, но позволит вам просмотреть приведенные примеры. Этого должно быть достаточно, чтобы приступить к примерам. Теперь рассмотрим учебную задачу.
Учебная задача
В нашем учебном примере мы рассмотрим данные о продажах компании ABC, вымышленного производителя украшений. ABC производит широкий спектр украшений от дешевого B за $12.99 до украшений E класса "ультра-делюкс" за $1,499.50. В данном примере мы сосредоточимся на продажах в континентальных Соединенных Штатах, где компания разделена на четыре подразделения по регионам. Компания продает товар распространителям по трем каналам: Интернет, телефонный центр, и различные розничные точки.
Вас попросили сделать графики, отображающие данные о продажах в долларах и натуральных показателях. Вам необходимо также разбить графики по позициям каталога для каждого региона и выполнить сравнение со спрогнозированными данными. Компания ABC также требует, чтобы вы сделали график, показывающий данные о продажах за год по каналам для каждого региона. Им также необходима быстрая навигация между двумя графиками.
Разработка базы данных
Модель данных компании ABC составляют шесть таблиц. Центральная таблица - abc_sales. Таблица содержит информацию о продажах, включая время, канал, штат, позицию каталога, количество проданных изделий и доход от сделки.
Другие таблицы, составляющие модель данных - abc_catalog, abc_channel, abc_forecast, abc_region и abc_state_region. Таблица abc_catalog содержит суррогатный ключ для позиций каталога, которые клиент может приобрести, описания позиций и цену за единицу. Таблица abc_channel содержит суррогатный ключ, наименование и описание канала продажи (web, телефон, розничная точка), через который может быть куплен товар из каталога. Таблица abc_forecast хранит информацию о спрогнозированных планах продаж по позициям каталога, каналам продаж и месяцам. Таблица содержит информацию об ожидаемых объемах продаж и доходах по каждому "срезу" данных. Таблица abc_region содержит суррогатный ключ и описание для каждого региона продаж компании. Таблица abc_state_region ставит в соответствие аббревиатуру штата конкретному региону продаж.
SQL-запросы, используемые для создания таблиц, а также заполнения небольших таблиц, находятся в файле mysql_ddl.sql в директории с исходным кодом данной статьи. Что касается других, то таблицы продаж и прогнозов продаж могут быть заполнены с использованием скриптов abc_gen_sales.php и abc_fcst_ins.php соответственно. Скрипт abc_gen_sales.php должен запускаться первым, так как работа abc_fcst_ins.php зависит от данных, которые создает первый скрипт.
Примечание переводчика: Для создания учебной БД потребуются еще и данные о переписи населения в США за 2000 год. Эта информация (в виде ASCII-файла) доступна по адресу http://www.census.gov/geo/www/gazetteer/tiger/tms/gazetteer/ustracts2k.zip.
Поэтапно процесс создания и заполнения учебной БД выглядит так:
- Скачать файл с данными переписи, распаковать и разместить его в директории с учебными примерами.
- jvn_parse_census.php (соединение с БД - см. комментарии в скрипте) - парсит файл ustracts2k.txt (данные переписи в формате ASCII), создает и заполняет таблицу census_data, необходимую для работы скрипта abc_gen_sales.php.
- mysql_ddl.sql.
- abc_gen_sales.php.
- abc_fcst_ins.php.
Примечание: Оригиналы данных скриптов были написаны в декабре 2002 года, данные подобраны из расчета, что текущей датой является 15 декабря 2002 года, чтобы графики выводились практически за полный год. Так как сейчас 2003 год, скрипты были модифицированы таким образом, чтобы запрашивать и выводить данные за предыдущий год.
Примечание переводчика: статья переводилась на русский язык в феврале 2004 года, и в скрипты были внесены необходимые изменения.
Сравнение данных о продажах со спрогнозированными показателями по регионам
Нашей первой задачей будет создание графика, сравнивающего объем продаж и доход с прогнозом для каждого региона. Предполагается, что эта информация является собственностью компании и не предназначена для публичного распространения.
При разработке PHP-скриптов, создающих графики с использованием JpGraph, я предпочитаю использовать следующий 4-х шаговый процесс:
- Получение и обработка данных для построения графика.
- Создание объекта Graph и задание его основных свойств (таких как цвет и координатные оси графиков).
- Создание объектов Plot, для размещения на объекте Graph.
- Завершение объекта Graph и его вывод графика.
Согласно вышеописанному процессу вы, в первую очередь, должны получить данные о продажах и прогнозные данные. Чтобы посмотреть, как используется ADOdb для получения данных этого графика, посмотрите строки 26-122 скрипта abc_reg_sales_graph.php. Цель этой статьи - построение графиков, поэтому вопросы извлечения данных из базы и конструирования графиков детально не рассматриваются. Поскольку графики строятся по регионам, ваш скрипт должен иметь соответствующий параметр и проверять его корректность. Если параметр корректен, его значение присваивается переменной $region_id, в противном случае генерируется ошибка. Здесь имеет место небольшая проблема. Так как данные, выводимые скриптом - графический объект, страницы сайта должны ссылаться на него используя HTML-тэг <img>, т.е так:
<img src="abc_reg_sales_graph.php">
Если скрипт выведет текстовое сообщение (например, сообщение об ошибке PHP), это будет понято как некорректное изображение, и все, что увидит пользователь - символ отсутствующего изображения на том месте, где должен быть ваш график. Код, показанный в листинге 2, проверяет переданный параметр "регион" и формирует сообщение об ошибке в графическом виде. Данный код предполагает, что вы уже выбрали из базы данных список регионов продаж и сохранили результат запроса в глобальной переменной-массиве $regions. Также предполагается, что в скрипт включены библиотечные файлы jpgraph.php и jpgraph_canvas.php.
Листинг 2: Создание сообщения об ошибки в графическом виде (abc_reg_sales_graph.php)
<?php
$region_id = check_passed_region('region');
if (!$region_id) {
graph_error('region parameter incorrect');
}
function check_passed_region( $parm ) {
global $regions;
if (array_key_exists($parm,$_GET)) {
$val = $_GET[$parm];
if (array_key_exists($val, $regions)) {
return $val;
}
}
return false;
}
function graph_error($msg) {
$graph = new CanvasGraph(WIDTH, HEIGHT);
$t1 = new Text($msg);
$t1->Pos(0.05, 0.5);
$t1->SetOrientation('h');
$t1->SetFont(FF_ARIAL, FS_BOLD);
$t1->SetColor('red');
$graph->AddText($t1);
$graph->Stroke();
exit;
}
?>
После извлечения из БД данных в виде набора записей часто необходимо преобразовать их в формат, более подходящий для построения графика. Это означает создание нескольких массивов с индексами, начинающимися с нуля для каждого числового ряда.
Вместо того, чтобы создавать несколько глобальных массивов для каждого ряда, я предпочитаю иметь единый ассоциативный массив с именем $graphData, содержащий массивы для каждого ряда и имеющий индексы, совпадающие с именами этих рядов. Код в листинге 3 собирает эти массивы в единый массив $graphData, который будет использоваться в дальнейшем для построения графиков.
Листинг 3: Формирование единого массива $graphData (abc_reg_sales_graph.php)
<?php
$graphData['f_qty'] = array();
$graphData['labelX'] = array();
for ($i=0,$j=count($salesData); $i<$j; $i++) {
$row = $salesData[$i];
if ('A'==$row['short_desc']) {
$graphData['labelX'][] = strftime('%b', mktime(0, 0, 0, $row['m'], 1, $row['y']));
}
if (!array_key_exists($row['m']-1, $graphData['f_qty'])) {
$graphData['f_qty'][$row['m']-1] = $fcstData[$row['f_key']]['qty'];
$graphData['f_rev'][$row['m']-1] = $fcstData[$row['f_key']]['rev'];
$graphData['qty'][$row['m']-1] = $row['qty'];
$graphData['rev'][$row['m']-1] = $row['rev'];
} else {
$graphData['f_qty'][$row['m']-1] += fcstData[$row['f_key']]['qty'];
$graphData['f_rev'][$row['m']-1] += fcstData[$row['f_key']]['rev'];
$graphData['qty'][$row['m']-1] += $row['qty'];
$graphData['rev'][$row['m']-1] += $row['rev'];
}
if(!array_key_exists($row['short_desc'], $graphData)) {
$graphData[$row['short_desc']]['qty'] = array();
$graphData[$row['short_desc']]['rev'] = array();
}
$graphData[$row['short_desc']]['qty'][] = $row['qty'];
$graphData[$row['short_desc']]['rev'][] = $row['rev'];
}
?>
Примечание переводчика: Здесь автор статьи приводит довольно труднопереводимое высказывание, смысл которого сводится к следующему. Вместо того, чтобы создавать для каждого отображаемого на графике числового ряда отдельный массив, автор предлагает использовать один ассоциативный массив для всего графика. Т.е., вместо:
$gty = array(...);
$f_gty = array(...);
$graphData = array (
'gty' => array(...),
'f_gty' => array(…)
);
Этот код иллюстрирует сущность JpGraph API. Улучшение внешнего вида ваших графиков приведет к увеличению объема исходного кода, но код, приведенный в данном примере, является минимальным.
Эти несколько строк кода реализуют 4-х шаговый процесс, описанный выше. На шаге 2 Вы создаете и конфигурируете объект Graph. На шаге 3 создаются объекты BarPlot и LinePlot. Вы завершаете процесс на шаге 4, размещая графики на объекте-изображении и вызывая метод Graph::Stroke() для вывода графического объекта. Вызов метода Graph::Stroke() предписывает JpGraph напрямую вернуть браузеру изображение. Результат показан на рис. 1.
Рисунок 1. Ваш первый график
Листинг 4: Ваш первый график
<?php
$graph = new graph(WIDTH, HEIGHT);
$graph->SetScale('textlin');
$b1 = new BarPlot($graphData['qty']);
$l1 = new LinePlot($graphData['f_qty']);
$graph->Add($b1);
$graph->Add($l1);
$graph->Stroke();
?>
Непрерывность линии графика (спрогнозированных продаж - прим.перев.) на самом деле не соответствует действительности, так как данные в реальности не изменяются плавно от месяца к месяцу (прогнозные данные привязаны к месяцам). Данные прогноза будут лучше представлены "ступенчатой" линией графика.
Давайте внесем эти изменения и, заодно, покажем данные о доходах вместо данных об объемах продаж. Вы можете сделать это, изменив два графика так, как показано в листинге 5. Результат показан на рисунке 2.
Листинг 5: Графики для создания изображения, показанного на рис. 2
$b1 = new BarPlot($graphData['rev']);
$l1 = new LinePlot($graphData['f_rev']);
$l1->SetStepStyle();
$l1->SetColor('darkgreen');
$l1->SetWeight(3);
Рисунок 2. Использование "ступенчатой" линии графика.
Для представления объемов продаж и доходов на одном графике вам необходимо воспользоваться возможностью JpGraph создавать на графике вторую ось ординат.
На следующем шаге мы совместим в одном изображении график объемов продаж и график доходов. Здесь есть два принципиальных момента. Во-первых, взглянув на две диаграммы, вы можете увидеть, что графики продаж и доходов представлены в разных масштабах. Во-вторых, было бы неплохо, если бы изображение выводилось таким образом, чтобы линия графика выходила за границу диаграммы, соответствующей декабрю месяцу. В идеале вам нужна сгруппированная столбцовая диаграмма. Однако, JpGraph не позволяет группировать графики, построенные в разных масштабах. Чтобы добиться этого, мы пойдем на небольшой обман.
Создадим две сгруппированных столбцовых диаграммы (каждую в своем масштабе), в каждой из которых будет присутствовать диаграмма, представленная числовым рядом, состоящим только из нулей. Необходимый нам эффект достигается тем, что в сгруппированной диаграмме для одного масштаба диаграмма, состоящая из нулей, располагается справа, сдвигая основную диаграмму влево, а в сгруппированной диаграмме для другого масштаба - слева, сдвигая основную диаграмму вправо.
Для форматирования меток на второй оси ординат вы можете создать функцию обратного вызова.
Листинг 6: Создание графика, состоящего из нулей (abc_reg_sales_graph.php)
for ($i=0, $j = count($graphData['labelX']); $i < $j; $i++) {
$graphData['zero'][$i] = 0;
}
$graphData['f_rev'][$j] = $graphData['f_rev'][$j-1];
JpGraph будет вызывать эту функцию для каждой метки, наносимой на координатную ось, и будет использовать значение, возвращенное функцией вместо номера строки. Это позволит нам выводить числа в формате денежных сумм. Код показан в листинге 7.
Листинг 7: Функция обратного вызова для форматирования меток (format_callback.php)
$graph->y2axis->SetLabelFormatCallback ('y_fmt_dol_thou');
function y_fmt_dol_thou($val) {
return '$'.number_format($val/1000);
}
Для построения графика (см. рис. 3) измените ваш код, включив в него код из листинга 8.
Листинг 8: Код для построения графика на рис. 3
<?php
$graph->SetY2Scale('lin');
$graph->SetY2OrderBack(false);
//generate the individual plots
$b1 = new BarPlot($graphData['qty']);
$b2 = new BarPlot($graphData['rev']);
$b2->SetFillColor('lightgreen');
$b1z = new BarPlot($graphData['zero']);
$b2z = new BarPlot($graphData['zero']);
$l1 = new LinePlot($graphData['f_rev']);
$l1->SetStepStyle();
$l1->SetColor('darkgreen');
$l1->SetWeight(3);
//create the grouped plots
$gb1 = new GroupBarPlot(array($b1, $b1z));
$gb2 = new GroupBarPlot(array($b2z, $b2));
//add the plots to the graph object
$graph->Add($gb1);
$graph->AddY2($gb2);
$graph->AddY2($l1);
?>
Рис. 3. Сгруппированная столбцовая диаграмма, построенная в разных масштабах.
На данный момент вы имеете график достаточно представительного вида, но к нему можно еще добавить некоторую полезную информацию. Пользователи хотели бы видеть вклад, вносимый каждым товаром в объемы продаж и соответствующий доход. Это может быть достигнуто созданием "слоеной" столбцовой диаграммы, добавленной к каждой сгруппированной столбцовой диаграмме, как показано в листинге 9. Результат показан на рис. 4.
Листинг 9: Код для создания "слоеной" столбцовой диаграммы, показанной на рис. 4 (abc_reg_sales_graph.php)
<?php
$colors = array('pink', 'orange', 'yellow', 'lightgreen', 'lightblue');
$abqAdd = array();
$abrAdd = array();
for($i=0,$j=count($items); $i<$j; $i++) {
$key = $items[$i]['short_desc'];
$b1 = new BarPlot($graphData[$key]['qty']);
$b1->SetFillColor($colors[$i]);
$b1->SetLegend($items[$i]['item_desc']);
$abqAdd[] = $b1;
$b2 = new BarPlot($graphData[$key]['rev']);
$b2->SetFillColor($colors[$i]);
$abrAdd[] = $b2;
}
$ab1 = new AccBarPlot($abqAdd);
$ab2 = new AccBarPlot($abrAdd);
$b1z = new BarPlot($graphData['zero']);
$b2z = new BarPlot($graphData['zero']);
$gb1 = new GroupBarPlot(array($ab1, $b1z));
$gb2 = new GroupBarPlot(array($b2z, $ab2));
$graph->Add($gb1);
?>
Рис. 4. Использование "слоеной" столбцовой диаграммы.
Мы почти у цели. Необходимо лишь немного отформатировать область, в которой отрисовываются графики, надписи возле координатных осей, а также название региона, для которого строится график. Кроме того, мы должны убедиться в том, что график используется только для внутренних целей фирмы. Один из способов указать на то, что объект является частной собственностью - добавить фоновое изображение. В данном случае, мы используем строку "ABC Co. Proprietary" ("Собственность компании ABC" - Прим. перев.), написанную по диагонали и многократно повторенную.
Заметьте, что цвет надписи значительно темнее, чем тот, который вы хотели бы видеть на "водяных знаках". Для достижения требуемого эффекта можно использовать метод Graph::AdjBackgroundImage(), позволяющий отрегулировать яркость, контрастность и насыщенность изображения перед использованием его в графике.
Это избавит вас от усилий по редактированию изображения во внешнем графическом редакторе. Посмотрите файл img/abc-background.png в директории с исходными кодами данной статьи в качестве примера того, как может выглядеть фоновый рисунок. Вы можете использовать этот рисунок в качестве фонового изображения, используя код из листинга 10.
Примечание: Библиотека GD2, встроенная в PHP 4.3.0, конфликтует с методом Graph::AdjBackgroundImage(). Если вы используете эту версию PHP, вам придется отказаться от использования этого метода и обрабатывать изображение в графическом редакторе.
Листинг 10: Код для использования и настройки фонового изображения (abc_reg_sales_graph.php)
if (USING_TRUECOLOR) {
$graph->SetBackgroundImage('img/abc-background_prefade.png', BGIMG_FILLFRAME);
} else {
//AdjBackgroundImage only works with GD, not GD2 true color
$graph->SetBackgroundImage('img/abc-background.png', BGIMG_FILLFRAME);
$graph->AdjBackgroundImage(0.9, 0.3);
}
Вы можете использовать код, приведенный в листинге 11, чтобы добавить заключительные штрихи, такие как название графика и координатных осей, и настроить координаты надписей. Результат работы кода показан на рис. 5.
Листинг 11: Код для завершения построения графика на рис. 5 (abc_reg_sales_graph.php)
$graph->title->Set(date('Y')." Sales for{$regions[$region_id]} Region");
$graph->title->SetFont(FF_ARIAL, FS_BOLD, 12);
$graph->SetMarginColor('white');
$graph->yaxis->title->Set('Left Bar Units Sold');
$graph->yaxis->title->SetFont(FF_ARIAL, FS_BOLD, 10);
$graph->yaxis->SetLabelFormatCallback('y_fmt');
$graph->yaxis->SetTitleMargin(48);
$graph->y2axis->title->Set('Right Bar Revenue ( $ 000 )');
$graph->y2axis->title->SetFont(FF_ARIAL, FS_BOLD, 10);
$graph->y2axis->SetTitleMargin(45);
$graph->y2axis->SetLabelFormatCallback('y_fmt_dol_thou');
$graph->xaxis->SetTickLabels($graphData['labelX']);
$graph->legend->Pos(0.5, 0.95, 'center', 'center');
$graph->legend->SetLayout(LEGEND_HOR);
$graph->legend->SetFillColor('white');
$graph->legend->SetShadow(false);
$graph->legend->SetLineWeight(0);
$graph->AddY2($gb2);
Рис. 5. Законченный вид графика
Из соображений повышения производительности вы решили реализовать механизм кэширования изображений JpGraph для этого графика. Механизм кэширования основан на сохранении копии изображения как файла на сервере. Если копия, сохраненная к кэше, еще действительна, JpGraph вернет ее в ответ на запрос клиента вместо того, чтобы генерировать изображение "на лету". Заметьте, что вы должны иметь на сервере соответствующую привилегию для записи в директорию кэша. При создании экземпляра класса Graph вам необходимо передать конструктору, кроме ширины и высоты изображения, также имя файла кэшируемого изображения и таймаут в минутах (время, в течение которого действительно изображение, сохраненное в кэше) и, наконец, параметр, сообщающий JpGraph о том, что изображение все равно нужно выводить. Это значит, что вы будете продолжать использовать ссылку на PHP-скрипт как значение атрибута src тэга img. Код, необходимый для реализации механизма кэширования, приведен в листинге 12.
Листинг 12: Код, реализующий кэширование изображения. (abc_reg_sales_graph.php)
define('GRAPH_NAME', 'abc_reg_sales');
$graphName = GRAPH_NAME.$region_id.'.png';
$graphTimeout = 60*24;
$graph = new graph(WIDTH, HEIGHT, $graphName, $graphTimeout, true);
Теперь, если в течение 24 часов будет запрошен тот же график для того же региона, клиенту будет возвращена версия изображения, находящаяся в кэше, и выполнение скрипта прервется после строки 'new Graph()'.
Это означает, что для получения максимальной выгоды от кэширования вы должны создавать экземпляр класса Graph раньше, чем будут выполняться запросы к БД.
Сравнение объемов продаж через разные каналы по регионам
Второй график, который вас попросили сделать, показывает объемы продаж для каждого региона по каналам. Кроме того, нужно обеспечить простой способ навигации между первым и вторым графиками. Этот тип отчета предполагает просмотр информации о долях, поэтому наиболее эффективной будет круговая диаграмма. Просмотрите, пожалуйста, еще раз строки 22-82 файла abc_map_graph.php в директории с исходными кодами этой статьи, чтобы понять, как выполняются запросы к БД и формирование массивов для последующего построения графиков. В листинге 13 приведен код, необходимый для создания графика, показанного на рис. 6.
Листинг 13: Код для создания круговой диаграммы на рис. 6
$sliceColors = array('lightgreen', 'pink', 'lightblue');
$graph = new PieGraph(WIDTH, HEIGHT);
$graph->title->Set($regions[$region]['region'].' Region');
$graph->subtitle->Set('Sales by Channel since '.GRAPH_START);
$p1 = new PiePlot($graphData[$pickRegion]['rev']);
$p1->SetLegends($graphData[$pickRegion]['label']);
$p1->SetSliceColors($sliceColors);
$graph->Add($p1);
$graph->Stroke();
Рис. 6. Простая круговая диаграмма
Вы можете использовать фоновое изображение так, как это показано на примере первого графика, а также разместить дополнительную информацию на вашем графике. Разместим на графике карту США, разделенную по регионам продаж компании ABC. Если вы используете эту карту как фоновое изображение для графика, вы можете расположить круговые диаграммы на фоне соответствующего региона. Посмотрите файл img/abc-regions.png как пример использования фонового изображения в этом графике.
Для использования этого примера вам необходимо добавить несколько строк в цикл формирования массива $graphData, чтобы сделать возможным динамическое расположение круговых диаграмм для каждого региона:
$graphData['r'.$rIndex]['map_x'] = $regionData[$i]['map_x'];
$graphData['r'.$rIndex]['map_y'] = $regionData[$i]['map_y'];
Теперь взгляните на код листинга 14, создающий график, изображенный на рис. 7.
Листинг 14: Код для создания графика на рис. 7 (abc_map_graph.php)
$graph = new PieGraph(WIDTH, HEIGHT);
$graph->SetBackgroundImage('img/abc-regions.png', BGIMG_FILLFRAME);
for ($i=0; $i<$rIndex+1; $i++) {
$pickRegion = 'r'.$i;
$p1 = new PiePlot($graphData[$pickRegion]['rev']);
$p1->SetCenter($graphData[$pickRegion]['map_x'],
$graphData[$pickRegion]['map_y']);
$p1->SetSize(PIE_SIZE);
$p1->SetLabels($graphData[$pickRegion]['revFmt']);
$p1->SetSliceColors($sliceColors);
if (!$i) {
$p1->SetLegends($graphData['label']);
}
$graph->Add($p1);
}
$graph->legend->Pos(0.9, 0.85, 'center', 'center');
$graph->Stroke();
Рис. 7. Использование фонового изображения для показа регионов
В заключение заказчик хочет иметь возможность быстрой навигации между имеющимися круговыми диаграммами и соответствующими графиками продаж, построенными ранее. Вы можете реализовать эту возможность, используя карты-изображения на стороне клиента (Client Side Image Maps - CSIM - Прим. перев.). CSIM - HTML-технология, позволяющая определять области на изображении и связывать их с гиперссылками. Для реализации CSIM в этом графике вы должны определить гиперссылки и альтернативный текст для изображений (подсказки для пользователя). Для начала мы определим константу, используемую в большинстве ссылок:
define('DRILL_GRAPH', 'abc_reg_sales_graph.php?region=');
Теперь в массиве $graphData нужно определить ссылки и альтернативный текст для изображений:
$graphData['r'.$rIndex]['targets'][] = DRILL_GRAPH.$regionData[$i]['region_id'];
$graphData['r'.$rIndex]['alts'][] = "Click for more information regarding {$regions[$rIndex]['region']} sales.";
Так как в дополнение к двоичным данным необходимо выводить HTML (CSIM), мы не можем вернуть данные клиенту уже привычным нам способом. CSIM и изображение тесно зависят друг от друга. Чтобы сделать это, нам необходимо прибегнуть к кэшированию изображения. Это позволит нам вывести карту-изображение вместе с тэгом img и воспользоваться кэшированным изображением. Вместо того, чтобы воспользоваться механизмом кэширования JpGraph, описанным выше, мы применим другую технику и сохраним изображение в директории, из которой клиент сможет запросить его напрямую. Для этого надо создать директорию с именем 'img' в той же директории, где находится скрипт. Вы также должны иметь на сервере права на запись в эту директорию. При создании графика обрабатываем его так же, как если бы мы возвращали клиенту двоичный поток - изображение. В цикле формирования круговых диаграмм добавляем информацию о ссылках и альтернативном тексте (см. листинг 14 - Прим. перев.):
$p1->SetCSIMTargets(
$graphData[$pickRegion]['targets'],
$graphData[$pickRegion]['alts']
);
Далее выводим график, используя код из листинга 15.
Листинг 15: Код для формирования CSIM (abc_map_graph.php)
define('IMG_DIR', 'img/');
$graphName = IMG_DIR.'abc_channel_graph.png';
$graph = new PieGraph(WIDTH, HEIGHT);
//the rest of the graph code...
$graph->Stroke($graphName);
$mapName = 'ABC_Region_Drill';
$imgMap = $graph->GetHTMLImageMap($mapName);
print <<<EOS
$imgMap
<img src="$graphName" alt="ABC Sales by Channel"
ismap usemap="#$mapName" border="0">
EOS;
Данный код указывает JpGraph вывести изображение в файл img/abc_channel_graph.png. Далее помещаем в переменную $imgMap сгенерированную карту-изображение. Тэг img определен таким образом, что позволяет использовать сгенерированную карту-изображение.
Ключевые понятия
В данном учебном примере были рассмотрены следующие ключевые понятия, касающиеся построения графиков средствами JpGraph:
- использование в JpGraph линейных графиков, столбцовых и круговых диаграмм;
- использование "слоеных" и сгруппированных столбцовых диаграмм;
- использование второй оси ординат с другим масштабом;
- использование функции обратного вызова для форматирования меток на осях координат;
- генерация сообщений об ошибках в графическом виде;
- создание "водяных знаков" с использованием фоновых изображений;
- использование механизмов кэширования изображений JpGraph для увеличения производительности, а также облегчения использования CSIM;
- использование фонового изображения как части информационного контента диаграммы (расположение круговой диаграммы на фоне карты регионов продаж);
- использование карт-изображений на стороне клиента для быстрой навигации между графиками
Заключение
JpGraph предоставляет простое API, позволяющее быстро создавать профессиональную графику. Соединение возможностей JpGraph и средств PHP для доступа к БД дает вам в руки мощный инструмент для создания динамических графиков в web'е. В данной статье представлены некоторые из дополнительных возможностей JpGraph такие как: кэширование, фоновые изображения и карты-изображения на стороне клиента. Надеюсь, вы достаточно хорошо познакомились с данной технологией и сможете свободно использовать PHP и JpGraph в своих будущих data mining проектах.
|