source: trunk/workflow/inc/phplot/phplot.php @ 5934

Revision 5934, 159.5 KB checked in by marcosw, 12 years ago (diff)

Ticket #2398 - Compatibilizacao com PHP-5.3 em alguns módulos do expresso

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