Combined graphs

Introduction: Combining graphs in the same image

By combining several graphs into the same image one can reduce download time for HTML pages (fewer HTTP requests) and also make sure that any information/image that the user downloads has the complete information.

Figure 1 shows a basic example of a combination graph consisting of two subgraphs stacked on top of each other

Figure 1. A combination of a line graph at top and a bar plot in the bottom

Creating a combined graph

Creating a combined graph can be done either the easy way or the hard way

1. Hard way

By a bit of extra PHP/GD code to manually create a big enough empty image with plain GD commands and then get the image handles from the Stroke() methods in the graphs that should be included. It is then possible to use these handles together with the GD image copy command and copy the individual graph images onto the previously created empty large GD image.

The following code snippet illustrates this method

// Assume we would like to combine graph1,2 and 3
// ...... create graph 1 here.......
$handle1 =  $graph1->Stroke( _IMG_HANDLER);
 
// ...... create graph 2 here.......
$handle2 =  $graph2->Stroke( _IMG_HANDLER);

// ...... create graph 3 here.......
$handle3 =  $graph3->Stroke( _IMG_HANDLER);
 
 
// Now create the "melting graph" which should contain all three graphs
// $WIDTH1 and $HEIGHT1 are width and height of graph 1 ($handle1)
// $WIDTH2 and $HEIGHT2 are width and height of graph 2 ($handle2)
// $WIDTH3 and $HEIGHT3 are width and height of graph 3 ($handle3)
// $x2,$x3 and $y2,$y3 shift from top left of global graph (ie position of 
// graph2 and graph3 in global graph)

$image = imagecreatetruecolor($WIDTH1,$HEIGHT1);
imagecopy($image, $handle1,0, 0, 0, 0, $WIDTH1,$HEIGHT1);
imagecopy($image, $handle2,$x1,$y1,0,0,$WIDTH2,$HEIGHT2);
imagecopy($image, $handle3,$x2,$y2,0,0,$WIDTH3,$HEIGHT3);
 
// Stream the result back as a PNG image
header("Content-type: image/png");
imagepng ($image);

Advantages:
- Absolute full control on a pixel level.
Disadvantages:
+ You have to learn all about the GD library
+ You have to re-implement a lot of functionality already available in the library

2. Easy way

Use the image container class Class MGraph available in the jpgraph_mgraph.php file. This helper class abstracts away all the details about copying images as well as offering consistent interface (almost identical with the standard Graph class). A basic example will quickly reveal the elegance of using this helper class.

//--------------------------------------
// Graph 1
//--------------------------------------
$graph1 = new Graph(...);
... [Code to create graph1] ...

//--------------------------------------
// Graph 2
//--------------------------------------
$graph2 = new Graph(...);
... [Code to create graph2] ...

//--------------------------------------
// Create a combined graph
//--------------------------------------
$mgraph = new MGraph();
$xpos1=3;$ypos1=3;
$xpos2=3;$ypos2=200;
$mgraph->Add($graph1,$xpos1,$ypos1);
$mgraph->Add($graph2,$xpos2,$ypos2);
$mgraph->Stroke();

Basic formatting

The above example illustrates the most basic usage of the MGraph class. Each graph is added to the combined graph with a call to Add() which in addition to the graph argument also takes the target X and Y coordinates where the graph will be placed in the combined image. The target X and Y coordinates are relative to any optional specified left and top margin (see below).

In addition to adding graphs there are some basic formatting option for the combined graph that are listed below

  1. MGraph::MGraph($aWidth,$aHeight). Create a new instance of the MGraph class. If the width and height are supplied as arguments they will override the automatically determined width and height.
  2. MGraph::Add($aGraph,$aToX,$aToY,$aFromX,$aFromY,$aWidth,$aHeight)(). Add a new subgraph with destination position ($aToX, $aToY). By default the whole graph image is used. It is also possible to only specify that part of the graph should be used. The source rectangle is specified by ($aFromX, $aFromY, $aWidth, $aHeight).
  3. MGraph::SetFillColor($aColor). Background color of the combined graphs.
    Example: Setting an Orange background
    $mgraph->SetFillColor('orange');
  4. MGraph::SetFrame($aShow,$aColor,$aWeight) Adding a frame to the combined graph.
    Example: To add a blue frame with a 2 pixel width
    $mgraph->SetFrame(true,'blue ',2);
  5. MGraph::SetMargin($aLeft,$aRight,$aTop,$aBottom) Adding a margin to the image. By default the minimum necessary size for the combined graph to hold all the added subgraphs is used. Use this method to add some space to the edges around the combined image.
    When the individual graphs are positioned (in the Add() method) the position is counted relative to the left and top margin.
    Example: Use 10 pixel left and right margin, 5 pixel top and bottom margin
    $mgraph->SetMargin(10,10,5,5);

In addition the MGraph::Stroke($aFileName) accepts a filename in the same way as the normal Graph::Stroke()

Background images and mixing

In addition to the basic usage as shown above it is also possible to add a background image in the combined graph. What is important to remember then is that all the subgraphs are copied onto the combined graph so normally they will be on top of the background image and hide it.

There are then two ways of handling a background images

  1. Do nothing and the background image will only "shine through" for those areas of the combined graph that is not covered by any subgraph.
  2. Specify a "blend" factor for the subgraphs that specifies how much of the background image will be mixed with the subgraph. The blend factor is as usual specified as an integer in the interval 0-100. (Note: This is the same functionality as the standard Graph::SetBackgroundImageMix() for ordinary graphs.) A value of 0 means that 100% of the background is visible and a value of 100 means that 0% of the background is visible.
    To add a subgraphs with a blend factor the method MGrahp::AddMix() is used. This behaves exactly as the standard Add() apart from the fact that the 3:rd argument is the blend factor (after the to X, Y coordinates).
    Example: Add a graph with 50% mix of the background
    $mgraph->AddMix($graph,0,0,50);

The background image itself is added with the method

MGraph::SetBackgroundImage($aFile,$aCenter_aX=TRUE,$aY=NULL)

The filename specifies the full name of the background image (including extension). Please note that the extension must be a valid image extension in order for the library to determine the format of the image file.
The second argument can be either numeric or a boolean in which case it specifies if the background image should be centered or if it should just be copied from the top left position. If it is numerical it is interpretated as the X-position for the optional X and Y coordinates specifying the position of the top left corner of the background image.
Figure 2. shows a variant of the first figure where a centered background image is mixed in with two subgraphs.

Figure 2. Mixing a background image with two subgraphs. In this case the mixing factor was 85 for both subgraphs. (Note: To reduce load time on this web-page the image above is quite hard compressed in JPEG so You might be able to spot artifacts in the high frequency areas.)

Caveats

  • Image maps can not be used with combined graphs.
  • Creating multiple graphs in the same script will require some additional memory. Make sure that the memory limit in php.ini is sufficient.
  • Adding background images can significantly increase the final size of the image. Using the JPEG image format when photo like background images are used gives better compressing than PNG (since JPEG is a lossy format). For example setting the JPEG quality parameter to 60 (default is 75).
    $mgraph->SetImgFormat('jpeg',60);
    Note: You can use the same method for ordinary Graphs as well.
  • Compared with background images for ordinary graphs there is currently no possibility for automatic re-sizing of the background images.

Example

We end this tutorial by listing the full source of the script that was used to create the image in Figure 2. This is also available in the Example directory as combgraphex1.php

include ("../jpgraph.php");
include ("../jpgraph_line.php");
include ("../jpgraph_bar.php");
include ("../jpgraph_mgraph.php");

//------------------------------------------------------------------
// Create some random data for the plot. We use the current time for the
// first X-position
//------------------------------------------------------------------
$datay = array();
$datax = array();
$ts = time();
$n=70; // Number of data points
for($i=0; $i < $n; ++$i ) {
    $datax[$i] = $ts+$i*150000; 
    $datay[$i] = rand(5,60);
    $datay2[$i] = rand(1,8);
}

// Now get labels at the start of each month
list($tickPositions,$minTickPositions) = 
    DateScaleUtils::getTicks($datax,DSUTILS_MONTH1);

// Now create the real graph
// Combine a line and a bar graph

// We add some grace to the end of the X-axis scale so that the first and last
// data point isn't exactly at the very end or beginning of the scale
$grace = 400000;
$xmin = $datax[0]-$grace;
$xmax = $datax[$n-1]+$grace;;

// Overall width of graphs
$w = 450;
// Left and right margin for each graph
$lm=25; $rm=15; 

//----------------------
// Setup the line graph
//----------------------
$graph = new Graph($w,250);
$graph->SetScale('linlin',0,0,$xmin,$xmax);
$graph->SetMargin($lm,$rm,10,30);
$graph->SetMarginColor('white');
$graph->SetFrame(false);
$graph->SetBox(true);
$graph->title->Set('Example of combined graph with background');
$graph->title->SetFont(FF_ARIAL,FS_NORMAL,14);
$graph->xaxis->SetTickPositions($tickPositions,$minTickPositions);
$graph->xaxis->SetLabelFormatString('My',true);
$graph->xgrid->Show();
$p1 = new LinePlot($datay,$datax);
$graph->Add($p1);

//----------------------
// Setup the bar graph
//----------------------
$graph2 = new Graph($w,110);
$graph2->SetScale('linlin',0,0,$xmin,$xmax);
$graph2->SetMargin($lm,$rm,5,10);
$graph2->SetMarginColor('white');
$graph2->SetFrame(false);
$graph2->SetBox(true);
$graph2->xgrid->Show();
$graph2->xaxis->SetTickPositions($tickPositions,$minTickPositions);
$graph2->xaxis->SetLabelFormatString('My',true);
$graph2->xaxis->SetPos('max');
$graph2->xaxis->HideLabels();
$graph2->xaxis->SetTickSide(SIDE_DOWN);
$b1 = new BarPlot($datay2,$datax);
$b1->SetFillColor('teal');
$b1->SetColor('teal:1.2');
$graph2->Add($b1);

//-----------------------
// Create a multigraph
//----------------------
$mgraph = new MGraph();
$mgraph->SetImgFormat('jpeg',60);
$mgraph->SetMargin(2,2,2,2);
$mgraph->SetFrame(true,'darkgray',2);
$mgraph->SetBackgroundImage('tiger1.jpg');
$mgraph->AddMix($graph,0,0,85);
$mgraph->AddMix($graph2,0,250,85);
$mgraph->Stroke();

Note:

Requires 1.23p or 2.3p
This script uses Tilde-processing that is only available in 1.23p and 2.3p. Those versions are planned to be released in early March. The libray is already available upon request. The postponed release is only due to some yet missing documentation.

HowTo's

1. Combined graphs

2. Using manual ticks

3. Greek characters

4. Multiple Y axes

5. Synchronized Y axes

6. Adding new TTF fonts

7. Tables - Part I

8. Tables - Part II

9. USPS Confirmation Barcodes

10. Accumulated line plots with given X-labels