Additional opportunities of JpGraph
JpGraph is an object-oriented PHP-library that allows creating professional graphics without using a lot of code. That article is a tutorial that illustrates additional opportunities of the JpGraph library such as:
- General methods of script development using JpGraph
- Consistent process of graph development
- JpGraph caching mechanism use for increasing the productivity
Installation and required software
At first you have to download the initial code: http://www.aditus.nu/jpgraph/jpdownload.php.
Then extract the archive to the directory shown in the PHP include_path option.
For checking the accuracy of installation look through the following page: /Examples/testsuit.php. That page generates more than 200 presentation graphs, using JpGraph library and allows look over the initial code.
All scripts in that article has been developed and tested on PHP 4.3.0 installed as a Apache 1.3.27 module.
Listing 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;
?>
Given file creates object-connection ADOdb - $conn that is used for database access in the other scripts. You have to change parameters of the connect() method call according to your settings.
$conn->Connect("localhost", "username", "passwd", "dbname");
ADOdb should be installed to the one of the directories shown in the PHP include_path option. In ADOdb you can get the result of SQL query using the following call:
$rs = $conn->Execute($sql_statement, $bind_array);
In the case of successful query method will return object – record set ADOdb, otherwise method will return FALSE. After that there used two methods of that objects: GetArray() and GetAssoc().GetArray() method returns records vector where every record is an associative array like 'COLUMN' => 'VALUE'. GetAssoc() method returns an associative array.
Listed below examples work if you have TrueType font Arial, otherwise there will appear an error. If you want to escape that problem you can change all references to constant FF_ARIAL to FF_FONT1. FF_FONT1 is a standard font.
Example
In our example we’ll view the sale data of ABC Company, made-up jewelry producer. In that example we’ll talk about sale in continental United States
Database development
Data model of ABC Company is six tables. Central table is abc_sales. That table includes information about sales, time, state, the amount of sold items and profit.
The other tables are abc_catalog, abc_channel, abc_forecast, abc_region and abc_state_region. abc_catalog table includes substitute key for catalog positions that can be purchased by user. abc_channel table includes substitute key, name and description of the sale channel (web, telephone) with the help of which commodities can be bought. abc_forecast table includes information about forecasted plans of sales. That table includes information about expected sales volumes and profits. abc_region table includes substitute key and descriptions for every region.
SQL queries used for table creating are situated in the file mysql_ddl.sql in the directory with the initial code of that article. What about sale and forecast tables, they can be filled using abc_gen_sales.php and abc_fcst_ins.php correspondingly. abc_gen_sales.php script must be run first because abc_fcst_ins.php script working depends on data created by first script.
Step-by-step process of training database creating and filling:
- Download file with census data, extract and put it to the directory with the examples.
- jvn_parse_census.php file (connections with database) parses ustracts2k.txt file (census data in ASCII format), creates and fills the census_data table required for abc_gen_sales.php script working.
- mysql_ddl.sql.
- abc_gen_sales.php.
- abc_fcst_ins.php.
Comparison of sales data with forecasted indexes in every region
Our first task is creating of the graph that compares sales volume and profits with forecast for every region.
While developing the PHP-scripts that create graphs using JpGraph I prefer using the following process:
- data receiving and processing for construction of graph
- creating the Graph object and setting its properties
- creating Plot objects for locating on the Graph object
- graph output
According to the listed above process you have to get sales data and forecast data. Graphs are constructed region by region, that’s why your script should have corresponding parameter and check its correctness. If parameter is correct its value is set to $region_id variable, otherwise there is generated an error. But here we have one problem. As output data is a graphic object site pages should refer to it using HTML tag «img»:
<img src="abc_reg_sales_graph.php">
Listing 2: creating an error message in the graphic form (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;
}
?>
After data extracting from the database data should be transformed to the format appropriate to the graph construction. It means creating of several arrays with indexes starting with null for every number series.
Instead of creating of several global arrays for every series, I prefer having only one associative array $graphData name that includes arrays for every series.
Listing 3: $graphData (abc_reg_sales_graph.php) array forming.
<?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"];
}
?>
You can also use only one associative array for the whole graph instead of creating separate array for every number series. Use :
$gty = array(...);
$f_gty = array(...);
instead of:
$graphData = array (
"gty" => array(...),
"f_gty" => array(…)
);
Our graph will compare the amount of sold items with forecasted amount. In the listing 4 there is a code that forms that graph.
That code illustrates the entity of JpGraph API.
At the second step you create and configure Graph object. At the third step you create BarPlot and LinePlot objects. At the fourth step you finish the process and calling Graph::Stroke() method for graphic object output. The result is shown on the Pic.1.
Pic.1. Your first graph

Listing 4: your first graph
<?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();
?>
Graph line continuity doesn’t represent the facts; data can’t change smoothly from month to month. That’s why it is better to use “step” line in the graph.
Let’s make changes and show profit data instead of sales volumes data. Follow the listing 5. The result is shown on the Pic.2.
Listing 5:
$b1 = new BarPlot($graphData["rev"]);
$l1 = new LinePlot($graphData["f_rev"]);
$l1->SetStepStyle();
$l1->SetColor("darkgreen");
$l1->SetWeight(3);
Pic.2 Using the “step” line of the graph.

If you want to set sales and profit volumes on the same graph you have to use an opportunity of second ordinate axis creating.
At the next step we’ll combine the graph of sales volumes and graph of profit.
Let’s create two grouped diagrams. Every diagram will include graph from the number series composed of nulls.
For formatting the marks on the second ordinate axis you can create the callback function.
Listing 6: creating the graph composed of nulls (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 will call that function for every mark on the coordinate and will use the value returned by function instead of the line number. You can find the code in the listing 7.
Listing 7: callback function for mark formatting (format_callback.php)
$graph->y2axis->SetLabelFormatCallback ("y_fmt_dol_thou");
function y_fmt_dol_thou($val) {
return "$".number_format($val/1000);
}
For graph creating (Pic.3) change your code having included the code from listing 8.
Listing 8: code for graph creating (Pic.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);
?>
Pic.3. Grouped diagram, constructed on different scales

At that moment you have enough impressive graph but you can also add some useful information. Users would like to see the efficiency of every item, its influence on sales volumes and profit. It can be reached by creating a “layered” diagram.
Listing 9: code for creating a “layered” diagram.
<?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);
?>
Pic.4. “Layered” diagram using

We have almost reached our aim. Now we have to format the area where our graphs are constructed, inscriptions near the coordinates, and name of the region we graph is constructed for. More than that we must be sure that our graph is used for the internal purposes. One of the ways of indicating that our object is a private property is to add background image .In our case we use the line "ABC Co. Proprietary".
Inscription color is dark enough. You can use method Graph::AdjBackgroundImage() for adjusting brightness, contrast, saturation of the image.
Notice: integrated to PHP 4.3.0 library GD2 conflicts with method Graph::AdjBackgroundImage(). If you use that version of PHP, you’ll have to refuse of using that method. In that case use the graphics editor.
Listing 10: code for background image using (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);
}
You can also use the code in the listing 11 for adding the graph’s and coordinates’ name.
Listing 11: code for graph’s construction finishing (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);
Pic.5. Completed graph.

For more efficiency we’ll realize the caching mechanism.
Listing 12: image caching code (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);
Sales volumes comparison for every region
Second graph shows the sales volumes for every region. More than that, you have to provide simple way of navigation between first and second graphs. Here we’ll use the pie chart. Look through the lines 22-28 in the abc_map_graph.php file in the directory with initial codes for more understanding the graph construction. Listing 13 includes the code for creating the graph shown on the Pic.6.
Listing 13: code for creating a pie chart on the Pic.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()
Pic.6. Simple pie chart

You can also use the background image as it is shown above and add the additional information. Let’s place USA map on the graph. If you use that map as a background image you can place the pie charts on the corresponding region. Look at the img/abc-regions.png file as an example of background image using.
For using that example you have to add several lines to the $graphData array forming cycle:
$graphData["r".$rIndex]["map_x"] = $regionData[$i]["map_x"];
$graphData["r".$rIndex]["map_y"] = $regionData[$i]["map_y"];
Now look at the code in the listing 14 that creates a graph on the Pic.7.
Listing 14: code that creates a graph on the Pic.7.
$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();
Pic.7. Using of the background image for showing the regions.

Customer wants have the opportunity of quick navigation between pie charts and corresponding sales graphs. You can realize it using Client Side Image Maps – CSIM. CSIM is a HTML technology that allows defining the areas on the images and connects them to the hyperlinks. For CSIM realization in that graph you have to define the hyperlinks and alternative text for images. At first we’ll define the constant that is used in the major part of the links:
define("DRILL_GRAPH", "abc_reg_sales_graph.php?region=");
Now we have to define the hyperlinks and alternative text for images in the $graphData array.
$graphData["r".$rIndex]["targets"]
[] = DRILL_GRAPH.$regionData[$i]["region_id"];
$graphData["r".$rIndex]["alts"][] =
"Click for more information regarding {$regions[$rIndex]["region"]} sales.";
As we have to add HTML (CSIM) to the binary data we can’t return data to the client by the custom way. In our case we’ll cache the image. Instead of using JpGraph caching method we’ll use another technology and save our image to directory where client will be able to call it. That’s why we have to create directory “img” in the same directory where our script is situated. In the pie charts forming cycle we add the information about links and alternative text (listing 14):
$p1->SetCSIMTargets(
$graphData[$pickRegion]["targets"],
$graphData[$pickRegion]["alts"]
);
Then construct the graph using cod in the listing 15.
Listing 15: CSIM forming code (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;
Given code makes JpGraph output the image to the img/abc_channel_graph.png file. Then we put to the $imgMap variable generated image-map.
Main concepts
In that article we used the following concepts for graph creating by means of JpGraph:
- using in JpGraph graphs and pie charts;
- using “layered” and grouped graphs;
- using second ordinate axis with other scale;
- using the callback function for marks formatting in the coordinates;
- error messages generation;
- using caching mechanisms of the images for increasing the efficiency;
- using Client Side Image Maps for quick navigation between graphs



