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