6.5. Using Callbacks to Annotate Plots

This section contains information about using PHPlot callbacks to annotate a plot with text and graphics. This is an advanced topic, and requires some knowledge of both PHPlot and the PHP GD extension.

Warning

The information in this section uses features which are recent additions to PHPlot, and in some cases uses PHPlot internal variables and functions. As a result, these methods are less likely to work with older releases, and more at risk to change or break in future releases.

This section will first provide general information and advice about annotating plots using callbacks. After, portions of the script from Section 5.22, “Example - Annotating a Plot Using a Callback” will be explained in more detail.

The emphasis here is on using callbacks, but annotation is also possible without callbacks. You can use SetPrintImage(False) to disable automatic output of your image. Then, when DrawGraph returns, you can annotate your plot using GD functions on the img member variable of your PHPlot object. Use of callbacks is preferred, however, because it makes your script somewhat less dependent on PHPlot internals (such as the img variable).

6.5.1. Setting the callback

Use SetCallback to establish a drawing callback. You can find a list of callbacks in Section 6.3, “Available Callbacks”. The various callbacks with names starting 'draw_' are called at different points in the drawing process. Drawn objects will cover items drawn at an earlier stage. For example, if you draw a line from a 'draw_titles' callback (which is called after the plot titles are drawn, but before the graph is drawn), the line would be 'behind' and possibly covered by the plotted data.

Note that PHPlot does very little except save parameter values until you call DrawGraph. For that reason, you should use GD functions for annotation only from a drawing callback (that is, a callback with a name starting with 'draw_'). The drawing callbacks are called after PHPlot calculations and image resource setup, at which point everything is ready for drawing. In addition, you should not use PHPlot functions which control plot appearance from your drawing callback. These would either have no affect, because it is too late, or produce unexpected results.

6.5.2. Coordinates

When drawing with GD, you will use the Device Coordinate system. The coordinates in this system are pixels, with the origin in the upper left corner of your image. Y advances down and X advances to the right.

If you want to make annotations relative to specific values in your plot data, you need to translate those values from World Coordinates to device coordinates. Use the PHPlot function GetDeviceXY to perform this translation. You will need access to your PHPlot object from inside your callback function in order to use this (or any other PHPlot method function). You can make it global, or designate it as the passthrough argument to SetCallback.

If your annotations will fall outside the plot area (for example, in an area you reserved for annotation using SetPlotAreaPixels or SetMarginsPixels, then you need not be concerned with coordinate translation. Of course, you can also add annotations at fixed pixel coordinates inside the plot area, however these may overlay (if done from a draw_graph or later callback) or underlay (if done before the draw_graph callback) the plotted data.

6.5.3. Colors

Every GD drawing function you use will require a color value argument. You are recommended to allocate your own colors in your callback using the GD function imagecolorresolve(). This function will always return a color index, by either re-using an existing color in the image's color map, or by allocating a new color. Using imagecolorresolve() rather than trying to access the PHPlot internal variables for color indexes will protect your script from breaking if the way PHPlot manages its internal colors ever changes.

6.5.4. Text

Text can be added to your plot using GD functions which include imagestring, for build-in simple fonts, and imagettftext for TrueType font text. To use these functions, you need device coordinates, as described above.

You can also add text to your plot using the PHPlot function DrawText. This is documented only for internal use by PHPlot, so there is a risk of future incompatibility. But this function provides support for controlling the text justification, and works better with multi-line text.

6.5.5. Example

This example creates a bar chart and adds annotation. The goal is to draw an ellipse and add text to the highest and lowest bars in a bar chart. Refer to Section 5.22, “Example - Annotating a Plot Using a Callback” for the complete script and output from this example.

The script starts with the usual PHPlot object creation and setup.

$plot = new PHPlot(800, 600);
$plot->SetTitle('Monthly Widget Sales');
...

(For the complete script, see the example referenced above.)

Before calling DrawGraph, establish the drawing callback. This uses the draw_all callback, which gets called when all drawing is complete in DrawGraph. (Note: If using PHPlot-5.0.7 or earlier, use 'draw_graph' instead, as 'draw_all' was not yet available.) The name of our callback function is annotate_plot, and we are passing the PHPlot object ($plot) as a pass-through parameter. You can use a global or class callback instead - see Section 6.1, “Callbacks Application Interface” for more on these options.

$plot->SetCallback('draw_all', 'annotate_plot', $plot);

Here is the declaration of our callback function. The $img parameter is provided by PHPlot itself, and is the GD resource for our image. The $plot parameter is the pass-through argument we provided above when establishing the callback. Some callbacks make other parameters available. In fact, 'draw_all' provides the plot area coordinates as an additional parameter, but we don't need that here so we do not have to include that in the function declaration.

function annotate_plot($img, $plot)
{

As stated above, you should allocate your own colors, rather than trying to get into PHPlot's internals for color values. Here we allocate two colors and assign the color indexes to local variables:

$red = imagecolorresolve($img, 255, 0, 0);
$green = imagecolorresolve($img, 0, 216, 0);

Next, we want to draw graphics centered on two points in our data. The points were calculated as best_index (X), best_sales (Y), worst_index (X), and worst_sales (Y). In order to draw at these locations, we need to translate the values from World Coordinates to Device Coordinates. This is done using the PHPlot function GetDeviceXY.

list($best_x, $best_y) = $plot->GetDeviceXY($best_index, $best_sales);
list($worst_x, $worst_y) = $plot->GetDeviceXY($worst_index, $worst_sales);

Now we are ready to draw some ellipses, centered on our two data points. The values 50 and 20 are the width and height, in pixels.

imageellipse($img, $best_x, $best_y, 50, 20, $green);
imageellipse($img, $worst_x, $worst_y, 50, 20, $red);

As stated above, we have two options for text, and the example uses each method. We can draw text using the GD functions, but we have to do a little more work to position the text. Here the text is approximately centered horizontally and above the data point. (Note ImageString by default uses the upper left corner of the text string for positioning.)

$font = '3';
$fh = imagefontheight($font);
$fw = imagefontwidth($font);
imagestring($img, $font, $best_x-$fw*4, $best_y-$fh-10, 'Good Job!', $green);

Or, we can use the PHPlot internal function DrawText. With a PHPlot version 5.1.0 and later, we omit the font specification and it will default to the generic font, which can be set with SetFont('generic', ...)

$plot->DrawText('', 0, $worst_x, $worst_y-10, $red, 'Bad News!', 'center', 'bottom');