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