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.
Поэтапно процесс создания и заполнения учебной БД выглядит так:
1. Скачать файл с данными переписи, распаковать и разместить его в директории с учебными примерами.
2. jvn_parse_census.php (соединение с БД - см. комментарии в скрипте) - парсит файл ustracts2k.txt (данные переписи в формате ASCII), создает и заполняет таблицу census_data, необходимую для работы скрипта abc_gen_sales.php.
3. mysql_ddl.sql.
4. abc_gen_sales.php.
5. abc_fcst_ins.php.
Примечание: Оригиналы данных скриптов были написаны в декабре 2002 года, данные подобраны из расчета, что текущей датой является 15 декабря 2002 года, чтобы графики выводились практически за полный год. Так как сейчас 2003 год, скрипты были модифицированы таким образом, чтобы запрашивать и выводить данные за предыдущий год.
Примечание переводчика: статья переводилась на русский язык в феврале 2004 года, и в скрипты были внесены необходимые изменения.
Сравнение данных о продажах со спрогнозированными показателями по регионам
Нашей первой задачей будет создание графика, сравнивающего объем продаж и доход с прогнозом для каждого региона. Предполагается, что эта информация является собственностью компании и не предназначена для публичного распространения.
При разработке PHP-скриптов, создающих графики с использованием JpGraph, я предпочитаю использовать следующий 4-х шаговый процесс:
1. Получение и обработка данных для построения графика.
2. Создание объекта Graph и задание его основных свойств (таких как цвет и координатные оси графиков).
3. Создание объектов Plot, для размещения на объекте Graph.
4. Завершение объекта 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(…)
);
Теперь давайте посмотрим на некоторые данные, отображаемые на нашем первом графике. Этот график будет сравнивать количество реализованных единиц товара со спрогнозированным количеством. В листинге 4 приведен код, формирующий этот график.
Этот код иллюстрирует сущность 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. Читать дальше...
Information
- Posted on 27.04.2013 17:21
- Просмотры: 3574