Charts and graphs can be a very powerful means of communication. PHP, compiled with GD support, allows for dynamic image generation. This, combined with PHP?s renowned flexibility in retrieving data, gives us the perfect environment to dynamically generate charts and graphs.
The JpGraph library of PHP classes is a very useful tool. This set of graphing classes lets you easily construct professional looking graphs without having to delve into the GD library calls and their PHP wrapper functions. Now you have the power to concentrate on delivering the information and integrating graphs into your web application, instead of the tedious job of building of images from low-level graphics primitives.
Learning Objectives
In this tutorial you will learn:
* The PHP compile options required for GD support
* How to apply the JpGraph charting class
* Tips on the visual presentation of graphics
Definitions
* GIF ? CompuServe Graphical Interchange Format ? image compression format supported by most browsers, limited to 256 colors
* JPEG ? Joint Photographic Experts Group (JPG) ? image compression format that supports full color (picture quality) images, and enjoys the support of most web browsers. In contrast with GIF or PNG, JPEG is a ?lossy? compression algorithm
* PNG ? Portable Network Graphics (unofficially: ?PNG is Not GIF?) ? image compression format that is the successor to (in most ways superior to) GIF, and is supported by most modern web browsers
* LZW ? Lempel Ziv Welch - the compression algorithm implemented in GIF images, patented by Unisys corporation (and apparently by IBM 3 weeks earlier, if you are interested in such things)
* GD ? graphics library which can be compiled with PHP to allow for image manipulations and generation (Note: From PHP 4.3.0 GD will be bundled with PHP, allowing for ease of configuration.)
* TTF ? True Type Fonts ? fonts based on vector graphics that allow for much better scaling and rotation
* Time Series Data ? information that can be associated with regular time intervals, i.e. Sales per Month or Hits per Hour.
Background Information
The Internet is a communication medium. Text and data in tables are both ways to communicate information, but as the clich? goes: A picture is worth a thousand words. To this end, graphical representation of data (charts or graphs) is often a much more efficient way to communicate than simply presenting the same data in a table. This is particularly true in time-series data, where your user can usually identify trends far more easily in graphical format than by reviewing a series numbers.
The author of this tutorial originally started doing web development using ASP on IIS. In this environment, an OCX control called GraphicServer provided substantial graphing capabilities. When migrating to PHP, the author evaluated a number of graphing solutions using the GraphicServer capabilities as a benchmark.
Whenever faced with a task that you suspect other developers have faced as well, you should conduct a search for projects available on the web that may have already implemented much of your functional requirements. Good places to start such a search include the Zend code repository (http://www.zend.com/codex.php), SourceForge, and a number of other PHP and programming code repositories on the Internet. When selecting any project for addition to your own, it is important to see:
1. recent development efforts (an active TODO list, a recently published version, etc.)
2. an active development community (the author is accessible via email, perhaps mailing lists or message boards are available for larger projects, etc.)
3. licensing requirements match you application
After evaluating several available graphing solutions, the author settled on JpGraph as having the best features, capabilities, and design.
Prerequisites
To work with the examples provided in this tutorial, the reader will need to:
* have access to PHP with GD and TTF enabled
* have downloaded and installed the JpGraph class and supporting documentation
* have reviewed the JpGraph Quickstart and the source code from several of the example charts to give the reader a complete background on the use of the JpGraph class.
* have sufficient PHP skills to get interesting data to graph into your PHP script. (In most real world examples, this data will be retrieved from a database, but for the purposes of this tutorial, we will assign data directly to arrays.)
How it Works
JpGraph
The JpGraph website is located at http://www.aditus.nu/jpgraph/. From this site you can download the class, documentation and fonts. There are also many tutorials and example graphs with source available. The is a link to the JpGraph support message boards at http://jpgraph.fan-atics.com/ for additional installation or use support by the JpGraph community.
Starting with version 1.6, JpGraph?s author has switched to a QT style license. A brief summary of this license is that the class can be freely used in open source projects, but the commercial application of this class is restricted. If you intend to use this particular graphing library in a commercial application, please contact JpGraph?s author, on the JpGraph site, for terms of commercial usage. As with any project you consider for inclusion in your work, make sure the licensing terms are compatible and acceptable before using the software.
After downloading the tar ball, you need to unzip and untar the files. Once they were untarred, I moved the entire source to the PHP library directory to make this graphing capability available to all applications. If you do not have access to this directory (in a hosted PHP environment), you can include it in another directory accessible to your application, or just copy the relevant class files to you working directory.
You will probably want to make several small changes to the directories in jpgraph.php. These include CACHE_DIR, APACHE_CACHE_DIR and TTF_DIR defines. The CACHE_DIR is a full path, and should be set to a location that is writeable by the web server user. APACHE_CACHE_DIR is the location you browse to in order to view the results in the CACHE_DIR (if you can not create a virtual directory on the web server, CACHE_DIR has to be somewhere in your document root). TTF_DIR should be set to the location of your TTF files.
JpGraph is completely Object Oriented (OO). This means that all of the work you do will be done by instantiating a class ($graph = new graph(...)), calling methods of the classes (methods are functions in the class, like $graph->SetScale('textlin') ), or changing properties (class local variables, not usually done with JpGraph). OO development lets you structure your code so you can get much better re-use from your code, makes it easy to correct and extend your existing code, and has many other benefits.
PHP Compile Options
To do all this with JpGraph, you need to have several PHP compile options enabled. Check your phpinfo() for --with-gd, --with-jpeg-dir=/usr and --with-png. To use the True Type Font features, you will also need --with-ttf.
If you do not have the libraries installed on your system to support compiling PHP with these options, follow the links from the image functions page of the manual http://www.zend.com/manual/ref.image.php. The section of the PHP manual on build problems http://www.zend.com/manual/faq.build.php can also be useful when trying to reconfigure PHP.
When you have successfully created PHP with GD, your GD section of the phpinfo() page will look like this:
Visual Presentation of Data
Having the preliminaries out of the way, including PHP working with GD, and how to retrieve the data you want to chart, JpGraph provides the ability to easily output the data into charts with a variety of formats (line, bar, pie, spider, scatter or combinations thereof). For example, given this array of data: $data = array(7, 10, 9, 11,
Line
$graph = new graph(150, 150, 'fig1.png', 0, false);
$graph->img->SetMargin(30, 30, 30, 30);
$graph->SetScale('textlin');
$line1 = new LinePlot($data);
$graph->Add($line1);
$graph->Stroke();
Bar
$graph = new graph(150, 150, 'fig2.png', 0, false);
$graph->img->SetMargin(30, 30, 30, 30);
$graph->SetScale('textlin');
$bar1 = new BarPlot($data);
$graph->Add($bar1);
$graph->Stroke();
Pie
$graph = new PieGraph(150, 150, 'fig3.png', 0, false);
$pie1 = new PiePlot($data);
$graph->Add($pie1);
$graph->Stroke();
Spider
$graph = new SpiderGraph(150, 150, 'fig4.png', 0, false);
$spid1 = new SpiderPlot($data);
$graph->Add($spid1);
$graph->Stroke();
Each of these formats has unique strengths and weaknesses, which are briefly outlined in the following paragraphs.
Line Graphs:
Line graphs are good for presenting information that is related, and presented in a sequence. Line graphs are a particularly good choice if you are trying to convey trend information to the user. Examples of this kind of data might include time-series related data like sales per month, users per day, or stock price charts.
Bar Graphs:
Bar graphs can be used in the same fashion as line graphs to represent a trend. Bar graphs are also useful in representing similar information that is not intended to be in a series. For example, you might want to present a graph of sales per state. In this graph, the sales could vary significantly from state to state, and presenting this information as line graph would probably be confusing (the line would likely go up and down significantly with no relationship to a trend).
Note: The special case where you are presenting non-time series data related to frequency, and you sort it in descending order is called a Pareto chart. This type of chart is particularly useful to diagnose things like frequency of occurrence of problems.
Pie Charts:
Pie charts are very useful to convey percentage information, or a sense of relationship between different parts that make up a set of data. Whenever you want like to show how each of the items in a collection of data relates to the others, pie charts are a good means of presentation.
Spider Charts:
Spider charts (sometimes called radar charts) are used to show the relationships of a number of distinct categories, comparing similar data for each category. An example of this might be comparing sales against a plan for a number of sales districts, each sales district would represent a spoke of the spiders web, and a line would connect to each spoke where the percent of plan for that district dictated. The author believes it is a matter of personal preference whether you use a spider chart or a bar chart with multiple bars for each district (sales and plan). A line chart would not be appropriate for this data because the different sales districts do not represent a series from which you could expect to infer a trend.
Other JpGraph Functionality:
It is worth noting that JpGraph also includes functionality for Gantt charts, a type of chart for representing tasks on a project, their durations and relationships. JpGraph can also be used to output simple text into an image. This could potentially be useful for things like dynamically generated buttons on a web site. The author has not delved into these functionalities, and therefore cannot comment further in this regard.
JpGraph also supports scatter graphs. This type of chart is used to represent data points with two dimensions (an x-axis and a y-axis component) and you usually expect to draw some conclusions regarding the data by seeing if patterns or groups form on the scatter chart.
General Guidelines:
Use of multiple y-axes should be done sparingly. It is essential when the information being presented is not close to the same scale (for example, sales in millions of dollars, along with those same sales as a percent of plan, these clearly would not graph well on the same scale). In most cases, it requires additional interpretation by the viewer and should be used sparingly.
To Cache or Not to Cache
JpGraph supports two methods of getting your image to the browser. The first is to stream the image directly from PHP, this is the ?inline? method. The second is to save the image to disk for later retrieval by the browser as the src attribute of an img tag, this is the ?cache? method.
If you have very dynamic data, data that can be retrieved quickly, and your chart would likely be different each time it is requested (for example, poll results), the ?inline? method may be preferable. To use this method, put all of the code to retrieve your data and generate the graph into a separate PHP file.
<?php
// @file mygraph.php
$data = get_my_data_somehow();
$graph = new graph(200, 100);
//the rest of the graph code
$graph->stroke();
?>
In your HTML file, refer to <img src=?mygraph.php?> to retrieve the image. JpGraph will stream the binary image directly from your PHP file back to the browser, and will take care of sending the correct MIME type header so the browser will know how to correctly render the image. This is a very common use of the JpGraph classes.
If you have data that takes a long time to retrieve, if your graph is not likely to change over the course of many requests, or if you want to use the same data elsewhere in the page without retrieving it a second time, you may prefer to use the ?cache? method of generating the JpGraph image. (Please note the full script for this tutorial includes an example of caching the image and presenting the data in a table as well as the chart, though this is not covered in detail in the body of this tutorial.) To use caching with JpGraph, you must provide the name for the file to be cached. This can be done as part of the graph object creations ($graph = new graph(200, 100, 'chart.png')
After using the stroke() method, you may now refer to the image in the src attribute of an img tag. You must use the path to the image cache directory you set up as the APACHE_CACHE_DIR define when configuring JpGraph. One advantage of using the cache method is that you can now display the graph multiple times from different pages without taxing your system to re-query the data and regenerate the graph image.
There are actually five parameters for the constructor method of the JpGraph graph class: image width, image height, cache file name, minutes of cache timeout, and the inline streaming flag.
Building the Graph
It is recommended that you review the Quickstart web page that is available in the JpGraph documentation download (jpgraph.htm). This walkthrough covers a variety of graph types and features of JpGraph and will let you come quickly up to speed.
If you have questions regarding specific features, look for an example graph displaying the feature you are interested in, on the JpGraph example page (http://www.aditus.nu/jpgraph/jpg_image_gallery/images.html). Each of the examples has source provided.
The approach adopted by this tutorial is to walk through the development of a single graph, start to finish. The scope of our project will be to graph ?actual? data against a ?plan? that was set at the beginning of the year. In addition, our ?company? prepares a forecast on a monthly basis, and wants its latest ?forecast? to appear on the graph as well.
The data for these graphs was generated using the following arrays:
$data['plan'] = array (
11.5, 12, 12, 13.5, 10, 9.5,
11, 12, 12, 11, 13, 12.5 );
$data['actual'] = array(
12, 14, 14.5, 13, 9, 4,
0, 0, 0, 0, 0, 0 );
$data['forecast'] = array (
0, 0, 0, 0, 0, 10, 12,
14, 13, 10, 11, 13 );
These series are each twelve data points long, each point representing a month of the year. For the purpose of this example, we will assume that we are part way through the month of June, and there is therefore a partial month?s worth of ?actual? data and a valid ?forecast? for the total month of June.
A note on retrieving data from a database. The purpose of this tutorial is not to cover the use of a database, but the author realizes many of the data sets readers will want to graph in dynamic web applications will be stored in a database. An example of using the ADOdb (http://php.weblogs.com/ADOdb) database abstraction layer to retrieve the data used in this example is included in the full script of the tutorial, but details of database interaction will not be covered in detail here in the Code Flow section. For more detail on using database abstraction layers, see this prior tutorial by the author: http://www.zend.com/zend/tut/tutsweatpart1.php.
When using JpGraph objects in you PHP scripts, there are three basic steps to generating any graph. The first step is to instantiate and set up the graph object itself. This can be compared to an artist preparing a canvas to paint on. It includes specifying properties like the size and color of the image.
The second step is to prepare each of the graphs. This means loading the data and assigning properties like what type of graph, and what color the graph is.
The third step is to finalize and output the image. This step includes adding the graphs you defined in step two to the final output, placing any titles, legends, etc. on the graph, and outputting the image.
General setup for all graphing scripts will include the following:
// graphing related includes
require_once('jpgraph/jpgraph.php');
// only include these if you are using each particular kind of graph
require_once('jpgraph/jpgraph_bar.php');
require_once('jpgraph/jpgraph_line.php');
require_once('jpgraph/jpgraph_pie.php');
require_once('jpgraph/jpgraph_spider.php');
$jpgcache = APACHE_CACHE_DIR;
$graph_name = 'chart.png';
Having done this, we can now create and display your first graph. Since all of the data is time series, and we would like to understand trends, maybe a line graph is the way to go. For our first iteration, lets make a line graph with each of the three data series (plan, actual and forecast) in a different color.
$graph = new graph(500, 200, $graph_name, 0, 0);
$graph->img->SetMargin(30, 30, 30, 30);
$graph->SetScale('textlin');
$line1 = new LinePlot($data['plan']);
$line1->SetColor('darkolivegreen');
$line2 = new LinePlot($data['actual']);
$line2->SetColor('blue');
$line3 = new LinePlot($data['forecast']);
$line3->SetColor('silver');
$graph->Add($line1);
$graph->Add($line2);
$graph->Add($line3);
$graph->Stroke();
Let?s quickly review the three steps required to generate the chart. The first three lines that start with $graph create the object, set a margin and set the scales for the graphs. The next step is to add the individual lines. We do this by creating new LinePlot objects, then calling their SetColor method. Finally, we can display the graph using print "<img src='{$jpgcache}{$graph_name}'>\n"; . For this brief bit of coding, we are rewarded with:
This graph certainly shows trend. However, two of the lines we want to plot are very similar in nature: ?actual? reflects history and ?forecast? reflects future expectations. Maybe a different style of graph will convey this better.
For a second attempt, let?s make all of the series into bar graphs. This can be achieved by changing each of the line plots to a bar plot. The changes for the ?plan? data series is shown below, the other graphs would change similarly:
$bar1 = new BarPlot($data['plan']);
$bar1->SetFillColor('darkolivegreen');
Note that if we just added the three plots to the graph, they would overlap each other and parts would be invisible. To avoid this we can use a feature of JpGraph called a GroupBarPlot. This will take an array of bar plots, and plot them next to each other in a single x-axis group on the plot. After creating the $gbplot, add it to the graph and output the graph using the Stroke() method.
$gbplot = new GroupBarPlot(array($bar1, $bar2, $bar3));
$graph->Add($gbplot);
This results in:
This result looks somewhat better than the line graph, but the gaps in the early months where there is no forecast, and the later months when there is no ?actual? look unseemly. It would be nice if we could stack these two series on top of each other, since one or the other is usually zero. Fortunately, JpGraph?s BarPlot supplies us with AccBarPlot, the accumulated (stacked) bar graph.
Lets change our code to create an AccBarPlot using the bar charts for actual and forecast, then add the plan bar chart and the new abplot chart to the graph.
$abplot = new AccBarPlot(array($bar2, $bar3));
$gbplot = new GroupBarPlot(array($bar1, $abplot));
Now we have:
This presents a problem in column 5. By just stacking the two, we over inflate the ?forecast? by the amount of the ?actual? in the month that they overlap. We can correct this by making a new data series for the ?remaining forecast? after any actual is accounted for. One way to accomplish this is:
for($i = 0; $i < count($data['actual']); $i++) {
$data['remain'][$i] =
($data['actual'][$i] > $data['forecast'][$i])
? 0
: $data['forecast'][$i] - $data['actual'][$i];
}
By substituting $data['remain'] for $data['forecast'] in the creation of $bar3, we get:
Some people find it harder to compare of two adjacent bar heights, than to compare the height of a bar to a line representing a target. JpGraph allows you to mix graph types in the same chart, so we can change the ?plan? chart into a line graph, and plot it with the accumulated bar chart of ?actual? and ?remaining?.
This chart removes so many bars that we can also shrink the overall size of the canvas.
$line1 = new LinePlot($data['plan']);
$line1->mark->SetType(MARK_CIRCLE);
$line1->SetColor('darkolivegreen');
$line1->SetWeight(3);
$line1->SetCenter();
The line graph mark->SetType() method allows you to make a special mark along the line for each actual data point. The SetWeight() method makes the line thicker, and the SetCenter() method ensures that the graph does not start flush with the y-axis.
Unfortunately, in version 1.6.1 of JpGraph, the AccBarPlot does not implement the SetCenter() method. What can be done to get around this issue? Reverting back to using $data['forecast'] for a bar chart, and then overlaying this bar chart with the actual, and with the plan line will result in:
This is a very useful graph, but there are some things we can do to make the graph more aesthetically pleasing.
We can use the following code to make data for the x-axis labels, and to create a title:
for($i = 0; $i < count($data['actual']); $i++) {
$labelx[] = strftime('%b %y', mktime(0, 0, 0, $i+1 , 1, date('Y')));
}
$title = date('Y').' Plan vs Actual & Forecast';
Next, we can change the background color, add a title in a particular font, and use the SetShadow method to add a nice border to the graph.
$graph->SetMarginColor('white');
$graph->title->Set($title);
$graph->title->SetFont(FF_VERDANA, FS_BOLD, 14);
$graph->SetShadow();
If you are using true type fonts, you have the ability to rotate fonts if needed. For example you could display the x-axis labels at an angle using the following code:
$graph->xaxis->SetFont(FF_ARIAL, FS_NORMAL,
$graph->xaxis->SetLabelAngle(30);
$graph->xaxis->SetTickLabels($labelx);
You can also use $bar1->SetWidth(0.75); to alter the width of each of the bars.
This is the result:
A few more finishing touches: $graph->xaxis->title->Set('Month'); and $graph->yaxis->title->Set('Units in Thousands'); to add axis titles, $graph->xaxis->SetLabelAngle(0); and change the format for the $labelx loop to make smaller x-axis labels (since the year is already included in the title of the graph).
Now we are ready to present our results to the boss.
Having reviewed our work, the boss immediately tells us this is very nice, but it needs several more changes that were out of the scope of the original project. The line graph for ?plan? is good, but it can sometimes be hard to determine if you hit or missed the target, so the graph should display ?actual? values in green for months that made the ?plan? and red in months that missed the ?plan?. For months that are still in progress, you should use the greater of the ?forecast? or the ?actual? to compare to ?plan? for color-coding. And the graph needs a legend to show what all of these colors mean.
Back to your PHP script, how can we implement these new requests?
JpGraph does allow you to change the color of specific bars on a plot by specifying an array of colors for the argument of the SetFillColor() method ($bar1->SetFillColor(array('red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet')); ). We have also already seen an example of displaying two plots, with the actual going over the forecast in the June month. We can change the data that we plot into three bar graphs and a line plot. The three bar graphs would be green for above plan, red for below plan and gray for forecast. The plan line will remain the same as the prior graph. The code to generate the two additional data series might look like:
for ($i = 0; $i < count($data['actual']); $i++) {
if (($data['actual'][$i] >= $data['plan'][$i]) ||
($data['forecast'][$i] >= $data['plan'][$i]))
{
$data['good'][$i] = $data['actual'][$i];
$data['bad'][$i] = 0;
} else {
$data['good'][$i] = 0;
$data['bad'][$i] = $data['actual'][$i];
}
}
For each of the plots we will be adding to the graph, we need to use the SetLegend() method to add values to the legend for the graph ( $bar1->SetLegend('Actual above Plan'); ). In the case of this graph, the legend might look good flat across the bottom, which can be accomplished with:
$graph->legend->SetLayout(LEGEND_HOR);
$graph->legend->Pos(0.52, 0.85, 'center');
Resulting in the final product:
While this is the extent of the graph for this tutorial, the reader could continue to improve on this. JpGraph supports gradient shading for the bars, which can make the graph much more visually appealing. You might also want to use JpGraph's CSIM (Client Side Image Map) functionality to implement ?drill downs? from this graph to other related charts or data.
The full script below also includes a simple example of displaying a chart of the data along with the graph, which is a popular requirement from users.
The Script
<?php
/**
* @file zendgraph.php
* @author Jason E. Sweat
* @purpose Zend tutorial for graphing using JpGraph
* @created 2002-04-13
*
*/
/* database related includes
all database interactions in this script are
done using the ADOdb database access class from
http://php.weblogs.com/ADOdb
to use static data instead,
define USE_DB to be false
*/
define('USE_DB', true);
if (USE_DB) {
require_once('adodb/adodb.inc.php');
}
// graphing related includes
require_once('jpgraph/jpgraph.php');
require_once('jpgraph/jpgraph_bar.php');
require_once('jpgraph/jpgraph_line.php');
// set the directory where images are cached by JpGraph
$jpgcache = APACHE_CACHE_DIR;
/*
This information is included to benefit readers who use MySQL
and would like to recreate the table and information to
generate this example graph.
--create table syntax for MySQL
CREATE TABLE data (
dt DATE NOT NULL,
plan DOUBLE DEFAULT '0',
actual DOUBLE DEFAULT '0',
forecast DOUBLE DEFAULT '0',
PRIMARY KEY (dt)
);
Please note that the first insert statement is the correct
way to fully qualify the insert statement, all the other
months have omitted, for brevity, the input field order,
and instead rely on the SQL engine to insert in the
same order as the fields are defined in the database.
--populate the table with data
insert into data (dt, plan, actual, forecast)
values ("2002-01-01", 11.5, 12, 0);
insert into data values ("2002-02-01", 12, 14, 0);
insert into data values ("2002-03-01", 12, 14.5, 0);
insert into data values ("2002-04-01", 13.5, 13, 0);
insert into data values ("2002-05-01", 10, 9, 0);
insert into data values ("2002-06-01", 9.5, 4, 10);
insert into data values ("2002-07-01", 11, 0, 12);
insert into data values ("2002-08-01", 12, 0, 14);
insert into data values ("2002-09-01", 12, 0, 13);
insert into data values ("2002-10-01", 11, 0, 10);
insert into data values ("2002-11-01", 13, 0, 11);
insert into data values ("2002-12-01", 12.5, 0, 13);
*/
if (USE_DB) {
// establish a connect to the database
$conn = &ADONewConnection('mysql');
$conn->Connect('localhost', 'zenduser', 'zendpass', 'zend');
$ADODB_FETCH_MODE = ADODB_FETCH_ASSOC;
// make an sql statement to retrieve the data you want
$sql = <<<EOS
SELECT dt,
MONTHNAME(dt) AS mon,
plan,
actual,
forecast
FROM data
WHERE dt >= '2002-01-01'
AND dt < '2003-01-01'
ORDER BY dt ASC
EOS;
// create a result set by executing your sql over the connection
$rs = $conn->Execute(trim($sql));
// iterate over the result set and create $data arrays
while ($rs && !$rs->EOF) {
$row = $rs->fields;
$labelx[] = substr($row['mon'],0,3);
$data['plan'][] = $row['plan'];
$data['actual'][] = $row['actual'];
$data['forecast'][] = $row['forecast'];
$rs->MoveNext();
}
} else { // USE_DB false
$data['plan'] = array (
11.5, 12, 12, 13.5, 10, 9.5,
11, 12, 12, 11, 13, 12.5 );
$data['actual'] = array(
12, 14, 14.5, 13, 9, 4,
0, 0, 0, 0, 0, 0 );
$data['forecast'] = array (
0, 0, 0, 0, 0, 10,
12, 14, 13, 10, 11, 13 );
for($i = 0; $i < count($data['actual']); $i++) {
$labelx[] = strftime('%b',
mktime(0, 0, 0, $i+1, 1, date('Y')));
}
}
// create the "good" and "bad" series for our red/green look
for($i = 0; $i < count($data['actual']); $i++) {
if ($data['actual'][$i] >= $data['plan'][$i]
|| $data['forecast'][$i] >= $data['plan'][$i]) {
$data['good'][$i] = $data['actual'][$i];
$data['bad'][$i] = 0;
} else {
$data['good'][$i] = 0;
$data['bad'][$i] = $data['actual'][$i];
}
}
// set title and name for the chart
$title = date('Y').' Plan vs Actual & Forecast';
$graph_name = 'chart.png';
/**
* JpGraph - Step 1
* Create the graph object and set general parameters
*/
// set general graph details
$graph = new graph(450, 300, $graph_name, 0, false);
// margins, background color, scale and shadow for the whole graph
$graph->img->SetMargin(50, 30, 30, 80);
$graph->SetMarginColor('white');
$graph->SetScale('textlin');
$graph->SetShadow();
// set the title and font
$graph->title->Set($title);
$graph->title->SetFont(FF_VERDANA, FS_BOLD, 14);
// set the y-axis and title it
$graph->yaxis->SetLabelAngle(0);
$graph->yaxis->title->Set('Units in Thousands');
$graph->yaxis->title->SetFont(FF_FONT1, FS_BOLD);
// set the x-axis, labels and title it
$graph->xaxis->SetLabelAngle(0);
$graph->xaxis->SetFont(FF_VERDANA, FS_NORMAL,
$graph->xaxis->SetTickLabels($labelx);
// set the graph title and font
$graph->xaxis->title->Set('Month');
$graph->xaxis->title->SetFont(FF_FONT1, FS_BOLD);
/**
* JpGraph - Step 2
* Set the individual plot details
*/
// line1 is the plan data line, green, thick and marked with circles
$line1 = new LinePlot($data['plan']);
$line1->mark->SetType(MARK_CIRCLE);
$line1->SetColor('darkolivegreen');
$line1->SetWeight(3);
$line1->SetCenter();
$line1->SetLegend('Plan');
// bar1 is the green bars for actual or forecast above plan
$bar1 = new BarPlot($data['good']);
$bar1->SetFillColor('green');
$bar1->SetAlign('center');
$bar1->SetWidth(0.75);
$bar1->SetLegend('Actual above Plan');
// bar2 is the red bars for actual and forecast below plan
$bar2 = new BarPlot($data['bad']);
$bar2->SetFillColor('red');
$bar2->SetAlign('center');
$bar2->SetWidth(0.75);
$bar2->SetLegend('Actual below Plan');
// bar3 is the gray bars for forecast data
$bar3 = new BarPlot($data['forecast']);
$bar3->SetFillColor('silver');
$bar3->SetAlign('center');
$bar3->SetWidth(0.75);
$bar3->SetLegend('Forecast');
/**
* JpGraph - Step 3
* Finalize and output the graph
*/
// set legend details
$graph->legend->SetLayout(LEGEND_HOR);
$graph->legend->Pos(0.52, 0.85, 'center');
// add the graphs bottom to top
$graph->Add($bar3);
$graph->Add($bar2);
$graph->Add($bar1);
$graph->Add($line1);
// output the graph to cache
$graph->Stroke();
$page_title = 'Zend Graphing Tutorial';
/* another example not covered in the tutorial,
output the data series values
into a table below the graph
*/
for($i = 0; $i < count($data['actual']); $i++) {
$row_head .= '<th>'.$labelx[$i].'</th>';
$row_plan .= '<td>'.number_format($data['plan'][$i], 1).'</td>';
$row_act .= '<td>'.number_format($data['actual'][$i], 1).'</td>';
$row_fcst .= '<td>'.number_format($data['forecast'][$i], 1).'</td>';
}
print <<<EOS
<html>
<head>
<title>$page_title</title>
</head>
<body>
<div style="text-align: center">
<h3>$page_title</h3>
<img src="{$jpgcache}{$graph_name}" />
<p />
<table border="1" cellpadding="5" cellspacing="0">
<tr align="center"><th>Data</th>$row_head</tr>
<tr align="right"><td><b>Plan</b></td>$row_plan</tr>
<tr align="right"><td><b>Actual</b></td>$row_act</tr>
<tr align="right"><td><b>Forecast</b></td>$row_fcst</tr>
</table>
</div>
</body>
</html>
EOS;
?>
http://www.zend.com/zend/tut/tutsweat3.php
