root/trunk/flowmon-web/charts/phplot-5.0.5/phplot.php

Revision 1, 187.8 kB (checked in by ixs, 16 years ago)

initial checkin of RH revision

Line 
1 <?php
2
3 /* $Id: phplot.php,v 1.141 2008/01/14 02:09:41 lbayuk Exp $ */
4
5 /*
6  * PHPLOT Version 5.0.5
7  * Copyright (C) 1998-2008 Afan Ottenheimer.  Released under
8  * the GPL and PHP licenses as stated in the the README file which should
9  * have been included with this document.
10  *
11  * Co-author and maintainer (2003-2005)
12  * Miguel de Benito Delgado <nonick AT vodafone DOT es>
13  *
14  * Maintainer (2006-present)
15  * <lbayuk AT users DOT sourceforge DOT net>
16  *
17  * Visit http://sourceforge.net/projects/phplot/
18  * for PHPlot documentation, downloads, and discussions.
19  *
20  * Requires PHP 5.2.x or later. (PHP 4 is unsupported as of Jan 2008)
21  */
22
23 class PHPlot {
24
25     /* I have removed internal variable declarations, some isset() checking was required,
26      * but now the variables left are those which can be tweaked by the user. This is intended to
27      * be the first step towards moving most of the Set...() methods into a subclass which will be
28      * used only when strictly necessary. Many users will be able to put default values here in the
29      * class and thus avoid memory overhead and reduce parsing times.
30      */
31     //////////////// CONFIG PARAMETERS //////////////////////
32
33     var $is_inline = FALSE;             // FALSE = Sends headers, TRUE = sends just raw image data
34     var $browser_cache = FALSE;         // FALSE = Sends headers for browser to not cache the image,
35                                         // (only if is_inline = FALSE also)
36
37     var $safe_margin = 5;               // Extra margin used in several places. In pixels
38
39     var $x_axis_position = '';          // Where to draw both axis (world coordinates),
40     var $y_axis_position = '';          // leave blank for X axis at 0 and Y axis at left of plot.
41
42     var $xscale_type = 'linear';        // linear, log
43     var $yscale_type = 'linear';
44
45 //Fonts
46     var $use_ttf  = FALSE;                  // Use True Type Fonts?
47     var $ttf_path = '.';                    // Default path to look in for TT Fonts.
48     var $default_ttfont = 'benjamingothic.ttf';
49     var $line_spacing = 4;                  // Pixels between lines.
50
51     // Font angles: 0 or 90 degrees for fixed fonts, any for TTF
52     var $x_label_angle = 0;                 // For labels on X axis (tick and data)
53     var $y_label_angle = 0;                 // For labels on Y axis (tick and data)
54
55 //Formats
56     var $file_format = 'png';
57     var $output_file = '';                  // For output to a file instead of stdout
58
59 //Data
60     var $data_type = 'text-data';           // text-data, data-data-error, data-data, text-data-single
61     var $plot_type= 'linepoints';           // bars, lines, linepoints, area, points, pie, thinbarline, squared
62
63     var $label_scale_position = 0.5;        // Shifts data labes in pie charts. 1 = top, 0 = bottom
64     var $group_frac_width = 0.7;            // Bars use this fraction (0 to 1) of a group's space
65     var $bar_extra_space = 0.5;             // Number of extra bar's worth of space in a group
66     var $bar_width_adjust = 1;              // 1 = bars of normal width, must be > 0
67
68     var $y_precision = 1;
69     var $x_precision = 1;
70
71     var $data_units_text = '';              // Units text for 'data' labels (i.e: '€', '$', etc.)
72
73 // Titles
74     var $title_txt = '';
75
76     var $x_title_txt = '';
77     var $x_title_pos = 'plotdown';          // plotdown, plotup, both, none
78
79     var $y_title_txt = '';
80     var $y_title_pos = 'plotleft';          // plotleft, plotright, both, none
81
82
83 //Labels
84     // There are two types of labels in PHPlot:
85     //    Tick labels: they follow the grid, next to ticks in axis.   (DONE)
86     //                 they are drawn at grid drawing time, by DrawXTicks() and DrawYTicks()
87     //    Data labels: they follow the data points, and can be placed on the axis or the plot (x/y)  (TODO)
88     //                 they are drawn at graph plotting time, by Draw*DataLabel(), called by DrawLines(), etc.
89     //                 Draw*DataLabel() also draws H/V lines to datapoints depending on draw_*_data_label_lines
90
91     // Tick Labels
92     var $x_tick_label_pos = 'plotdown';     // plotdown, plotup, both, xaxis, none
93     var $y_tick_label_pos = 'plotleft';     // plotleft, plotright, both, yaxis, none
94
95     // Data Labels:
96     var $x_data_label_pos = 'plotdown';     // plotdown, plotup, both, plot, all, none
97     var $y_data_label_pos = 'plotleft';     // plotleft, plotright, both, plot, all, plotin, none
98
99     var $draw_x_data_label_lines = FALSE;   // Draw a line from the data point to the axis?
100     var $draw_y_data_label_lines = FALSE;   // TODO
101
102     // Label types: (for tick, data and plot labels)
103     var $x_label_type = '';                 // data, time. Leave blank for no formatting.
104     var $y_label_type = '';                 // data, time. Leave blank for no formatting.
105     var $x_time_format = '%H:%M:%S';        // See http://www.php.net/manual/html/function.strftime.html
106     var $y_time_format = '%H:%M:%S';        // SetYTimeFormat() too...
107
108     // Skipping labels
109     // var $x_label_inc = 1;                   // Draw a label every this many (1 = all) (TODO)
110     // var $y_label_inc = 1;
111     // var $_x_label_cnt = 0;                  // internal count FIXME: work in progress
112
113 // Legend
114     var $legend = '';                       // An array with legend titles
115     // These variables are unset to take default values:
116     // var $legend_x_pos;                   // User-specified upper left coordinates of legend box
117     // var $legend_y_pos;
118     // var $legend_xy_world;                // If set, legend_x/y_pos are world coords, else pixel coords
119     // var $legend_text_align;              // left or right, Unset means right
120     // var $legend_colorbox_align;          // left, right, or none; Unset means same as text_align
121
122 //Ticks
123     var $x_tick_length = 5;                 // tick length in pixels for upper/lower axis
124     var $y_tick_length = 5;                 // tick length in pixels for left/right axis
125
126     var $x_tick_cross = 3;                  // ticks cross x axis this many pixels
127     var $y_tick_cross = 3;                  // ticks cross y axis this many pixels
128
129     var $x_tick_pos = 'plotdown';           // plotdown, plotup, both, xaxis, none
130     var $y_tick_pos = 'plotleft';           // plotright, plotleft, both, yaxis, none
131
132     var $num_x_ticks = '';
133     var $num_y_ticks = '';
134
135     var $x_tick_inc = '';                   // Set num_x_ticks or x_tick_inc, not both.
136     var $y_tick_inc = '';                   // Set num_y_ticks or y_tick_inc, not both.
137
138     var $skip_top_tick = FALSE;
139     var $skip_bottom_tick = FALSE;
140     var $skip_left_tick = FALSE;
141     var $skip_right_tick = FALSE;
142
143 //Grid Formatting
144     var $draw_x_grid = FALSE;
145     var $draw_y_grid = TRUE;
146
147     var $dashed_grid = TRUE;
148     var $grid_at_foreground = FALSE;        // Chooses whether to draw the grid below or above the graph
149
150 //Colors and styles       (all colors can be array (R,G,B) or named color)
151     var $color_array = 'small';             // 'small', 'large' or array (define your own colors)
152                                             // See rgb.inc.php and SetRGBArray()
153     var $i_border = array(194, 194, 194);
154     var $plot_bg_color = 'white';
155     var $bg_color = 'white';
156     var $label_color = 'black';
157     var $text_color = 'black';
158     var $grid_color = 'black';
159     var $light_grid_color = 'gray';
160     var $tick_color = 'black';
161     var $title_color = 'black';
162     var $data_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1');
163     var $error_bar_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1');
164     var $data_border_colors = array('black');
165
166     var $line_widths = 1;                  // single value or array
167     var $line_styles = array('solid', 'solid', 'dashed');   // single value or array
168     var $dashed_style = '2-4';              // colored dots-transparent dots
169
170     var $point_sizes = array(5,5,3);         // single value or array
171     var $point_shapes = array('diamond');   // rect, circle, diamond, triangle, dot, line, halfline, cross
172
173     var $error_bar_size = 5;                // right and left size of tee
174     var $error_bar_shape = 'tee';           // 'tee' or 'line'
175     var $error_bar_line_width = 1;          // single value (or array TODO)
176
177     var $plot_border_type = 'sides';        // left, sides, none, full
178     var $image_border_type = 'none';        // 'raised', 'plain', 'none'
179
180     var $shading = 5;                       // 0 for no shading, > 0 is size of shadows in pixels
181
182     var $draw_plot_area_background = FALSE;
183     var $draw_broken_lines = FALSE;          // Tells not to draw lines for missing Y data.
184
185 //Miscellaneous
186     var $callbacks = array(                  // Valid callback reasons (see SetCallBack)
187         'draw_setup' => NULL,
188         'draw_image_background' => NULL,
189         'draw_plotarea_background' => NULL,
190         'draw_titles' => NULL,
191         'draw_axes' => NULL,
192         'draw_graph' => NULL,
193         'draw_border' => NULL,
194         'draw_legend' => NULL,
195         'debug_textbox' => NULL// For testing/debugging text box alignment
196         'debug_scale' => NULL,    // For testing/debugging scale setup
197     );
198
199
200 //////////////////////////////////////////////////////
201 //BEGIN CODE
202 //////////////////////////////////////////////////////
203
204     /*!
205      * Constructor: Setup img resource, colors and size of the image, and font sizes.
206      *
207      * \param which_width       int    Image width in pixels.
208      * \param which_height      int    Image height in pixels.
209      * \param which_output_file string Filename for output.
210      * \param which_input_fule  string Path to a file to be used as background.
211      */
212     function PHPlot($which_width=600, $which_height=400, $which_output_file=NULL, $which_input_file=NULL)
213     {
214         $this->SetRGBArray($this->color_array);
215
216         $this->background_done = FALSE;     // TRUE after background image is drawn once
217         $this->plot_margins_set = FALSE;    // TRUE with user-set plot area or plot margins.
218
219         if ($which_output_file)
220             $this->SetOutputFile($which_output_file);
221
222         if ($which_input_file)
223             $this->SetInputFile($which_input_file);
224         else {
225             $this->image_width = $which_width;
226             $this->image_height = $which_height;
227
228             $this->img = ImageCreate($this->image_width, $this->image_height);
229             if (! $this->img)
230                 return $this->PrintError('PHPlot(): Could not create image resource.');
231
232         }
233
234         $this->SetDefaultStyles();
235         $this->SetDefaultFonts();
236
237         $this->SetTitle('');
238         $this->SetXTitle('');
239         $this->SetYTitle('');
240
241         $this->print_image = TRUE;      // Use for multiple plots per image (TODO: automatic)
242     }
243
244     /*!
245      * Reads an image file. Stores width and height, and returns the image
246      * resource. On error, calls PrintError and returns False.
247      * This is used by the constructor via SetInputFile, and by tile_img().
248      */
249     function GetImage($image_filename, &$width, &$height)
250     {
251         $error = '';
252         $size = getimagesize($image_filename);
253         if (!$size) {
254             $error = "Unable to query image file $image_filename";
255         } else {
256             $image_type = $size[2];
257             switch($image_type) {
258             case IMAGETYPE_GIF:
259                 $img = @ ImageCreateFromGIF ($image_filename);
260                 break;
261             case IMAGETYPE_PNG:
262                 $img = @ ImageCreateFromPNG ($image_filename);
263                 break;
264             case IMAGETYPE_JPEG:
265                 $img = @ ImageCreateFromJPEG ($image_filename);
266                 break;
267             default:
268                 $error = "Unknown image type ($image_type) for image file $image_filename";
269                 break;
270             }
271         }
272         if (empty($error) && !$img) {
273             # getimagesize is OK, but GD won't read it. Maybe unsupported format.
274             $error = "Failed to read image file $image_filename";
275         }
276         if (!empty($error)) {
277             return $this->PrintError("GetImage(): $error");
278         }
279         $width = $size[0];
280         $height = $size[1];
281         return $img;
282     }
283
284     /*!
285      * Selects an input file to be used as background for the whole graph.
286      * This resets the graph size to the image's size.
287      * Note: This is used by the constructor. It is deprecated for direct use.
288      */
289     function SetInputFile($which_input_file)
290     {
291         $im = $this->GetImage($which_input_file, $this->image_width, $this->image_height);
292         if (!$im)
293             return FALSE// GetImage already produced an error message.
294
295         // Deallocate any resources previously allocated
296         if (isset($this->img))
297             imagedestroy($this->img);
298
299         $this->img = $im;
300
301         // Do not overwrite the input file with the background color.
302         $this->background_done = TRUE;
303
304         return TRUE;
305     }
306
307 /////////////////////////////////////////////
308 //////////////                         COLORS
309 /////////////////////////////////////////////
310
311     /*!
312      * Returns an index to a color passed in as anything (string, hex, rgb)
313      *
314      * \param which_color * Color (can be '#AABBCC', 'Colorname', or array(r,g,b))
315      * Returns a GD color index (integer >= 0), or NULL on error.
316      */
317     function SetIndexColor($which_color)
318     {
319         list ($r, $g, $b) = $this->SetRGBColor($which_color);  //Translate to RGB
320         if (!isset($r)) return NULL;
321         return ImageColorResolve($this->img, $r, $g, $b);
322     }
323
324
325     /*!
326      * Returns an index to a slightly darker color than the one requested.
327      * Returns a GD color index (integer >= 0), or NULL on error.
328      */
329     function SetIndexDarkColor($which_color)
330     {
331         list ($r, $g, $b) = $this->SetRGBColor($which_color);
332         if (!isset($r)) return NULL;
333         $r = max(0, $r - 0x30);
334         $g = max(0, $g - 0x30);
335         $b = max(0, $b - 0x30);
336         return ImageColorResolve($this->img, $r, $g, $b);
337     }
338
339     /*!
340      * Sets/reverts all colors and styles to their defaults. If session is set, then only updates indices,
341      * as they are lost with every script execution, else, sets the default colors by name or value and
342      * then updates indices too.
343      *
344      * FIXME Isn't this too slow?
345      *
346      */
347     function SetDefaultStyles()
348     {
349         /* Some of the Set*() functions use default values when they get no parameters. */
350
351         if (! isset($this->session_set)) {
352             // If sessions are enabled, this variable will be preserved, so upon future executions, we
353             // will have it set, as well as color names (though not color indices, that's why we
354             // need to rebuild them)
355             $this->session_set = TRUE;
356
357             // These only need to be set once
358             $this->SetLineWidths();
359             $this->SetLineStyles();
360             $this->SetDefaultDashedStyle($this->dashed_style);
361             $this->SetPointSizes($this->point_sizes);
362         }
363
364         $this->SetImageBorderColor($this->i_border);
365         $this->SetPlotBgColor($this->plot_bg_color);
366         $this->SetBackgroundColor($this->bg_color);
367         $this->SetLabelColor($this->label_color);
368         $this->SetTextColor($this->text_color);
369         $this->SetGridColor($this->grid_color);
370         $this->SetLightGridColor($this->light_grid_color);
371         $this->SetTickColor($this->tick_color);
372         $this->SetTitleColor($this->title_color);
373         $this->SetDataColors();
374         $this->SetErrorBarColors();
375         $this->SetDataBorderColors();
376         return TRUE;
377     }
378
379
380     /*
381      *
382      */
383     function SetBackgroundColor($which_color)
384     {
385         $this->bg_color= $which_color;
386         $this->ndx_bg_color= $this->SetIndexColor($this->bg_color);
387         return isset($this->ndx_bg_color);
388     }
389
390     /*
391      *
392      */
393     function SetPlotBgColor($which_color)
394     {
395         $this->plot_bg_color= $which_color;
396         $this->ndx_plot_bg_color= $this->SetIndexColor($this->plot_bg_color);
397         return isset($this->ndx_plot_bg_color);
398     }
399
400    /*
401     *
402     */
403     function SetTitleColor($which_color)
404     {
405         $this->title_color= $which_color;
406         $this->ndx_title_color= $this->SetIndexColor($this->title_color);
407         return isset($this->ndx_title_color);
408     }
409
410     /*
411      *
412      */
413     function SetTickColor ($which_color)
414     {
415         $this->tick_color= $which_color;
416         $this->ndx_tick_color= $this->SetIndexColor($this->tick_color);
417         return isset($this->ndx_tick_color);
418     }
419
420
421     /*
422      * Do not use. Use SetTitleColor instead.
423      */
424     function SetLabelColor ($which_color)
425     {
426         $this->label_color = $which_color;
427         $this->ndx_title_color= $this->SetIndexColor($this->label_color);
428         return isset($this->ndx_title_color);
429     }
430
431
432     /*
433      *
434      */
435     function SetTextColor ($which_color)
436     {
437         $this->text_color= $which_color;
438         $this->ndx_text_color= $this->SetIndexColor($this->text_color);
439         return isset($this->ndx_text_color);
440     }
441
442
443     /*
444      *
445      */
446     function SetLightGridColor ($which_color)
447     {
448         $this->light_grid_color= $which_color;
449         $this->ndx_light_grid_color= $this->SetIndexColor($this->light_grid_color);
450         return isset($this->ndx_light_grid_color);
451     }
452
453
454     /*
455      *
456      */
457     function SetGridColor ($which_color)
458     {
459         $this->grid_color = $which_color;
460         $this->ndx_grid_color= $this->SetIndexColor($this->grid_color);
461         return isset($this->ndx_grid_color);
462     }
463
464
465     /*
466      *
467      */
468     function SetImageBorderColor($which_color)
469     {
470         $this->i_border = $which_color;
471         $this->ndx_i_border = $this->SetIndexColor($this->i_border);
472         $this->ndx_i_border_dark = $this->SetIndexDarkColor($this->i_border);
473         return isset($this->ndx_i_border);
474     }
475
476
477     /*
478      *
479      */
480     function SetTransparentColor($which_color)
481     {
482         $ndx = $this->SetIndexColor($which_color);
483         if (!isset($ndx))
484             return FALSE;
485         ImageColorTransparent($this->img, $ndx);
486         return TRUE;
487     }
488
489
490     /*!
491      * Sets the array of colors to be used. It can be user defined, a small predefined one
492      * or a large one included from 'rgb.inc.php'.
493      *
494      * \param which_color_array If an array, the used as color array. If a string can
495      *        be one of 'small' or 'large'.
496      */
497     function SetRGBArray ($which_color_array)
498     {
499         if ( is_array($which_color_array) ) {           // User defined array
500             $this->rgb_array = $which_color_array;
501             return TRUE;
502         } elseif ($which_color_array == 'small') {      // Small predefined color array
503             $this->rgb_array = array(
504                 'white'          => array(255, 255, 255),
505                 'snow'           => array(255, 250, 250),
506                 'PeachPuff'      => array(255, 218, 185),
507                 'ivory'          => array(255, 255, 240),
508                 'lavender'       => array(230, 230, 250),
509                 'black'          => array(  0,   0,   0),
510                 'DimGrey'        => array(105, 105, 105),
511                 'gray'           => array(190, 190, 190),
512                 'grey'           => array(190, 190, 190),
513                 'navy'           => array(  0,   0, 128),
514                 'SlateBlue'      => array(10690, 205),
515                 'blue'           => array(  0,   0, 255),
516                 'SkyBlue'        => array(135, 206, 235),
517                 'cyan'           => array(  0, 255, 255),
518                 'DarkGreen'      => array(  0, 100,   0),
519                 'green'          => array(  0, 255,   0),
520                 'YellowGreen'    => array(154, 20550),
521                 'yellow'         => array(255, 255,   0),
522                 'orange'         => array(255, 165,   0),
523                 'gold'           => array(255, 215,   0),
524                 'peru'           => array(205, 13363),
525                 'beige'          => array(245, 245, 220),
526                 'wheat'          => array(245, 222, 179),
527                 'tan'            => array(210, 180, 140),
528                 'brown'          => array(1654242),
529                 'salmon'         => array(250, 128, 114),
530                 'red'            => array(255,   0,   0),
531                 'pink'           => array(255, 192, 203),
532                 'maroon'         => array(1764896),
533                 'magenta'        => array(255,   0, 255),
534                 'violet'         => array(238, 130, 238),
535                 'plum'           => array(221, 160, 221),
536                 'orchid'         => array(218, 112, 214),
537                 'purple'         => array(16032, 240),
538                 'azure1'         => array(240, 255, 255),
539                 'aquamarine1'    => array(127, 255, 212)
540                 );
541             return TRUE;
542         } elseif ($which_color_array === 'large')  {    // Large color array
543             include("./rgb.inc.php");
544             $this->rgb_array = $RGBArray;
545         } else {                                        // Default to black and white only.
546             $this->rgb_array = array('white' => array(255, 255, 255), 'black' => array(0, 0, 0));
547         }
548
549         return TRUE;
550     }
551
552     /*!
553      * Returns an array in R, G, B format 0-255
554      *
555      *  \param color_asked array(R,G,B) or string (named color or '#AABBCC')
556      */
557     function SetRGBColor($color_asked)
558     {
559         if (empty($color_asked)) {
560             $ret_val = array(0, 0, 0);
561         } elseif (count($color_asked) == 3 ) {    // already array of 3 rgb
562             $ret_val = $color_asked;
563         } elseif ($color_asked[0] == '#') {       // Hex RGB notation #RRGGBB
564             $ret_val = array(hexdec(substr($color_asked, 1, 2)),
565                              hexdec(substr($color_asked, 3, 2)),
566                              hexdec(substr($color_asked, 5, 2)));
567
568         } elseif (isset($this->rgb_array[$color_asked])) {  // Color by name
569             $ret_val = $this->rgb_array[$color_asked];
570         } else {
571             return $this->PrintError("SetRGBColor(): Color '$color_asked' is not valid.");
572         }
573         return $ret_val;
574     }
575
576
577     /*!
578      * Sets the colors for the data.
579      */
580     function SetDataColors($which_data = NULL, $which_border = NULL)
581     {
582         if (is_null($which_data) && is_array($this->data_colors)) {
583             // use already set data_colors
584         } else if (! is_array($which_data)) {
585             $this->data_colors = ($which_data) ? array($which_data) : array('blue', 'red', 'green', 'orange');
586         } else {
587             $this->data_colors = $which_data;
588         }
589
590         $i = 0;
591         foreach ($this->data_colors as $col) {
592             $ndx = $this->SetIndexColor($col);
593             if (!isset($ndx))
594                 return FALSE;
595             $this->ndx_data_colors[$i] = $ndx;
596             $this->ndx_data_dark_colors[$i] = $this->SetIndexDarkColor($col);
597             $i++;
598         }
599
600         // For past compatibility:
601         return $this->SetDataBorderColors($which_border);
602     } // function SetDataColors()
603
604
605     /*!
606      *
607      */
608     function SetDataBorderColors($which_br = NULL)
609     {
610         if (is_null($which_br) && is_array($this->data_border_colors)) {
611             // use already set data_border_colors
612         } else if (! is_array($which_br)) {
613             // Create new array with specified color
614             $this->data_border_colors = ($which_br) ? array($which_br) : array('black');
615         } else {
616             $this->data_border_colors = $which_br;
617         }
618
619         $i = 0;
620         foreach($this->data_border_colors as $col) {
621             $ndx = $this->SetIndexColor($col);
622             if (!isset($ndx))
623                 return FALSE;
624             $this->ndx_data_border_colors[$i] = $ndx;
625             $i++;
626         }
627         return TRUE;
628     } // function SetDataBorderColors()
629
630
631     /*!
632      * Sets the colors for the data error bars.
633      */
634     function SetErrorBarColors($which_err = NULL)
635     {
636         if (is_null($which_err) && is_array($this->error_bar_colors)) {
637             // use already set error_bar_colors
638         } else if (! is_array($which_err)) {
639             $this->error_bar_colors = ($which_err) ? array($which_err) : array('black');
640         } else {
641             $this->error_bar_colors = $which_err;
642         }
643
644         $i = 0;
645         foreach($this->error_bar_colors as $col) {
646             $ndx = $this->SetIndexColor($col);
647             if (!isset($ndx))
648                 return FALSE;
649             $this->ndx_error_bar_colors[$i] = $ndx;
650             $i++;
651         }
652         return TRUE;
653     } // function SetErrorBarColors()
654
655
656     /*!
657      * Sets the default dashed style.
658      *  \param which_style A string specifying order of colored and transparent dots,
659      *         i.e: '4-3' means 4 colored, 3 transparent;
660      *              '2-3-1-2' means 2 colored, 3 transparent, 1 colored, 2 transparent.
661      */
662     function SetDefaultDashedStyle($which_style)
663     {
664         // String: "numcol-numtrans-numcol-numtrans..."
665         $asked = explode('-', $which_style);
666
667         if (count($asked) < 2) {
668             return $this->PrintError("SetDefaultDashedStyle(): Wrong parameter '$which_style'.");
669         }
670
671         // Build the string to be eval()uated later by SetDashedStyle()
672         $this->default_dashed_style = 'array( ';
673
674         $t = 0;
675         foreach($asked as $s) {
676             if ($t % 2 == 0) {
677                 $this->default_dashed_style .= str_repeat('$which_ndxcol,', $s);
678             } else {
679                 $this->default_dashed_style .= str_repeat('IMG_COLOR_TRANSPARENT,', $s);
680             }
681             $t++;
682         }
683         // Remove trailing comma and add closing parenthesis
684         $this->default_dashed_style = substr($this->default_dashed_style, 0, -1);
685         $this->default_dashed_style .= ')';
686
687         return TRUE;
688     }
689
690
691     /*!
692      * Sets the style before drawing a dashed line. Defaults to $this->default_dashed_style
693      *   \param which_ndxcol Color index to be used.
694      */
695     function SetDashedStyle($which_ndxcol)
696     {
697         // See SetDefaultDashedStyle() to understand this.
698         eval ("\$style = $this->default_dashed_style;");
699         return imagesetstyle($this->img, $style);
700     }
701
702
703     /*!
704      * Sets line widths on a per-line basis.
705      */
706     function SetLineWidths($which_lw=NULL)
707     {
708         if (is_null($which_lw)) {
709             // Do nothing, use default value.
710         } else if (is_array($which_lw)) {
711             // Did we get an array with line widths?
712             $this->line_widths = $which_lw;
713         } else {
714             $this->line_widths = array($which_lw);
715         }
716         return TRUE;
717     }
718
719     /*!
720      *
721      */
722     function SetLineStyles($which_ls=NULL)
723     {
724         if (is_null($which_ls)) {
725             // Do nothing, use default value.
726         } else if ( is_array($which_ls)) {
727             // Did we get an array with line styles?
728             $this->line_styles = $which_ls;
729         } else {
730             $this->line_styles = ($which_ls) ? array($which_ls) : array('solid');
731         }
732         return TRUE;
733     }
734
735
736 /////////////////////////////////////////////
737 //////////////                 TEXT and FONTS
738 /////////////////////////////////////////////
739
740
741     /*!
742      * Sets number of pixels between lines of the same text.
743      */
744     function SetLineSpacing($which_spc)
745     {
746         $this->line_spacing = $which_spc;
747         return TRUE;
748     }
749
750
751     /*!
752      * Enables use of TrueType fonts in the graph. Font initialisation methods
753      * depend on this setting, so when called, SetUseTTF() resets the font
754      * settings
755      */
756     function SetUseTTF($which_ttf)
757     {
758         $this->use_ttf = $which_ttf;
759         return $this->SetDefaultFonts();
760     }
761
762     /*!
763      * Sets the directory name to look into for TrueType fonts.
764      */
765     function SetTTFPath($which_path)
766     {
767         // Maybe someone needs really dynamic config. He'll need this:
768         // clearstatcache();
769
770         if (is_dir($which_path) && is_readable($which_path)) {
771             $this->ttf_path = $which_path;
772             return TRUE;
773         }
774         return $this->PrintError("SetTTFPath(): $which_path is not a valid path.");
775     }
776
777     /*!
778      * Sets the default TrueType font and updates all fonts to that.
779      * The default font might be a full path, or relative to the TTFPath,
780      * so let SetFont check that it exists.
781      * Side effect: enable use of TrueType fonts.
782      */
783     function SetDefaultTTFont($which_font)
784     {
785         $this->default_ttfont = $which_font;
786         return $this->SetUseTTF(TRUE);
787     }
788
789     /*!
790      * Sets fonts to their defaults
791      */
792     function SetDefaultFonts()
793     {
794         // TTF:
795         if ($this->use_ttf) {
796             return $this->SetFont('generic', '', 8)
797                 && $this->SetFont('title', '', 14)
798                 && $this->SetFont('legend', '', 8)
799                 && $this->SetFont('x_label', '', 6)
800                 && $this->SetFont('y_label', '', 6)
801                 && $this->SetFont('x_title', '', 10)
802                 && $this->SetFont('y_title', '', 10);
803         }
804         // Fixed GD Fonts:
805         return $this->SetFont('generic', 2)
806             && $this->SetFont('title', 5)
807             && $this->SetFont('legend', 2)
808             && $this->SetFont('x_label', 1)
809             && $this->SetFont('y_label', 1)
810             && $this->SetFont('x_title', 3)
811             && $this->SetFont('y_title', 3);
812     }
813
814     /*!
815      * Sets Fixed/Truetype font parameters.
816      *  \param $which_elem Is the element whose font is to be changed.
817      *         It can be one of 'title', 'legend', 'generic',
818      *         'x_label', 'y_label', x_title' or 'y_title'
819      *  \param $which_font Can be a number (for fixed font sizes) or
820      *         a string with the font pathname or filename when using TTFonts.
821      *         For TTFonts, an empty string means use the default font.
822      *  \param $which_size Point size (TTF only)
823      * Calculates and updates internal height and width variables.
824      */
825     function SetFont($which_elem, $which_font, $which_size = 12)
826     {
827         // TTF:
828         if ($this->use_ttf) {
829             // Empty font name means use the default font.
830             if (empty($which_font))
831                 $which_font = $this->default_ttfont;
832             $path = $which_font;
833
834             // First try the font name directly, if not then try with path.
835             if (!is_file($path) || !is_readable($path)) {
836                 $path = $this->ttf_path . '/' . $which_font;
837                 if (!is_file($path) || !is_readable($path)) {
838                     return $this->PrintError("SetFont(): Can't find TrueType font $which_font");
839                 }
840             }
841
842             switch ($which_elem) {
843             case 'generic':
844                 $this->generic_font['font'] = $path;
845                 $this->generic_font['size'] = $which_size;
846                 break;
847             case 'title':
848                 $this->title_font['font'] = $path;
849                 $this->title_font['size'] = $which_size;
850                 break;
851             case 'legend':
852                 $this->legend_font['font'] = $path;
853                 $this->legend_font['size'] = $which_size;
854                 break;
855             case 'x_label':
856                 $this->x_label_font['font'] = $path;
857                 $this->x_label_font['size'] = $which_size;
858                 break;
859             case 'y_label':
860                 $this->y_label_font['font'] = $path;
861                 $this->y_label_font['size'] = $which_size;
862                 break;
863             case 'x_title':
864                 $this->x_title_font['font'] = $path;
865                 $this->x_title_font['size'] = $which_size;
866                 break;
867             case 'y_title':
868                 $this->y_title_font['font'] = $path;
869                 $this->y_title_font['size'] = $which_size;
870                 break;
871             default:
872                 return $this->PrintError("SetFont(): Unknown element '$which_elem' specified.");
873             }
874             return TRUE;
875
876         }
877
878         // Fixed fonts:
879         if ($which_font > 5 || $which_font < 0) {
880             return $this->PrintError('SetFont(): Non-TTF font size must be 1, 2, 3, 4 or 5');
881         }
882
883         switch ($which_elem) {
884         case 'generic':
885             $this->generic_font['font'] = $which_font;
886             $this->generic_font['height'] = ImageFontHeight($which_font);
887             $this->generic_font['width'] = ImageFontWidth($which_font);
888             break;
889         case 'title':
890            $this->title_font['font'] = $which_font;
891            $this->title_font['height'] = ImageFontHeight($which_font);
892            $this->title_font['width'] = ImageFontWidth($which_font);
893            break;
894         case 'legend':
895             $this->legend_font['font'] = $which_font;
896             $this->legend_font['height'] = ImageFontHeight($which_font);
897             $this->legend_font['width'] = ImageFontWidth($which_font);
898             break;
899         case 'x_label':
900             $this->x_label_font['font'] = $which_font;
901             $this->x_label_font['height'] = ImageFontHeight($which_font);
902             $this->x_label_font['width'] = ImageFontWidth($which_font);
903             break;
904         case 'y_label':
905             $this->y_label_font['font'] = $which_font;
906             $this->y_label_font['height'] = ImageFontHeight($which_font);
907             $this->y_label_font['width'] = ImageFontWidth($which_font);
908             break;
909         case 'x_title':
910             $this->x_title_font['font'] = $which_font;
911             $this->x_title_font['height'] = ImageFontHeight($which_font);
912             $this->x_title_font['width'] = ImageFontWidth($which_font);
913             break;
914         case 'y_title':
915             $this->y_title_font['font'] = $which_font;
916             $this->y_title_font['height'] = ImageFontHeight($which_font);
917             $this->y_title_font['width'] = ImageFontWidth($which_font);
918             break;
919         default:
920             return $this->PrintError("SetFont(): Unknown element '$which_elem' specified.");
921         }
922         return TRUE;
923     }
924
925     /*!
926      * Text drawing and sizing functions:
927      * ProcessText is meant for use only by DrawText and SizeText.
928      *    ProcessText(True, ...)  - Draw a block of text
929      *    ProcessText(False, ...) - Just return ($width, $height) of
930      *       the orthogonal bounding box containing the text.
931      * ProcessText is further split into separate functions for GD and TTF
932      * text, due to the size of the code.
933      *
934      * Horizontal and vertical alignment are relative to the drawing. That is:
935      * vertical text (90 deg) gets centered along Y postition with
936      * v_align = 'center', and adjusted to the right of X position with
937      * h_align = 'right'.  Another way to look at this is to say
938      * that text rotation happens first, then alignment.
939      *
940      * Original multiple lines code submitted by Remi Ricard.
941      * Original vertical code submitted by Marlin Viss.
942      *
943      * Text routines rewritten by ljb to fix alignment and position problems.
944      * Here is my explanation and notes. More information and pictures will be
945      * placed in the PHPlot Reference Manual.
946      *
947      *    + Process TTF text one line at a time, not as a block. (See below)
948      *    + Flipped top vs bottom vertical alignment. The usual interpretation
949      *  is: bottom align means bottom of the text is at the specified Y
950      *  coordinate. For some reason, PHPlot did left/right the correct way,
951      *  but had top/bottom reversed. I fixed it, and left the default valign
952      *  argument as bottom, but the meaning of the default value changed.
953      *
954      *    For GD font text, only single-line text is handled by GD, and the
955      *  basepoint is the upper left corner of each text line.
956      *    For TTF text, multi-line text could be handled by GD, with the text
957      *  basepoint at the lower left corner of the first line of text.
958      *  (Behavior of TTF drawing routines on multi-line text is not documented.)
959      *  But you cannot do left/center/right alignment on each line that way,
960      *  or proper line spacing.
961      *    Therefore, for either text type, we have to break up the text into
962      *  lines and position each line independently.
963      *
964      *    There are 9 alignment modes: Horizontal = left, center, or right, and
965      *  Vertical = top, center, or bottom. Alignment is interpreted relative to
966      *  the image, not as the text is read. This makes sense when you consider
967      *  for example X axis labels. They need to be centered below the marks
968      *  (center, top alignment) regardless of the text angle.
969      *
970      *    GD font text is supported (by libgd) at 0 degrees and 90 degrees only.
971      *  Multi-line or single line text works with any of the 9 alignment modes.
972      *
973      *    TTF text can be at any angle. The 9 aligment modes work for all angles,
974      *  but the results might not be what you expect for multi-line text. See
975      *  the PHPlot Reference Manual for pictures and details. In short, alignment
976      *  applies to the orthogonal (aligned with X and Y axes) bounding box that
977      *  contains the text, and to each line in the multi-line text box. Since
978      *  alignment is relative to the image, 45 degree multi-line text aligns
979      *  differently from 46 degree text.
980      *
981      *    Note that PHPlot allows multi-line text for the 3 titles, and they
982      *  are only drawn at 0 degrees (main and X titles) or 90 degrees (Y title).
983      *  Data labels can also be multi-line, and they can be drawn at any angle.
984      *  -ljb 2007-11-03
985      *
986      */
987
988     /*
989      * ProcessTextGD() - Draw or size GD fixed-font text.
990      * This is intended for use only by ProcessText().
991      *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
992      *    $font_number : GD font number, 1-5.
993      *    $font_width : Fixed character cell width for the font
994      *    $font_height : Fixed character cell height for the font
995      *    $angle : Text angle in degrees. GD only supports 0 and 90. We treat >= 45 as 90, else 0.
996      *    $x, $y : Reference point for the text (ignored if !$draw_it)
997      *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
998      *    $text : The text to draw or size. Put a newline between lines.
999      *    $h_factor : Horizontal alignment factor: 0(left), .5(center), or 1(right) (ignored if !$draw_it)
1000      *    $v_factor : Vertical alignment factor: 0(top), .5(center), or 1(bottom) (ignored if !$draw_it)
1001      * Returns: True, if drawing text, or an array of ($width, $height) if not.
1002      */
1003     function ProcessTextGD($draw_it, $font_number, $font_width, $font_height, $angle, $x, $y, $color,
1004                            $text, $h_factor, $v_factor)
1005     {
1006         # Break up the text into lines, trim whitespace, find longest line.
1007         # Save the lines and length for drawing below.
1008         $longest = 0;
1009         foreach (explode("\n", $text) as $each_line) {
1010             $lines[] = $line = trim($each_line);
1011             $line_lens[] = $line_len = strlen($line);
1012             if ($line_len > $longest) $longest = $line_len;
1013         }
1014         $n_lines = count($lines);
1015         $spacing = $this->line_spacing * ($n_lines - 1); // Total inter-line spacing
1016
1017         # Width, height are based on font size and longest line, line count respectively.
1018         # These are relative to the text angle.
1019         $total_width = $longest * $font_width;
1020         $total_height = $n_lines * $font_height + $spacing;
1021
1022         if (!$draw_it) {
1023             if ($angle < 45) return array($total_width, $total_height);
1024             return array($total_height, $total_width);
1025         }
1026
1027         $interline_step = $font_height + $this->line_spacing; // Line-to-line step
1028
1029         if ($angle >= 45) {
1030             // Vertical text (90 degrees):
1031             // (Remember the alignment convention with vertical text)
1032             // For 90 degree text, alignment factors change like this:
1033             $temp = $v_factor;
1034             $v_factor = $h_factor;
1035             $h_factor = 1 - $temp;
1036
1037             $draw_func = 'ImageStringUp';
1038
1039             // Rotation matrix "R" for 90 degrees (with Y pointing down):
1040             $r00 = 0$r01 = 1;
1041             $r10 = -1; $r11 = 0;
1042
1043         } else {
1044             // Horizontal text (0 degrees):
1045             $draw_func = 'ImageString';
1046
1047             // Rotation matrix "R" for 0 degrees:
1048             $r00 = 1; $r01 = 0;
1049             $r10 = 0; $r11 = 1;
1050         }
1051
1052         // Adjust for vertical alignment (horizontal text) or horizontal aligment (vertical text):
1053         $factor = (int)($total_height * $v_factor);
1054         $xpos = $x - $r01 * $factor;
1055         $ypos = $y - $r11 * $factor;
1056
1057         # Debug callback provides the bounding box:
1058         if ($this->GetCallback('debug_textbox')) {
1059             if ($angle >= 45) {
1060                 $bbox_width  = $total_height;
1061                 $bbox_height = $total_width;
1062                 $px = $xpos;
1063                 $py = $ypos - (1 - $h_factor) * $total_width;
1064             } else {
1065                 $bbox_width  = $total_width;
1066                 $bbox_height = $total_height;
1067                 $px = $xpos - $h_factor * $total_width;
1068                 $py = $ypos;
1069             }
1070             $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
1071         }
1072
1073         for ($i = 0; $i < $n_lines; $i++) {
1074
1075             $line = $lines[$i];
1076             $line_len = $line_lens[$i];
1077
1078             // Adjust for alignment of this line within the text block:
1079             $factor = (int)($line_len * $font_width * $h_factor);
1080             $x = $xpos - $r00 * $factor;
1081             $y = $ypos - $r10 * $factor;
1082
1083             // Call ImageString or ImageStringUp:
1084             $draw_func($this->img, $font_number, $x, $y, $line, $color);
1085
1086             // Step to the next line of text. This is a rotation of (x=0, y=interline_spacing)
1087             $xpos += $r01 * $interline_step;
1088             $ypos += $r11 * $interline_step;
1089         }
1090         return TRUE;
1091     }
1092
1093
1094     /*
1095      * ProcessTextTTF() - Draw or size TTF text.
1096      * This is intended for use only by ProcessText().
1097      *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
1098      *    $font_file : Path or filename to a TTF font file.
1099      *    $font_size : Font size in "points".
1100      *    $angle : Text angle in degrees.
1101      *    $x, $y : Reference point for the text (ignored if !$draw_it)
1102      *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
1103      *    $text : The text to draw or size. Put a newline between lines.
1104      *    $h_factor : Horizontal alignment factor: 0(left), .5(center), or 1(right) (ignored if !$draw_it)
1105      *    $v_factor : Vertical alignment factor: 0(top), .5(center), or 1(bottom) (ignored if !$draw_it)
1106      * Returns: True, if drawing text, or an array of ($width, $height) if not.
1107      */
1108     function ProcessTextTTF($draw_it, $font_file, $font_size, $angle, $x, $y, $color,
1109                             $text, $h_factor, $v_factor)
1110     {
1111         # Break up the text into lines, trim whitespace.
1112         # Calculate the total width and height of the text box at 0 degrees.
1113         # This has to be done line-by-line so the interline-spacing is right.
1114         # Save the trimmed line, and its 0-degree height and width for later
1115         # when the text is drawn.
1116         # Note: Total height = sum of each line height plus inter-line spacing
1117         #   (which is added after the loop). Total width = width of widest line.
1118         $total_height = 0;
1119         $total_width = 0;
1120         foreach (explode("\n", $text) as $each_line) {
1121             $lines[] = $line = trim($each_line);
1122             $bbox = ImageTTFBBox($font_size, 0, $font_file, $line);
1123             $height = $bbox[1] - $bbox[5];
1124             $width = $bbox[2] - $bbox[0];
1125             $total_height += $height;
1126             if ($width > $total_width) $total_width = $width;
1127             $line_widths[] = $width;
1128             $line_heights[] = $height;
1129         }
1130         $n_lines = count($lines);
1131         $total_height += ($n_lines - 1) * $this->line_spacing;
1132
1133         # Calculate the rotation matrix for the text's angle. Remember that GD points Y down,
1134         # so the sin() terms change sign.
1135         $theta = deg2rad($angle);
1136         $cos_t = cos($theta);
1137         $sin_t = sin($theta);
1138         $r00 = $cos_t;    $r01 = $sin_t;
1139         $r10 = -$sin_t;   $r11 = $cos_t;
1140
1141         # Make a bounding box of the right size, with upper left corner at (0,0).
1142         # By convention, the point order is: LL, LR, UR, UL.
1143         # Note this is still working with the text at 0 degrees.
1144         $b[0] = 0;             $b[1] = $total_height;
1145         $b[2] = $total_width$b[3] = $b[1];
1146         $b[4] = $b[2];         $b[5] = 0;
1147         $b[6] = $b[0];         $b[7] = $b[5];
1148
1149         # Rotate the bounding box, then offset to the reference point:
1150         for ($i = 0; $i < 8; $i += 2) {
1151             $x_b = $b[$i];
1152             $y_b = $b[$i+1];
1153             $c[$i]   = $x + $r00 * $x_b + $r01 * $y_b;
1154             $c[$i+1] = $y + $r10 * $x_b + $r11 * $y_b;
1155         }
1156
1157         # Get an orthogonal (aligned with X and Y axes) bounding box around it, by
1158         # finding the min and max X and Y:
1159         $bbox_ref_x = $bbox_max_x = $c[0];
1160         $bbox_ref_y = $bbox_max_y = $c[1];
1161         for ($i = 2; $i < 8; $i += 2) {
1162             $x_b = $c[$i];
1163             if ($x_b < $bbox_ref_x) $bbox_ref_x = $x_b; elseif ($bbox_max_x < $x_b) $bbox_max_x = $x_b;
1164             $y_b = $c[$i+1];
1165             if ($y_b < $bbox_ref_y) $bbox_ref_y = $y_b; elseif ($bbox_max_y < $y_b) $bbox_max_y = $y_b;
1166         }
1167         $bbox_width = $bbox_max_x - $bbox_ref_x;
1168         $bbox_height = $bbox_max_y - $bbox_ref_y;
1169
1170         if (!$draw_it) {
1171             # Return the bounding box, rounded up (so it always contains the text):
1172             return array((int)ceil($bbox_width), (int)ceil($bbox_height));
1173         }
1174
1175         # Calculate the offsets from the supplied reference point to the
1176         # required upper-left corner of the text.
1177         # Start at the reference point at the upper left corner of the bounding
1178         # box (bbox_ref_x, bbox_ref_y) then adjust it for the 9 point alignment.
1179         # h,v_factor are 0,0 for top,left, .5,.5 for center,center, 1,1 for bottom,right.
1180         #    $off_x = $bbox_ref_x + $bbox_width * $h_factor - $x;
1181         #    $off_y = $bbox_ref_y + $bbox_height * $v_factor - $y;
1182         # Then use that offset to calculate back to the supplied reference point x, y
1183         # to get the text base point.
1184         #    $qx = $x - $off_x;
1185         #    $qy = $y - $off_y;
1186         # Reduces to:
1187         $qx = 2 * $x - $bbox_ref_x - $bbox_width * $h_factor;
1188         $qy = 2 * $y - $bbox_ref_y - $bbox_height * $v_factor;
1189
1190         # Check for debug callback. Don't calculate bounding box unless it is wanted.
1191         if ($this->GetCallback('debug_textbox')) {
1192             # Calculate the orthogonal bounding box coordinates for debug testing.
1193
1194             # qx, qy is upper left corner relative to the text.
1195             # Calculate px,py: upper left corner (absolute) of the bounding box.
1196             # There are 4 equation sets for this, depending on the quadrant:
1197             if ($sin_t > 0) {
1198                 if ($cos_t > 0) {
1199                     # Quadrant: 0d - 90d:
1200                     $px = $qx; $py = $qy - $total_width * $sin_t;
1201                 } else {
1202                     # Quadrant: 90d - 180d:
1203                    $px = $qx + $total_width * $cos_t; $py = $qy - $bbox_height;
1204                 }
1205             } else {
1206                 if ($cos_t < 0) {
1207                     # Quadrant: 180d - 270d:
1208                     $px = $qx - $bbox_width; $py = $qy + $total_height * $cos_t;
1209                 } else {
1210                     # Quadrant: 270d - 360d:
1211                     $px = $qx + $total_height * $sin_t; $py = $qy;
1212                 }
1213             }
1214             $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
1215         }
1216
1217         # Since alignment is applied after rotation, which parameter is used
1218         # to control alignment of each line within the text box varies with
1219         # the angle.
1220         #   Angle (degrees):       Line alignment controlled by:
1221         #  -45 < angle <= 45          h_align
1222         #   45 < angle <= 135         reversed v_align
1223         #  135 < angle <= 225         reversed h_align
1224         #  225 < angle <= 315         v_align
1225         if ($cos_t >= $sin_t) {
1226             if ($cos_t >= -$sin_t) $line_align_factor = $h_factor;
1227             else $line_align_factor = $v_factor;
1228         } else {
1229             if ($cos_t >= -$sin_t) $line_align_factor = 1-$v_factor;
1230             else $line_align_factor = 1-$h_factor;
1231         }
1232
1233         # Now we have the start point, spacing and in-line alignment factor.
1234         # We are finally ready to start drawing the text, line by line.
1235         for ($i = 0; $i < $n_lines; $i++) {
1236             $line = $lines[$i];
1237             # These are height and width at 0 degrees, calculated above:
1238             $height = $line_heights[$i];
1239             $width = $line_widths[$i];
1240
1241             # For drawing TTF text, the lower left corner is the base point.
1242             # The adjustment is inside the loop, since lines can have different
1243             # heights. The following also adjusts for horizontal (relative to
1244             # the text) alignment of the current line within the box.
1245             # What is happening is rotation of this vector by the text angle:
1246             #    (x = (total_width - line_width) * factor, y = line_height)
1247
1248             $width_factor = ($total_width - $width) * $line_align_factor;
1249             $rx = $qx + $r00 * $width_factor + $r01 * $height;
1250             $ry = $qy + $r10 * $width_factor + $r11 * $height;
1251
1252             # Finally, draw the text:
1253             ImageTTFText($this->img, $font_size, $angle, $rx, $ry, $color, $font_file, $line);
1254
1255             # Step to position of next line.
1256             # This is a rotation of (x=0,y=text_height+line_height) by $angle:
1257             $interline_step = $this->line_spacing + $height;
1258             $qx += $r01 * $interline_step;
1259             $qy += $r11 * $interline_step;
1260         }
1261         return True;
1262     }
1263
1264     /*
1265      * ProcessText() - Wrapper for ProcessTextTTF() and ProcessTextGD(). See notes above.
1266      * This is intended for use from within PHPlot only, and only by DrawText() and SizeText().
1267      *    $draw_it : True to draw the text, False to just return the orthogonal width and height.
1268      *    $font : PHPlot font array. (Contents depend on use_ttf flag).
1269      *    $angle : Text angle in degrees
1270      *    $x, $y : Reference point for the text (ignored if !$draw_it)
1271      *    $color : GD color index to use for drawing the text (ignored if !$draw_it)
1272      *    $text : The text to draw or size. Put a newline between lines.
1273      *    $halign : Horizontal alignment: left, center, or right (ignored if !$draw_it)
1274      *    $valign : Vertical alignment: top, center, or bottom (ignored if !$draw_it)
1275      *      Note: Alignment is relative to the image, not the text.
1276      * Returns: True, if drawing text, or an array of ($width, $height) if not.
1277      */
1278     function ProcessText($draw_it, $font, $angle, $x, $y, $color, $text, $halign, $valign)
1279     {
1280         # Empty text case:
1281         if ($text === '') {
1282             if ($draw_it) return TRUE;
1283             return array(0, 0);
1284         }
1285
1286         # Calculate width and height offset factors using the alignment args:
1287         if ($valign == 'top') $v_factor = 0;
1288         elseif ($valign == 'center') $v_factor = 0.5;
1289         else $v_factor = 1.0; # 'bottom'
1290         if ($halign == 'left') $h_factor = 0;
1291         elseif ($halign == 'center') $h_factor = 0.5;
1292         else $h_factor = 1.0; # 'right'
1293
1294         if ($this->use_ttf) {
1295             return $this->ProcessTextTTF($draw_it, $font['font'], $font['size'],
1296                                          $angle, $x, $y, $color, $text, $h_factor, $v_factor);
1297         }
1298
1299         return $this->ProcessTextGD($draw_it, $font['font'], $font['width'], $font['height'],
1300                                     $angle, $x, $y, $color, $text, $h_factor, $v_factor);
1301     }
1302
1303
1304     /*
1305      * Draws a block of text. See comments above before ProcessText().
1306      *    $which_font : PHPlot font array. (Contents depend on use_ttf flag).
1307      *    $which_angle : Text angle in degrees
1308      *    $which_xpos, $which_ypos: Reference point for the text
1309      *    $which_color : GD color index to use for drawing the text
1310      *    $which_text :  The text to draw, with newlines (\n) between lines.
1311      *    $which_halign : Horizontal (relative to the image) alignment: left, center, or right.
1312      *    $which_valign : Vertical (relative to the image) alignment: top, center, or bottom.
1313      */
1314     function DrawText($which_font, $which_angle, $which_xpos, $which_ypos, $which_color, $which_text,
1315                       $which_halign = 'left', $which_valign = 'bottom')
1316     {
1317         return $this->ProcessText(True,
1318                            $which_font, $which_angle, $which_xpos, $which_ypos,
1319                            $which_color, $which_text, $which_halign, $which_valign);
1320     }
1321
1322     /*
1323      * Returns the size of block of text. This is the orthogonal width and height of a bounding
1324      * box aligned with the X and Y axes of the text. Only for angle=0 is this the actual
1325      * width and height of the text block, but for any angle it is the amount of space needed
1326      * to contain the text.
1327      *    $which_font : PHPlot font array. (Contents depend on use_ttf flag).
1328      *    $which_angle : Text angle in degrees
1329      *    $which_text :  The text to draw, with newlines (\n) between lines.
1330      * Returns a two element array with: $width, $height.
1331      * This is just a wrapper for ProcessText() - see above.
1332      */
1333     function SizeText($which_font, $which_angle, $which_text)
1334     {
1335         // Color, position, and alignment are not used when calculating the size.
1336         return $this->ProcessText(False,
1337                            $which_font, $which_angle, 0, 0, 1, $which_text, '', '');
1338     }
1339
1340
1341 /////////////////////////////////////////////
1342 ///////////            INPUT / OUTPUT CONTROL
1343 /////////////////////////////////////////////
1344
1345     /*!
1346      * Sets output file format.
1347      */
1348     function SetFileFormat($format)
1349     {
1350         $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__);
1351         if (!$asked) return False;
1352         switch ($asked) {
1353         case 'jpg':
1354             $format_test = IMG_JPG;
1355             break;
1356         case 'png':
1357             $format_test = IMG_PNG;
1358             break;
1359         case 'gif':
1360             $format_test = IMG_GIF;
1361             break;
1362         case 'wbmp':
1363             $format_test = IMG_WBMP;
1364             break;
1365         }
1366         if (!(imagetypes() & $format_test)) {
1367             return $this->PrintError("SetFileFormat(): File format '$format' not supported");
1368         }
1369         $this->file_format = $asked;
1370         return TRUE;
1371     }
1372
1373
1374     /*!
1375      * Selects an input file to be used as graph background and scales or tiles this image
1376      * to fit the sizes.
1377      *  \param input_file string Path to the file to be used (jpeg, png and gif accepted)
1378      *  \param mode       string 'centeredtile', 'tile', 'scale' (the image to the graph's size)
1379      */
1380     function SetBgImage($input_file, $mode='centeredtile')
1381     {
1382         $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
1383         $this->bgimg  = $input_file;
1384         return (boolean)$this->bgmode;
1385     }
1386
1387     /*!
1388      * Selects an input file to be used as plot area background and scales or tiles this image
1389      * to fit the sizes.
1390      *  \param input_file string Path to the file to be used (jpeg, png and gif accepted)
1391      *  \param mode       string 'centeredtile', 'tile', 'scale' (the image to the graph's size)
1392      */
1393     function SetPlotAreaBgImage($input_file, $mode='tile')
1394     {
1395         $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
1396         $this->plotbgimg  = $input_file;
1397         return (boolean)$this->plotbgmode;
1398     }
1399
1400
1401     /*!
1402      * Sets the name of the file to be used as output file.
1403      */
1404     function SetOutputFile($which_output_file)
1405     {
1406         $this->output_file = $which_output_file;
1407         return TRUE;
1408     }
1409
1410     /*!
1411      * Sets the output image as 'inline', that is: no Content-Type headers are sent
1412      * to the browser. Needed if you want to embed the images.
1413      */
1414     function SetIsInline($which_ii)
1415     {
1416         $this->is_inline = (bool)$which_ii;
1417         return TRUE;
1418     }
1419
1420
1421     /*!
1422      * Performs the actual outputting of the generated graph.
1423      */
1424     function PrintImage()
1425     {
1426         // Browser cache stuff submitted by Thiemo Nagel
1427         if ( (! $this->browser_cache) && (! $this->is_inline)) {
1428             header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
1429             header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
1430             header('Cache-Control: no-cache, must-revalidate');
1431             header('Pragma: no-cache');
1432         }
1433
1434         switch($this->file_format) {
1435         case 'png':
1436             if (! $this->is_inline) {
1437                 Header('Content-type: image/png');
1438             }
1439             if ($this->is_inline && $this->output_file != '') {
1440                 ImagePng($this->img, $this->output_file);
1441             } else {
1442                 ImagePng($this->img);
1443             }
1444             break;
1445         case 'jpg':
1446             if (! $this->is_inline) {
1447                 Header('Content-type: image/jpeg');
1448             }
1449             if ($this->is_inline && $this->output_file != '') {
1450                 ImageJPEG($this->img, $this->output_file);
1451             } else {
1452                 ImageJPEG($this->img);
1453             }
1454             break;
1455         case 'gif':
1456             if (! $this->is_inline) {
1457                 Header('Content-type: image/gif');
1458             }
1459             if ($this->is_inline && $this->output_file != '') {
1460                 ImageGIF($this->img, $this->output_file);
1461             } else {
1462                 ImageGIF($this->img);
1463             }
1464
1465             break;
1466         case 'wbmp':        // wireless bitmap, 2 bit.
1467             if (! $this->is_inline) {
1468                 Header('Content-type: image/wbmp');
1469             }
1470             if ($this->is_inline && $this->output_file != '') {
1471                 ImageWBMP($this->img, $this->output_file);
1472             } else {
1473                 ImageWBMP($this->img);
1474             }
1475
1476             break;
1477         default:
1478             return $this->PrintError('PrintImage(): Please select an image type!');
1479         }
1480         return TRUE;
1481     }
1482
1483     /*!
1484      *  Error handling for 'fatal' errors:
1485      *   $error_message       Text of the error message
1486      *  Standard output from PHPlot is expected to be an image file, such as
1487      *  when handling an <img> tag browser request. So it is not permitted to
1488      *  output text to standard output. (You should have display_errors=off)
1489      *  Here is how PHPlot handles fatal errors:
1490      *    + Write the error message into an image, and output the image.
1491      *    + If no image can be output, write nothing and produce an HTTP
1492      *      error header.
1493      *    + Trigger a user-level error containing the error message.
1494      *      If no error handler was set up, the script will log the
1495      *      error and exit with non-zero status.
1496      * 
1497      *  PrintError() and DrawError() are now equivalent. Both are provided for
1498      *  compatibility. (In earlier releases, PrintError sent the message to
1499      *  stdout only, and DrawError sent it in an image only.)
1500      *
1501      *  This function does not return, unless the calling script has set up
1502      *  an error handler which does not exit. In that case, PrintError will
1503      *  return False. But not all of PHPlot will handle this correctly, so
1504      *  it is probably a bad idea for an error handler to return.
1505      */
1506     function PrintError($error_message)
1507     {
1508         // Be sure not to loop recursively, e.g. PrintError - PrintImage - PrintError.
1509         if (isset($this->in_error)) return FALSE;
1510         $this->in_error = TRUE;
1511
1512         // Output an image containing the error message:
1513         if (!empty($this->img)) {
1514             $ypos = $this->image_height/2;
1515             $xpos = $this->image_width/2;
1516             $bgcolor = ImageColorResolve($this->img, 255, 255, 255);
1517             $fgcolor = ImageColorResolve($this->img, 0, 0, 0);
1518             ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, $bgcolor);
1519
1520             // Switch to built-in fonts, in case of error with TrueType fonts:
1521             $this->SetUseTTF(FALSE);
1522
1523             $this->DrawText($this->generic_font, 0, $xpos, $ypos, $fgcolor,
1524                             wordwrap($error_message), 'center', 'center');
1525
1526             $this->PrintImage();
1527         } elseif (! $this->is_inline) {
1528             Header('HTTP/1.0 500 Internal Server Error');
1529         }
1530         trigger_error($error_message, E_USER_ERROR);
1531         unset($this->in_error);
1532         return FALSE# In case error handler returns, rather than doing exit().
1533     }
1534
1535     /*!
1536      * Display an error message and exit.
1537      * This is provided for backward compatibility only. Use PrintError() instead.
1538      *   $error_message       Text of the error message
1539      *   $where_x, $where_y   Ignored, provided for compatibility.
1540      */
1541     function DrawError($error_message, $where_x = NULL, $where_y = NULL)
1542     {
1543         return $this->PrintError($error_message);
1544     }
1545
1546 /////////////////////////////////////////////
1547 ///////////                            LABELS
1548 /////////////////////////////////////////////
1549
1550
1551     /*!
1552      * Sets position for X labels following data points.
1553      */
1554     function SetXDataLabelPos($which_xdlp)
1555     {
1556         $which_xdlp = $this->CheckOption($which_xdlp, 'plotdown, plotup, both, xaxis, all, none',
1557                                          __FUNCTION__);
1558         if (!$which_xdlp) return FALSE;
1559         $this->x_data_label_pos = $which_xdlp;
1560         if ($this->x_data_label_pos != 'none')
1561             $this->x_tick_label_pos = 'none';
1562
1563         return TRUE;
1564     }
1565
1566     /*!
1567      * Sets position for Y labels near data points.
1568      * For past compatability we accept plotleft, ...but pass it to SetTickLabelPos
1569      * eventually to specify how far up/down or left/right of the data point
1570      */
1571     function SetYDataLabelPos($which_ydlp, $which_distance_from_point=0)
1572     {
1573         $which_ydlp = $this->CheckOption($which_ydlp, 'plotleft, plotright, both, yaxis, all, plotin, none',
1574                                           __FUNCTION__);
1575         if (!$which_ydlp) return FALSE;
1576         $this->y_data_label_pos = $which_ydlp;
1577         //This bit in SetYDataLabelPos about plotleft is for those who were
1578         //using this function to set SetYTickLabelPos.
1579         if ( ($which_ydlp == 'plotleft') || ($which_ydlp == 'plotright') ||
1580              ($which_ydlp == 'both') || ($which_ydlp == 'yaxis') ) {
1581
1582             //Call sety_TICK_labelpos instead of sety_DATA_labelpos
1583             $this->SetYTickLabelPos($which_ydlp);
1584
1585         } elseif ($which_ydlp != 'none') {
1586             //right now its plotin or none
1587             $this->y_data_label_pos = 'plotin';
1588         }
1589
1590         return TRUE;
1591     }
1592
1593
1594     /*!
1595      * Sets position for X labels following ticks (hence grid lines)
1596      */
1597     function SetXTickLabelPos($which_xtlp)
1598     {
1599         $which_xtlp = $this->CheckOption($which_xtlp, 'plotdown, plotup, both, xaxis, all, none',
1600                                          __FUNCTION__);
1601         if (!$which_xtlp) return FALSE;
1602         $this->x_tick_label_pos = $which_xtlp;
1603         if ($which_xtlp != 'none')
1604             $this->x_data_label_pos = 'none';
1605
1606         return TRUE;
1607     }
1608
1609     /*!
1610      * Sets position for Y labels following ticks (hence grid lines)
1611      */
1612     function SetYTickLabelPos($which_ytlp)
1613     {
1614         $this->y_tick_label_pos = $this->CheckOption($which_ytlp, 'plotleft, plotright, both, yaxis, all, none',
1615                                                       __FUNCTION__);
1616         return (boolean)$this->y_tick_label_pos;
1617     }
1618
1619     /*!
1620      * Sets type for tick and data labels on X axis.
1621      * \note 'title' type left for backwards compatibility.
1622      */
1623     function SetXLabelType($which_xlt)
1624     {
1625         $this->x_label_type = $this->CheckOption($which_xlt, 'data, time, title', __FUNCTION__);
1626         return (boolean)$this->x_label_type;
1627     }
1628
1629     /*!
1630      * Sets type for tick and data labels on Y axis.
1631      */
1632     function SetYLabelType($which_ylt)
1633     {
1634         $this->y_label_type = $this->CheckOption($which_ylt, 'data, time', __FUNCTION__);
1635         return (boolean)$this->y_label_type;
1636     }
1637
1638     function SetXTimeFormat($which_xtf)
1639     {
1640         $this->x_time_format = $which_xtf;
1641         return TRUE;
1642     }
1643
1644     function SetYTimeFormat($which_ytf)
1645     {
1646         $this->y_time_format = $which_ytf;
1647         return TRUE;
1648     }
1649
1650     function SetNumberFormat($decimal_point, $thousands_sep)
1651     {
1652         $this->decimal_point = $decimal_point;
1653         $this->thousands_sep = $thousands_sep;
1654         return TRUE;
1655     }
1656
1657
1658     function SetXLabelAngle($which_xla)
1659     {
1660         $this->x_label_angle = $which_xla;
1661         return TRUE;
1662     }
1663
1664     function SetYLabelAngle($which_yla)
1665     {
1666         $this->y_label_angle = $which_yla;
1667         return TRUE;
1668     }
1669
1670 /////////////////////////////////////////////
1671 ///////////                              MISC
1672 /////////////////////////////////////////////
1673
1674     /*!
1675      * Checks the valididy of an option.
1676      *   $which_opt  String to check, such as the provided value of a function argument.
1677      *   $which_acc  String of accepted choices. Must be lower-case, and separated
1678      *               by exactly ', ' (comma, space).
1679      *   $which_func Name of the calling function, for error messages.
1680      * Returns the supplied option value, downcased and trimmed, if it is valid.
1681      * Reports an error if the supplied option is not valid.
1682      */
1683     function CheckOption($which_opt, $which_acc, $which_func)
1684     {
1685         $asked = strtolower(trim($which_opt));
1686
1687         # Look for the supplied value in a comma/space separated list.
1688         if (strpos(", $which_acc,", ", $asked,") !== False)
1689             return $asked;
1690
1691         $this->PrintError("$which_func(): '$which_opt' not in available choices: '$which_acc'.");
1692         return NULL;
1693     }
1694
1695
1696     /*!
1697      *  \note Submitted by Thiemo Nagel
1698      */
1699     function SetBrowserCache($which_browser_cache)
1700     {
1701         $this->browser_cache = $which_browser_cache;
1702         return TRUE;
1703     }
1704
1705     /*!
1706      * Whether to show the final image or not
1707      */
1708     function SetPrintImage($which_pi)
1709     {
1710         $this->print_image = $which_pi;
1711         return TRUE;
1712     }
1713
1714     /*!
1715      * Sets the graph's legend. If argument is not an array, appends it to the legend.
1716      */
1717     function SetLegend($which_leg)
1718     {
1719         if (is_array($which_leg)) {             // use array
1720             $this->legend = $which_leg;
1721         } elseif (! is_null($which_leg)) {     // append string
1722             $this->legend[] = $which_leg;
1723         } else {
1724             return $this->PrintError("SetLegend(): argument must not be null.");
1725         }
1726         return TRUE;
1727     }
1728
1729     /*!
1730      * Specifies the position of the legend's upper/leftmost corner,
1731      * in pixel (device) coordinates.
1732      */
1733     function SetLegendPixels($which_x, $which_y)
1734     {
1735         $this->legend_x_pos = $which_x;
1736         $this->legend_y_pos = $which_y;
1737         // Make sure this is unset, meaning we have pixel coords:
1738         unset($this->legend_xy_world);
1739
1740         return TRUE;
1741     }
1742
1743     /*!
1744      * Specifies the position of the legend's upper/leftmost corner,
1745      * in world (data space) coordinates.
1746      * Since the scale factor to convert world to pixel coordinates
1747      * is probably not available, set a flag and defer conversion
1748      * to later.
1749      */
1750     function SetLegendWorld($which_x, $which_y)
1751     {
1752         $this->legend_x_pos = $which_x;
1753         $this->legend_y_pos = $which_y;
1754         $this->legend_xy_world = True;
1755
1756         return TRUE;
1757     }
1758
1759     /*
1760      * Set legend text alignment, color box alignment, and style options
1761      *     $text_align accepts 'left' or 'right'.
1762      *     $colorbox_align accepts 'left', 'right', 'none', or missing/empty. If missing or empty,
1763      *        the same alignment as $text_align is used. Color box is positioned first.
1764      *     $style is reserved for future use.
1765      */
1766     function SetLegendStyle($text_align, $colorbox_align = '', $style = '')
1767     {
1768         $this->legend_text_align = $this->CheckOption($text_align, 'left, right', __FUNCTION__);
1769         if (empty($colorbox_align))
1770             $this->legend_colorbox_align = $this->legend_text_align;
1771         else
1772             $this->legend_colorbox_align = $this->CheckOption($colorbox_align, 'left, right, none', __FUNCTION__);
1773         return ((boolean)$this->legend_text_align && (boolean)$this->legend_colorbox_align);
1774     }
1775
1776     /*!
1777      * Accepted values are: left, sides, none, full
1778      */
1779     function SetPlotBorderType($pbt)
1780     {
1781         $this->plot_border_type = $this->CheckOption($pbt, 'left, sides, none, full', __FUNCTION__);
1782         return (boolean)$this->plot_border_type;
1783     }
1784
1785     /*!
1786      * Accepted values are: raised, plain
1787      */
1788     function SetImageBorderType($sibt)
1789     {
1790         $this->image_border_type = $this->CheckOption($sibt, 'raised, plain', __FUNCTION__);
1791         return (boolean)$this->image_border_type;
1792     }
1793
1794
1795     /*!
1796      * \param dpab bool
1797      */
1798     function SetDrawPlotAreaBackground($dpab)
1799     {
1800         $this->draw_plot_area_background = (bool)$dpab;
1801         return TRUE;
1802     }
1803
1804
1805     /*!
1806      * \param dyg bool
1807      */
1808     function SetDrawYGrid($dyg)
1809     {
1810         $this->draw_y_grid = (bool)$dyg;
1811         return TRUE;
1812     }
1813
1814
1815     /*!
1816      * \param dxg bool
1817      */
1818     function SetDrawXGrid($dxg)
1819     {
1820         $this->draw_x_grid = (bool)$dxg;
1821         return TRUE;
1822     }
1823
1824
1825     /*!
1826      * \param ddg bool
1827      */
1828     function SetDrawDashedGrid($ddg)
1829     {
1830         $this->dashed_grid = (bool)$ddg;
1831         return TRUE;
1832     }
1833
1834
1835     /*!
1836      * \param dxdl bool
1837      */
1838     function SetDrawXDataLabelLines($dxdl)
1839     {
1840         $this->draw_x_data_label_lines = (bool)$dxdl;
1841         return TRUE;
1842     }
1843
1844
1845     /*!
1846      * TODO: draw_y_data_label_lines not implemented.
1847      * \param dydl bool
1848      */
1849     function SetDrawYDataLabelLines($dydl)
1850     {
1851         $this->draw_y_data_label_lines = $dydl;
1852         return TRUE;
1853     }
1854
1855     /*!
1856      * Sets the graph's title.
1857      * TODO: add parameter to choose title placement: left, right, centered=
1858      */
1859     function SetTitle($which_title)
1860     {
1861         $this->title_txt = $which_title;
1862         return TRUE;
1863     }
1864
1865     /*!
1866      * Sets the X axis title and position.
1867      */
1868     function SetXTitle($which_xtitle, $which_xpos = 'plotdown')
1869     {
1870         if ($which_xtitle == '')
1871             $which_xpos = 'none';
1872
1873         $this->x_title_pos = $this->CheckOption($which_xpos, 'plotdown, plotup, both, none', __FUNCTION__);
1874         if (!$this->x_title_pos) return FALSE;
1875         $this->x_title_txt = $which_xtitle;
1876         return TRUE;
1877     }
1878
1879
1880     /*!
1881      * Sets the Y axis title and position.
1882      */
1883     function SetYTitle($which_ytitle, $which_ypos = 'plotleft')
1884     {
1885         if ($which_ytitle == '')
1886             $which_ypos = 'none';
1887
1888         $this->y_title_pos = $this->CheckOption($which_ypos, 'plotleft, plotright, both, none', __FUNCTION__);
1889         if (!$this->y_title_pos) return FALSE;
1890         $this->y_title_txt = $which_ytitle;
1891         return TRUE;
1892     }
1893
1894     /*!
1895      * Sets the size of the drop shadow for bar and pie charts.
1896      * \param which_s int Size in pixels.
1897      */
1898     function SetShading($which_s)
1899     {
1900         $this->shading = (int)$which_s;
1901         return TRUE;
1902     }
1903
1904     function SetPlotType($which_pt)
1905     {
1906         $this->plot_type = $this->CheckOption($which_pt,
1907                            'bars, stackedbars, lines, linepoints, area, points, pie, thinbarline, squared',
1908                             __FUNCTION__);
1909         return (boolean)$this->plot_type;
1910     }
1911
1912     /*!
1913      * Sets the position of Y axis.
1914      * \param pos int Position in world coordinates.
1915      */
1916     function SetYAxisPosition($pos)
1917     {
1918         $this->y_axis_position = (int)$pos;
1919         return TRUE;
1920     }
1921
1922     /*!
1923      * Sets the position of X axis.
1924      * \param pos int Position in world coordinates.
1925      */
1926     function SetXAxisPosition($pos)
1927     {
1928         $this->x_axis_position = (int)$pos;
1929         return TRUE;
1930     }
1931
1932
1933     function SetXScaleType($which_xst)
1934     {
1935         $this->xscale_type = $this->CheckOption($which_xst, 'linear, log', __FUNCTION__);
1936         return (boolean)$this->xscale_type;
1937     }
1938
1939     function SetYScaleType($which_yst)
1940     {
1941         $this->yscale_type = $this->CheckOption($which_yst, 'linear, log'__FUNCTION__);
1942         return (boolean)$this->yscale_type;
1943     }
1944
1945     function SetPrecisionX($which_prec)
1946     {
1947         $this->x_precision = $which_prec;
1948         $this->SetXLabelType('data');
1949         return TRUE;
1950     }
1951
1952     function SetPrecisionY($which_prec)
1953     {
1954         $this->y_precision = $which_prec;
1955         $this->SetYLabelType('data');
1956         return TRUE;
1957     }
1958
1959     function SetErrorBarLineWidth($which_seblw)
1960     {
1961         $this->error_bar_line_width = $which_seblw;
1962         return TRUE;
1963     }
1964
1965     function SetLabelScalePosition($which_blp)
1966     {
1967         //0 to 1
1968         $this->label_scale_position = $which_blp;
1969         return TRUE;
1970     }
1971
1972     function SetErrorBarSize($which_ebs)
1973     {
1974         //in pixels
1975         $this->error_bar_size = $which_ebs;
1976         return TRUE;
1977     }
1978
1979     /*!
1980      * Can be one of: 'tee', 'line'
1981      */
1982     function SetErrorBarShape($which_ebs)
1983     {
1984         $this->error_bar_shape = $this->CheckOption($which_ebs, 'tee, line', __FUNCTION__);
1985         return (boolean)$this->error_bar_shape;
1986     }
1987
1988     /*!
1989      * Sets point shape for each data set via an array.
1990      * Shape can be one of: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot',
1991      * 'diamond', 'triangle', 'trianglemid', or 'none'.
1992      */
1993     function SetPointShapes($which_pt)
1994     {
1995         if (is_null($which_pt)) {
1996             // Do nothing, use default value.
1997         } else if (is_array($which_pt)) {
1998             // Did we get an array with point shapes?
1999             $this->point_shapes = $which_pt;
2000         } else {
2001             // Single value into array
2002             $this->point_shapes = array($which_pt);
2003         }
2004
2005         foreach ($this->point_shapes as $shape)
2006         {
2007             if (!$this->CheckOption($shape,
2008                'halfline, line, plus, cross, rect, circle, dot, diamond, triangle, trianglemid, none',
2009                 __FUNCTION__))
2010                 return FALSE;
2011         }
2012
2013         // Make both point_shapes and point_sizes same size.
2014         $ps = count($this->point_sizes);
2015         $pt = count($this->point_shapes);
2016
2017         if ($ps < $pt) {
2018             $this->pad_array($this->point_sizes, $pt);
2019         } else if ($pt > $ps) {
2020             $this->pad_array($this->point_shapes, $ps);
2021         }
2022         return TRUE;
2023     }
2024
2025     /*!
2026      * Sets the point size for point plots.
2027      * \param ps int Size in pixels.
2028      * \note Test this more extensively
2029      */
2030     function SetPointSizes($which_ps)
2031     {
2032         if (is_null($which_ps)) {
2033             // Do nothing, use default value.
2034         } else if (is_array($which_ps)) {
2035             // Did we get an array with point sizes?
2036             $this->point_sizes = $which_ps;
2037         } else {
2038             // Single value into array
2039             $this->point_sizes = array($which_ps);
2040         }
2041
2042         // Make both point_shapes and point_sizes same size.
2043         $ps = count($this->point_sizes);
2044         $pt = count($this->point_shapes);
2045
2046         if ($ps < $pt) {
2047             $this->pad_array($this->point_sizes, $pt);
2048         } else if ($pt > $ps) {
2049             $this->pad_array($this->point_shapes, $ps);
2050         }
2051
2052         // Fix odd point sizes for point shapes which need it
2053         for ($i = 0; $i < $pt; $i++) {
2054             if ($this->point_shapes[$i] == 'diamond' or $this->point_shapes[$i] == 'triangle') {
2055                 if ($this->point_sizes[$i] % 2 != 0) {
2056                     $this->point_sizes[$i]++;
2057                 }
2058             }
2059         }
2060         return TRUE;
2061     }
2062
2063
2064     /*!
2065      * Tells not to draw lines for missing Y data. Only works with 'lines' and 'squared' plots.
2066      * \param bl bool
2067      */
2068     function SetDrawBrokenLines($bl)
2069     {
2070         $this->draw_broken_lines = (bool)$bl;
2071         return TRUE;
2072     }
2073
2074
2075     /*!
2076      *  text-data: ('label', y1, y2, y3, ...)
2077      *  text-data-single: ('label', data), for some pie charts.
2078      *  data-data: ('label', x, y1, y2, y3, ...)
2079      *  data-data-error: ('label', x1, y1, e1+, e2-, y2, e2+, e2-, y3, e3+, e3-, ...)
2080      */
2081     function SetDataType($which_dt)
2082     {
2083         //The next four lines are for past compatibility.
2084         if ($which_dt == 'text-linear') { $which_dt = 'text-data'; }
2085         elseif ($which_dt == 'linear-linear') { $which_dt = 'data-data'; }
2086         elseif ($which_dt == 'linear-linear-error') { $which_dt = 'data-data-error'; }
2087         elseif ($which_dt == 'text-data-pie') { $which_dt = 'text-data-single'; }
2088
2089
2090         $this->data_type = $this->CheckOption($which_dt, 'text-data, text-data-single, '.
2091                                                          'data-data, data-data-error', __FUNCTION__);
2092         return (boolean)$this->data_type;
2093     }
2094
2095     /*!
2096      * Copy the array passed as data values. We convert to numerical indexes, for its
2097      * use for (or while) loops, which sometimes are faster. Performance improvements
2098      * vary from 28% in DrawLines() to 49% in DrawArea() for plot drawing functions.
2099      */
2100     function SetDataValues(&$which_dv)
2101     {
2102         $this->num_data_rows = count($which_dv);
2103         $this->total_records = 0;               // Perform some useful calculations.
2104         $this->records_per_group = 1;
2105         for ($i = 0, $recs = 0; $i < $this->num_data_rows; $i++) {
2106             // Copy
2107             $this->data[$i] = array_values($which_dv[$i]);   // convert to numerical indices.
2108
2109             // Compute some values
2110             $recs = count($this->data[$i]);
2111             $this->total_records += $recs;
2112
2113             if ($recs > $this->records_per_group)
2114                 $this->records_per_group = $recs;
2115
2116             $this->num_recs[$i] = $recs;
2117         }
2118         return TRUE;
2119     }
2120
2121     /*!
2122      * Pad styles arrays for later use by plot drawing functions:
2123      * This removes the need for $max_data_colors, etc. and $color_index = $color_index % $max_data_colors
2124      * in DrawBars(), DrawLines(), etc.
2125      */
2126     function PadArrays()
2127     {
2128         $this->pad_array($this->line_widths, $this->records_per_group);
2129         $this->pad_array($this->line_styles, $this->records_per_group);
2130
2131         $this->pad_array($this->data_colors, $this->records_per_group);
2132         $this->pad_array($this->data_border_colors, $this->records_per_group);
2133         $this->pad_array($this->error_bar_colors, $this->records_per_group);
2134
2135         $this->SetDataColors();
2136         $this->SetDataBorderColors();
2137         $this->SetErrorBarColors();
2138
2139         return TRUE;
2140     }
2141
2142     /*!
2143      * Pads an array with itself. This only works on 0-based sequential integer indexed arrays.
2144      *  \param arr array  Original array (reference), or scalar.
2145      *  \param size int   Minimum size of the resulting array.
2146      * If $arr is a scalar, it will be converted first to a single element array.
2147      * If $arr has at least $size elements, it is unchanged.
2148      * Otherwise, append elements of $arr to itself until it reaches $size elements.
2149      */
2150     function pad_array(&$arr, $size)
2151     {
2152         if (! is_array($arr)) {
2153             $arr = array($arr);
2154         }
2155         $n = count($arr);
2156         $base = 0;
2157         while ($n < $size) $arr[$n++] = $arr[$base++];
2158     }
2159
2160     /*
2161      * Format a floating-point number.
2162      * This is like PHP's number_format, but uses class variables for separators.
2163      * The separators will default to locale-specific values, if available.
2164      */
2165     function number_format($number, $decimals=0)
2166     {
2167         if (!isset($this->decimal_point) || !isset($this->thousands_sep)) {
2168             // Load locale-specific values from environment:
2169             @setlocale(LC_ALL, '');
2170             // Fetch locale settings:
2171             $locale = @localeconv();
2172             if (!empty($locale) && isset($locale['decimal_point']) &&
2173                     isset($locale['thousands_sep'])) {
2174               $this->decimal_point = $locale['decimal_point'];
2175               $this->thousands_sep = $locale['thousands_sep'];
2176             } else {
2177               // Locale information not available.
2178               $this->decimal_point = '.';
2179               $this->thousands_sep = ',';
2180             }
2181         }
2182         return number_format($number, $decimals, $this->decimal_point, $this->thousands_sep);
2183     }
2184
2185     /*
2186      * Register a callback (hook) function
2187      *   reason - A pre-defined name where a callback can be defined.
2188      *   function - The name of a function to register for callback, or an instance/method
2189      *      pair in an array (see 'callbacks' in the PHP reference manual).
2190      *   arg - Optional argument to supply to the callback function when it is triggered.
2191      *      (Often called "clientData")
2192      * Returns: True if the callback reason is valid, else False.
2193      */
2194     function SetCallback($reason, $function, $arg = NULL)
2195     {
2196         // Use array_key_exists because valid reason keys have NULL as value.
2197         if (!array_key_exists($reason, $this->callbacks))
2198             return False;
2199         $this->callbacks[$reason] = array($function, $arg);
2200         return True;
2201     }
2202
2203     /*
2204      * Return the name of a function registered for callback. See SetCallBack.
2205      *   reason - A pre-defined name where a callback can be defined.
2206      * Returns the current callback function (name or array) for the given reason,
2207      * or False if there was no active callback or the reason is not valid.
2208      * Note you can safely test the return value with a simple 'if', as
2209      * no valid function name evaluates to false.
2210      */
2211     function GetCallback($reason)
2212     {
2213         if (isset($this->callbacks[$reason]))
2214             return $this->callbacks[$reason][0];
2215         return False;
2216     }
2217
2218     /*
2219      * Un-register (remove) a function registered for callback.
2220      *   reason - A pre-defined name where a callback can be defined.
2221      * Returns: True if it was a valid callback reason, else False.
2222      * Note: Returns True whether or not there was a callback registered.
2223      */
2224     function RemoveCallback($reason)
2225     {
2226         if (!array_key_exists($reason, $this->callbacks))
2227             return False;
2228         $this->callbacks[$reason] = NULL;
2229         return True;
2230     }
2231
2232     /*
2233      * Invoke a callback, if one is registered.
2234      * Accepts a variable number of arguments >= 1:
2235      *    reason : A string naming the callback.
2236      *    ... : Zero or more additional arguments to be passed to the
2237      *      callback function, after the passthru argument:
2238      *           callback_function($image, $passthru, ...)
2239      * Returns: nothing.
2240      */
2241     function DoCallback()  # Note: Variable arguments
2242     {
2243         $args = func_get_args();
2244         $reason = $args[0];
2245         if (!isset($this->callbacks[$reason]))
2246             return;
2247         list($function, $args[0]) = $this->callbacks[$reason];
2248         array_unshift($args, $this->img);
2249         # Now args[] looks like: img, passthru, extra args...
2250         call_user_func_array($function, $args);
2251     }
2252
2253
2254 //////////////////////////////////////////////////////////
2255 ///////////         DATA ANALYSIS, SCALING AND TRANSLATION
2256 //////////////////////////////////////////////////////////
2257
2258     /*!
2259      * Analizes data and sets up internal maxima and minima
2260      * Needed by: CalcMargins(), ...
2261      *   Text-Data is different than data-data graphs. For them what
2262      *   we have, instead of X values, is # of records equally spaced on data.
2263      *   text-data is passed in as $data[] = (title, y1, y2, y3, y4, ...)
2264      *   data-data is passed in as $data[] = (title, x, y1, y2, y3, y4, ...)
2265      */
2266     function FindDataLimits()
2267     {
2268         // Set some default min and max values before running through the data
2269         switch ($this->data_type) {
2270         case 'text-data':
2271         case 'text-data-single':
2272             $minx = 0;
2273             $maxx = $this->num_data_rows - 1 ;
2274             $miny = $this->data[0][1];
2275             $maxy = $miny;
2276             break;
2277         default:  //Everything else: data-data, etc, take first value
2278             $minx = $this->data[0][1];
2279             $maxx = $minx;
2280             $miny = $this->data[0][2];
2281             $maxy = $miny;
2282             break;
2283         }
2284
2285         $mine = 0// Maximum value for the -error bar (assume error bars always > 0)
2286         $maxe = 0// Maximum value for the +error bar (assume error bars always > 0)
2287
2288         if ($this->plot_type == 'stackedbars') {
2289             $maxmaxy = $minminy = 0;
2290         } else {
2291             $minminy = $miny;
2292             $maxmaxy = $maxy;
2293         }
2294
2295         // Process each row of data
2296         for ($i=0; $i < $this->num_data_rows; $i++) {
2297             $j = 1; // Skip over the data label for this row.
2298
2299             switch ($this->data_type) {
2300             case 'text-data':           // Data is passed in as (title, y1, y2, y3, ...)
2301             case 'text-data-single':    // This one is for some pie charts
2302                 // $numrecs = @ count($this->data[$i]);
2303                 $maxy = (double)$this->data[$i][$j++];
2304                 if ($this->plot_type == 'stackedbars') {
2305                     $miny = 0;
2306                 } else {
2307                     $miny = $maxy;
2308                 }
2309                 for (; $j < $this->num_recs[$i]; $j++) {
2310                     $val = (double)$this->data[$i][$j];
2311                     if ($this->plot_type == 'stackedbars') {
2312                         $maxy += abs($val);      // only positive values for the moment
2313                     } else {
2314                         if ($val > $maxy) $maxy = $val;
2315                         if ($val < $miny) $miny = $val;
2316                     }
2317                 }
2318                 break;
2319             case 'data-data':           // Data is passed in as (title, x, y, y2, y3, ...)
2320                 // X value:
2321                 $val = (double)$this->data[$i][$j++];
2322                 if ($val > $maxx) $maxx = $val;
2323                 if ($val < $minx) $minx = $val;
2324
2325                 $miny = $maxy = (double)$this->data[$i][$j++];
2326                 // $numrecs = @ count($this->data[$i]);
2327                 for (; $j < $this->num_recs[$i]; $j++) {
2328                     $val = (double)$this->data[$i][$j];
2329                     if ($val > $maxy) $maxy = $val;
2330                     if ($val < $miny) $miny = $val;
2331                 }
2332                 break;
2333             case 'data-data-error':     // Data is passed in as (title, x, y, err+, err-, y2, err2+, err2-,...)
2334                 // X value:
2335                 $val = (double)$this->data[$i][$j++];
2336                 if ($val > $maxx) $maxx = $val;
2337                 if ($val < $minx) $minx = $val;
2338
2339                 $miny = $maxy = (double)$this->data[$i][$j];
2340                 // $numrecs = @ count($this->data[$i]);
2341                 for (; $j < $this->num_recs[$i];) {
2342                     // Y value:
2343                     $val = (double)$this->data[$i][$j++];
2344                     if ($val > $maxy) $maxy = $val;
2345                     if ($val < $miny) $miny = $val;
2346                     // Error +:
2347                     $val = (double)$this->data[$i][$j++];
2348                     if ($val > $maxe) $maxe = $val;
2349                     // Error -:
2350                     $val = (double)$this->data[$i][$j++];
2351                     if ($val > $mine) $mine = $val;
2352                 }
2353                 $maxy = $maxy + $maxe;
2354                 $miny = $miny - $mine;      // assume error bars are always > 0
2355                 break;
2356             default:
2357                 return $this->PrintError("FindDataLimits(): Unknown data type '$this->data_type'.");
2358             }
2359             // Remember this row's min and max Y values:
2360             $this->data_miny[$i] = $miny;
2361             $this->data_maxy[$i] = $maxy;
2362
2363             if ($miny < $minminy) $minminy = $miny;   // global min
2364             if ($maxy > $maxmaxy) $maxmaxy = $maxy;   // global max
2365         }
2366
2367         $this->min_x = $minx;
2368         $this->max_x = $maxx;
2369         $this->min_y = $minminy;
2370         $this->max_y = $maxmaxy;
2371
2372         if ($this->GetCallback('debug_scale')) {
2373             $this->DoCallback('debug_scale', __FUNCTION__, array(
2374                 'min_x' => $this->min_x, 'min_y' => $this->min_y,
2375                 'max_x' => $this->max_x, 'max_y' => $this->max_y));
2376         }
2377         return TRUE;
2378     }
2379
2380     /*!
2381      * Calculates image margins on the fly from title positions and sizes,
2382      * and tick labels positions and sizes.
2383      *
2384      * TODO: add x_tick_label_width and y_tick_label_height and use them to calculate
2385      *       max_x_labels and max_y_labels, to be used by drawing functions to avoid overlapping.
2386      *
2387      * A picture of the locations of elements and spacing can be found in the
2388      * PHPlot Reference Manual.
2389      *
2390      * Calculates the following (class variables unless noted):
2391      *
2392      * Plot area margins (see note below):
2393      *     y_top_margin
2394      *     y_bot_margin
2395      *     x_left_margin
2396      *     x_right_margin
2397      *
2398      * Title sizes (these are now local, not class variables, since they are not used elsewhere):
2399      *     title_height : Height of main title
2400      *     x_title_height : Height of X axis title, 0 if no X title
2401      *     y_title_width : Width of Y axis title, 0 if no Y title
2402      *
2403      * Tick/Data label offsets, relative to plot_area:
2404      *     x_label_top_offset, x_label_bot_offset, x_label_axis_offset
2405      *     y_label_left_offset, y_label_right_offset, y_label_axis_offset
2406      *
2407      * Title offsets, relative to plot area:
2408      *     x_title_top_offset, x_title_bot_offset
2409      *     y_title_left_offset, y_title_left_offset
2410      *
2411      *  Note: The margins are calculated, but not stored, if margins or plot area were
2412      *  set by the user with SetPlotAreaPixels or SetMarginsPixels (as indicated by
2413      *  the plot_margins_set flag). The margin calculation is mixed in with the offset
2414      *  variables, so it doesn't see worth the trouble to separate them.
2415      *
2416      * If the $maximize argument is true, we use the full image size, minus safe_margin
2417      * and main title, for the plot. This is for pie charts which have no axes or X/Y titles.
2418      */
2419     function CalcMargins($maximize)
2420     {
2421         // This is the line-to-line or line-to-text spacing:
2422         $gap = $this->safe_margin;
2423
2424         // Minimum margin on each side. This reduces the chance that the
2425         // right-most tick label (for example) will run off the image edge
2426         // if there are no titles on that side.
2427         $min_margin = 3 * $gap;
2428
2429         // Calculate the title sizes:
2430         list($unused, $title_height) = $this->SizeText($this->title_font, 0, $this->title_txt);
2431         list($unused, $x_title_height) = $this->SizeText($this->x_title_font, 0, $this->x_title_txt);
2432         list($y_title_width, $unused) = $this->SizeText($this->y_title_font, 90, $this->y_title_txt);
2433
2434         // Special case for maximum area usage with no X/Y titles or labels, only main title:
2435         if ($maximize) {
2436             if (!$this->plot_margins_set) {
2437                 $this->x_left_margin = $gap;
2438                 $this->x_right_margin = $gap;
2439                 $this->y_top_margin = $gap;
2440                 $this->y_bot_margin = $gap;
2441                 if ($title_height > 0)
2442                     $this->y_top_margin += $title_height + $gap;
2443             }
2444             return TRUE;
2445         }
2446
2447         // Make local variables for these. (They get used a lot and I'm tired of this, this, this.)
2448         $x_tick_label_pos = $this->x_tick_label_pos;
2449         $x_data_label_pos = $this->x_data_label_pos;
2450         $x_tick_pos       = $this->x_tick_pos;
2451         $x_tick_len       = $this->x_tick_length;
2452         $y_tick_label_pos = $this->y_tick_label_pos;
2453         $y_tick_pos       = $this->y_tick_pos;
2454         $y_tick_len       = $this->y_tick_length;
2455
2456         //////// Calculate maximum X/Y axis label height / width:
2457
2458         // Calculate maximum height of X tick or data labels, depending on which one is on.
2459         // It is possible for both to be on, but this is only valid if all the data labels are empty.
2460         if ($x_data_label_pos == 'none') {
2461             $x_label_height = 0;
2462         } else {
2463             $x_label_height = $this->CalcMaxDataLabelSize();
2464         }
2465         if ($x_tick_label_pos != 'none' &&
2466             ($height = $this->CalcMaxTickLabelSize('x')) > $x_label_height) {
2467                 $x_label_height = $height;
2468         }
2469
2470         // If Y tick labels are on, calculate the width of the widest tick label:
2471         if ($y_tick_label_pos == 'none')
2472             $y_label_width = 0;
2473         else
2474             $y_label_width = $this->CalcMaxTickLabelSize('y');
2475
2476         ///////// Calculate margins:
2477
2478         // For X/Y tick and label position of 'xaxis' or 'yaxis', determine if the axis happens to be
2479         // on an edge of a plot. If it is, we need to account for the margins there.
2480         if ($this->x_axis_position <= $this->plot_min_y)
2481             $x_axis_pos = 'bottom';
2482         elseif ($this->x_axis_position >= $this->plot_max_y)
2483             $x_axis_pos = 'top';
2484         else
2485             $x_axis_pos = 'none';
2486
2487         if ($this->y_axis_position <= $this->plot_min_x)
2488             $y_axis_pos = 'left';
2489         elseif ($this->y_axis_position >= $this->plot_max_x)
2490             $y_axis_pos = 'right';
2491         else
2492             $y_axis_pos = 'none';
2493
2494         // y_top_margin: Main title, Upper X title, X ticks and tick labels, and X data labels:
2495         // y_bot_margin: Lower title, ticks and tick labels, and data labels:
2496         $top_margin = $gap;
2497         $bot_margin = $gap;
2498         $this->x_title_top_offset = $gap;
2499         $this->x_title_bot_offset = $gap;
2500
2501         if ($title_height > 0)
2502             $top_margin += $title_height + $gap;
2503
2504         if ($x_title_height > 0) {
2505             $pos = $this->x_title_pos;
2506             if ($pos == 'plotup' || $pos == 'both')
2507                 $top_margin += $x_title_height + $gap;
2508             if ($pos == 'plotdown' || $pos == 'both')
2509                 $bot_margin += $x_title_height + $gap;
2510         }
2511
2512         if ($x_tick_label_pos == 'plotup' || $x_tick_label_pos == 'both'
2513            || $x_data_label_pos == 'plotup' || $x_data_label_pos == 'both'
2514            || ($x_tick_label_pos == 'xaxis' && $x_axis_pos == 'top')) {
2515             // X Tick or Data Labels above the plot:
2516             $top_margin += $x_label_height + $gap;
2517             $this->x_title_top_offset += $x_label_height + $gap;
2518         }
2519         if ($x_tick_label_pos == 'plotdown' || $x_tick_label_pos == 'both'
2520            || $x_data_label_pos == 'plotdown' || $x_data_label_pos == 'both'
2521            || ($x_tick_label_pos == 'xaxis' && $x_axis_pos == 'bottom')) {
2522             // X Tick or Data Labels below the plot:
2523             $bot_margin += $x_label_height + $gap;
2524             $this->x_title_bot_offset += $x_label_height + $gap;
2525         }
2526         if ($x_tick_pos == 'plotup' || $x_tick_pos == 'both'
2527            || ($x_tick_pos == 'xaxis' && $x_axis_pos == 'top')) {
2528             // X Ticks above the plot:
2529             $top_margin += $x_tick_len;
2530             $this->x_label_top_offset = $x_tick_len + $gap;
2531             $this->x_title_top_offset += $x_tick_len;
2532         } else {
2533             // No X Ticks above the plot:
2534             $this->x_label_top_offset = $gap;
2535         }
2536         if ($x_tick_pos == 'plotdown' || $x_tick_pos == 'both'
2537            || ($x_tick_pos == 'xaxis' && $x_axis_pos == 'bottom')) {
2538             // X Ticks below the plot:
2539             $bot_margin += $x_tick_len;
2540             $this->x_label_bot_offset = $x_tick_len + $gap;
2541             $this->x_title_bot_offset += $x_tick_len;
2542         } else {
2543             // No X Ticks below the plot:
2544             $this->x_label_bot_offset = $gap;
2545         }
2546         // Label offsets for on-axis ticks:
2547         if ($x_tick_pos == 'xaxis') {
2548             $this->x_label_axis_offset = $x_tick_len + $gap;
2549         } else {
2550             $this->x_label_axis_offset = $gap;
2551         }
2552
2553         // x_left_margin: Left Y title, Y ticks and tick labels:
2554         // x_right_margin: Right Y title, Y ticks and tick labels:
2555         $left_margin = $gap;
2556         $right_margin = $gap;
2557         $this->y_title_left_offset = $gap;
2558         $this->y_title_right_offset = $gap;
2559
2560         if ($y_title_width > 0) {
2561             $pos = $this->y_title_pos;
2562             if ($pos == 'plotleft' || $pos == 'both')
2563                 $left_margin += $y_title_width + $gap;
2564             if ($pos == 'plotright' || $pos == 'both')
2565                 $right_margin += $y_title_width + $gap;
2566         }
2567
2568         if ($y_tick_label_pos == 'plotleft' || $y_tick_label_pos == 'both'
2569            || ($y_tick_label_pos == 'yaxis' && $y_axis_pos == 'left')) {
2570             // Y Tick Labels left of plot:
2571             $left_margin += $y_label_width + $gap;
2572             $this->y_title_left_offset += $y_label_width + $gap;
2573         }
2574         if ($y_tick_label_pos == 'plotright' || $y_tick_label_pos == 'both'
2575            || ($y_tick_label_pos == 'yaxis' && $y_axis_pos == 'right')) {
2576             // Y Tick Labels right of plot:
2577             $right_margin += $y_label_width + $gap;
2578             $this->y_title_right_offset += $y_label_width + $gap;
2579         }
2580         if ($y_tick_pos == 'plotleft' || $y_tick_pos == 'both'
2581            || ($y_tick_pos == 'yaxis' && $y_axis_pos == 'left')) {
2582             // Y Ticks left of plot:
2583             $left_margin += $y_tick_len;
2584             $this->y_label_left_offset = $y_tick_len + $gap;
2585             $this->y_title_left_offset += $y_tick_len;
2586         } else {
2587             // No Y Ticks left of plot:
2588             $this->y_label_left_offset = $gap;
2589         }
2590         if ($y_tick_pos == 'plotright' || $y_tick_pos == 'both'
2591            || ($y_tick_pos == 'yaxis' && $y_axis_pos == 'right')) {
2592             // Y Ticks right of plot:
2593             $right_margin += $y_tick_len;
2594             $this->y_label_right_offset = $y_tick_len + $gap;
2595             $this->y_title_right_offset += $y_tick_len;
2596         } else {
2597             // No Y Ticks right of plot:
2598             $this->y_label_right_offset = $gap;
2599         }
2600
2601         // Label offsets for on-axis ticks:
2602         if ($x_tick_pos == 'yaxis') {
2603             $this->y_label_axis_offset = $y_tick_len + $gap;
2604         } else {
2605             $this->y_label_axis_offset = $gap;
2606         }
2607
2608         // Apply the minimum margins and store in the object.
2609         // Do not set margins if they are user-defined (see note at top of function).
2610         if (!$this->plot_margins_set) {
2611             $this->y_top_margin = max($min_margin, $top_margin);
2612             $this->y_bot_margin = max($min_margin, $bot_margin);
2613             $this->x_left_margin = max($min_margin, $left_margin);
2614             $this->x_right_margin = max($min_margin, $right_margin);
2615         }
2616  
2617         if ($this->GetCallback('debug_scale')) {
2618             // (Too bad compact() doesn't work on class member variables...)
2619             $this->DoCallback('debug_scale', __FUNCTION__, array(
2620                 'x_label_height' => $x_label_height,
2621                 'y_label_width' => $y_label_width,
2622                 'x_tick_len' => $x_tick_len,
2623                 'y_tick_len' => $y_tick_len,
2624                 'x_left_margin' => $this->x_left_margin,
2625                 'x_right_margin' => $this->x_right_margin,
2626                 'y_top_margin' => $this->y_top_margin,
2627                 'y_bot_margin' => $this->y_bot_margin,
2628                 'x_label_top_offset' => $this->x_label_top_offset,
2629                 'x_label_bot_offset' => $this->x_label_bot_offset,
2630                 'y_label_left_offset' => $this->y_label_left_offset,
2631                 'y_label_right_offset' => $this->y_label_right_offset,
2632                 'x_title_top_offset' => $this->x_title_top_offset,
2633                 'x_title_bot_offset' => $this->x_title_bot_offset,
2634                 'y_title_left_offset' => $this->y_title_left_offset,
2635                 'y_title_right_offset' => $this->y_title_right_offset));
2636         }
2637
2638         return TRUE;
2639     }
2640
2641     /*
2642      * Calculate the plot area (device coordinates) from the margins.
2643      * (This used to be part of SetPlotAreaPixels.)
2644      * The margins might come from SetMarginsPixels, SetPlotAreaPixels,
2645      * or CalcMargins.
2646      */
2647     function CalcPlotAreaPixels()
2648     {
2649         $this->plot_area = array($this->x_left_margin, $this->y_top_margin,
2650                                  $this->image_width - $this->x_right_margin,
2651                                  $this->image_height - $this->y_bot_margin);
2652         $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0];
2653         $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1];
2654
2655         $this->DoCallback('debug_scale', __FUNCTION__, $this->plot_area);
2656         return TRUE;
2657     }
2658
2659
2660     /*!
2661      * Set the margins in pixels (left, right, top, bottom)
2662      * This determines the plot area, equivalent to SetPlotAreaPixels().
2663      * Deferred calculations now occur in CalcPlotAreaPixels().
2664      */
2665     function SetMarginsPixels($which_lm, $which_rm, $which_tm, $which_bm)
2666     {
2667         $this->x_left_margin = $which_lm;
2668         $this->x_right_margin = $which_rm;
2669         $this->y_top_margin = $which_tm;
2670         $this->y_bot_margin = $which_bm;
2671
2672         $this->plot_margins_set = TRUE;
2673
2674         return TRUE;
2675     }
2676
2677     /*!
2678      * Sets the limits for the plot area.
2679      * This stores the margins, not the area. That may seem odd, but
2680      * the idea is to make SetPlotAreaPixels and SetMarginsPixels two
2681      * ways to accomplish the same thing, and the deferred calculations
2682      * in CalcMargins and CalcPlotAreaPixels don't need to know which
2683      * was used.
2684      *   (x1, y1) - Upper left corner of the plot area
2685      *   (x2, y2) - Lower right corner of the plot area
2686      */
2687     function SetPlotAreaPixels($x1, $y1, $x2, $y2)
2688     {
2689         $this->x_left_margin = $x1;
2690         $this->x_right_margin = $this->image_width - $x2;
2691         $this->y_top_margin = $y1;
2692         $this->y_bot_margin = $this->image_height - $y2;
2693
2694         $this->plot_margins_set = TRUE;
2695
2696         return TRUE;
2697     }
2698
2699     /*
2700      * Calculate the World Coordinate limits of the plot area.
2701      * This goes with SetPlotAreaWorld, but the calculations are
2702      * deferred until the graph is being drawn.
2703      * Uses: plot_min_x, plot_max_x, plot_min_y, plot_max_y
2704      * which can be user-supplied or NULL to auto-calculate.
2705      * Pre-requisites: FindDataLimits()
2706      */
2707     function CalcPlotAreaWorld()
2708     {
2709         if (isset($this->plot_min_x) && $this->plot_min_x !== '')
2710             $xmin = $this->plot_min_x;
2711         elseif ($this->data_type == 'text-data'// Valid for data without X values only.
2712             $xmin = 0;
2713         else
2714             $xmin = $this->min_x;
2715
2716         if (isset($this->plot_max_x) && $this->plot_max_x !== '')
2717             $xmax = $this->plot_max_x;
2718         elseif ($this->data_type == 'text-data'// Valid for data without X values only.
2719             $xmax = $this->max_x + 1;
2720         else
2721             $xmax = $this->max_x;
2722
2723         // Leave room above and below the highest and lowest data points.
2724
2725         if (!isset($this->plot_min_y) || $this->plot_min_y === '')
2726             $ymin = floor($this->min_y - abs($this->min_y) * 0.1);
2727         else
2728             $ymin = $this->plot_min_y;
2729
2730         if (!isset($this->plot_max_y) || $this->plot_max_y === '')
2731             $ymax = ceil($this->max_y + abs($this->max_y) * 0.1);
2732         else
2733             $ymax = $this->plot_max_y;
2734
2735         // Error checking
2736
2737         if ($ymin == $ymax)     // Minimum height
2738             $ymax += 1;
2739
2740         if ($this->yscale_type == 'log') {
2741             if ($ymin <= 0) {
2742                 $ymin = 1;
2743             }
2744             if ($ymax <= 0) {
2745                 // Note: Error messages reference the user function, not this function.
2746                 return $this->PrintError('SetPlotAreaWorld(): Log plots need data greater than 0');
2747             }
2748         }
2749
2750         if ($ymax <= $ymin) {
2751             return $this->PrintError('SetPlotAreaWorld(): Error in data - max not greater than min');
2752         }
2753
2754         $this->plot_min_x = $xmin;
2755         $this->plot_max_x = $xmax;
2756         $this->plot_min_y = $ymin;
2757         $this->plot_max_y = $ymax;
2758         if ($this->GetCallback('debug_scale')) {
2759             $this->DoCallback('debug_scale', __FUNCTION__, array(
2760                 'plot_min_x' => $this->plot_min_x, 'plot_min_y' => $this->plot_min_y,
2761                 'plot_max_x' => $this->plot_max_x, 'plot_max_y' => $this->plot_max_y));
2762         }
2763
2764         return TRUE;
2765     }
2766
2767     /*!
2768      * Stores the desired World Coordinate range of the plot.
2769      * The user calls this to force one or more of the range limits to
2770      * specific values. Anything not set will be calculated in CalcPlotAreaWorld().
2771      */
2772     function SetPlotAreaWorld($xmin=NULL, $ymin=NULL, $xmax=NULL, $ymax=NULL)
2773     {
2774         $this->plot_min_x = $xmin;
2775         $this->plot_max_x = $xmax;
2776         $this->plot_min_y = $ymin;
2777         $this->plot_max_y = $ymax;
2778         return TRUE;
2779     } //function SetPlotAreaWorld
2780
2781
2782     /*!
2783      * For bar plots, which have equally spaced x variables.
2784      */
2785     function CalcBarWidths()
2786     {
2787         // group_width is the width of a group, including padding
2788         $group_width = $this->plot_area_width / $this->num_data_rows;
2789
2790         // Actual number of bar spaces in the group. This includes the drawn bars, and
2791         // 'bar_extra_space'-worth of extra bars.
2792         // Note that 'records_per_group' includes the label, so subtract one to get
2793         // the number of points in the group. 'stackedbars' have 1 bar space per group.
2794         if ($this->plot_type == 'stackedbars') {
2795           $num_spots = 1 + $this->bar_extra_space;
2796         } else {
2797           $num_spots = $this->records_per_group - 1 + $this->bar_extra_space;
2798         }
2799
2800         // record_bar_width is the width of each bar's allocated area.
2801         // If bar_width_adjust=1 this is the width of the bar, otherwise
2802         // the bar is centered inside record_bar_width.
2803         // The equation is:
2804         //   group_frac_width * group_width = record_bar_width * num_spots
2805         $this->record_bar_width = $this->group_frac_width * $group_width / $num_spots;
2806
2807         // Note that the extra space due to group_frac_width and bar_extra_space will be
2808         // evenly divided on each side of the group: the drawn bars are centered in the group.
2809
2810         // Within each bar's allocated space, if bar_width_adjust=1 the bar fills the
2811         // space, otherwise it is centered.
2812         // This is the actual drawn bar width:
2813         $this->actual_bar_width = $this->record_bar_width * $this->bar_width_adjust;
2814         // This is the gap on each side of the bar (0 if bar_width_adjust=1):
2815         $this->bar_adjust_gap = ($this->record_bar_width - $this->actual_bar_width) / 2;
2816
2817         return TRUE;
2818     }
2819
2820     /*
2821      * Calculate X and Y Axis Positions, world coordinates.
2822      * This needs the min/max x/y range set by CalcPlotAreaWorld.
2823      * It adjusts or sets x_axis_position and y_axis_position per the data.
2824      * Empty string means the values need to be calculated; otherwise they
2825      * are supplied but need to be validated against the World area.
2826      *
2827      * Note: This used to be in CalcTranslation, but CalcMargins needs it too.
2828      * This does not calculate the pixel values of the axes. That happens in
2829      * CalcTranslation, after scaling is set up (which has to happen after
2830      * margins are set up).
2831      */
2832     function CalcAxisPositions()
2833     {
2834         // If no user-provided Y axis position, default to axis on left side.
2835         // Otherwise, make sure user-provided position is inside the plot area.
2836         if ($this->y_axis_position === '')
2837             $this->y_axis_position = $this->plot_min_x;
2838         else
2839             $this->y_axis_position = min(max($this->plot_min_x, $this->y_axis_position), $this->plot_max_x);
2840
2841         // If no user-provided X axis position, default to axis at Y=0 (if in range), or min_y
2842         //   if the range does not include 0, or 1 for log plots.
2843         // Otherwise, make sure user-provided position is inside the plot area.
2844         if ($this->x_axis_position === '') {
2845             if ($this->yscale_type == 'log')
2846                 $this->x_axis_position = 1;
2847             elseif ($this->plot_min_y <= 0 && 0 <= $this->plot_max_y)
2848                 $this->x_axis_position = 0;
2849              else
2850                 $this->x_axis_position = $this->plot_min_y;
2851         } else
2852             $this->x_axis_position = min(max($this->plot_min_y, $this->x_axis_position), $this->plot_max_y);
2853
2854         if ($this->GetCallback('debug_scale')) {
2855             $this->DoCallback('debug_scale', __FUNCTION__, array(
2856                 'x_axis_position' => $this->x_axis_position,
2857                 'y_axis_position' => $this->y_axis_position));
2858         }
2859
2860         return TRUE;
2861     }
2862
2863     /*!
2864      * Calculates scaling stuff...
2865      */
2866     function CalcTranslation()
2867     {
2868         if ($this->plot_max_x - $this->plot_min_x == 0) { // Check for div by 0
2869             $this->xscale = 0;
2870         } else {
2871             if ($this->xscale_type == 'log') {
2872                 $this->xscale = ($this->plot_area_width)/(log10($this->plot_max_x) - log10($this->plot_min_x));
2873             } else {
2874                 $this->xscale = ($this->plot_area_width)/($this->plot_max_x - $this->plot_min_x);
2875             }
2876         }
2877
2878         if ($this->plot_max_y - $this->plot_min_y == 0) { // Check for div by 0
2879             $this->yscale = 0;
2880         } else {
2881             if ($this->yscale_type == 'log') {
2882                 $this->yscale = ($this->plot_area_height)/(log10($this->plot_max_y) - log10($this->plot_min_y));
2883             } else {
2884                 $this->yscale = ($this->plot_area_height)/($this->plot_max_y - $this->plot_min_y);
2885             }
2886         }
2887         // GD defines x = 0 at left and y = 0 at TOP so -/+ respectively
2888         if ($this->xscale_type == 'log') {
2889             $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * log10($this->plot_min_x) );
2890         } else {
2891             $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * $this->plot_min_x);
2892         }
2893         if ($this->yscale_type == 'log') {
2894             $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * log10($this->plot_min_y));
2895         } else {
2896             $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * $this->plot_min_y);
2897         }
2898
2899         // Convert axis positions to device coordinates:
2900         $this->y_axis_x_pixels = $this->xtr($this->y_axis_position);
2901         $this->x_axis_y_pixels = $this->ytr($this->x_axis_position);
2902
2903         if ($this->GetCallback('debug_scale')) {
2904             $this->DoCallback('debug_scale', __FUNCTION__, array(
2905                 'xscale' => $this->xscale, 'yscale' => $this->yscale,
2906                 'plot_origin_x' => $this->plot_origin_x, 'plot_origin_y' => $this->plot_origin_y,
2907                 'y_axis_x_pixels' => $this->y_axis_x_pixels,
2908                 'x_axis_y_pixels' => $this->x_axis_y_pixels));
2909         }
2910
2911         return TRUE;
2912     } // function CalcTranslation()
2913
2914
2915     /*!
2916      * Translate X world coordinate into pixel coordinate
2917      * See CalcTranslation() for calculation of xscale.
2918      */
2919     function xtr($x_world)
2920     {
2921         if ($this->xscale_type == 'log') {
2922             $x_pixels = $this->plot_origin_x + log10($x_world) * $this->xscale ;
2923         } else {
2924             $x_pixels = $this->plot_origin_x + $x_world * $this->xscale ;
2925         }
2926         return round($x_pixels);
2927     }
2928
2929
2930     /*!
2931      * Translate Y world coordinate into pixel coordinate.
2932      * See CalcTranslation() for calculation of yscale.
2933      */
2934     function ytr($y_world)
2935     {
2936         if ($this->yscale_type == 'log') {
2937             //minus because GD defines y = 0 at top. doh!
2938             $y_pixels $this->plot_origin_y - log10($y_world) * $this->yscale ;
2939         } else {
2940             $y_pixels $this->plot_origin_y - $y_world * $this->yscale ;
2941         }
2942         return round($y_pixels);
2943     }
2944
2945     /*
2946      * Calculate tick parameters: Start, end, and delta values. This is used
2947      * by both DrawXTicks() and DrawYTicks().
2948      * This currently uses the same simplistic method previously used by
2949      * PHPlot (basically just range/10), but splitting this out into its
2950      * own function is the first step in replacing the method.
2951      * This is also used by CalcMaxTickSize() for CalcMargins().
2952      *
2953      *   $which : 'x' or 'y' : Which tick parameters to calculate
2954      *
2955      * Returns an array of 3 elements: tick_start, tick_end, tick_step
2956      */
2957     function CalcTicks($which)
2958     {
2959         if ($which == 'x') {
2960             $num_ticks = $this->num_x_ticks;
2961             $tick_inc = $this->x_tick_inc;
2962             $data_max = $this->plot_max_x;
2963             $data_min = $this->plot_min_x;
2964             $skip_lo = $this->skip_left_tick;
2965             $skip_hi = $this->skip_right_tick;
2966         } elseif ($which == 'y') {
2967             $num_ticks = $this->num_y_ticks;
2968             $tick_inc = $this->y_tick_inc;
2969             $data_max = $this->plot_max_y;
2970             $data_min = $this->plot_min_y;
2971             $skip_lo = $this->skip_bottom_tick;
2972             $skip_hi = $this->skip_top_tick;
2973         } else {
2974           return $this->PrintError("CalcTicks: Invalid usage ($which)");
2975         }
2976
2977         if (!empty($tick_inc)) {
2978             $tick_step = $tick_inc;
2979         } elseif (!empty($num_ticks)) {
2980             $tick_step = ($data_max - $data_min) / $num_ticks;
2981         } else {
2982             $tick_step = ($data_max - $data_min) / 10;
2983         }
2984
2985         // NOTE: When working with floats, because of approximations when adding $tick_step,
2986         // the value may not quite reach the end, or may exceed it very slightly.
2987         // So apply a "fudge" factor.
2988         $tick_start = (double)$data_min;
2989         $tick_end = (double)$data_max + ($data_max - $data_min) / 10000.0;
2990
2991         if ($skip_lo)
2992             $tick_start += $tick_step;
2993
2994         if ($skip_hi)
2995             $tick_end -= $tick_step;
2996
2997         return array($tick_start, $tick_end, $tick_step);
2998     }
2999
3000     /*
3001      * Calculate the size of the biggest tick label. This is used by CalcMargins().
3002      * For 'x' ticks, it returns the height . For 'y' ticks, it returns the width.
3003      * This means height along Y, or width along X - not relative to the text angle.
3004      * That is what we need to calculate the needed margin space.
3005      * (Previous versions of PHPlot estimated this, using the maximum X or Y value,
3006      * or maybe the longest string. That doesn't work. -10 is longer than 9, etc.
3007      * So this gets the actual size of each label, slow as that may be.
3008      */
3009     function CalcMaxTickLabelSize($which)
3010     {
3011         list($tick_val, $tick_end, $tick_step) = $this->CalcTicks($which);
3012
3013         if ($which == 'x') {
3014           $font = $this->x_label_font;
3015           $angle = $this->x_label_angle;
3016         } elseif ($which == 'y') {
3017           $font = $this->y_label_font;
3018           $angle = $this->y_label_angle;
3019         } else {
3020           return $this->PrintError("CalcMaxTickLabelSize: Invalid usage ($which)");
3021         }
3022
3023         $max_width = 0;
3024         $max_height = 0;
3025
3026         // Loop over ticks, same as DrawXTicks and DrawYTicks:
3027         while ($tick_val <= $tick_end) {
3028             $tick_label = $this->FormatLabel($which, $tick_val);
3029             list($width, $height) = $this->SizeText($font, $angle, $tick_label);
3030             if ($width > $max_width) $max_width = $width;
3031             if ($height > $max_height) $max_height = $height;
3032             $tick_val += $tick_step;
3033         }
3034         if ($this->GetCallback('debug_scale')) {
3035             $this->DoCallback('debug_scale', __FUNCTION__, array(
3036                 'which' => $which, 'height' => $max_height, 'width' => $max_width));
3037         }
3038
3039         if ($which == 'x')
3040             return $max_height;
3041         return $max_width;
3042     }
3043
3044     /*
3045      * Calculate the size of the biggest X data label. This is used by CalcMargins().
3046      * Returns the height along Y axis of the biggest X data label.
3047      * (This calculates width and height, but only height is used at present.)
3048      */
3049     function CalcMaxDataLabelSize()
3050     {
3051         $font = $this->x_label_font;
3052         $angle = $this->x_label_angle;
3053         $max_width = 0;
3054         $max_height = 0;
3055
3056         // Loop over all data labels and find the biggest:
3057         for ($i = 0; $i < $this->num_data_rows; $i++) {
3058             $label = $this->FormatLabel('x', $this->data[$i][0]);
3059             list($width, $height) = $this->SizeText($font, $angle, $label);
3060             if ($width > $max_width) $max_width = $width;
3061             if ($height > $max_height) $max_height = $height;
3062         }
3063         if ($this->GetCallback('debug_scale')) {
3064             $this->DoCallback('debug_scale', __FUNCTION__, array(
3065                 'height' => $max_height, 'width' => $max_width));
3066         }
3067
3068         return $max_height;
3069     }
3070
3071     /*!
3072      * Formats a tick or data label.
3073      *    which_pos - 'x' or 'y', selects formatting controls.
3074      *    which_lab - String to format as a label.
3075      * \note Time formatting suggested by Marlin Viss
3076      */
3077     function FormatLabel($which_pos, $which_lab)
3078     {
3079         $lab = $which_lab;   // Default to no formatting.
3080         if ($lab !== '') {   // Don't format empty strings (especially as time or numbers)
3081             if ($which_pos == 'x') {
3082                 switch ($this->x_label_type) {
3083                 case 'title'// Note: This is obsolete
3084                     $lab = @ $this->data[$which_lab][0];
3085                     break;
3086                 case 'data':
3087                     $lab = $this->number_format($which_lab, $this->x_precision).$this->data_units_text;
3088                     break;
3089                 case 'time':
3090                     $lab = strftime($this->x_time_format, $which_lab);
3091                     break;
3092                 }
3093             } elseif ($which_pos == 'y') {
3094                 switch ($this->y_label_type) {
3095                 case 'data':
3096                     $lab = $this->number_format($which_lab, $this->y_precision).$this->data_units_text;
3097                     break;
3098                 case 'time':
3099                     $lab = strftime($this->y_time_format, $which_lab);
3100                     break;
3101                 }
3102             }
3103         }
3104         return $lab;
3105     } //function FormatLabel
3106
3107
3108
3109 /////////////////////////////////////////////
3110 ///////////////                         TICKS
3111 /////////////////////////////////////////////
3112
3113     /*!
3114      * Use either this or SetNumXTicks() to set where to place x tick marks
3115      */
3116     function SetXTickIncrement($which_ti='')
3117     {
3118         $this->x_tick_inc = $which_ti;
3119         if (!empty($which_ti)) {
3120             $this->num_x_ticks = ''; //either use num_x_ticks or x_tick_inc, not both
3121         }
3122         return TRUE;
3123     }
3124
3125     /*!
3126      * Use either this or SetNumYTicks() to set where to place y tick marks
3127      */
3128     function SetYTickIncrement($which_ti='')
3129     {
3130         $this->y_tick_inc = $which_ti;
3131         if (!empty($which_ti)) {
3132             $this->num_y_ticks = ''; //either use num_y_ticks or y_tick_inc, not both
3133         }
3134         return TRUE;
3135     }
3136
3137
3138     function SetNumXTicks($which_nt)
3139     {
3140         $this->num_x_ticks = $which_nt;
3141         if (!empty($which_nt)) {
3142             $this->x_tick_inc = ''//either use num_x_ticks or x_tick_inc, not both
3143         }
3144         return TRUE;
3145     }
3146
3147     function SetNumYTicks($which_nt)
3148     {
3149         $this->num_y_ticks = $which_nt;
3150         if (!empty($which_nt)) {
3151             $this->y_tick_inc = ''//either use num_y_ticks or y_tick_inc, not both
3152         }
3153         return TRUE;
3154     }
3155
3156     /*!
3157      *
3158      */
3159     function SetYTickPos($which_tp)
3160     {
3161         $this->y_tick_pos = $this->CheckOption($which_tp, 'plotleft, plotright, both, yaxis, none', __FUNCTION__);
3162         return (boolean)$this->y_tick_pos;
3163     }
3164     /*!
3165      *
3166      */
3167     function SetXTickPos($which_tp)
3168     {
3169         $this->x_tick_pos = $this->CheckOption($which_tp, 'plotdown, plotup, both, xaxis, none', __FUNCTION__);
3170         return (boolean)$this->x_tick_pos;
3171     }
3172
3173     /*!
3174      * \param skip bool
3175      */
3176     function SetSkipTopTick($skip)
3177     {
3178         $this->skip_top_tick = (bool)$skip;
3179         return TRUE;
3180     }
3181
3182     /*!
3183      * \param skip bool
3184      */
3185     function SetSkipBottomTick($skip)
3186     {
3187         $this->skip_bottom_tick = (bool)$skip;
3188         return TRUE;
3189     }
3190
3191     /*!
3192      * \param skip bool
3193      */
3194     function SetSkipLeftTick($skip)
3195     {
3196         $this->skip_left_tick = (bool)$skip;
3197         return TRUE;
3198     }
3199
3200     /*!
3201      * \param skip bool
3202      */
3203     function SetSkipRightTick($skip)
3204     {
3205         $this->skip_right_tick = (bool)$skip;
3206         return TRUE;
3207     }
3208
3209     function SetXTickLength($which_xln)
3210     {
3211         $this->x_tick_length = $which_xln;
3212         return TRUE;
3213     }
3214
3215     function SetYTickLength($which_yln)
3216     {
3217         $this->y_tick_length = $which_yln;
3218         return TRUE;
3219     }
3220
3221     function SetXTickCrossing($which_xc)
3222     {
3223         $this->x_tick_cross = $which_xc;
3224         return TRUE;
3225     }
3226
3227     function SetYTickCrossing($which_yc)
3228     {
3229         $this->y_tick_cross = $which_yc;
3230         return TRUE;
3231     }
3232
3233
3234 /////////////////////////////////////////////
3235 ////////////////////          GENERIC DRAWING
3236 /////////////////////////////////////////////
3237
3238     /*!
3239      * Fills the background.
3240      */
3241     function DrawBackground()
3242     {
3243         // Don't draw this twice if drawing two plots on one image
3244         if (! $this->background_done) {
3245             if (isset($this->bgimg)) {    // If bgimg is defined, use it
3246                 $this->tile_img($this->bgimg, 0, 0, $this->image_width, $this->image_height, $this->bgmode);
3247             } else {                        // Else use solid color
3248                 ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height,
3249                                      $this->ndx_bg_color);
3250             }
3251             $this->background_done = TRUE;
3252         }
3253         return TRUE;
3254     }
3255
3256
3257     /*!
3258      * Fills the plot area background.
3259      */
3260     function DrawPlotAreaBackground()
3261     {
3262         if (isset($this->plotbgimg)) {
3263             $this->tile_img($this->plotbgimg, $this->plot_area[0], $this->plot_area[1],
3264                             $this->plot_area_width, $this->plot_area_height, $this->plotbgmode);
3265         }
3266         else {
3267             if ($this->draw_plot_area_background) {
3268                 ImageFilledRectangle($this->img, $this->plot_area[0], $this->plot_area[1],
3269                                      $this->plot_area[2], $this->plot_area[3], $this->ndx_plot_bg_color);
3270             }
3271         }
3272
3273         return TRUE;
3274     }
3275
3276
3277     /*!
3278      * Tiles an image at some given coordinates.
3279      *
3280      * \param $file   string Filename of the picture to be used as tile.
3281      * \param $xorig  int    X coordinate of the plot where the tile is to begin.
3282      * \param $yorig  int    Y coordinate of the plot where the tile is to begin.
3283      * \param $width  int    Width of the area to be tiled.
3284      * \param $height int    Height of the area to be tiled.
3285      * \param $mode   string One of 'centeredtile', 'tile', 'scale'.
3286      */
3287     function tile_img($file, $xorig, $yorig, $width, $height, $mode)
3288     {
3289         $im = $this->GetImage($file, $tile_width, $tile_height);
3290         if (!$im)
3291             return FALSE// GetImage already produced an error message.
3292
3293         if ($mode == 'scale') {
3294             imagecopyresized($this->img, $im, $xorig, $yorig, 0, 0, $width, $height, $tile_width, $tile_height);
3295             return TRUE;
3296         } else if ($mode == 'centeredtile') {
3297             $x0 = - floor($tile_width/2);   // Make the tile look better
3298             $y0 = - floor($tile_height/2);
3299         } else if ($mode = 'tile') {
3300             $x0 = 0;
3301             $y0 = 0;
3302         }
3303
3304         // Actually draw the tile
3305
3306         // But first on a temporal image.
3307         $tmp = ImageCreate($width, $height);
3308         if (! $tmp)
3309             return $this->PrintError('tile_img(): Could not create image resource.');
3310
3311         for ($x = $x0; $x < $width; $x += $tile_width)
3312             for ($y = $y0; $y < $height; $y += $tile_height)
3313                 imagecopy($tmp, $im, $x, $y, 0, 0, $tile_width, $tile_height);
3314
3315         // Copy the temporal image onto the final one.
3316         imagecopy($this->img, $tmp, $xorig, $yorig, 0,0, $width, $height);
3317
3318         // Free resources
3319         imagedestroy($tmp);
3320         imagedestroy($im);
3321
3322         return TRUE;
3323     }  // function tile_img
3324
3325
3326     /*!
3327      * Draws a border around the final image.
3328      */
3329     function DrawImageBorder()
3330     {
3331         switch ($this->image_border_type) {
3332         case 'raised':
3333             ImageLine($this->img, 0, 0, $this->image_width-1, 0, $this->ndx_i_border);
3334             ImageLine($this->img, 1, 1, $this->image_width-2, 1, $this->ndx_i_border);
3335             ImageLine($this->img, 0, 0, 0, $this->image_height-1, $this->ndx_i_border);
3336             ImageLine($this->img, 1, 1, 1, $this->image_height-2, $this->ndx_i_border);
3337             ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1,
3338                       $this->image_height-1, $this->ndx_i_border_dark);
3339             ImageLine($this->img, 0, $this->image_height-1, $this->image_width-1,
3340                       $this->image_height-1, $this->ndx_i_border_dark);
3341             ImageLine($this->img, $this->image_width-2, 1, $this->image_width-2,
3342                       $this->image_height-2, $this->ndx_i_border_dark);
3343             ImageLine($this->img, 1, $this->image_height-2, $this->image_width-2,
3344                       $this->image_height-2, $this->ndx_i_border_dark);
3345             break;
3346         case 'plain':
3347             ImageLine($this->img, 0, 0, $this->image_width-1, 0, $this->ndx_i_border_dark);
3348             ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1,
3349                       $this->image_height-1, $this->ndx_i_border_dark);
3350             ImageLine($this->img, $this->image_width-1, $this->image_height-1, 0, $this->image_height-1,
3351                       $this->ndx_i_border_dark);
3352             ImageLine($this->img, 0, 0, 0, $this->image_height-1, $this->ndx_i_border_dark);
3353             break;
3354         case 'none':
3355             break;
3356         default:
3357             return $this->PrintError("DrawImageBorder(): unknown image_border_type: '$this->image_border_type'");
3358         }
3359         return TRUE;
3360     }
3361
3362
3363     /*!
3364      * Adds the title to the graph.
3365      */
3366     function DrawTitle()
3367     {
3368         // Center of the plot area
3369         //$xpos = ($this->plot_area[0] + $this->plot_area_width )/ 2;
3370
3371         // Center of the image:
3372         $xpos = $this->image_width / 2;
3373
3374         // Place it at almost at the top
3375         $ypos = $this->safe_margin;
3376
3377         $this->DrawText($this->title_font, 0, $xpos, $ypos,
3378                         $this->ndx_title_color, $this->title_txt, 'center', 'top');
3379
3380         return TRUE;
3381
3382     }
3383
3384
3385     /*!
3386      * Draws the X-Axis Title
3387      */
3388     function DrawXTitle()
3389     {
3390         if ($this->x_title_pos == 'none')
3391             return TRUE;
3392
3393         // Center of the plot
3394         $xpos = ($this->plot_area[2] + $this->plot_area[0]) / 2;
3395
3396         // Upper title
3397         if ($this->x_title_pos == 'plotup' || $this->x_title_pos == 'both') {
3398             $ypos = $this->plot_area[1] - $this->x_title_top_offset;
3399             $this->DrawText($this->x_title_font, 0, $xpos, $ypos, $this->ndx_title_color,
3400                             $this->x_title_txt, 'center', 'bottom');
3401         }
3402         // Lower title
3403         if ($this->x_title_pos == 'plotdown' || $this->x_title_pos == 'both') {
3404             $ypos = $this->plot_area[3] + $this->x_title_bot_offset;
3405             $this->DrawText($this->x_title_font, 0, $xpos, $ypos, $this->ndx_title_color,
3406                             $this->x_title_txt, 'center', 'top');
3407         }
3408         return TRUE;
3409     }
3410
3411     /*!
3412      * Draws the Y-Axis Title
3413      */
3414     function DrawYTitle()
3415     {
3416         if ($this->y_title_pos == 'none')
3417             return TRUE;
3418
3419         // Center the title vertically to the plot area
3420         $ypos = ($this->plot_area[3] + $this->plot_area[1]) / 2;
3421
3422         if ($this->y_title_pos == 'plotleft' || $this->y_title_pos == 'both') {
3423             $xpos = $this->plot_area[0] - $this->y_title_left_offset;
3424             $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color,
3425                             $this->y_title_txt, 'right', 'center');
3426         }
3427         if ($this->y_title_pos == 'plotright' || $this->y_title_pos == 'both') {
3428             $xpos = $this->plot_area[2] + $this->y_title_right_offset;
3429             $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color,
3430                             $this->y_title_txt, 'left', 'center');
3431         }
3432
3433         return TRUE;
3434     }
3435
3436
3437     /*
3438      * \note Horizontal grid lines overwrite horizontal axis with y=0, so call this first, then DrawXAxis()
3439      */
3440     function DrawYAxis()
3441     {
3442         // Draw ticks, labels and grid, if any
3443         $this->DrawYTicks();
3444
3445         // Draw Y axis at X = y_axis_x_pixels
3446         ImageLine($this->img, $this->y_axis_x_pixels, $this->plot_area[1],
3447                   $this->y_axis_x_pixels, $this->plot_area[3], $this->ndx_grid_color);
3448
3449         return TRUE;
3450     }
3451
3452     /*
3453      *
3454      */
3455     function DrawXAxis()
3456     {
3457         // Draw ticks, labels and grid
3458         $this->DrawXTicks();
3459
3460         /* This tick and label tend to overlap with regular Y Axis labels,
3461          * as Mike Pullen pointed out.
3462          *
3463         //Draw Tick and Label for X axis
3464         if (! $this->skip_bottom_tick) {
3465             $ylab =$this->FormatLabel('y', $this->x_axis_position);
3466             $this->DrawYTick($ylab, $this->x_axis_y_pixels);
3467         }
3468         */
3469         //Draw X Axis at Y = x_axis_y_pixels
3470         ImageLine($this->img, $this->plot_area[0]+1, $this->x_axis_y_pixels,
3471                   $this->plot_area[2]-1, $this->x_axis_y_pixels, $this->ndx_grid_color);
3472
3473         return TRUE;
3474     }
3475
3476     /*!
3477      * Draw one Y tick mark and its tick label. Called from DrawYTicks() and DrawXAxis()
3478      */
3479     function DrawYTick($which_ylab, $which_ypix)
3480     {
3481         // Ticks on Y axis
3482         if ($this->y_tick_pos == 'yaxis') {
3483             ImageLine($this->img, $this->y_axis_x_pixels - $this->y_tick_length, $which_ypix,
3484                       $this->y_axis_x_pixels + $this->y_tick_cross, $which_ypix, $this->ndx_tick_color);
3485         }
3486
3487         // Ticks to the left of the Plot Area
3488         if (($this->y_tick_pos == 'plotleft') || ($this->y_tick_pos == 'both') ) {
3489             ImageLine($this->img, $this->plot_area[0] - $this->y_tick_length, $which_ypix,
3490                       $this->plot_area[0] + $this->y_tick_cross, $which_ypix, $this->ndx_tick_color);
3491         }
3492
3493         // Ticks to the right of the Plot Area
3494         if (($this->y_tick_pos == 'plotright') || ($this->y_tick_pos == 'both') ) {
3495             ImageLine($this->img, $this->plot_area[2] + $this->y_tick_length, $which_ypix,
3496                       $this->plot_area[2] - $this->y_tick_cross, $which_ypix, $this->ndx_tick_color);
3497         }
3498
3499         // Labels on Y axis
3500         if ($this->y_tick_label_pos == 'yaxis') {
3501             $this->DrawText($this->y_label_font, $this->y_label_angle,
3502                             $this->y_axis_x_pixels - $this->y_label_axis_offset, $which_ypix,
3503                             $this->ndx_text_color, $which_ylab, 'right', 'center');
3504         }
3505
3506         // Labels to the left of the plot area
3507         if ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both') {
3508             $this->DrawText($this->y_label_font, $this->y_label_angle,
3509                             $this->plot_area[0] - $this->y_label_left_offset, $which_ypix,
3510                             $this->ndx_text_color, $which_ylab, 'right', 'center');
3511         }
3512         // Labels to the right of the plot area
3513         if ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both') {
3514             $this->DrawText($this->y_label_font, $this->y_label_angle,
3515                             $this->plot_area[2] + $this->y_label_right_offset, $which_ypix,
3516                             $this->ndx_text_color, $which_ylab, 'left', 'center');
3517         }
3518         return TRUE;
3519     } // Function DrawYTick()
3520
3521
3522     /*!
3523      * Draws Grid, Ticks and Tick Labels along Y-Axis
3524      * Ticks and ticklabels can be left of plot only, right of plot only,
3525      * both on the left and right of plot, or crossing a user defined Y-axis
3526      * TODO: marks at whole numbers (-10, 10, 20, 30 ...) no matter where the plot begins (-3, 4.7, etc.)
3527      */
3528     function DrawYTicks()
3529     {
3530         // Sets the line style for IMG_COLOR_STYLED lines (grid)
3531         if ($this->dashed_grid) {
3532             $this->SetDashedStyle($this->ndx_light_grid_color);
3533             $style = IMG_COLOR_STYLED;
3534         } else {
3535             $style = $this->ndx_light_grid_color;
3536         }
3537
3538         // Calculate the tick start, end, and step:
3539         list($y_tmp, $y_end, $delta_y) = $this->CalcTicks('y');
3540
3541         for (;$y_tmp <= $y_end; $y_tmp += $delta_y) {
3542             $ylab = $this->FormatLabel('y', $y_tmp);
3543             $y_pixels = $this->ytr($y_tmp);
3544
3545             // Horizontal grid line
3546             if ($this->draw_y_grid) {
3547                 ImageLine($this->img, $this->plot_area[0]+1, $y_pixels, $this->plot_area[2]-1, $y_pixels, $style);
3548             }
3549
3550             // Draw tick mark(s)
3551             $this->DrawYTick($ylab, $y_pixels);
3552         }
3553         return TRUE;
3554     } // function DrawYTicks
3555
3556     /*!
3557      * Draw one X tick mark and its tick label.
3558      */
3559     function DrawXTick($which_xlab, $which_xpix)
3560     {
3561         // Ticks on X axis
3562         if ($this->x_tick_pos == 'xaxis') {
3563             ImageLine($this->img, $which_xpix, $this->x_axis_y_pixels - $this->x_tick_cross,
3564                       $which_xpix, $this->x_axis_y_pixels + $this->x_tick_length, $this->ndx_tick_color);
3565         }
3566
3567         // Ticks on top of the Plot Area
3568         if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both') {
3569             ImageLine($this->img, $which_xpix, $this->plot_area[1] - $this->x_tick_length,
3570                       $which_xpix, $this->plot_area[1] + $this->x_tick_cross, $this->ndx_tick_color);
3571         }
3572
3573         // Ticks on bottom of Plot Area
3574         if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both') {
3575             ImageLine($this->img, $which_xpix, $this->plot_area[3] + $this->x_tick_length,
3576                       $which_xpix, $this->plot_area[3] - $this->x_tick_cross, $this->ndx_tick_color);
3577         }
3578
3579         // Label on X axis
3580         if ($this->x_tick_label_pos == 'xaxis') {
3581             $this->DrawText($this->x_label_font, $this->x_label_angle,
3582                             $which_xpix, $this->x_axis_y_pixels + $this->x_label_axis_offset,
3583                             $this->ndx_text_color, $which_xlab, 'center', 'top');
3584         }
3585
3586         // Label on top of the Plot Area
3587         if ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both') {
3588             $this->DrawText($this->x_label_font, $this->x_label_angle,
3589                             $which_xpix, $this->plot_area[1] - $this->x_label_top_offset,
3590                             $this->ndx_text_color, $which_xlab, 'center', 'bottom');
3591         }
3592
3593         // Label on bottom of the Plot Area
3594         if ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both') {
3595             $this->DrawText($this->x_label_font, $this->x_label_angle,
3596                             $which_xpix, $this->plot_area[3] + $this->x_label_bot_offset,
3597                             $this->ndx_text_color, $which_xlab, 'center', 'top');
3598         }
3599         return TRUE;
3600     }
3601
3602     /*!
3603      * Draws Grid, Ticks and Tick Labels along X-Axis
3604      * Ticks and tick labels can be down of plot only, up of plot only,
3605      * both on up and down of plot, or crossing a user defined X-axis
3606      *
3607      * \note Original vertical code submitted by Marlin Viss
3608      */
3609     function DrawXTicks()
3610     {
3611         // Sets the line style for IMG_COLOR_STYLED lines (grid)
3612         if ($this->dashed_grid) {
3613             $this->SetDashedStyle($this->ndx_light_grid_color);
3614             $style = IMG_COLOR_STYLED;
3615         } else {
3616             $style = $this->ndx_light_grid_color;
3617         }
3618
3619         // Calculate the tick start, end, and step:
3620         list($x_tmp, $x_end, $delta_x) = $this->CalcTicks('x');
3621
3622         for (;$x_tmp <= $x_end; $x_tmp += $delta_x) {
3623             $xlab = $this->FormatLabel('x', $x_tmp);
3624             $x_pixels = $this->xtr($x_tmp);
3625
3626             // Vertical grid lines
3627             if ($this->draw_x_grid) {
3628                 ImageLine($this->img, $x_pixels, $this->plot_area[1], $x_pixels, $this->plot_area[3], $style);
3629             }
3630
3631             // Draw tick mark(s)
3632             $this->DrawXTick($xlab, $x_pixels);
3633         }
3634         return TRUE;
3635     } // function DrawXTicks
3636
3637
3638     /*!
3639      *
3640      */
3641     function DrawPlotBorder()
3642     {
3643         switch ($this->plot_border_type) {
3644         case 'left':    // for past compatibility
3645         case 'plotleft':
3646             ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
3647                       $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
3648             break;
3649         case 'right':
3650         case 'plotright':
3651             ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y),
3652                       $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
3653             break;
3654         case 'both':
3655         case 'sides':
3656              ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
3657                       $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
3658             ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y),
3659                       $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
3660             break;
3661         case 'none':
3662             //Draw No Border
3663             break;
3664         case 'full':
3665         default:
3666             ImageRectangle($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
3667                            $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
3668             break;
3669         }
3670         return TRUE;
3671     }
3672
3673
3674     /*!
3675      * Draws the data label associated with a point in the plot at specified x/y world position.
3676      * This is currently only used for Y data labels for bar charts.
3677      */
3678     function DrawDataLabel($which_font, $which_angle, $x_world, $y_world, $which_color, $which_text,
3679                       $which_halign = 'center', $which_valign = 'bottom', $x_adjustment=0, $y_adjustment=0)
3680     {
3681         $data_label = $this->FormatLabel('y', $which_text);
3682         //since DrawDataLabel is going to be called alot - perhaps for speed it is better to
3683         //not use this if statement and just always assume which_font is x_label_font (ditto for color).
3684         if ( empty($which_font) )
3685             $which_font = $this->x_label_font;
3686
3687         $which_angle = empty($which_angle)?'0':$this->x_label_angle;
3688
3689         if ( empty($which_color) )
3690             $which_color = $this->ndx_title_color;
3691
3692         $x_pixels = $this->xtr($x_world) + $x_adjustment;
3693         $y_pixels = $this->ytr($y_world) + $y_adjustment;
3694
3695         $this->DrawText($which_font, $which_angle, $x_pixels, $y_pixels,
3696                         $which_color, $data_label, $which_halign, $which_valign);
3697
3698         return TRUE;
3699
3700     }
3701     /*!
3702      * Draws the data label associated with a point in the plot.
3703      * This is different from x_labels drawn by DrawXTicks() and care
3704      * should be taken not to draw both, as they'd probably overlap.
3705      * Calling of this function in DrawLines(), etc is decided after x_data_label_pos value.
3706      * Leave the last parameter out, to avoid the drawing of vertical lines, no matter
3707      * what the setting is (for plots that need it, like DrawSquared())
3708      */
3709     function DrawXDataLabel($xlab, $xpos, $row=FALSE)
3710     {
3711         // FIXME!! not working...
3712         // if (($this->_x_label_cnt++ % $this->x_label_inc) != 0)
3713         //    return;
3714
3715         $xlab = $this->FormatLabel('x', $xlab);
3716
3717         // Labels below the plot area
3718         if ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both')
3719             $this->DrawText($this->x_label_font, $this->x_label_angle,
3720                             $xpos, $this->plot_area[3] + $this->x_label_bot_offset,
3721                             $this->ndx_text_color, $xlab, 'center', 'top');
3722
3723         // Labels above the plot area
3724         if ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both')
3725             $this->DrawText($this->x_label_font, $this->x_label_angle,
3726                             $xpos, $this->plot_area[1] - $this->x_label_top_offset,
3727                             $this->ndx_text_color, $xlab, 'center', 'bottom');
3728
3729         // $row=0 means this is the first row. $row=FALSE means don't do any rows.
3730         if ($row !== FALSE && $this->draw_x_data_label_lines)
3731             $this->DrawXDataLine($xpos, $row);
3732         return TRUE;
3733     }
3734
3735     /*!
3736      * Draws Vertical lines from data points up and down.
3737      * Which lines are drawn depends on the value of x_data_label_pos,
3738      * and whether this is at all done or not, on draw_x_data_label_lines
3739      *
3740      * \param xpos int position in pixels of the line.
3741      * \param row int index of the data row being drawn.
3742      */
3743     function DrawXDataLine($xpos, $row)
3744     {
3745         // Sets the line style for IMG_COLOR_STYLED lines (grid)
3746         if($this->dashed_grid) {
3747             $this->SetDashedStyle($this->ndx_light_grid_color);
3748             $style = IMG_COLOR_STYLED;
3749         } else {
3750             $style = $this->ndx_light_grid_color;
3751         }
3752
3753         // Lines from the bottom up
3754         if ($this->x_data_label_pos == 'both') {
3755             ImageLine($this->img, $xpos, $this->plot_area[3], $xpos, $this->plot_area[1], $style);
3756         }
3757         // Lines from the bottom of the plot up to the max Y value at this X:
3758         else if ($this->x_data_label_pos == 'plotdown') {
3759             $ypos = $this->ytr($this->data_maxy[$row]);
3760             ImageLine($this->img, $xpos, $ypos, $xpos, $this->plot_area[3], $style);
3761         }
3762         // Lines from the top of the plot down to the min Y value at this X:
3763         else if ($this->x_data_label_pos == 'plotup') {
3764             $ypos = $this->ytr($this->data_miny[$row]);
3765             ImageLine($this->img, $xpos, $this->plot_area[1], $xpos, $ypos, $style);
3766         }
3767         return TRUE;
3768     }
3769
3770
3771     /*!
3772      * Draws the graph legend
3773      *
3774      * \note Base code submitted by Marlin Viss
3775      */
3776     function DrawLegend()
3777     {
3778         // Find maximum legend label line width.
3779         $max_width = 0;
3780         foreach ($this->legend as $line) {
3781             list($width, $unused) = $this->SizeText($this->legend_font, 0, $line);
3782             if ($width > $max_width) $max_width = $width;
3783         }
3784
3785         // For the color box and line spacing, use a typical font character: 8.
3786         list($char_w, $char_h) = $this->SizeText($this->legend_font, 0, '8');
3787
3788         // Normalize text alignment and colorbox alignment variables:
3789         $text_align = isset($this->legend_text_align) ? $this->legend_text_align : 'right';
3790         $colorbox_align = isset($this->legend_colorbox_align) ? $this->legend_colorbox_align : 'right';
3791
3792         // Sizing parameters:
3793         $v_margin = $char_h/2;                         // Between vertical borders and labels
3794         $dot_height = $char_h + $this->line_spacing;   // Height of the small colored boxes
3795         // Overall legend box width e.g.: | space colorbox space text space |
3796         // where colorbox and each space are 1 char width.
3797         if ($colorbox_align != 'none') {
3798             $width = $max_width + 4 * $char_w;
3799             $draw_colorbox = True;
3800         } else {
3801             $width = $max_width + 2 * $char_w;
3802             $draw_colorbox = False;
3803         }
3804
3805         //////// Calculate box position
3806         // User-defined position specified?
3807         if ( !isset($this->legend_x_pos) || !isset($this->legend_y_pos)) {
3808             // No, use default
3809             $box_start_x = $this->plot_area[2] - $width - $this->safe_margin;
3810             $box_start_y = $this->plot_area[1] + $this->safe_margin;
3811         } elseif (isset($this->legend_xy_world)) {
3812             // User-defined position in world-coordinates (See SetLegendWorld).
3813             $box_start_x = $this->xtr($this->legend_x_pos);
3814             $box_start_y = $this->ytr($this->legend_y_pos);
3815             unset($this->legend_xy_world);
3816         } else {
3817             // User-defined position in pixel coordinates.
3818             $box_start_x = $this->legend_x_pos;
3819             $box_start_y = $this->legend_y_pos;
3820         }
3821
3822         // Lower right corner
3823         $box_end_y = $box_start_y + $dot_height*(count($this->legend)) + 2*$v_margin;
3824         $box_end_x = $box_start_x + $width;
3825
3826         // Draw outer box
3827         ImageFilledRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_bg_color);
3828         ImageRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_grid_color);
3829
3830         $color_index = 0;
3831         $max_color_index = count($this->ndx_data_colors) - 1;
3832
3833         // Calculate color box and text horizontal positions.
3834         if (!$draw_colorbox) {
3835             if ($text_align == 'left')
3836                 $x_pos = $box_start_x + $char_w;
3837             else
3838                 $x_pos = $box_end_x - $char_w;
3839         } elseif ($colorbox_align == 'left') {
3840             $dot_left_x = $box_start_x + $char_w;
3841             $dot_right_x = $dot_left_x + $char_w;
3842             if ($text_align == 'left')
3843                 $x_pos = $dot_left_x + 2 * $char_w;
3844             else
3845                 $x_pos = $box_end_x - $char_w;
3846         } else {
3847             $dot_left_x = $box_end_x - 2 * $char_w;
3848             $dot_right_x = $dot_left_x + $char_w;
3849             if ($text_align == 'left')
3850                 $x_pos = $box_start_x + $char_w;
3851             else
3852                 $x_pos = $dot_left_x - $char_w;
3853         }
3854
3855         // Calculate starting position of first text line.  The bottom of each color box
3856         // lines up with the bottom (baseline) of its text line.
3857         $y_pos = $box_start_y + $v_margin + $dot_height;
3858
3859         foreach ($this->legend as $leg) {
3860             // Draw text with requested alignment:
3861             $this->DrawText($this->legend_font, 0, $x_pos, $y_pos,
3862                             $this->ndx_text_color, $leg, $text_align, 'bottom');
3863             if ($draw_colorbox) {
3864                 // Draw a box in the data color
3865                 $y1 = $y_pos - $dot_height + 1;
3866                 $y2 = $y_pos - 1;
3867                 ImageFilledRectangle($this->img, $dot_left_x, $y1, $dot_right_x, $y2,
3868                                      $this->ndx_data_colors[$color_index]);
3869                 // Draw a rectangle around the box
3870                 ImageRectangle($this->img, $dot_left_x, $y1, $dot_right_x, $y2,
3871                                $this->ndx_text_color);
3872             }
3873             $y_pos += $dot_height;
3874
3875             $color_index++;
3876             if ($color_index > $max_color_index)
3877                 $color_index = 0;
3878         }
3879         return TRUE;
3880     } // Function DrawLegend()
3881
3882
3883     /*!
3884      * TODO Draws a legend over (or below) an axis of the plot.
3885      */
3886     function DrawAxisLegend()
3887     {
3888         // Calculate available room
3889         // Calculate length of all items (boxes included)
3890         // Calculate number of lines and room it would take. FIXME: this should be known in CalcMargins()
3891         // Draw.
3892     }
3893
3894 /////////////////////////////////////////////
3895 ////////////////////             PLOT DRAWING
3896 /////////////////////////////////////////////
3897
3898
3899     /*!
3900      * Draws a pie chart. Data has to be 'text-data' type.
3901      *
3902      *  This can work in two ways: the classical, with a column for each sector
3903      *  (computes the column totals and draws the pie with that)
3904      *  OR
3905      *  Takes each row as a sector and uses it's first value. This has the added
3906      *  advantage of using the labels provided, which is not the case with the
3907      *  former method. This might prove useful for pie charts from GROUP BY sql queries
3908      */
3909     function DrawPieChart()
3910     {
3911         $xpos = $this->plot_area[0] + $this->plot_area_width/2;
3912         $ypos = $this->plot_area[1] + $this->plot_area_height/2;
3913         $diameter = min($this->plot_area_width, $this->plot_area_height);
3914         $radius = $diameter/2;
3915
3916         // Get sum of each column? One pie slice per column
3917         if ($this->data_type === 'text-data') {
3918             for ($i = 0; $i < $this->num_data_rows; $i++) {
3919                 for ($j = 1; $j < $this->num_recs[$i]; $j++) {      // Label ($row[0]) unused in these pie charts
3920                     @ $sumarr[$j] += abs($this->data[$i][$j]);      // NOTE!  sum > 0 to make pie charts
3921                 }
3922             }
3923         }
3924         // Or only one column per row, one pie slice per row?
3925         else if ($this->data_type == 'text-data-single') {
3926             for ($i = 0; $i < $this->num_data_rows; $i++) {
3927                 $legend[$i] = $this->data[$i][0];                   // Set the legend to column labels
3928                 $sumarr[$i] = $this->data[$i][1];
3929             }
3930         }
3931         else if ($this->data_type == 'data-data') {
3932             for ($i = 0; $i < $this->num_data_rows; $i++) {
3933                 for ($j = 2; $j < $this->num_recs[$i]; $j++) {
3934                     @ $sumarr[$j] += abs($this->data[$i][$j]);
3935                 }
3936             }
3937         }
3938         else {
3939             return $this->PrintError("DrawPieChart(): Data type '$this->data_type' not supported.");
3940         }
3941
3942         $total = array_sum($sumarr);
3943
3944         if ($total == 0) {
3945             return $this->PrintError('DrawPieChart(): Empty data set');
3946         }
3947
3948         if ($this->shading) {
3949             $diam2 = $diameter / 2;
3950         } else {
3951             $diam2 = $diameter;
3952         }
3953         $max_data_colors = count ($this->data_colors);
3954
3955         for ($h = $this->shading; $h >= 0; $h--) {
3956             $color_index = 0;
3957             $start_angle = 0;
3958             $end_angle = 0;
3959             foreach ($sumarr as $val) {
3960                 // For shaded pies: the last one (at the top of the "stack") has a brighter color:
3961                 if ($h == 0)
3962                     $slicecol = $this->ndx_data_colors[$color_index];
3963                 else
3964                     $slicecol = $this->ndx_data_dark_colors[$color_index];
3965
3966                 $label_txt = $this->number_format(($val / $total * 100), $this->y_precision) . '%';
3967                 $val = 360 * ($val / $total);
3968
3969                 // NOTE that imagefilledarc measures angles CLOCKWISE (go figure why),
3970                 // so the pie chart would start clockwise from 3 o'clock, would it not be
3971                 // for the reversal of start and end angles in imagefilledarc()
3972                 // Also note ImageFilledArc only takes angles in integer degrees, and if the
3973                 // the start and end angles match then you get a full circle not a zero-width
3974                 // pie. This is bad. So skip any zero-size wedge. On the other hand, we cannot
3975                 // let cumulative error from rounding to integer result in missing wedges. So
3976                 // keep the running total as a float, and round the angles. It should not
3977                 // be necessary to check that the last wedge ends at 360 degrees.
3978                 $start_angle = $end_angle;
3979                 $end_angle += $val;
3980                 // This method of conversion to integer - truncate after reversing it - was
3981                 // chosen to match the implicit method of PHPlot<=5.0.4 to get the same slices.
3982                 $arc_start_angle = (int)(360 - $start_angle);
3983                 $arc_end_angle = (int)(360 - $end_angle);
3984
3985                 if ($arc_start_angle > $arc_end_angle) {
3986                     $mid_angle = deg2rad($end_angle - ($val / 2));
3987
3988                     // Draw the slice
3989                     ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
3990                                    $arc_end_angle, $arc_start_angle,
3991                                    $slicecol, IMG_ARC_PIE);
3992
3993                     // Draw the labels only once
3994                     if ($h == 0) {
3995                         // Draw the outline
3996                         if (! $this->shading)
3997                             ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
3998                                            $arc_end_angle, $arc_start_angle,
3999                                            $this->ndx_grid_color, IMG_ARC_PIE | IMG_ARC_EDGED |IMG_ARC_NOFILL);
4000
4001
4002                         // The '* 1.2' trick is to get labels out of the pie chart so there are more
4003                         // chances they can be seen in small sectors.
4004                         $label_x = $xpos + ($diameter * 1.2 * cos($mid_angle)) * $this->label_scale_position;
4005                         $label_y = $ypos+$h - ($diam2 * 1.2 * sin($mid_angle)) * $this->label_scale_position;
4006
4007                         $this->DrawText($this->generic_font, 0, $label_x, $label_y, $this->ndx_grid_color,
4008                                         $label_txt, 'center', 'center');
4009                     }
4010                 }
4011                 if (++$color_index >= $max_data_colors)
4012                     $color_index = 0;
4013             }   // end for
4014         }   // end for
4015         return TRUE;
4016     }
4017
4018
4019     /*!
4020      * Supported data formats: data-data-error, text-data-error (doesn't exist yet)
4021      * ( data comes in as array("title", x, y, error+, error-, y2, error2+, error2-, ...) )
4022      */
4023     function DrawDotsError()
4024     {
4025         if ($this->data_type != 'data-data-error') {
4026             return $this->PrintError("DrawDotsError(): Data type '$this->data_type' not supported.");
4027         }
4028
4029         // Suppress duplicate X data labels in linepoints mode; let DrawLinesError() do them.
4030         $do_labels = ($this->plot_type != 'linepoints');
4031
4032         for($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4033             $record = 1;                                // Skip record #0 (title)
4034
4035             $x_now = $this->data[$row][$record++];  // Read it, advance record index
4036
4037             $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates.
4038
4039             // Draw X Data labels?
4040             if ($this->x_data_label_pos != 'none' && $do_labels)
4041                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);
4042
4043             // Now go for Y, E+, E-
4044             for ($idx = 0; $record < $this->num_recs[$row]; $idx++) {
4045                     // Y:
4046                     $y_now = $this->data[$row][$record++];
4047                     $this->DrawDot($x_now, $y_now, $idx, $this->ndx_data_colors[$idx]);
4048
4049                     // Error +
4050                     $val = $this->data[$row][$record++];
4051                     $this->DrawYErrorBar($x_now, $y_now, $val, $this->error_bar_shape,
4052                                          $this->ndx_error_bar_colors[$idx]);
4053                     // Error -
4054                     $val = $this->data[$row][$record++];
4055                     $this->DrawYErrorBar($x_now, $y_now, -$val, $this->error_bar_shape,
4056                                          $this->ndx_error_bar_colors[$idx]);
4057             }
4058         }
4059         return TRUE;
4060     } // function DrawDotsError()
4061
4062
4063     /*
4064      * Supported data types:
4065      *  - data-data ("title", x, y1, y2, y3, ...)
4066      *  - text-data ("title", y1, y2, y3, ...)
4067      */
4068     function DrawDots()
4069     {
4070         if (!$this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__))
4071             return FALSE;
4072
4073         // Suppress duplicate X data labels in linepoints mode; let DrawLines() do them.
4074         $do_labels = ($this->plot_type != 'linepoints');
4075
4076         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4077             $rec = 1;                    // Skip record #0 (data label)
4078
4079             // Do we have a value for X?
4080             if ($this->data_type == 'data-data')
4081                 $x_now = $this->data[$row][$rec++];  // Read it, advance record index
4082             else
4083                 $x_now = 0.5 + $cnt++;       // Place text-data at X = 0.5, 1.5, 2.5, etc...
4084
4085             $x_now_pixels = $this->xtr($x_now);
4086
4087             // Draw X Data labels?
4088             if ($this->x_data_label_pos != 'none' && $do_labels)
4089                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);
4090
4091             // Proceed with Y values
4092             for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) {
4093                 if (is_numeric($this->data[$row][$rec])) {              // Allow for missing Y data
4094                     $this->DrawDot($x_now, $this->data[$row][$rec],
4095                                    $idx, $this->ndx_data_colors[$idx]);
4096                 }
4097             }
4098         }
4099         return TRUE;
4100     } //function DrawDots
4101
4102
4103     /*!
4104      * A clean, fast routine for when you just want charts like stock volume charts
4105      */
4106     function DrawThinBarLines()
4107     {
4108         if (!$this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__))
4109             return FALSE;
4110
4111         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4112             $rec = 1;                    // Skip record #0 (data label)
4113
4114             // Do we have a value for X?
4115             if ($this->data_type == 'data-data')
4116                 $x_now = $this->data[$row][$rec++];  // Read it, advance record index
4117             else
4118                 $x_now = 0.5 + $cnt++;       // Place text-data at X = 0.5, 1.5, 2.5, etc...
4119
4120             $x_now_pixels = $this->xtr($x_now);
4121
4122             // Draw X Data labels?
4123             if ($this->x_data_label_pos != 'none')
4124                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);
4125
4126             // Proceed with Y values
4127             for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) {
4128                 if (is_numeric($this->data[$row][$rec])) {              // Allow for missing Y data
4129                     ImageSetThickness($this->img, $this->line_widths[$idx]);
4130                     // Draws a line from user defined x axis position up to ytr($val)
4131                     ImageLine($this->img, $x_now_pixels, $this->x_axis_y_pixels, $x_now_pixels,
4132                               $this->ytr($this->data[$row][$rec]), $this->ndx_data_colors[$idx]);
4133                 }
4134             }
4135         }
4136
4137         ImageSetThickness($this->img, 1);
4138         return TRUE;
4139     }  //function DrawThinBarLines
4140
4141     /*!
4142      *
4143      */
4144     function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color)
4145     {
4146         /*
4147         // TODO: add a parameter to show datalabels next to error bars?
4148         // something like this:
4149         if ($this->x_data_label_pos == 'plot')
4150             $this->DrawText($this->error_font, 90, $x1, $y2,
4151                             $color, $label, 'center', 'bottom');
4152         */
4153
4154         $x1 = $this->xtr($x_world);
4155         $y1 = $this->ytr($y_world);
4156         $y2 = $this->ytr($y_world+$error_height) ;
4157
4158         ImageSetThickness($this->img, $this->error_bar_line_width);
4159         ImageLine($this->img, $x1, $y1 , $x1, $y2, $color);
4160
4161         switch ($error_bar_type) {
4162         case 'line':
4163             break;
4164         case 'tee':
4165             ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
4166             break;
4167         default:
4168             ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
4169             break;
4170         }
4171
4172         ImageSetThickness($this->img, 1);
4173         return TRUE;
4174     }
4175
4176     /*!
4177      * Draws a styled dot. Uses world coordinates.
4178      * Supported types: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot',
4179      * 'diamond', 'triangle', 'trianglemid'
4180      */
4181     function DrawDot($x_world, $y_world, $record, $color)
4182     {
4183         // TODO: optimize, avoid counting every time we are called.
4184         $record = $record % count ($this->point_shapes);
4185
4186         $half_point = $this->point_sizes[$record] / 2;
4187
4188         $x_mid = $this->xtr($x_world);
4189         $y_mid = $this->ytr($y_world);
4190
4191         $x1 = $x_mid - $half_point;
4192         $x2 = $x_mid + $half_point;
4193         $y1 = $y_mid - $half_point;
4194         $y2 = $y_mid + $half_point;
4195
4196         switch ($this->point_shapes[$record]) {
4197         case 'halfline':
4198             ImageLine($this->img, $x1, $y_mid, $x_mid, $y_mid, $color);
4199             break;
4200         case 'line':
4201             ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
4202             break;
4203         case 'plus':
4204             ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
4205             ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
4206             break;
4207         case 'cross':
4208             ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
4209             ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
4210             break;
4211         case 'rect':
4212             ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
4213             break;
4214         case 'circle':
4215             ImageArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record], $this->point_sizes[$record],
4216                      0, 360, $color);
4217             break;
4218         case 'dot':
4219             ImageFilledArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record],
4220                            $this->point_sizes[$record], 0, 360, $color, IMG_ARC_PIE);
4221             break;
4222         case 'diamond':
4223             $arrpoints = array( $x1, $y_mid, $x_mid, $y1, $x2, $y_mid, $x_mid, $y2);
4224             ImageFilledPolygon($this->img, $arrpoints, 4, $color);
4225             break;
4226         case 'triangle':
4227             $arrpoints = array( $x1, $y_mid, $x2, $y_mid, $x_mid, $y2);
4228             ImageFilledPolygon($this->img, $arrpoints, 3, $color);
4229             break;
4230         case 'trianglemid':
4231             $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y_mid);
4232             ImageFilledPolygon($this->img, $arrpoints, 3, $color);
4233             break;
4234         case 'none':
4235             break;
4236         default:
4237             ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
4238             break;
4239         }
4240         return TRUE;
4241     }
4242
4243     /*!
4244      * Draw an area plot. Supported data types:
4245      *      'text-data'
4246      *      'data-data'
4247      * NOTE: This function used to add first and last data values even on incomplete
4248      *       sets. That is not the behaviour now. As for missing data in between,
4249      *       there are two posibilities: replace the point with one on the X axis (previous
4250      *       way), or forget about it and use the preceding and following ones to draw the polygon.
4251      *       There is the possibility to use both, we just need to add the method to set
4252      *       it. Something like SetMissingDataBehaviour(), for example.
4253      */
4254     function DrawArea()
4255     {
4256         $incomplete_data_defaults_to_x_axis = FALSE;        // TODO: make this configurable
4257
4258         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4259             $rec = 1;                                       // Skip record #0 (data label)
4260
4261             if ($this->data_type == 'data-data')            // Do we have a value for X?
4262                 $x_now = $this->data[$row][$rec++];         // Read it, advance record index
4263             else
4264                 $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...
4265
4266             $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates
4267
4268
4269             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4270                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);
4271
4272             // Proceed with Y values
4273             // Create array of points for imagefilledpolygon()
4274             for($idx = 0; $rec < $this->num_recs[$row]; $rec++, $idx++) {
4275                 if (is_numeric($this->data[$row][$rec])) {              // Allow for missing Y data
4276                     $y_now_pixels = $this->ytr($this->data[$row][$rec]);
4277
4278                     $posarr[$idx][] = $x_now_pixels;
4279                     $posarr[$idx][] = $y_now_pixels;
4280
4281                     $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1;
4282                 }
4283                 // If there's missing data...
4284                 else {
4285                     if (isset ($incomplete_data_defaults_to_x_axis)) {
4286                         $posarr[$idx][] = $x_now_pixels;
4287                         $posarr[$idx][] = $this->x_axis_y_pixels;
4288                         $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1;
4289                     }
4290                 }
4291             }
4292         }   // end for
4293
4294         $end = count($posarr);
4295         for ($i = 0; $i < $end; $i++) {
4296             // Prepend initial points. X = first point's X, Y = x_axis_y_pixels
4297             $x = $posarr[$i][0];
4298             array_unshift($posarr[$i], $x, $this->x_axis_y_pixels);
4299
4300             // Append final points. X = last point's X, Y = x_axis_y_pixels
4301             $x = $posarr[$i][count($posarr[$i])-2];
4302             array_push($posarr[$i], $x, $this->x_axis_y_pixels);
4303
4304             $num_points[$i] += 2;
4305
4306             // Draw the poligon
4307             ImageFilledPolygon($this->img, $posarr[$i], $num_points[$i], $this->ndx_data_colors[$i]);
4308         }
4309         return TRUE;
4310     } // function DrawArea()
4311
4312
4313     /*!
4314      * Draw Lines. Supported data-types:
4315      *      'data-data',
4316      *      'text-data'
4317      * NOTE: Please see the note regarding incomplete data sets on DrawArea()
4318      */
4319     function DrawLines()
4320     {
4321         // This will tell us if lines have already begun to be drawn.
4322         // It is an array to keep separate information for every line, with a single
4323         // variable we would sometimes get "undefined offset" errors and no plot...
4324         $start_lines = array_fill(0, $this->records_per_group, FALSE);
4325
4326         if ($this->data_type == 'text-data') {
4327             $lastx[0] = $this->xtr(0);
4328             $lasty[0] = $this->xtr(0);
4329         }
4330
4331         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4332             $record = 1;                                    // Skip record #0 (data label)
4333
4334             if ($this->data_type == 'data-data')            // Do we have a value for X?
4335                 $x_now = $this->data[$row][$record++];      // Read it, advance record index
4336             else
4337                 $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...
4338
4339             $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates
4340
4341             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4342                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);
4343
4344             for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) {
4345                 if (($line_style = $this->line_styles[$idx]) == 'none')
4346                     continue; //Allow suppressing entire line, useful with linepoints
4347                 if (is_numeric($this->data[$row][$record])) {           //Allow for missing Y data
4348                     $y_now_pixels = $this->ytr($this->data[$row][$record]);
4349
4350                     if ($start_lines[$idx] == TRUE) {
4351                         // Set line width, revert it to normal at the end
4352                         ImageSetThickness($this->img, $this->line_widths[$idx]);
4353
4354                         if ($line_style == 'dashed') {
4355                             $this->SetDashedStyle($this->ndx_data_colors[$idx]);
4356                             ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx],
4357                                       IMG_COLOR_STYLED);
4358                         } else {
4359                             ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx],
4360                                       $this->ndx_data_colors[$idx]);
4361                         }
4362
4363                     }
4364                     $lasty[$idx] = $y_now_pixels;
4365                     $lastx[$idx] = $x_now_pixels;
4366                     $start_lines[$idx] = TRUE;
4367                 }
4368                 // Y data missing... should we leave a blank or not?
4369                 else if ($this->draw_broken_lines) {
4370                     $start_lines[$idx] = FALSE;
4371                 }
4372             }   // end for
4373         }   // end for
4374
4375         ImageSetThickness($this->img, 1);       // Revert to original state for lines to be drawn later.
4376         return TRUE;
4377     } // function DrawLines()
4378
4379
4380     /*!
4381      * Draw lines with error bars - data comes in as
4382      *      array("label", x, y, error+, error-, y2, error2+, error2-, ...);
4383      */
4384     function DrawLinesError()
4385     {
4386         if ($this->data_type != 'data-data-error') {
4387             return $this->PrintError("DrawLinesError(): Data type '$this->data_type' not supported.");
4388         }
4389
4390         $start_lines = array_fill(0, $this->records_per_group, FALSE);
4391
4392         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4393             $record = 1;                                    // Skip record #0 (data label)
4394
4395             $x_now = $this->data[$row][$record++];          // Read X value, advance record index
4396
4397             $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates.
4398
4399
4400             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4401                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);
4402
4403             // Now go for Y, E+, E-
4404             for ($idx = 0; $record < $this->num_recs[$row]; $idx++) {
4405                 if (($line_style = $this->line_styles[$idx]) == 'none')
4406                     continue; //Allow suppressing entire line, useful with linepoints
4407                 // Y
4408                 $y_now = $this->data[$row][$record++];
4409                 $y_now_pixels = $this->ytr($y_now);
4410
4411                 if ($start_lines[$idx] == TRUE) {
4412                     ImageSetThickness($this->img, $this->line_widths[$idx]);
4413
4414                     if ($line_style == 'dashed') {
4415                         $this->SetDashedStyle($this->ndx_data_colors[$idx]);
4416                         ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx],
4417                                   IMG_COLOR_STYLED);
4418                     } else {
4419                         ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx],
4420                                   $this->ndx_data_colors[$idx]);
4421                     }
4422                 }
4423
4424                 // Error+
4425                 $val = $this->data[$row][$record++];
4426                 $this->DrawYErrorBar($x_now, $y_now, $val, $this->error_bar_shape,
4427                                      $this->ndx_error_bar_colors[$idx]);
4428
4429                 // Error-
4430                 $val = $this->data[$row][$record++];
4431                 $this->DrawYErrorBar($x_now, $y_now, -$val, $this->error_bar_shape,
4432                                      $this->ndx_error_bar_colors[$idx]);
4433
4434                 // Update indexes:
4435                 $start_lines[$idx] = TRUE;   // Tells us if we already drew the first column of points,
4436                                              // thus having $lastx and $lasty ready for the next column.
4437                 $lastx[$idx] = $x_now_pixels;
4438                 $lasty[$idx] = $y_now_pixels;
4439             }   // end while
4440         }   // end for
4441
4442         ImageSetThickness($this->img, 1);   // Revert to original state for lines to be drawn later.
4443         return TRUE;
4444     }   // function DrawLinesError()
4445
4446
4447
4448     /*!
4449      * This is a mere copy of DrawLines() with one more line drawn for each point
4450      */
4451     function DrawSquared()
4452     {
4453         // This will tell us if lines have already begun to be drawn.
4454         // It is an array to keep separate information for every line, for with a single
4455         // variable we could sometimes get "undefined offset" errors and no plot...
4456         $start_lines = array_fill(0, $this->records_per_group, FALSE);
4457
4458         if ($this->data_type == 'text-data') {
4459             $lastx[0] = $this->xtr(0);
4460             $lasty[0] = $this->xtr(0);
4461         }
4462
4463         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
4464             $record = 1;                                    // Skip record #0 (data label)
4465
4466             if ($this->data_type == 'data-data')            // Do we have a value for X?
4467                 $x_now = $this->data[$row][$record++];      // Read it, advance record index
4468             else
4469                 $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...
4470
4471             $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates
4472
4473             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4474                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels); // notice there is no last param.
4475
4476             // Draw Lines
4477             for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) {
4478                 if (is_numeric($this->data[$row][$record])) {               // Allow for missing Y data
4479                     $y_now_pixels = $this->ytr($this->data[$row][$record]);
4480
4481                     if ($start_lines[$idx] == TRUE) {
4482                         // Set line width, revert it to normal at the end
4483                         ImageSetThickness($this->img, $this->line_widths[$idx]);
4484
4485                         if ($this->line_styles[$idx] == 'dashed') {
4486                             $this->SetDashedStyle($this->ndx_data_colors[$idx]);
4487                             ImageLine($this->img, $lastx[$idx], $lasty[$idx], $x_now_pixels, $lasty[$idx],
4488                                       IMG_COLOR_STYLED);
4489                             ImageLine($this->img, $x_now_pixels, $lasty[$idx], $x_now_pixels, $y_now_pixels,
4490                                       IMG_COLOR_STYLED);
4491                         } else {
4492                             ImageLine($this->img, $lastx[$idx], $lasty[$idx], $x_now_pixels, $lasty[$idx],
4493                                       $this->ndx_data_colors[$idx]);
4494                             ImageLine($this->img, $x_now_pixels, $lasty[$idx], $x_now_pixels, $y_now_pixels,
4495                                       $this->ndx_data_colors[$idx]);
4496                         }
4497                     }
4498                     $lastx[$idx] = $x_now_pixels;
4499                     $lasty[$idx] = $y_now_pixels;
4500                     $start_lines[$idx] = TRUE;
4501                 }
4502                 // Y data missing... should we leave a blank or not?
4503                 else if ($this->draw_broken_lines) {
4504                     $start_lines[$idx] = FALSE;
4505                 }
4506             }
4507         }   // end while
4508
4509         ImageSetThickness($this->img, 1);
4510         return TRUE;
4511     } // function DrawSquared()
4512
4513
4514     /*!
4515      * Data comes in as array("title", x, y, y2, y3, ...)
4516      */
4517     function DrawBars()
4518     {
4519         if ($this->data_type != 'text-data') {
4520             return $this->PrintError('DrawBars(): Bar plots must be text-data: use function SetDataType("text-data")');
4521         }
4522
4523         // This is the X offset from the bar group's label center point to the left side of the first bar
4524         // in the group. See also CalcBarWidths above.
4525         $x_first_bar = (($this->records_per_group - 1) * $this->record_bar_width) / 2 - $this->bar_adjust_gap;
4526
4527         for ($row = 0; $row < $this->num_data_rows; $row++) {
4528             $record = 1;                                    // Skip record #0 (data label)
4529
4530             $x_now_pixels = $this->xtr(0.5 + $row);         // Place text-data at X = 0.5, 1.5, 2.5, etc...
4531
4532             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4533                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);
4534
4535             // Lower left X of first bar in the group:
4536             $x1 = $x_now_pixels - $x_first_bar;
4537
4538             // Draw the bars in the group:
4539             for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) {
4540                 if (is_numeric($this->data[$row][$record])) {       // Allow for missing Y data
4541                     $x2 = $x1 + $this->actual_bar_width;
4542
4543                     if ($this->data[$row][$record] < $this->x_axis_position) {
4544                         $y1 = $this->x_axis_y_pixels;
4545                         $y2 = $this->ytr($this->data[$row][$record]);
4546                         $upgoing_bar = False;
4547                     } else {
4548                         $y1 = $this->ytr($this->data[$row][$record]);
4549                         $y2 = $this->x_axis_y_pixels;
4550                         $upgoing_bar = True;
4551                     }
4552
4553                     // Draw the bar
4554                     ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $this->ndx_data_colors[$idx]);
4555
4556                     if ($this->shading) {                           // Draw the shade?
4557                         ImageFilledPolygon($this->img, array($x1, $y1,
4558                                                        $x1 + $this->shading, $y1 - $this->shading,
4559                                                        $x2 + $this->shading, $y1 - $this->shading,
4560                                                        $x2 + $this->shading, $y2 - $this->shading,
4561                                                        $x2, $y2,
4562                                                        $x2, $y1),
4563                                            6, $this->ndx_data_dark_colors[$idx]);
4564                     }
4565                     // Or draw a border?
4566                     else {
4567                         ImageRectangle($this->img, $x1, $y1, $x2,$y2, $this->ndx_data_border_colors[$idx]);
4568                     }
4569
4570                     // Draw optional data labels above the bars (or below, for negative values).
4571                     if ( $this->y_data_label_pos == 'plotin') {
4572                         if ($upgoing_bar) {
4573                           $v_align = 'bottom';
4574                           $y_offset = -5 - $this->shading;
4575                         } else {
4576                           $v_align = 'top';
4577                           $y_offset = 2;
4578                         }
4579                         $this->DrawDataLabel($this->y_label_font, NULL, $row+0.5, $this->data[$row][$record], '',
4580                                 $this->data[$row][$record], 'center', $v_align,
4581                                 ($idx + 0.5) * $this->record_bar_width - $x_first_bar, $y_offset);
4582                     }
4583
4584                 }
4585                 // Step to next bar in group:
4586                 $x1 += $this->record_bar_width;
4587             }   // end for
4588         }   // end for
4589         return TRUE;
4590     } //function DrawBars
4591
4592
4593     /*!
4594      * Data comes in as array("title", x, y, y2, y3, ...)
4595      * \note Original stacked bars idea by Laurent Kruk < lolok at users.sourceforge.net >
4596      */
4597     function DrawStackedBars()
4598     {
4599         if ($this->data_type != 'text-data') {
4600             return $this->PrintError('DrawStackedBars(): Bar plots must be text-data: use SetDataType("text-data")');
4601         }
4602
4603         // This is the X offset from the bar's label center point to the left side of the bar.
4604         $x_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap;
4605
4606         for ($row = 0; $row < $this->num_data_rows; $row++) {
4607             $record = 1;                                    // Skip record #0 (data label)
4608
4609             $x_now_pixels = $this->xtr(0.5 + $row);         // Place text-data at X = 0.5, 1.5, 2.5, etc...
4610
4611             if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
4612                 $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);
4613
4614             // Lower left and lower right X of the bars in this group:
4615             $x1 = $x_now_pixels - $x_first_bar;
4616             $x2 = $x1 + $this->actual_bar_width;
4617
4618             // Draw the bars
4619             $oldv = 0;
4620             for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) {
4621                 if (is_numeric($this->data[$row][$record])) {       // Allow for missing Y data
4622
4623                     $y1 = $this->ytr(abs($this->data[$row][$record]) + $oldv);
4624                     $y2 = $this->ytr($this->x_axis_position + $oldv);
4625                     $oldv += abs($this->data[$row][$record]);
4626
4627                     // Draw the bar
4628                     ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $this->ndx_data_colors[$idx]);
4629
4630                     if ($this->shading) {                           // Draw the shade?
4631                         ImageFilledPolygon($this->img, array($x1, $y1,
4632                                                        $x1 + $this->shading, $y1 - $this->shading,
4633                                                        $x2 + $this->shading, $y1 - $this->shading,
4634                                                        $x2 + $this->shading, $y2 - $this->shading,
4635                                                        $x2, $y2,
4636                                                        $x2, $y1),
4637                                            6, $this->ndx_data_dark_colors[$idx]);
4638                     }
4639                     // Or draw a border?
4640                     else {
4641                         ImageRectangle($this->img, $x1, $y1, $x2,$y2, $this->ndx_data_border_colors[$idx]);
4642                     }
4643                 }
4644             }   // end for
4645         }   // end for
4646         return TRUE;
4647     } //function DrawStackedBars
4648
4649
4650     /*!
4651      *
4652      */
4653     function DrawGraph()
4654     {
4655         // Test for missing image, missing data, empty data:
4656         if (! $this->img) {
4657             return $this->PrintError('DrawGraph(): No image resource allocated');
4658         }
4659         if (empty($this->data) || ! is_array($this->data)) {
4660             return $this->PrintError("DrawGraph(): No data array");
4661         }
4662         if ($this->total_records == 0) {
4663             return $this->PrintError('DrawGraph(): Empty data set');
4664         }
4665
4666         // For pie charts: don't draw grid or border or axes, and maximize area usage.
4667         // These controls can be split up in the future if needed.
4668         $draw_axes = ($this->plot_type != 'pie');
4669
4670         // Get maxima and minima for scaling:
4671         if (!$this->FindDataLimits())
4672             return FALSE;
4673
4674         // Set plot area world values (plot_max_x, etc.):
4675         if (!$this->CalcPlotAreaWorld())
4676             return FALSE;
4677
4678         // Calculate X and Y axis positions in World Coordinates:
4679         $this->CalcAxisPositions();
4680
4681         // Calculate the plot margins, if needed.
4682         // For pie charts, set the $maximize argument to maximize space usage.
4683         $this->CalcMargins(!$draw_axes);
4684
4685         // Calculate the actual plot area in device coordinates:
4686         $this->CalcPlotAreaPixels();
4687
4688         // Calculate the mapping between world and device coordinates:
4689         $this->CalcTranslation();
4690
4691         // Pad color and style arrays to fit records per group:
4692         $this->PadArrays();
4693         $this->DoCallback('draw_setup');
4694
4695         $this->DrawBackground();
4696         $this->DrawImageBorder();
4697         $this->DoCallback('draw_image_background');
4698
4699         $this->DrawPlotAreaBackground();
4700         $this->DoCallback('draw_plotarea_background');
4701
4702         $this->DrawTitle();
4703         $this->DrawXTitle();
4704         $this->DrawYTitle();
4705         $this->DoCallback('draw_titles');
4706
4707         if ($draw_axes && ! $this->grid_at_foreground) {   // Usually one wants grids to go back, but...
4708             $this->DrawYAxis();     // Y axis must be drawn before X axis (see DrawYAxis())
4709             $this->DrawXAxis();
4710             $this->DoCallback('draw_axes');
4711         }
4712
4713         switch ($this->plot_type) {
4714         case 'thinbarline':
4715             $this->DrawThinBarLines();
4716             break;
4717         case 'area':
4718             $this->DrawArea();
4719             break;
4720         case 'squared':
4721             $this->DrawSquared();
4722             break;
4723         case 'lines':
4724             if ( $this->data_type == 'data-data-error') {
4725                 $this->DrawLinesError();
4726             } else {
4727                 $this->DrawLines();
4728             }
4729             break;
4730         case 'linepoints':
4731             if ( $this->data_type == 'data-data-error') {
4732                 $this->DrawLinesError();
4733                 $this->DrawDotsError();
4734             } else {
4735                 $this->DrawLines();
4736                 $this->DrawDots();
4737             }
4738             break;
4739         case 'points';
4740             if ( $this->data_type == 'data-data-error') {
4741                 $this->DrawDotsError();
4742             } else {
4743                 $this->DrawDots();
4744             }
4745             break;
4746         case 'pie':
4747             $this->DrawPieChart();
4748             break;
4749         case 'stackedbars':
4750             $this->CalcBarWidths();
4751             $this->DrawStackedBars();
4752             break;
4753         case 'bars':
4754         default:
4755             $this->plot_type = 'bars'// Set it if it wasn't already set. (necessary?)
4756             $this->CalcBarWidths();
4757             $this->DrawBars();
4758             break;
4759         }   // end switch
4760         $this->DoCallback('draw_graph');
4761
4762         if ($draw_axes && $this->grid_at_foreground) {   // Usually one wants grids to go back, but...
4763             $this->DrawYAxis();     // Y axis must be drawn before X axis (see DrawYAxis())
4764             $this->DrawXAxis();
4765             $this->DoCallback('draw_axes');
4766         }
4767
4768         if ($draw_axes) {
4769             $this->DrawPlotBorder();
4770             $this->DoCallback('draw_border');
4771         }
4772
4773         if ($this->legend) {
4774             $this->DrawLegend();
4775             $this->DoCallback('draw_legend');
4776         }
4777
4778         if ($this->print_image && !$this->PrintImage())
4779             return FALSE;
4780
4781         return TRUE;
4782     } //function DrawGraph()
4783
4784 /////////////////////////////////////////////
4785 //////////////////         DEPRECATED METHODS
4786 /////////////////////////////////////////////
4787
4788     /*!
4789      * Deprecated, use SetYTickPos()
4790      */
4791     function SetDrawVertTicks($which_dvt)
4792     {
4793         if ($which_dvt != 1)
4794             $this->SetYTickPos('none');
4795         return TRUE;
4796     }
4797
4798     /*!
4799      * Deprecated, use SetXTickPos()
4800      */
4801     function SetDrawHorizTicks($which_dht)
4802     {
4803         if ($which_dht != 1)
4804            $this->SetXTickPos('none');
4805         return TRUE;
4806     }
4807
4808     /*!
4809      * \deprecated Use SetNumXTicks()
4810      */
4811     function SetNumHorizTicks($n)
4812     {
4813         return $this->SetNumXTicks($n);
4814     }
4815
4816     /*!
4817      * \deprecated Use SetNumYTicks()
4818      */
4819     function SetNumVertTicks($n)
4820     {
4821         return $this->SetNumYTicks($n);
4822     }
4823
4824     /*!
4825      * \deprecated Use SetXTickIncrement()
4826      */
4827     function SetHorizTickIncrement($inc)
4828     {
4829         return $this->SetXTickIncrement($inc);
4830     }
4831
4832
4833     /*!
4834      * \deprecated Use SetYTickIncrement()
4835      */
4836     function SetVertTickIncrement($inc)
4837     {
4838         return $this->SetYTickIncrement($inc);
4839     }
4840
4841     /*!
4842      * \deprecated Use SetYTickPos()
4843      */
4844     function SetVertTickPosition($which_tp)
4845     {
4846         return $this->SetYTickPos($which_tp);
4847     }
4848
4849     /*!
4850      * \deprecated Use SetXTickPos()
4851      */
4852     function SetHorizTickPosition($which_tp)
4853     {
4854         return $this->SetXTickPos($which_tp);
4855     }
4856
4857     /*!
4858      * \deprecated Use SetFont()
4859      */
4860     function SetTitleFontSize($which_size)
4861     {
4862         return $this->SetFont('title', $which_size);
4863     }
4864
4865     /*!
4866      * \deprecated Use SetFont()
4867      */
4868     function SetAxisFontSize($which_size)
4869     {
4870         $this->SetFont('x_label', $which_size);
4871         $this->SetFont('y_label', $which_size);
4872     }
4873
4874     /*!
4875      * \deprecated Use SetFont()
4876      */
4877     function SetSmallFontSize($which_size)
4878     {
4879         return $this->SetFont('generic', $which_size);
4880     }
4881
4882     /*!
4883      * \deprecated Use SetFont()
4884      */
4885     function SetXLabelFontSize($which_size)
4886     {
4887         return $this->SetFont('x_title', $which_size);
4888     }
4889
4890     /*!
4891      * \deprecated Use SetFont()
4892      */
4893     function SetYLabelFontSize($which_size)
4894     {
4895         return $this->SetFont('y_title', $which_size);
4896     }
4897
4898     /*!
4899      * \deprecated Use SetXTitle()
4900      */
4901     function SetXLabel($which_xlab)
4902     {
4903         return $this->SetXTitle($which_xlab);
4904     }
4905
4906     /*!
4907      * \deprecated Use SetYTitle()
4908      */
4909     function SetYLabel($which_ylab)
4910     {
4911         return $this->SetYTitle($which_ylab);
4912     }
4913
4914     /*!
4915      * \deprecated Use SetXTickLength() and SetYTickLength() instead.
4916      */
4917     function SetTickLength($which_tl)
4918     {
4919         $this->SetXTickLength($which_tl);
4920         $this->SetYTickLength($which_tl);
4921         return TRUE;
4922     }
4923
4924     /*!
4925      * \deprecated  Use SetYLabelType()
4926      */
4927     function SetYGridLabelType($which_yglt)
4928     {
4929         return $this->SetYLabelType($which_yglt);
4930     }
4931
4932     /*!
4933      * \deprecated  Use SetXLabelType()
4934      */
4935     function SetXGridLabelType($which_xglt)
4936     {
4937         return $this->SetXLabelType($which_xglt);
4938     }
4939     /*!
4940      * \deprecated Use SetYTickLabelPos()
4941      */
4942     function SetYGridLabelPos($which_yglp)
4943     {
4944         return $this->SetYTickLabelPos($which_yglp);
4945     }
4946     /*!
4947      * \deprecated Use SetXTickLabelPos()
4948      */
4949     function SetXGridLabelPos($which_xglp)
4950     {
4951         return $this->SetXTickLabelPos($which_xglp);
4952     }
4953
4954
4955     /*!
4956      * \deprecated Use SetXtitle()
4957      */
4958     function SetXTitlePos($xpos)
4959     {
4960         $this->x_title_pos = $xpos;
4961         return TRUE;
4962     }
4963
4964     /*!
4965      * \deprecated Use SetYTitle()
4966      */
4967     function SetYTitlePos($xpos)
4968     {
4969         $this->y_title_pos = $xpos;
4970         return TRUE;
4971     }
4972
4973     /*!
4974      * \deprecated Use SetXLabelAngle()
4975      */
4976     function SetXDataLabelAngle($which_xdla)
4977     {
4978         return $this->SetXLabelAngle($which_xdla);
4979     }
4980
4981     /*!
4982      * Draw Labels (not grid labels) on X Axis, following data points. Default position is
4983      * down of plot. Care must be taken not to draw these and x_tick_labels as they'd probably overlap.
4984      *
4985      * \deprecated Use SetXDataLabelPos()
4986      */
4987     function SetDrawXDataLabels($which_dxdl)
4988     {
4989         if ($which_dxdl == '1' )
4990             $this->SetXDataLabelPos('plotdown');
4991         else
4992             $this->SetXDataLabelPos('none');
4993     }
4994
4995     /*!
4996      * \deprecated
4997      */
4998     function SetNewPlotAreaPixels($x1, $y1, $x2, $y2)
4999     {
5000         //Like in GD 0, 0 is upper left set via pixel Coordinates
5001         $this->plot_area = array($x1, $y1, $x2, $y2);
5002         $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0];
5003         $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1];
5004         $this->y_top_margin = $this->plot_area[1];
5005
5006         if (isset($this->plot_max_x))
5007             $this->CalcTranslation();
5008
5009         return TRUE;
5010     }
5011
5012     /*!
5013      * \deprecated Use _SetRGBColor()
5014      */
5015     function SetColor($which_color)
5016     {
5017         $this->SetRGBColor($which_color);
5018         return TRUE;
5019     }
5020
5021     /*
5022      * \deprecated Use SetLineWidths().
5023      */
5024     function SetLineWidth($which_lw)
5025     {
5026
5027         $this->SetLineWidths($which_lw);
5028
5029         if (!$this->error_bar_line_width) {
5030             $this->SetErrorBarLineWidth($which_lw);
5031         }
5032         return TRUE;
5033     }
5034
5035     /*
5036      * \deprecated Use SetPointShapes().
5037      */
5038     function SetPointShape($which_pt)
5039     {
5040         $this->SetPointShapes($which_pt);
5041         return TRUE;
5042     }
5043
5044     /*
5045      * \deprecated Use SetPointSizes().
5046      */
5047     function SetPointSize($which_ps)
5048     {
5049         $this->SetPointSizes($which_ps);
5050         return TRUE;
5051     }
5052 // class PHPlot
5053
Note: See TracBrowser for help on using the browser.