"Fossies" - the Fresh Open Source Software Archive

Member "4.6.1/vendor/phplot/phplot.php" (8 Apr 2021, 401023 Bytes) of package /linux/www/studip-4.6.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file.

    1 <?php
    2 /**
    3  * PHPlot - A class for creating scientific and business graphs, charts, plots
    4  *
    5  * This file contains two PHP classes which are used to create graphs,
    6  * charts, and plots. The PHPlot class is the basic class which creates
    7  * indexed-color images, and the extended PHPlot_truecolor class creates
    8  * full-color (24-bit) images.
    9  * PHPlot currently requires PHP 5.3 or later.
   10  *
   11  * $Id: phplot.php 1774 2015-11-03 00:18:50Z lbayuk $
   12  *
   13  * @version 6.2.0
   14  * @copyright 1998-2015 Afan Ottenheimer
   15  * @license GNU Lesser General Public License, version 2.1
   16  * @link http://sourceforge.net/projects/phplot/ PHPlot Web Site with downloads, tracker, discussion
   17  * @link http://phplot.sourceforge.net PHPlot Project Web Site with links to documentation
   18  * @author lbayuk (2006-present) <lbayuk@users.sourceforge.net>
   19  * @author Miguel de Benito Delgado (co-author and maintainer, 2003-2005)
   20  * @author Afan Ottenheimer (original author)
   21  */
   22 
   23 /*
   24  * This is free software; you can redistribute it and/or
   25  * modify it under the terms of the GNU Lesser General Public
   26  * License as published by the Free Software Foundation;
   27  * version 2.1 of the License.
   28  *
   29  * This software is distributed in the hope that it will be useful,
   30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   32  * Lesser General Public License for more details.
   33  *
   34  * You should have received a copy of the GNU Lesser General Public
   35  * License along with this software; if not, write to the Free Software
   36  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
   37  * ---------------------------------------------------------------------
   38  */
   39 
   40 /**
   41  * Class for creating a plot
   42  *
   43  * The PHPlot class represents a plot (chart, graph) with all associated
   44  * parameters. This creates a palette (indexed) color image which is limited
   45  * to 256 total colors. For truecolor images (24 bit R, G, B), see the
   46  * PHPlot_truecolor class.
   47  *
   48  * In most cases, methods of PHPlot just change the internal properties, and
   49  * nothing actually happens until the DrawGraph() method is used. Therefore
   50  * methods can be used in any order up until DrawGraph(); the order should not
   51  * affect the results.
   52  *
   53  * Note: Without a background image, the PHPlot class creates a palette
   54  * (indexed) color image, and the PHPlot_truecolor class creates a truecolor
   55  * image. If a background image is used with the constructor of either class,
   56  * the type of image produced matches the type of the background image.
   57  *
   58  */
   59 class PHPlot
   60 {
   61     /** PHPlot version constant as a string */
   62     const version = '6.2.0';
   63     /** PHPlot version constant as a number = major * 10000 + minor * 100 + patch */
   64     const version_id = 60200;
   65 
   66     // All class variables are declared here, and initialized (if applicable).
   67     // Starting with PHPlot-6.0, most variables have 'protected' visibility
   68     // For more information on these variables, see the Reference Manual, Developer's Guide, List
   69     // of Member Variables. The list below is in alphabetical order, matching the manual.
   70 
   71     /** Calculated width of bars for bar charts */
   72     protected $actual_bar_width;
   73     /** Calculated bar gap */
   74     protected $bar_adjust_gap;
   75     /** Extra space between groups of bars */
   76     public $bar_extra_space = 0.5;
   77     /** Width of bar relative to space for one bar */
   78     public $bar_width_adjust = 1;
   79     /** Color (R,G,B,A) for image background */
   80     protected $bg_color;
   81     /** Background image filename */
   82     protected $bgimg;
   83     /** Background image tiling mode */
   84     protected $bgmode;
   85     /** Scale factor for box widths in box plots */
   86     public $boxes_frac_width = 0.3;
   87     /** Maximum half-width for boxes in box plots */
   88     public $boxes_max_width = 8;
   89     /** Minimum half-width for boxes in box plots */
   90     public $boxes_min_width = 2;
   91     /** Ratio of the width of the 'T' ends of box plot whiskers to the width of the boxes */
   92     public $boxes_t_width = 0.6;
   93     /** Flag: Don't send cache suppression headers */
   94     protected $browser_cache = FALSE;
   95     /** Max bubble size for bubbles plots */
   96     public $bubbles_max_size;
   97     /** Min bubbles size for bubble plots */
   98     public $bubbles_min_size = 6;
   99     /** Callback (hook) function information, indexed by callback reason */
  100     protected $callbacks = array(
  101         'data_points' => NULL,
  102         'draw_setup' => NULL,
  103         'draw_image_background' => NULL,
  104         'draw_plotarea_background' => NULL,
  105         'draw_titles' => NULL,
  106         'draw_axes' => NULL,
  107         'draw_graph' => NULL,
  108         'draw_border' => NULL,
  109         'draw_legend' => NULL,
  110         'draw_all' => NULL,
  111         'data_color' => NULL,
  112         'debug_textbox' => NULL,
  113         'debug_scale' => NULL,
  114     );
  115     /** Flag: Draw dashed or solid grid lines? */
  116     protected $dashed_grid = TRUE;
  117     /** Initial dashed pattern code */
  118     protected $dashed_style = '2-4';
  119     /** The (converted) data array */
  120     protected $data;
  121     /** Array of colors (R,G,B,A) for data borders available with some plot types */
  122     protected $data_border_colors;
  123     /** Array of colors (R,G,B,A) for data lines/marks/bars/etc. */
  124     protected $data_colors;
  125     /** Maximum number of dependent variable values */
  126     protected $data_columns;
  127     /** Array: Per row maximum Y value */
  128     protected $data_max;
  129     /** Array: Per row minimum Y value */
  130     protected $data_min;
  131     /** Format of the data array */
  132     protected $data_type = 'text-data';
  133     /** Obsolete - suffix for 'data'-formatted labels */
  134     public $data_units_text = '';
  135     /** Angle (in degrees) for data value labels */
  136     public $data_value_label_angle = 90;
  137     /** Distance (in pixels) for data value labels */
  138     public $data_value_label_distance = 5;
  139     /** Color (R,G,B,A) to use for axis data labels */
  140     protected $datalabel_color;
  141     /** Flag: data type has error bars */
  142     protected $datatype_error_bars;
  143     /** Flag: data type has implied X or Y */
  144     protected $datatype_implied;
  145     /** Flag: data type is one-column data for pie chart */
  146     protected $datatype_pie_single;
  147     /** Flag: data type has swapped X and Y values (horizontal plot) */
  148     protected $datatype_swapped_xy;
  149     /** Flag: data type includes Y and Z value pairs */
  150     protected $datatype_yz;
  151     /** Static array of data type information */
  152     static protected $datatypes = array(   // See DecodeDataType() and $datatype_* flags
  153         'text-data'          => array('implied' => TRUE),
  154         'text-data-single'   => array('implied' => TRUE, 'pie_single' => TRUE),
  155         'data-data'          => array(),
  156         'data-data-error'    => array('error_bars' => TRUE),
  157         'data-data-yx'       => array('swapped_xy' => TRUE),
  158         'text-data-yx'       => array('implied' => TRUE, 'swapped_xy' => TRUE),
  159         'data-data-xyz'      => array('yz' => TRUE),
  160         'data-data-yx-error' => array('swapped_xy' => TRUE, 'error_bars' => TRUE),
  161     );
  162     /** Static array of data type aliases => primary name */
  163     static protected $datatypes_map = array(
  164         'text-linear' => 'text-data',
  165         'linear-linear' => 'data-data',
  166         'linear-linear-error' => 'data-data-error',
  167         'text-data-pie' => 'text-data-single',
  168         'data-data-error-yx' => 'data-data-yx-error',
  169     );
  170     /** Character to use for decimal point in formatted numbers */
  171     protected $decimal_point;
  172     /** The default color array, used to initialize data_colors and error_bar_colors */
  173     protected $default_colors = array(
  174         'SkyBlue', 'green', 'orange', 'blue', 'red', 'DarkGreen', 'purple', 'peru',
  175         'cyan', 'salmon', 'SlateBlue', 'YellowGreen', 'magenta', 'aquamarine1', 'gold', 'violet'
  176     );
  177     /** Dashed-line template, as a string of space-separated markers (see SetDefaultDashedStyle) */
  178     protected $default_dashed_style;
  179     /** Default TrueType font file */
  180     protected $default_ttfont;
  181     /** Array of flags for elements that must be drawn at most once */
  182     protected $done = array();
  183     /** Flag: How to handle missing Y values */
  184     protected $draw_broken_lines = FALSE;
  185     /** Flag: Draw data borders, available with some plot types */
  186     protected $draw_data_borders;
  187     /** Flag: Draw borders on pie chart segments */
  188     protected $draw_pie_borders;
  189     /** Flag: Draw the background of the plot area */
  190     protected $draw_plot_area_background = FALSE;
  191     /** Flag: Draw X data label lines */
  192     protected $draw_x_data_label_lines = FALSE;
  193     /** Flag: Draw X grid lines? */
  194     protected $draw_x_grid;
  195     /** Flag: Draw Y data label lines */
  196     protected $draw_y_data_label_lines = FALSE;
  197     /** Flag: Draw Y grid lines? */
  198     protected $draw_y_grid;
  199     /** Color (R,G,B,A) to use for data value labels */
  200     protected $dvlabel_color;
  201     /** Array of colors (R,G,B,A) for error bars */
  202     protected $error_bar_colors;
  203     /** Thickness of error bar lines */
  204     protected $error_bar_line_width = 1;
  205     /** Shape (style) of error bars: line or tee */
  206     protected $error_bar_shape = 'tee';
  207     /** Size of error bars */
  208     protected $error_bar_size = 5;
  209     /** Image format: png, gif, jpg, wbmp */
  210     protected $file_format = 'png';
  211     /** Array of font information (should be protected, but public for possible callback use) */
  212     public $fonts;
  213     /** Flag: Draw grid on top of or behind the plot */
  214     public $grid_at_foreground = FALSE;
  215     /** Color (R,G,B,A) to use for axes, plot area border, legend border, pie chart lines and text */
  216     protected $grid_color;
  217     /** Controls fraction of bar group space used for bar */
  218     public $group_frac_width = 0.7;
  219     /** Color (R,G,B,A) for image border, if drawn */
  220     protected $i_border;
  221     /** Image border type */
  222     protected $image_border_type = 'none';
  223     /** Width of image border in pixels */
  224     protected $image_border_width;
  225     /** Image height */
  226     protected $image_height;
  227     /** Image width */
  228     protected $image_width;
  229     /** Image resource (should be protected, but public to reduce breakage) */
  230     public $img;
  231     /** Prevent recursion in error message image production */
  232     protected $in_error;
  233     /** Flag: don't send headers */
  234     protected $is_inline = FALSE;
  235     /** Label format info */
  236     protected $label_format = array('x' => array(), 'xd' => array(), 'y' => array(), 'yd' => array());
  237     /** Pie chart label position factor */
  238     protected $label_scale_position = 0.5;
  239     /** Legend text array */
  240     protected $legend;
  241     /** Color (R,G,B,A) for the legend background */
  242     protected $legend_bg_color;
  243     /** Alignment of color boxes or shape markers in the legend: left, right, or none */
  244     protected $legend_colorbox_align = 'right';
  245     /** Color control for colorbox borders in legend  */
  246     protected $legend_colorbox_borders = 'textcolor';
  247     /** Adjusts width of color boxes in the legend */
  248     public $legend_colorbox_width = 1;
  249     /** Array holding legend position information */
  250     protected $legend_pos;
  251     /** Flag: reverse the order of lines in the legend box, bottom to top  */
  252     protected $legend_reverse_order = FALSE;
  253     /** Legend style setting, left or right */
  254     protected $legend_text_align = 'right';
  255     /** Color (R,G,B,A) for the legend text  */
  256     protected $legend_text_color;
  257     /** Draw color boxes (if false or unset) or shape markers (if true) in the legend  */
  258     protected $legend_use_shapes = FALSE;
  259     /** Color (R,G,B,A) for grid lines and X data lines */
  260     protected $light_grid_color;
  261     /** Controls inter-line spacing of text */
  262     protected $line_spacing = 4;
  263     /** Plot line style(s) */
  264     protected $line_styles = array('solid', 'solid', 'dashed');
  265     /** Plot line width(s) */
  266     protected $line_widths = 1;
  267     /** Flag to avoid importing locale info */
  268     public $locale_override = FALSE;
  269     /** Overall max X value in the data array */
  270     protected $max_x;
  271     /** Overall max Y value in the data array */
  272     protected $max_y;
  273     /** Overall max Z value in the data array (for X/Y/Z data type only)  */
  274     protected $max_z;
  275     /** Overall min X value in the data array */
  276     protected $min_x;
  277     /** Overall min Y value in the data array */
  278     protected $min_y;
  279     /** Overall min Z value in the data array (for X/Y/Z data type only)  */
  280     protected $min_z;
  281     /** Color index of image background */
  282     protected $ndx_bg_color;
  283     /** Color index array for data borders */
  284     protected $ndx_data_border_colors;
  285     /** Color index array for plot data lines/marks/bars/etc. */
  286     protected $ndx_data_colors;
  287     /** Color index array for plot data, darker shade */
  288     protected $ndx_data_dark_colors;
  289     /** Color index for axis data labels  */
  290     protected $ndx_datalabel_color;
  291     /** Color index for data value labels  */
  292     protected $ndx_dvlabel_color;
  293     /** Color index array for error bars */
  294     protected $ndx_error_bar_colors;
  295     /** Color index for axes, plot area border, legend border, pie chart lines and text */
  296     protected $ndx_grid_color;
  297     /** Color index for image border lines */
  298     protected $ndx_i_border;
  299     /** Color index for image border lines, darker shade */
  300     protected $ndx_i_border_dark;
  301     /** Color index for the legend background  */
  302     protected $ndx_legend_bg_color;
  303     /** Color index for the legend text  */
  304     protected $ndx_legend_text_color;
  305     /** Color index for grid lines and X data lines */
  306     protected $ndx_light_grid_color;
  307     /** Color index for unshaded pie chart segment borders  */
  308     protected $ndx_pieborder_color;
  309     /** Color index for pie chart data labels  */
  310     protected $ndx_pielabel_color;
  311     /** Color index of plot area background */
  312     protected $ndx_plot_bg_color;
  313     /** Color index for labels and legend text */
  314     protected $ndx_text_color;
  315     /** Color index for tick marks */
  316     protected $ndx_tick_color;
  317     /** Color index for tick labels  */
  318     protected $ndx_ticklabel_color;
  319     /** Color index for main title */
  320     protected $ndx_title_color;
  321     /** Color index for X title  */
  322     protected $ndx_x_title_color;
  323     /** Color index for Y title  */
  324     protected $ndx_y_title_color;
  325     /** Number of rows in the data array (number of points along X, or number of bar groups, for example) */
  326     protected $num_data_rows;
  327     /** Array with number of entries in each data row (including label and X if present) */
  328     protected $num_recs;
  329     /** Forced number of X tick marks */
  330     protected $num_x_ticks = '';
  331     /** Forced number of Y tick marks */
  332     protected $num_y_ticks = '';
  333     /** Scale factor for element widths in OHLC plots. */
  334     public $ohlc_frac_width = 0.3;
  335     /** Maximum half-width for elements in OHLC plots */
  336     public $ohlc_max_width = 8;
  337     /** Minimum half-width for elements in OHLC plots */
  338     public $ohlc_min_width = 2;
  339     /** Redirect to output file */
  340     protected $output_file;
  341     /** Aspect ratio for shaded pie charts */
  342     public $pie_diam_factor = 0.5;
  343     /** Flag: True to draw pie chart segments clockwise, false or unset for counter-clockwise.  */
  344     protected $pie_direction_cw = FALSE;
  345     /** Flag: If true, do not include label sizes when calculating pie size.  */
  346     protected $pie_full_size = FALSE;
  347     /** Source of label text for pie charts (percent, value, label, or index)  */
  348     protected $pie_label_source;
  349     /** Minimum amount of the plot area that will be reserved for the pie */
  350     public $pie_min_size_factor = 0.5;
  351     /** Starting angle in degrees for the first segment in a pie chart */
  352     protected $pie_start_angle = 0;
  353     /** Color (R,G,B,A) to use for unshaded pie chart segment borders */
  354     protected $pieborder_color;
  355     /** Color (R,G,B,A) to use for pie chart data labels */
  356     protected $pielabel_color;
  357     /** Calculated plot area array: ([0],[1]) is top left, ([2],[3]) is bottom right */
  358     protected $plot_area;
  359     /** Height of the plot area */
  360     protected $plot_area_height;
  361     /** Width of the plot area */
  362     protected $plot_area_width;
  363     /** Color (R,G,B,A) for plot area background */
  364     protected $plot_bg_color;
  365     /** Where to draw plot borders. Can be scalar or array of choices. */
  366     protected $plot_border_type;
  367     /** Max X of the plot area in world coordinates */
  368     protected $plot_max_x;
  369     /** Max Y of the plot area in world coordinates */
  370     protected $plot_max_y;
  371     /** Min X of the plot area in world coordinates */
  372     protected $plot_min_x;
  373     /** Min Y of the plot area in world coordinates */
  374     protected $plot_min_y;
  375     /** X device coordinate of the plot area origin */
  376     protected $plot_origin_x;
  377     /** Y device coordinate of the plot area origin */
  378     protected $plot_origin_y;
  379     /** Selected plot type */
  380     protected $plot_type = 'linepoints';
  381     /** Plot area background image filename */
  382     protected $plotbgimg;
  383     /** Plot area background image tiling mode */
  384     protected $plotbgmode;
  385     /** Array of plot type information, indexed by plot type */
  386     static protected $plots = array(
  387         'area' => array(
  388             'draw_method' => 'DrawArea',
  389             'abs_vals' => TRUE,
  390         ),
  391         'bars' => array(
  392             'draw_method' => 'DrawBars',
  393         ),
  394         'boxes' => array(
  395             'draw_method' => 'DrawBoxes',
  396             'adjust_type' => 1, // See GetRangeEndAdjust()
  397         ),
  398         'bubbles' => array(
  399             'draw_method' => 'DrawBubbles',
  400             'adjust_type' => 1, // See GetRangeEndAdjust()
  401         ),
  402         'candlesticks' => array(
  403             'draw_method' => 'DrawOHLC',
  404             'draw_arg' => array(TRUE, FALSE), // Draw candlesticks, only fill if "closed down"
  405             'adjust_type' => 2, // See GetRangeEndAdjust()
  406         ),
  407         'candlesticks2' => array(
  408             'draw_method' => 'DrawOHLC',
  409             'draw_arg' => array(TRUE, TRUE), // Draw candlesticks, fill always
  410             'adjust_type' => 2, // See GetRangeEndAdjust()
  411         ),
  412         'linepoints' => array(
  413             'draw_method' => 'DrawLinePoints',
  414             'legend_alt_marker' => 'shape',
  415         ),
  416         'lines' => array(
  417             'draw_method' => 'DrawLines',
  418             'legend_alt_marker' => 'line',
  419         ),
  420         'ohlc' => array(
  421             'draw_method' => 'DrawOHLC',
  422             'draw_arg' => array(FALSE), // Don't draw candlesticks
  423             'adjust_type' => 2, // See GetRangeEndAdjust()
  424         ),
  425         'pie' => array(
  426             'draw_method' => 'DrawPieChart',
  427             'suppress_axes' => TRUE,
  428             'abs_vals' => TRUE,
  429         ),
  430         'points' => array(
  431             'draw_method' => 'DrawDots',
  432             'legend_alt_marker' => 'shape',
  433         ),
  434         'squared' => array(
  435             'draw_method' => 'DrawSquared',
  436             'legend_alt_marker' => 'line',
  437         ),
  438         'squaredarea' => array(
  439             'draw_method' => 'DrawSquaredArea',
  440             'abs_vals' => TRUE,
  441         ),
  442         'stackedarea' => array(
  443             'draw_method' => 'DrawArea',
  444             'draw_arg' => array(TRUE), // Tells DrawArea to draw stacked area plot
  445             'sum_vals' => TRUE,
  446             'abs_vals' => TRUE,
  447         ),
  448         'stackedbars' => array(
  449             'draw_method' => 'DrawStackedBars',
  450             'sum_vals' => TRUE,
  451         ),
  452         'stackedsquaredarea' => array(
  453             'draw_method' => 'DrawSquaredArea',
  454             'draw_arg' => array(TRUE), // Tells DrawSquaredArea the data is cumulative
  455             'sum_vals' => TRUE,
  456             'abs_vals' => TRUE,
  457         ),
  458         'thinbarline' => array(
  459             'draw_method' => 'DrawThinBarLines',
  460         ),
  461     );
  462     /** Size of point_shapes and point_sizes arrays  */
  463     protected $point_counts;
  464     /** Marker shapes for point plots */
  465     protected $point_shapes = array(
  466             'diamond', 'dot', 'delta', 'home', 'yield', 'box', 'circle', 'up', 'down', 'cross'
  467     );
  468     /** Marker sizes for point plots */
  469     protected $point_sizes = array(6);
  470     /** Flag: Automatic PrintImage after DrawGraph? */
  471     protected $print_image = TRUE;
  472     /** Tuning parameters for plot range calculation */
  473     protected $rangectl = array( 'x' => array(
  474                                    'adjust_mode' => 'T',      // T=adjust to next tick
  475                                    'adjust_amount' => NULL,   // See GetRangeEndAdjust()
  476                                    'zero_magnet' => 0.857142, // Value is 6/7
  477                                  ),
  478                                  'y' => array(
  479                                    'adjust_mode' => 'T',      // T=adjust to next tick
  480                                    'adjust_amount' => NULL,   // See GetRangeEndAdjust()
  481                                    'zero_magnet' => 0.857142, // Value is 6/7
  482                                 ));
  483     /** Area for each bar in a bar chart */
  484     protected $record_bar_width;
  485     /** Maximum of num_recs[], max number of entries (including label and X if present) for all data rows */
  486     protected $records_per_group;
  487     /** Array mapping color names to array of R, G, B values */
  488     protected $rgb_array;
  489     /** Fixed extra margin used in multiple places */
  490     public $safe_margin = 5;
  491     /** Stores PHPlot version when object was serialized */
  492     protected $saved_version;
  493     /** Drop shadow size for pie and bar charts */
  494     protected $shading = 5;
  495     /** Skip bottom tick mark */
  496     protected $skip_bottom_tick = FALSE;
  497     /** Skip left tick mark */
  498     protected $skip_left_tick = FALSE;
  499     /** Skip right tick mark */
  500     protected $skip_right_tick = FALSE;
  501     /** Skip top tick mark */
  502     protected $skip_top_tick = FALSE;
  503     /** MIME boundary sequence used with streaming plots  */
  504     protected $stream_boundary;
  505     /** Boundary and MIME header, output before each frame in a plot stream  */
  506     protected $stream_frame_header;
  507     /** Name of the GD output function for this image type, used with streaming plots  */
  508     protected $stream_output_f;
  509     /** Flag: Don't produce an error image on fatal error */
  510     protected $suppress_error_image = FALSE;
  511     /** Flag: Don't draw the X axis line */
  512     protected $suppress_x_axis = FALSE;
  513     /** Flag: Don't draw the Y axis line */
  514     protected $suppress_y_axis = FALSE;
  515     /** Color (R,G,B,A) for labels and legend text */
  516     protected $text_color;
  517     /** Character to use to group 1000s in formatted numbers */
  518     protected $thousands_sep;
  519     /** Color (R,G,B,A) for tick marks */
  520     protected $tick_color;
  521     /** Tuning parameters for tick increment calculation  */
  522     protected $tickctl = array( 'x' => array(
  523                                   'tick_mode' => NULL,
  524                                   'min_ticks' => 8,
  525                                   'tick_inc_integer' => FALSE,
  526                                 ),
  527                                 'y' => array(
  528                                   'tick_mode' => NULL,
  529                                   'min_ticks' => 8,
  530                                   'tick_inc_integer' => FALSE,
  531                                ));
  532     /** Color (R,G,B,A) to use for tick labels  */
  533     protected $ticklabel_color;
  534     /** Color (R,G,B,A) for main title (and default for X and Y titles) */
  535     protected $title_color;
  536     /** Y offset of main title position  */
  537     protected $title_offset;
  538     /** Main title text */
  539     protected $title_txt = '';
  540     /** Total number of entries (rows times columns in each row) in the data array. */
  541     protected $total_records;
  542     /** Color (R,G,B,A) designated as transparent  */
  543     protected $transparent_color;
  544     /** Flag: True if serialized object had a truecolor image */
  545     protected $truecolor;
  546     /** TrueType font directory */
  547     protected $ttf_path = '.';
  548     /** Default font type, True for TrueType, False for GD */
  549     protected $use_ttf = FALSE;
  550     /** Position of X axis (in world coordinates) */
  551     protected $x_axis_position;
  552     /** Device coordinate for the X axis */
  553     protected $x_axis_y_pixels;
  554     /** Effective X data label text angle   */
  555     protected $x_data_label_angle;
  556     /** X data label text angle (see also x_data_label_angle)  */
  557     protected $x_data_label_angle_u = '';
  558     /** Position of X data labels */
  559     protected $x_data_label_pos;
  560     /** X tick label text angle (and default for x_data_label_angle) */
  561     protected $x_label_angle = 0;
  562     /** Label offset relative to plot area */
  563     protected $x_label_axis_offset;
  564     /** Label offset relative to plot area */
  565     protected $x_label_bot_offset;
  566     /** Label offset relative to plot area */
  567     protected $x_label_top_offset;
  568     /** Calculated plot area margin - left side */
  569     protected $x_left_margin;
  570     /** Calculated plot area margin - right side */
  571     protected $x_right_margin;
  572     /** X tick anchor point  */
  573     protected $x_tick_anchor;
  574     /** Length of X tick marks (inside plot area) */
  575     protected $x_tick_cross = 3;
  576     /** Effective step between X tick marks */
  577     protected $x_tick_inc;
  578     /** Step between X tick marks (see also x_tick_inc)  */
  579     protected $x_tick_inc_u = '';
  580     /** Position of X tick labels */
  581     protected $x_tick_label_pos;
  582     /** Length of X tick marks (outside plot area) */
  583     protected $x_tick_length = 5;
  584     /** Position of X tick marks */
  585     protected $x_tick_pos = 'plotdown';
  586     /** Title offset relative to plot area */
  587     protected $x_title_bot_offset;
  588     /** Color (R,G,B,A) for X title  */
  589     protected $x_title_color;
  590     /** X Axis title position */
  591     protected $x_title_pos = 'none';
  592     /** Title offset relative to plot area */
  593     protected $x_title_top_offset;
  594     /** X Axis title text */
  595     protected $x_title_txt = '';
  596     /** X scale factor for converting World to Device coordinates */
  597     protected $xscale;
  598     /** Linear or log scale on X */
  599     protected $xscale_type = 'linear';
  600     /** Position of Y axis (in world coordinates) */
  601     protected $y_axis_position;
  602     /** Device coordinate for the Y axis */
  603     protected $y_axis_x_pixels;
  604     /** Calculated plot area margin - bottom */
  605     protected $y_bot_margin;
  606     /** Y data label text angle  */
  607     protected $y_data_label_angle = 0;
  608     /** Position of Y data labels */
  609     protected $y_data_label_pos;
  610     /** Y tick label text angle */
  611     protected $y_label_angle = 0;
  612     /** Label offset relative to plot area */
  613     protected $y_label_axis_offset;
  614     /** Label offset relative to plot area */
  615     protected $y_label_left_offset;
  616     /** Label offset relative to plot area */
  617     protected $y_label_right_offset;
  618     /** Y tick anchor point  */
  619     protected $y_tick_anchor;
  620     /** Length of Y tick marks (inside plot area) */
  621     protected $y_tick_cross = 3;
  622     /** Effective step between Y tick marks */
  623     protected $y_tick_inc;
  624     /** Step between Y tick marks (see also y_tick_inc)  */
  625     protected $y_tick_inc_u = '';
  626     /** Position of Y tick labels */
  627     protected $y_tick_label_pos;
  628     /** Length of Y tick marks (outside plot area) */
  629     protected $y_tick_length = 5;
  630     /** Position of Y tick marks */
  631     protected $y_tick_pos = 'plotleft';
  632     /** Color (R,G,B,A) for Y title  */
  633     protected $y_title_color;
  634     /** Title offset relative to plot area */
  635     protected $y_title_left_offset;
  636     /** Y Axis title position */
  637     protected $y_title_pos = 'none';
  638     /** Title offset relative to plot area */
  639     protected $y_title_right_offset;
  640     /** Y Axis title text */
  641     protected $y_title_txt = '';
  642     /** Calculated plot area margin - top */
  643     protected $y_top_margin;
  644     /** Y scale factor for converting World to Device coordinates */
  645     protected $yscale;
  646     /** Linear or log scale on Y */
  647     protected $yscale_type = 'linear';
  648 
  649     /**
  650      * Constructor: Sets up GD palette image resource, and initializes plot style controls
  651      *
  652      * @param int $width  Image width in pixels
  653      * @param int $height  Image height in pixels
  654      * @param string $output_file  Path for output file. Omit, or NULL, or '' to mean no output file
  655      * @param string $input_file   Path to a file to be used as background. Omit, NULL, or '' for none
  656      */
  657     function __construct($width=600, $height=400, $output_file=NULL, $input_file=NULL)
  658     {
  659         $this->initialize('imagecreate', $width, $height, $output_file, $input_file);
  660     }
  661 
  662     /**
  663      * Initializes a PHPlot object (used by PHPlot and PHPlot_truecolor constructors)
  664      *
  665      * @param string $imagecreate_function  GD function to use: imagecreate or imagecreatetruecolor
  666      * @param int $width  Image width in pixels
  667      * @param int $height  Image height in pixels
  668      * @param string $output_file  Path for output file. Omit, or NULL, or '' to mean no output file
  669      * @param string $input_file   Path to a file to be used as background. Omit, NULL, or '' for none
  670      * @since 5.6.0
  671      */
  672     protected function initialize($imagecreate_function, $width, $height, $output_file, $input_file)
  673     {
  674         $this->SetRGBArray('small');
  675 
  676         if (isset($output_file) && $output_file !== '')
  677             $this->SetOutputFile($output_file);
  678 
  679         if (isset($input_file) && $input_file !== '') {
  680             $this->SetInputFile($input_file);
  681         } else {
  682             $this->image_width = $width;
  683             $this->image_height = $height;
  684             $this->img = call_user_func($imagecreate_function, $width, $height);
  685             if (!$this->img)
  686                 return $this->PrintError(get_class($this) . '(): Could not create image resource.');
  687         }
  688         $this->SetDefaultStyles();
  689         $this->SetDefaultFonts();
  690     }
  691 
  692     /**
  693      * Prepares object for serialization
  694      *
  695      * The image resource cannot be serialized. But rather than try to filter it out from the other
  696      * properties, just let PHP serialize it (it will become an integer=0), and then fix it in __wakeup.
  697      * This way the object is still usable after serialize().
  698      * Note: This does not work if an input file was provided to the constructor.
  699      *
  700      * @return string[] Array of object property names, as required by PHP spec for __sleep()
  701      * @since 5.8.0
  702      */
  703     function __sleep()
  704     {
  705         $this->truecolor = imageistruecolor($this->img); // Remember image type
  706         $this->saved_version = self::version; // Remember version of PHPlot, for checking on unserialize
  707         return array_keys(get_object_vars($this));
  708     }
  709 
  710     /**
  711      * Cleans up object after unserialization
  712      *
  713      * Recreates the image resource (which is not serializable), after validating the PHPlot version.
  714      * @since 5.8.0
  715      */
  716     function __wakeup()
  717     {
  718         if (strcmp($this->saved_version, self::version) != 0)
  719             $this->PrintError(get_class($this) . '(): Unserialize version mismatch');
  720         $imagecreate_function = $this->truecolor ? 'imagecreatetruecolor' : 'imagecreate';
  721         $this->img = call_user_func($imagecreate_function, $this->image_width, $this->image_height);
  722         if (!$this->img)
  723             $this->PrintError(get_class($this) . '(): Could not create image resource.');
  724         unset($this->truecolor, $this->saved_version);
  725     }
  726 
  727     /**
  728      * Reads an image file (used by constructor via SetInput file, and by tile_img for backgrounds)
  729      *
  730      * @param string $image_filename  Filename of the image file to read
  731      * @param int $width  Reference variable for width of the image in pixels
  732      * @param int $height  Reference variable for height of the image in pixels
  733      * @return resource  Image resource (False on error if an error handler returns True)
  734      * @since 5.0.4
  735      */
  736     protected function GetImage($image_filename, &$width, &$height)
  737     {
  738         $error = '';
  739         $size = getimagesize($image_filename);
  740         if (!$size) {
  741             $error = "Unable to query image file $image_filename";
  742         } else {
  743             $image_type = $size[2];
  744             switch ($image_type) {
  745             case IMAGETYPE_GIF:
  746                 $img = @ ImageCreateFromGIF ($image_filename);
  747                 break;
  748             case IMAGETYPE_PNG:
  749                 $img = @ ImageCreateFromPNG ($image_filename);
  750                 break;
  751             case IMAGETYPE_JPEG:
  752                 $img = @ ImageCreateFromJPEG ($image_filename);
  753                 break;
  754             default:
  755                 $error = "Unknown image type ($image_type) for image file $image_filename";
  756                 break;
  757             }
  758         }
  759         if (empty($error) && !$img) {
  760             // getimagesize is OK, but GD won't read it. Maybe unsupported format.
  761             $error = "Failed to read image file $image_filename";
  762         }
  763         if (!empty($error)) {
  764             return $this->PrintError("GetImage(): $error");
  765         }
  766         $width = $size[0];
  767         $height = $size[1];
  768         return $img;
  769     }
  770 
  771     /**
  772      * Selects an input file to be used as background for the whole graph
  773      *
  774      * @param string $which_input_file  Pathname to the image file to use as a background
  775      * @deprecated  Public use discouraged; intended for use by class constructor
  776      * @return bool  True (False on error if an error handler returns True)
  777      */
  778     function SetInputFile($which_input_file)
  779     {
  780         $im = $this->GetImage($which_input_file, $this->image_width, $this->image_height);
  781         if (!$im)
  782             return FALSE;  // GetImage already produced an error message.
  783 
  784         // Deallocate any resources previously allocated
  785         if (isset($this->img))
  786             imagedestroy($this->img);
  787 
  788         $this->img = $im;
  789 
  790         // Do not overwrite the input file with the background color.
  791         $this->done['background'] = TRUE;
  792 
  793         return TRUE;
  794     }
  795 
  796 /////////////////////////////////////////////
  797 //////////////                         COLORS
  798 /////////////////////////////////////////////
  799 
  800     /**
  801      * Allocates a GD color index for a color specified as an array (R,G,B,A)
  802      *
  803      * At drawing time, this allocates a GD color index for the specified color, which
  804      * is specified as a 4 component array. Earlier, when a color is specified,
  805      * SetRGBColor() parsed and checked it and converted it to this component array form.
  806      *
  807      * @param int[] $color  Color specification as (R, G, B, A), or unset variable
  808      * @param int $default_color_index  An already-allocated GD color index to use if $color is unset
  809      * @return int  A GD color index that can be used when drawing
  810      * @since 5.2.0
  811      */
  812     protected function GetColorIndex(&$color, $default_color_index = 0)
  813     {
  814         if (empty($color)) return $default_color_index;
  815         list($r, $g, $b, $a) = $color;
  816         return imagecolorresolvealpha($this->img, $r, $g, $b, $a);
  817     }
  818 
  819     /**
  820      * Allocates an array of GD color indexes from an array of color specification arrays
  821      *
  822      * This is used for the data_colors array, for example.
  823      * Note: $color_array must use 0-based sequential integer indexes.
  824      *
  825      * @param array $color_array  Array of color specifications, each an array (R,G,B,A)
  826      * @param int $max_colors  Limit color allocation to no more than this number of colors
  827      * @return int[]  Array of GD color indexes that can be used when drawing
  828      * @since 5.3.1
  829      */
  830     protected function GetColorIndexArray($color_array, $max_colors)
  831     {
  832         $n = min(count($color_array), $max_colors);
  833         $result = array();
  834         for ($i = 0; $i < $n; $i++)
  835             $result[] = $this->GetColorIndex($color_array[$i]);
  836         return $result;
  837     }
  838 
  839     /**
  840      * Allocates an array of GD color indexes for darker shades from an array of color specifications
  841      *
  842      * This is used for shadow colors such as those in bar charts with shading.
  843      *
  844      * @param array $color_array  Array of color specifications, each an array (R,G,B,A)
  845      * @param int $max_colors  Limit color allocation to no more than this number of colors
  846      * @return int[]  Array of GD color indexes that can be used when drawing shadow colors
  847      * @since 5.3.1
  848      */
  849     protected function GetDarkColorIndexArray($color_array, $max_colors)
  850     {
  851         $n = min(count($color_array), $max_colors);
  852         $result = array();
  853         for ($i = 0; $i < $n; $i++)
  854             $result[] = $this->GetDarkColorIndex($color_array[$i]);
  855         return $result;
  856     }
  857 
  858     /**
  859      * Allocates a GD color index for a darker shade of a color specified as an array (R,G,B,A)
  860      *
  861      * See notes on GetColorIndex() above.
  862      *
  863      * @param int[] $color  Color specification as (R, G, B, A)
  864      * @return int  A GD color index that can be used when drawing a shadow color
  865      * @since 5.2.0
  866      */
  867     protected function GetDarkColorIndex($color)
  868     {
  869         list ($r, $g, $b, $a) = $color;
  870         $r = max(0, $r - 0x30);
  871         $g = max(0, $g - 0x30);
  872         $b = max(0, $b - 0x30);
  873         return imagecolorresolvealpha($this->img, $r, $g, $b, $a);
  874     }
  875 
  876     /**
  877      * Sets or reverts all colors and styles to their defaults
  878      *
  879      * @return bool  True always
  880      */
  881     protected function SetDefaultStyles()
  882     {
  883         $this->SetDefaultDashedStyle($this->dashed_style);
  884         $this->SetImageBorderColor(array(194, 194, 194));
  885         $this->SetPlotBgColor('white');
  886         $this->SetBackgroundColor('white');
  887         $this->SetTextColor('black');
  888         $this->SetGridColor('black');
  889         $this->SetLightGridColor('gray');
  890         $this->SetTickColor('black');
  891         $this->SetTitleColor('black');
  892         // These functions set up the default colors when called without parameters
  893         $this->SetDataColors();
  894         $this->SetErrorBarColors();
  895         $this->SetDataBorderColors();
  896         return TRUE;
  897     }
  898 
  899     /**
  900      * Sets the overall image background color
  901      *
  902      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  903      * @return bool  True (False on error if an error handler returns True)
  904      */
  905     function SetBackgroundColor($which_color)
  906     {
  907         return (bool)($this->bg_color = $this->SetRGBColor($which_color));
  908     }
  909 
  910     /**
  911      * Sets the plot area background color, which is only drawn if SetDrawPlotAreaBackground is used.
  912      *
  913      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  914      * @return bool  True (False on error if an error handler returns True)
  915      */
  916     function SetPlotBgColor($which_color)
  917     {
  918         return (bool)($this->plot_bg_color = $this->SetRGBColor($which_color));
  919     }
  920 
  921     /**
  922      * Sets the color of the plot title, and the default color of the X and Y titles.
  923      *
  924      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  925      * @return bool  True (False on error if an error handler returns True)
  926      */
  927     function SetTitleColor($which_color)
  928     {
  929         return (bool)($this->title_color = $this->SetRGBColor($which_color));
  930     }
  931 
  932     /**
  933      * Sets the color of the X title, overriding the color set with SetTitleColor()
  934      *
  935      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  936      * @return bool  True (False on error if an error handler returns True)
  937      * @since 5.2.0
  938      */
  939     function SetXTitleColor($which_color)
  940     {
  941         return (bool)($this->x_title_color = $this->SetRGBColor($which_color));
  942     }
  943 
  944     /**
  945      * Sets the color of the Y title, overriding the color set with SetTitleColor()
  946      *
  947      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  948      * @return bool  True (False on error if an error handler returns True)
  949      * @since 5.2.0
  950      */
  951     function SetYTitleColor($which_color)
  952     {
  953         return (bool)($this->y_title_color = $this->SetRGBColor($which_color));
  954     }
  955 
  956     /**
  957      * Sets the color of the axis tick marks
  958      *
  959      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  960      * @return bool  True (False on error if an error handler returns True)
  961      */
  962     function SetTickColor($which_color)
  963     {
  964         return (bool)($this->tick_color = $this->SetRGBColor($which_color));
  965     }
  966 
  967     /**
  968      * @deprecated  Use SetTitleColor() instead
  969      */
  970     function SetLabelColor($which_color)
  971     {
  972         return $this->SetTitleColor($which_color);
  973     }
  974 
  975     /**
  976      * Sets the general text color, which is the default color for legend text, tick and data labels
  977      *
  978      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  979      * @return bool  True (False on error if an error handler returns True)
  980      */
  981     function SetTextColor($which_color)
  982     {
  983         return (bool)($this->text_color = $this->SetRGBColor($which_color));
  984     }
  985 
  986     /**
  987      * Sets the color for data labels, overriding the default set with SetTextColor()
  988      *
  989      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
  990      * @return bool  True (False on error if an error handler returns True)
  991      * @since 5.7.0
  992      */
  993     function SetDataLabelColor($which_color)
  994     {
  995         return (bool)($this->datalabel_color = $this->SetRGBColor($which_color));
  996     }
  997 
  998     /**
  999      * Sets the color for data value labels, overriding SetTextColor() and SetDataLabelColor()
 1000      *
 1001      * Note: Data Value Labels are the labels within the plot area (not the axis labels).
 1002      *
 1003      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1004      * @return bool  True (False on error if an error handler returns True)
 1005      * @since 5.7.0
 1006      */
 1007     function SetDataValueLabelColor($which_color)
 1008     {
 1009         return (bool)($this->dvlabel_color = $this->SetRGBColor($which_color));
 1010     }
 1011 
 1012     /**
 1013      * Sets the color for pie chart data labels, overriding the default set with SetGridColor()
 1014      *
 1015      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1016      * @return bool  True (False on error if an error handler returns True)
 1017      * @since 5.7.0
 1018      */
 1019     function SetPieLabelColor($which_color)
 1020     {
 1021         return (bool)($this->pielabel_color = $this->SetRGBColor($which_color));
 1022     }
 1023 
 1024     /**
 1025      * Sets the color for pie chart segment borders
 1026      *
 1027      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1028      * @return bool  True (False on error if an error handler returns True)
 1029      * @since 6.0.0
 1030      */
 1031     function SetPieBorderColor($which_color)
 1032     {
 1033         return (bool)($this->pieborder_color = $this->SetRGBColor($which_color));
 1034     }
 1035 
 1036     /**
 1037      * Sets the color for tick labels, overriding the default set with SetTextColor()
 1038      *
 1039      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1040      * @return bool  True (False on error if an error handler returns True)
 1041      * @since 5.7.0
 1042      */
 1043     function SetTickLabelColor($which_color)
 1044     {
 1045         return (bool)($this->ticklabel_color = $this->SetRGBColor($which_color));
 1046     }
 1047 
 1048     /**
 1049      * Sets the X and Y grid colors, and the data label line color
 1050      *
 1051      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1052      * @return bool  True (False on error if an error handler returns True)
 1053      */
 1054     function SetLightGridColor($which_color)
 1055     {
 1056         return (bool)($this->light_grid_color = $this->SetRGBColor($which_color));
 1057     }
 1058 
 1059     /**
 1060      * Sets the color used for the X and Y axis lines, plot border, and legend border
 1061      *
 1062      * Also sets the default color for the pie chart data labels and pie chart segment borders.
 1063      * These can be overridden by SetPieLabelColor() and SetPieBorderColor() respectively.
 1064      * Note: This has nothing to do with the grid, and we don't know where this name came from.
 1065      *
 1066      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1067      * @return bool  True (False on error if an error handler returns True)
 1068      */
 1069     function SetGridColor($which_color)
 1070     {
 1071         return (bool)($this->grid_color = $this->SetRGBColor($which_color));
 1072     }
 1073 
 1074     /**
 1075      * Sets the color used for the image border, drawn if SetImageBorderType() enables it
 1076      *
 1077      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1078      * @return bool  True (False on error if an error handler returns True)
 1079      */
 1080     function SetImageBorderColor($which_color)
 1081     {
 1082         return (bool)($this->i_border = $this->SetRGBColor($which_color));
 1083     }
 1084 
 1085     /**
 1086      * Designates a color to be transparent, if transparency is supported by the image format
 1087      *
 1088      * @param string|int[]|null $which_color  Color to make transparent; empty|omit|NULL to reset to none
 1089      * @return bool  True (False on error if an error handler returns True)
 1090      */
 1091     function SetTransparentColor($which_color = NULL)
 1092     {
 1093         $this->transparent_color = empty($which_color) ? NULL : $this->SetRGBColor($which_color);
 1094         return ($this->transparent_color !== FALSE); // True unless SetRGBColor() returned an error
 1095     }
 1096 
 1097     /**
 1098      * Sets the color used for the legend background, which defaults to the image background color
 1099      *
 1100      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1101      * @return bool  True (False on error if an error handler returns True)
 1102      * @since 6.0.0
 1103      */
 1104     function SetLegendBgColor($which_color)
 1105     {
 1106         return (bool)($this->legend_bg_color = $this->SetRGBColor($which_color));
 1107     }
 1108 
 1109     /**
 1110      * Sets the color used for the legend text, which defaults to the general text color
 1111      *
 1112      * @param string|int[] $which_color  Color name or spec (#rrggbb, (r,g,b) array, etc)
 1113      * @return bool  True (False on error if an error handler returns True)
 1114      * @since 6.0.0
 1115      */
 1116     function SetLegendTextColor($which_color)
 1117     {
 1118         return (bool)($this->legend_text_color = $this->SetRGBColor($which_color));
 1119     }
 1120 
 1121     /**
 1122      * Sets the array of colors to be used (the color map)
 1123      *
 1124      * The color map maps color names into arrays of R, G, B and optionally A values.
 1125      * The selected color map can be user defined, a small predefined one,
 1126      * or a large one included from the file 'rgb.inc.php'.
 1127      *
 1128      * @param array|string $which_color_array  Color map array (name=>(R,G,B[,A]), or keyword small | large
 1129      * @return bool  True always
 1130      */
 1131     function SetRGBArray($which_color_array)
 1132     {
 1133         if (is_array($which_color_array)) {           // User defined array
 1134             $this->rgb_array = $which_color_array;
 1135         } elseif ($which_color_array == 'small') {      // Small predefined color array
 1136             $this->rgb_array = array(
 1137                 'white'          => array(255, 255, 255),
 1138                 'snow'           => array(255, 250, 250),
 1139                 'PeachPuff'      => array(255, 218, 185),
 1140                 'ivory'          => array(255, 255, 240),
 1141                 'lavender'       => array(230, 230, 250),
 1142                 'black'          => array(  0,   0,   0),
 1143                 'DimGrey'        => array(105, 105, 105),
 1144                 'gray'           => array(190, 190, 190),
 1145                 'grey'           => array(190, 190, 190),
 1146                 'navy'           => array(  0,   0, 128),
 1147                 'SlateBlue'      => array(106,  90, 205),
 1148                 'blue'           => array(  0,   0, 255),
 1149                 'SkyBlue'        => array(135, 206, 235),
 1150                 'cyan'           => array(  0, 255, 255),
 1151                 'DarkGreen'      => array(  0, 100,   0),
 1152                 'green'          => array(  0, 255,   0),
 1153                 'YellowGreen'    => array(154, 205,  50),
 1154                 'yellow'         => array(255, 255,   0),
 1155                 'orange'         => array(255, 165,   0),
 1156                 'gold'           => array(255, 215,   0),
 1157                 'peru'           => array(205, 133,  63),
 1158                 'beige'          => array(245, 245, 220),
 1159                 'wheat'          => array(245, 222, 179),
 1160                 'tan'            => array(210, 180, 140),
 1161                 'brown'          => array(165,  42,  42),
 1162                 'salmon'         => array(250, 128, 114),
 1163                 'red'            => array(255,   0,   0),
 1164                 'pink'           => array(255, 192, 203),
 1165                 'maroon'         => array(176,  48,  96),
 1166                 'magenta'        => array(255,   0, 255),
 1167                 'violet'         => array(238, 130, 238),
 1168                 'plum'           => array(221, 160, 221),
 1169                 'orchid'         => array(218, 112, 214),
 1170                 'purple'         => array(160,  32, 240),
 1171                 'azure1'         => array(240, 255, 255),
 1172                 'aquamarine1'    => array(127, 255, 212)
 1173                 );
 1174         } elseif ($which_color_array == 'large')  {    // Large color array
 1175             if (!@include('rgb.inc.php')) {
 1176                 return $this->PrintError("SetRGBArray(): Large color map could not be loaded "
 1177                                        . "from 'rgb.inc.php'.");
 1178             }
 1179             $this->rgb_array = $ColorArray;
 1180         } else {
 1181             return $this->PrintError("SetRGBArray(): Invalid color map selection");
 1182         }
 1183 
 1184         return TRUE;
 1185     }
 1186 
 1187     /**
 1188      * Parses a color specification and returns the color component values
 1189      *
 1190      * Accepted color specification forms are (1) component array: array(R,G,B)
 1191      * or array(R,G,B,A); (2) component string hexadecimal: "#RRGGBB" or
 1192      * "#RRGGBBAA"; (3) A color name from the color map array with optional
 1193      * alpha value suffix as ":alpha".  R, G, and B are integers 0-255, and
 1194      * Alpha is 0 (opaque) to 127 (transparent).
 1195      *
 1196      * @param string|int[] $color_asked  Color spec to parse (color name, #rrggbb, (r,g,b) array, etc)
 1197      * @param int $alpha  Optional default alpha value (0-127, default 0 for opaque)
 1198      * @return int[]  Color component values as array (red, green, blue, alpha)
 1199      * @deprecated  Public use discouraged; intended for class internal use
 1200      */
 1201     function SetRGBColor($color_asked, $alpha = 0)
 1202     {
 1203         if (empty($color_asked)) {
 1204             $ret_val = array(0, 0, 0);
 1205 
 1206         } elseif (is_array($color_asked) && (($n = count($color_asked)) == 3 || $n == 4) ) {
 1207             // Already an array of 3 or 4 elements:
 1208             $ret_val = $color_asked;
 1209 
 1210         } elseif (preg_match('/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i',
 1211                              $color_asked, $ss)) {
 1212             // #RRGGBB or #RRGGBBAA notation:
 1213             $ret_val = array(hexdec($ss[1]), hexdec($ss[2]), hexdec($ss[3]));
 1214             if (isset($ss[4])) $ret_val[] = hexdec($ss[4]);
 1215 
 1216         } elseif (isset($this->rgb_array[$color_asked])) {
 1217             // Color by name:
 1218             $ret_val = $this->rgb_array[$color_asked];
 1219 
 1220         } elseif (preg_match('/(.+):([\d]+)$/', $color_asked, $ss)
 1221                   && isset($this->rgb_array[$ss[1]])) {
 1222             // Color by name with ":alpha" suffix, alpha is a decimal number:
 1223             $ret_val = $this->rgb_array[$ss[1]];
 1224             $ret_val[3] = (int)$ss[2];
 1225 
 1226         } else {
 1227             return $this->PrintError("SetRGBColor(): Color '$color_asked' is not valid.");
 1228         }
 1229 
 1230         // Append alpha if not already provided for:
 1231         if (count($ret_val) == 3)
 1232             $ret_val[] = $alpha;
 1233         return $ret_val;
 1234     }
 1235 
 1236     /**
 1237      * Sets the colors used for plotting data sets, with optional default alpha value
 1238      *
 1239      * If passed an array, use the values as colors for sequential data sets.
 1240      * If passed a single color specification, plot all data sets using that
 1241      * single color.
 1242      * Special use cases for the $which_data argument: Missing or NULL loads
 1243      * the default data color map if no map is already set; an empty string or
 1244      * False loads the default color map even if a color map is already set.
 1245      * Note:  The default value for $alpha here is NULL, not 0, so we can tell
 1246      * if it was defaulted. But the effective default value is 0 (opaque).
 1247      *
 1248      * @param array|string $which_data  Array of color specifications, or one color, or empty
 1249      * @param array|string $which_border  Data border colors, deprecated - use SetDataBorderColors() instead
 1250      * @param int $alpha  Default alpha to apply to all data colors that do not have an alpha value
 1251      * @return bool  True (False on error if an error handler returns True)
 1252      */
 1253     function SetDataColors($which_data = NULL, $which_border = NULL, $alpha = NULL)
 1254     {
 1255         if (is_array($which_data)) {
 1256             $colors = $which_data;  // Use supplied array
 1257         } elseif (!empty($which_data)) {
 1258             $colors = array($which_data);  // Use supplied single color
 1259         } elseif (empty($this->data_colors) || !is_null($which_data)) {
 1260             $colors = $this->default_colors;  // Use default color array
 1261         } else {
 1262             // which_data is NULL or missing and a color array is already set.
 1263             // The existing color array is left alone, except that if $alpha is
 1264             // given this will replace the alpha value of each existing color.
 1265             // This makes SetDataColors(NULL, NULL, $alpha) work.
 1266             if (isset($alpha)) {
 1267                 $n_colors = count($this->data_colors);
 1268                 for ($i = 0; $i < $n_colors; $i++) {
 1269                     $this->data_colors[$i][3] = $alpha; // Component 3 = alpha value
 1270                 }
 1271             }
 1272             // No need to reparse the colors or anything else.
 1273             return TRUE;
 1274         }
 1275 
 1276         if (!isset($alpha))
 1277             $alpha = 0; // Actual default is opaque colors.
 1278 
 1279         // Check each color and convert to array (r,g,b,a) form.
 1280         // Use the $alpha argument as a default for the alpha value of each color.
 1281         $this->data_colors = array();
 1282         foreach ($colors as $color) {
 1283             $color_array = $this->SetRGBColor($color, $alpha);
 1284             if (!$color_array) return FALSE; // SetRGBColor already did an error message.
 1285             $this->data_colors[] = $color_array;
 1286         }
 1287 
 1288         // For past compatibility:
 1289         return $this->SetDataBorderColors($which_border);
 1290     }
 1291 
 1292     /**
 1293      * Sets the colors used for data borders in some plot types
 1294      *
 1295      * For plot types which support data borders, such as 'bars', this sets
 1296      * the colors used for those borders. See also SetDrawDataBorders().
 1297      * Special cases: If $which_br is missing or NULL, use the default of all
 1298      * black if colors were not already set; if an empty string or False then
 1299      * set the default of all black regardless.
 1300      *
 1301      * @param array|string $which_br  Array of color specifications, or one color, or empty
 1302      * @return bool  True (False on error if an error handler returns True)
 1303      */
 1304     function SetDataBorderColors($which_br = NULL)
 1305     {
 1306         if (is_array($which_br)) {
 1307             $colors = $which_br; // Use supplied array
 1308         } elseif (!empty($which_br)) {
 1309             $colors = array($which_br);  // Use supplied single color
 1310         } elseif (empty($this->data_border_colors) || !is_null($which_br)) {
 1311             $colors = array('black'); // Use default
 1312         } else {
 1313             return TRUE; // Do nothing: which_br is NULL or missing and a color array is already set.
 1314         }
 1315 
 1316         // Check each color and convert to array (r,g,b,a) form.
 1317         $this->data_border_colors = array();
 1318         foreach ($colors as $color) {
 1319             $color_array = $this->SetRGBColor($color);
 1320             if (!$color_array) return FALSE; // SetRGBColor already did an error message.
 1321             $this->data_border_colors[] = $color_array;
 1322         }
 1323         return TRUE;
 1324     }
 1325 
 1326     /**
 1327      * Sets the colors used for data error bars
 1328      *
 1329      * Special cases for $which_err: Missing or NULL loads the default colors
 1330      * (same as default data colors) if no colors are aleady set; an empty
 1331      * string or False loads the default colors even if colors were already set.
 1332      *
 1333      * @param array|string $which_err  Array of color specifications, or one color, or empty
 1334      * @return bool  True (False on error if an error handler returns True)
 1335      */
 1336     function SetErrorBarColors($which_err = NULL)
 1337     {
 1338         if (is_array($which_err)) {
 1339             $colors = $which_err;  // Use supplied array
 1340         } elseif (!empty($which_err)) {
 1341             $colors = array($which_err);  // Use supplied single color
 1342         } elseif (empty($this->error_bar_colors) || !is_null($which_err)) {
 1343             $colors = $this->default_colors;  // Use default color array
 1344         } else {
 1345             return TRUE; // Do nothing: which_err is NULL or missing and a color array is already set.
 1346         }
 1347 
 1348         // Check each color and convert to array (r,g,b,a) form.
 1349         $this->error_bar_colors = array();
 1350         foreach ($colors as $color) {
 1351             $color_array = $this->SetRGBColor($color);
 1352             if (!$color_array) return FALSE; // SetRGBColor already did an error message.
 1353             $this->error_bar_colors[] = $color_array;
 1354         }
 1355         return TRUE;
 1356     }
 1357 
 1358     /**
 1359      * Sets the default dashed line style (on/off pattern for dashed lines)
 1360      *
 1361      * For example: SetDashedStyle('2-3-1-2') means 2 dots of color, 3
 1362      * transparent, 1 color, then 2 transparent.
 1363      * This builds a template string $this->default_dashed_style which contains
 1364      * a space-separated series of marker (#) for pixels on, and the value of
 1365      * IMG_COLOR_TRANSPARENT for each pixel which is off.
 1366      * See SetDashedStyle() for how it is used.
 1367      *
 1368      * @param string $which_style  Dashed line specification, in the form <pixels_on>-<pixels_off>...
 1369      * @return bool  True (False on error if an error handler returns True)
 1370      */
 1371     function SetDefaultDashedStyle($which_style)
 1372     {
 1373         // Validate the argument as "(number)-(number)-..." with at least 2 numbers:
 1374         if (!preg_match('/^\d+-\d+(-\d+)*$/', $which_style)) {
 1375             return $this->PrintError("SetDefaultDashedStyle(): Wrong parameter '$which_style'.");
 1376         }
 1377         $result = '';
 1378         $use_color = TRUE;
 1379         $transparent = ' ' . IMG_COLOR_TRANSPARENT;
 1380         // Expand the dashed line style specifier:
 1381         foreach (explode('-', $which_style) as $n) {
 1382             $result .= str_repeat($use_color ? ' #' : $transparent, $n);
 1383             $use_color = !$use_color;  // Alternate color and transparent
 1384         }
 1385         $this->default_dashed_style = ltrim($result);
 1386         return TRUE;
 1387     }
 1388 
 1389     /**
 1390      * Returns a GD line style for drawing patterned or solid lines
 1391      *
 1392      * This returns a value which can be used in GD when drawing lines.
 1393      * If styles are off, it just returns the color supplied.
 1394      * If styles are on, it applies the given color to the pre-set dashed
 1395      * line style (see SetDefaultDashedStyle()) and sets that as the GD
 1396      * line style, then returns the special GD code for drawing styled lines.
 1397      *
 1398      * @param int $which_ndxcol  Color index to be used for drawing
 1399      * @param bool $use_style  TRUE or omit for dashed lines, FALSE for solid lines
 1400      * @return int  A GD color index for drawing: either $which_ndxcol or IMG_COLOR_STYLED
 1401      */
 1402     protected function SetDashedStyle($which_ndxcol, $use_style = TRUE)
 1403     {
 1404         if (!$use_style)
 1405             return $which_ndxcol; // Styles are off; use original color for drawing
 1406         // Set the line style, substituting the specified color for the # marker:
 1407         imagesetstyle($this->img, explode(' ', str_replace('#', $which_ndxcol, $this->default_dashed_style)));
 1408         return IMG_COLOR_STYLED; // Use this value as the color for drawing
 1409     }
 1410 
 1411     /**
 1412      * Sets line widths (thickness) for each data set
 1413      *
 1414      * @param int[]|int $which_lw  Array of line widths in pixels, or a single value to use for all data sets
 1415      * @return bool  True always
 1416      */
 1417     function SetLineWidths($which_lw=NULL)
 1418     {
 1419         if (is_array($which_lw)) {
 1420             $this->line_widths = $which_lw; // Use provided array
 1421         } elseif (!is_null($which_lw)) {
 1422             $this->line_widths = array($which_lw); // Convert value to array
 1423         }
 1424         return TRUE;
 1425     }
 1426 
 1427     /**
 1428      * Sets the line style (solid or dashed) for each data set
 1429      *
 1430      * @param string[]|string $which_ls  Array or single keyword: solid | dashed | none
 1431      * @return bool  True always
 1432      */
 1433     function SetLineStyles($which_ls=NULL)
 1434     {
 1435         if (is_array($which_ls)) {
 1436             $this->line_styles = $which_ls; // Use provided array
 1437         } elseif (!is_null($which_ls)) {
 1438             $this->line_styles = ($which_ls) ? array($which_ls) : array('solid');
 1439         }
 1440         return TRUE;
 1441     }
 1442 
 1443 /////////////////////////////////////////////
 1444 //////////////                 TEXT and FONTS
 1445 /////////////////////////////////////////////
 1446 
 1447     /**
 1448      * Sets spacing between lines of multi-line labels
 1449      *
 1450      * @param int $which_spc Text line spacing factor (pixels for GD text, scale control for TTF text)
 1451      * @return bool  True always
 1452      */
 1453     function SetLineSpacing($which_spc)
 1454     {
 1455         $this->line_spacing = $which_spc;
 1456         return TRUE;
 1457     }
 1458 
 1459     /**
 1460      * Sets the default font type
 1461      *
 1462      * @param bool $which_ttf  True to default to TrueType fonts, False to default to GD (fixed) fonts
 1463      * @return bool  True (False on error if an error handler returns True)
 1464      */
 1465     function SetUseTTF($which_ttf)
 1466     {
 1467         $this->use_ttf = $which_ttf;
 1468         return $this->SetDefaultFonts();
 1469     }
 1470 
 1471     /**
 1472      * Sets the default TrueType font directory
 1473      *
 1474      * @param string $which_path  Full path to a directory containing TrueType fonts
 1475      * @return bool  True (False on error if an error handler returns True)
 1476      */
 1477     function SetTTFPath($which_path)
 1478     {
 1479         if (!is_dir($which_path) || !is_readable($which_path)) {
 1480             return $this->PrintError("SetTTFPath(): $which_path is not a valid path.");
 1481         }
 1482         $this->ttf_path = $which_path;
 1483         return TRUE;
 1484     }
 1485 
 1486     /**
 1487      * Sets the default TrueType font, and resets all elements to use that TrueType font and default sizes
 1488      *
 1489      * @param string $which_font  Font filename or path; omit or NULL to use a default font
 1490      * @return bool  True (False on error if an error handler returns True)
 1491      */
 1492     function SetDefaultTTFont($which_font = NULL)
 1493     {
 1494         $this->default_ttfont = $which_font;
 1495         return $this->SetUseTTF(TRUE);
 1496     }
 1497 
 1498     /**
 1499      * Returns the default TrueType font name, searching for one if necessary
 1500      *
 1501      * If no default has been set, this tries some likely candidates for a font which
 1502      * can be loaded. If it finds one that works, that becomes the default TT font.
 1503      * If there is no default and it cannot find a working font, it falls back to
 1504      * the original PHPlot default (which will not likely work either).
 1505      *
 1506      * @return string  Default TrueType font filename or pathname
 1507      * @since 5.1.3
 1508      */
 1509     protected function GetDefaultTTFont()
 1510     {
 1511         if (!isset($this->default_ttfont)) {
 1512             // No default font yet. Try some common sans-serif fonts.
 1513             $fonts = array('LiberationSans-Regular.ttf',  // For Linux with a correct GD font search path
 1514                            'Verdana.ttf', 'Arial.ttf', 'Helvetica.ttf', // For Windows, maybe others
 1515                            'liberation/LiberationSans-Regular.ttf',     // For newer Ubuntu etc
 1516                            'ttf-liberation/LiberationSans-Regular.ttf', // For older Debian, Ubuntu, etc
 1517                            'benjamingothic.ttf',  // Original PHPlot default
 1518                           );
 1519             foreach ($fonts as $font) {
 1520                 // First try the font name alone, to see if GD can find and load it.
 1521                 if (@imagettfbbox(10, 0, $font, "1") !== False)
 1522                     break;
 1523                 // If the font wasn't found, try it with the default TTF path in front.
 1524                 $font_with_path = $this->ttf_path . DIRECTORY_SEPARATOR . $font;
 1525                 if (@imagettfbbox(10, 0, $font_with_path, "1") !== False) {
 1526                     $font = $font_with_path;
 1527                     break;
 1528                 }
 1529             }
 1530             // We either have a working font, or are using the last one regardless.
 1531             $this->default_ttfont = $font;
 1532         }
 1533         return $this->default_ttfont;
 1534     }
 1535 
 1536     /**
 1537      * Selects all the default font values and sizes
 1538      *
 1539      * @return bool  True (False on error if an error handler returns True)
 1540      */
 1541     protected function SetDefaultFonts()
 1542     {
 1543         if ($this->use_ttf) {
 1544             // Defaults for use of TrueType fonts:
 1545             return $this->SetFontTTF('generic', '', 8)
 1546                 && $this->SetFontTTF('title', '', 14)
 1547                 && $this->SetFontTTF('legend', '', 8)
 1548                 && $this->SetFontTTF('x_label', '', 6)
 1549                 && $this->SetFontTTF('y_label', '', 6)
 1550                 && $this->SetFontTTF('x_title', '', 10)
 1551                 && $this->SetFontTTF('y_title', '', 10);
 1552         }
 1553         // Defaults for use of GD fonts:
 1554         return $this->SetFontGD('generic', 2)
 1555             && $this->SetFontGD('title', 5)
 1556             && $this->SetFontGD('legend', 2)
 1557             && $this->SetFontGD('x_label', 1)
 1558             && $this->SetFontGD('y_label', 1)
 1559             && $this->SetFontGD('x_title', 3)
 1560             && $this->SetFontGD('y_title', 3);
 1561     }
 1562 
 1563     /**
 1564      * Selects a GD (fixed) font to use for a plot element
 1565      *
 1566      * Available element names are: title legend generic x_label y_label x_title y_title
 1567      *
 1568      * @param string $which_elem  Name of the element to change the font for
 1569      * @param int|string $which_font  GD font number: 1 2 3 4 or 5
 1570      * @param int $which_spacing  Optional spacing in pixels between text lines
 1571      * @return bool  True (False on error if an error handler returns True)
 1572      * @since 5.0.6
 1573      */
 1574     function SetFontGD($which_elem, $which_font, $which_spacing = NULL)
 1575     {
 1576         if ($which_font < 1 || 5 < $which_font) {
 1577             return $this->PrintError(__FUNCTION__ . ': Font size must be 1, 2, 3, 4 or 5');
 1578         }
 1579         if (!$this->CheckOption($which_elem,
 1580                                 'generic, title, legend, x_label, y_label, x_title, y_title',
 1581                                 __FUNCTION__)) {
 1582             return FALSE;
 1583         }
 1584 
 1585         // Store the font parameters: name/size, char cell height and width.
 1586         $this->fonts[$which_elem] = array('ttf' => FALSE,
 1587                                           'font' => $which_font,
 1588                                           'height' => ImageFontHeight($which_font),
 1589                                           'width' => ImageFontWidth($which_font),
 1590                                           'line_spacing' => $which_spacing);
 1591         return TRUE;
 1592     }
 1593 
 1594     /**
 1595      * Selects a TrueType font for to use for a plot element
 1596      *
 1597      * Available element names are: title legend generic x_label y_label x_title y_title
 1598      *
 1599      * @param string $which_elem  Name of the element to change the font for
 1600      * @param string $which_font  TrueType font file or pathname; empty or NULL for the default font
 1601      * @param int $which_size  Optional font size in points (default 12)
 1602      * @param int $which_spacing  Optional line spacing adjustment factor
 1603      * @return bool  True (False on error if an error handler returns True)
 1604      * @since 5.0.6
 1605      */
 1606     function SetFontTTF($which_elem, $which_font, $which_size = 12, $which_spacing = NULL)
 1607     {
 1608         if (!$this->CheckOption($which_elem,
 1609                                 'generic, title, legend, x_label, y_label, x_title, y_title',
 1610                                 __FUNCTION__)) {
 1611             return FALSE;
 1612         }
 1613 
 1614         // Empty font name means use the default font.
 1615         if (empty($which_font))
 1616             $which_font = $this->GetDefaultTTFont();
 1617         $path = $which_font;
 1618 
 1619         // First try the font name directly, if not then try with path.
 1620         // Use GD imagettfbbox() to determine if this is a valid font.
 1621         // The return $bbox is used below, if valid.
 1622         if (($bbox = @imagettfbbox($which_size, 0, $path, "E")) === False) {
 1623             $path = $this->ttf_path . DIRECTORY_SEPARATOR . $which_font;
 1624             if (($bbox = @imagettfbbox($which_size, 0, $path, "E")) === False) {
 1625                 return $this->PrintError(__FUNCTION__ . ": Can't find TrueType font $which_font");
 1626             }
 1627         }
 1628 
 1629         // Calculate the font height and inherent line spacing. TrueType fonts have this information
 1630         // internally, but PHP/GD has no way to directly access it. So get the bounding box size of
 1631         // an upper-case character without descenders, and the baseline-to-baseline height.
 1632         // Note: In practice, $which_size = $height, maybe +/-1 . But which_size is in points,
 1633         // and height is in pixels, and someday GD may be able to tell the difference.
 1634         // The character width is saved too, but not used by the normal text drawing routines - it
 1635         // isn't necessarily a fixed-space font. It is used in DrawLegend.
 1636         $height = $bbox[1] - $bbox[5];
 1637         $width = $bbox[2] - $bbox[0];
 1638         $bbox = ImageTTFBBox($which_size, 0, $path, "E\nE");
 1639         $spacing = $bbox[1] - $bbox[5] - 2 * $height;
 1640 
 1641         // Store the font parameters:
 1642         $this->fonts[$which_elem] = array('ttf' => TRUE,
 1643                                           'font' => $path,
 1644                                           'size' => $which_size,
 1645                                           'height' => $height,
 1646                                           'width' => $width,
 1647                                           'spacing' => $spacing,
 1648                                           'line_spacing' => $which_spacing);
 1649         return TRUE;
 1650     }
 1651 
 1652     /**
 1653      * Selects which font to use for a plot element
 1654      *
 1655      * This uses either GD (fixed) or TrueType fonts, depending on the default text
 1656      * font type as set with SetUseTTF().
 1657      *
 1658      * Available element names are: title legend generic x_label y_label x_title y_title
 1659      *
 1660      * @param string $which_elem  Name of the element to change the font for
 1661      * @param int|string $which_font  For GD fonts, a font number; for TrueType, a font filename or pathname
 1662      * @param int $which_size  Optional font size in points for TrueType fonts, ignored for GD fonts
 1663      * @param int $line_spacing  Optional line spacing adjustment factor
 1664      * @return bool  True (False on error if an error handler returns True)
 1665      */
 1666     function SetFont($which_elem, $which_font, $which_size = 12, $line_spacing = NULL)
 1667     {
 1668         if ($this->use_ttf)
 1669             return $this->SetFontTTF($which_elem, $which_font, $which_size, $line_spacing);
 1670         return $this->SetFontGD($which_elem, $which_font, $line_spacing);
 1671     }
 1672 
 1673     /**
 1674      * Returns the inter-line spacing for a font
 1675      *
 1676      * @param array $font  The font, specified as a PHPlot font array
 1677      * @return int  Spacing between text lines in pixels for text using this font
 1678      * @since 5.0.6
 1679      */
 1680     protected function GetLineSpacing($font)
 1681     {
 1682         // Use the per-font line spacing preference, if set, else the global value:
 1683         if (isset($font['line_spacing']))
 1684             $line_spacing = $font['line_spacing'];
 1685         else
 1686             $line_spacing = $this->line_spacing;
 1687 
 1688         // For GD fonts, that is the spacing in pixels.
 1689         // For TTF, adjust based on the 'natural' font spacing (see SetFontTTF):
 1690         if ($font['ttf']) {
 1691             $line_spacing = (int)($line_spacing * $font['spacing'] / 6.0);
 1692         }
 1693         return $line_spacing;
 1694     }
 1695 
 1696     /*
 1697      * Text drawing and sizing functions:
 1698      * ProcessText is meant for use only by DrawText and SizeText.
 1699      *    ProcessText(True, ...)  - Draw a block of text
 1700      *    ProcessText(False, ...) - Just return ($width, $height) of
 1701      *       the orthogonal bounding box containing the text.
 1702      * ProcessText is further split into separate functions for GD and TTF
 1703      * text, due to the size of the code.
 1704      *
 1705      * Horizontal and vertical alignment are relative to the drawing. That is:
 1706      * vertical text (90 deg) gets centered along Y position with
 1707      * v_align = 'center', and adjusted to the right of X position with
 1708      * h_align = 'right'.  Another way to look at this is to say
 1709      * that text rotation happens first, then alignment.
 1710      *
 1711      * Original multiple lines code submitted by Remi Ricard.
 1712      * Original vertical code submitted by Marlin Viss.
 1713      *
 1714      * Text routines rewritten by ljb to fix alignment and position problems.
 1715      * Here is my explanation and notes.
 1716      *
 1717      *    + Process TTF text one line at a time, not as a block. (See below)
 1718      *    + Flipped top vs bottom vertical alignment. The usual interpretation
 1719      *  is: bottom align means bottom of the text is at the specified Y
 1720      *  coordinate. For some reason, PHPlot did left/right the correct way,
 1721      *  but had top/bottom reversed. I fixed it, and left the default valign
 1722      *  argument as bottom, but the meaning of the default value changed.
 1723      *
 1724      *    For GD font text, only single-line text is handled by GD, and the
 1725      *  basepoint is the upper left corner of each text line.
 1726      *    For TTF text, multi-line text could be handled by GD, with the text
 1727      *  basepoint at the lower left corner of the first line of text.
 1728      *  (Behavior of TTF drawing routines on multi-line text is not documented.)
 1729      *  But you cannot do left/center/right alignment on each line that way,
 1730      *  or proper line spacing.
 1731      *    Therefore, for either text type, we have to break up the text into
 1732      *  lines and position each line independently.
 1733      *
 1734      *    There are 9 alignment modes: Horizontal = left, center, or right, and
 1735      *  Vertical = top, center, or bottom. Alignment is interpreted relative to
 1736      *  the image, not as the text is read. This makes sense when you consider
 1737      *  for example X axis labels. They need to be centered below the marks
 1738      *  (center, top alignment) regardless of the text angle.
 1739      *  'Bottom' alignment really means baseline alignment.
 1740      *
 1741      *    GD font text is supported (by libgd) at 0 degrees and 90 degrees only.
 1742      *  Multi-line or single line text works with any of the 9 alignment modes.
 1743      *
 1744      *    TTF text can be at any angle. The 9 alignment modes work for all angles,
 1745      *  but the results might not be what you expect for multi-line text.  Alignment
 1746      *  applies to the orthogonal (aligned with X and Y axes) bounding box that
 1747      *  contains the text, and to each line in the multi-line text box. Since
 1748      *  alignment is relative to the image, 45 degree multi-line text aligns
 1749      *  differently from 46 degree text.
 1750      *
 1751      *    Note that PHPlot allows multi-line text for the 3 titles, and they
 1752      *  are only drawn at 0 degrees (main and X titles) or 90 degrees (Y title).
 1753      *  Data labels can also be multi-line, and they can be drawn at any angle.
 1754      *  -ljb 2007-11-03
 1755      *
 1756      */
 1757 
 1758     /**
 1759      * Draws or returns the size of a text string using GD fixed fonts
 1760      *
 1761      * This is intended for use only by ProcessText(). See notes there, but note that
 1762      * the $font and alignment parameters are pre-processed there and differ here.
 1763      * GD text only supports 0 and 90 degrees. This function treats an angle >= 45
 1764      * as 90 degrees, and < 45 as 0 degrees.
 1765      *
 1766      * @param bool $draw_it  True to draw the text, False to just return the orthogonal width and height
 1767      * @param array $font  A PHPlot font array (with 'ttf' = False)
 1768      * @param float $angle  Text angle in degrees. GD only supports 0 and 90.
 1769      * @param int $x  Reference point X coordinate for the text (ignored if $draw_it is False)
 1770      * @param int $y  Reference point Y coordinate for the text (ignored if $draw_it is False)
 1771      * @param int $color  GD color index to use for drawing the text (ignored if $draw_it is False)
 1772      * @param string $text  The text to draw or size (can have newlines \n within)
 1773      * @param float $h_factor  Horizontal alignment factor: 0=left|0.5=center|1=right (ignored if !$draw_it)
 1774      * @param float $v_factor  Vertical alignment factor: 0=top|0.5=center|1=bottom (ignored if !$draw_it)
 1775      * @return bool|int[]  True, if drawing text; an array of ($width, $height) if not.
 1776      * @since 5.0.5
 1777      */
 1778     protected function ProcessTextGD($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor)
 1779     {
 1780         // Extract font parameters:
 1781         $font_number = $font['font'];
 1782         $font_width = $font['width'];
 1783         $font_height = $font['height'];
 1784         $line_spacing = $this->GetLineSpacing($font);
 1785 
 1786         // Break up the text into lines, trim whitespace, find longest line.
 1787         // Save the lines and length for drawing below.
 1788         $longest = 0;
 1789         foreach (explode("\n", $text) as $each_line) {
 1790             $lines[] = $line = trim($each_line);
 1791             $line_lens[] = $line_len = strlen($line);
 1792             if ($line_len > $longest) $longest = $line_len;
 1793         }
 1794         $n_lines = count($lines);
 1795 
 1796         // Width, height are based on font size and longest line, line count respectively.
 1797         // These are relative to the text angle.
 1798         $total_width = $longest * $font_width;
 1799         $total_height = $n_lines * $font_height + ($n_lines - 1) * $line_spacing;
 1800 
 1801         if (!$draw_it) {
 1802             if ($angle < 45) return array($total_width, $total_height);
 1803             return array($total_height, $total_width);
 1804         }
 1805 
 1806         $interline_step = $font_height + $line_spacing; // Line-to-line step
 1807 
 1808         if ($angle >= 45) {
 1809             // Vertical text (90 degrees):
 1810             // (Remember the alignment convention with vertical text)
 1811             // For 90 degree text, alignment factors change like this:
 1812             $temp = $v_factor;
 1813             $v_factor = $h_factor;
 1814             $h_factor = 1 - $temp;
 1815 
 1816             $draw_func = 'ImageStringUp';
 1817 
 1818             // Rotation matrix "R" for 90 degrees (with Y pointing down):
 1819             $r00 = 0;  $r01 = 1;
 1820             $r10 = -1; $r11 = 0;
 1821 
 1822         } else {
 1823             // Horizontal text (0 degrees):
 1824             $draw_func = 'ImageString';
 1825 
 1826             // Rotation matrix "R" for 0 degrees:
 1827             $r00 = 1; $r01 = 0;
 1828             $r10 = 0; $r11 = 1;
 1829         }
 1830 
 1831         // Adjust for vertical alignment (horizontal text) or horizontal alignment (vertical text):
 1832         $factor = (int)($total_height * $v_factor);
 1833         $xpos = $x - $r01 * $factor;
 1834         $ypos = $y - $r11 * $factor;
 1835 
 1836         // Debug callback provides the bounding box:
 1837         if ($this->GetCallback('debug_textbox')) {
 1838             if ($angle >= 45) {
 1839                 $bbox_width  = $total_height;
 1840                 $bbox_height = $total_width;
 1841                 $px = $xpos;
 1842                 $py = $ypos - (1 - $h_factor) * $total_width;
 1843             } else {
 1844                 $bbox_width  = $total_width;
 1845                 $bbox_height = $total_height;
 1846                 $px = $xpos - $h_factor * $total_width;
 1847                 $py = $ypos;
 1848             }
 1849             $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
 1850         }
 1851 
 1852         for ($i = 0; $i < $n_lines; $i++) {
 1853 
 1854             // Adjust for alignment of this line within the text block:
 1855             $factor = (int)($line_lens[$i] * $font_width * $h_factor);
 1856             $x = $xpos - $r00 * $factor;
 1857             $y = $ypos - $r10 * $factor;
 1858 
 1859             // Call ImageString or ImageStringUp:
 1860             $draw_func($this->img, $font_number, $x, $y, $lines[$i], $color);
 1861 
 1862             // Step to the next line of text. This is a rotation of (x=0, y=interline_spacing)
 1863             $xpos += $r01 * $interline_step;
 1864             $ypos += $r11 * $interline_step;
 1865         }
 1866         return TRUE;
 1867     }
 1868 
 1869     /**
 1870      * Draws or returns the size of a text string using TrueType fonts (TTF)
 1871      *
 1872      * This is intended for use only by ProcessText(). See notes there, but note that
 1873      * the $font and alignment parameters are pre-processed there and differ here.
 1874      *
 1875      * @param bool $draw_it  True to draw the text, False to just return the orthogonal width and height
 1876      * @param array $font  A PHPlot font array (with 'ttf' = True)
 1877      * @param float $angle  Text angle in degrees
 1878      * @param int $x  Reference point X coordinate for the text (ignored if $draw_it is False)
 1879      * @param int $y  Reference point Y coordinate for the text (ignored if $draw_it is False)
 1880      * @param int $color  GD color index to use for drawing the text (ignored if $draw_it is False)
 1881      * @param string $text  The text to draw or size (can have newlines \n within)
 1882      * @param float $h_factor  Horizontal alignment factor: 0=left|0.5=center|1=right (ignored if !$draw_it)
 1883      * @param float $v_factor  Vertical alignment factor: 0=top|0.5=center|1=bottom (ignored if !$draw_it)
 1884      * @return bool|int[]  True, if drawing text; an array of ($width, $height) if not.
 1885      * @since 5.0.5
 1886      */
 1887     protected function ProcessTextTTF($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor)
 1888     {
 1889         // Extract font parameters (see SetFontTTF):
 1890         $font_file = $font['font'];
 1891         $font_size = $font['size'];
 1892         $font_height = $font['height'];
 1893         $line_spacing = $this->GetLineSpacing($font);
 1894 
 1895         // Break up the text into lines, trim whitespace.
 1896         // Calculate the total width and height of the text box at 0 degrees.
 1897         // Save the trimmed lines and their widths for later when drawing.
 1898         // To get uniform spacing, don't use the actual line heights.
 1899         // Total height = Font-specific line heights plus inter-line spacing.
 1900         // Total width = width of widest line.
 1901         // Last Line Descent is the offset from the bottom to the text baseline.
 1902         // Note: For some reason, ImageTTFBBox uses (-1,-1) as the reference point.
 1903         //   So 1+bbox[1] is the baseline to bottom distance.
 1904         $total_width = 0;
 1905         $lastline_descent = 0;
 1906         foreach (explode("\n", $text) as $each_line) {
 1907             $lines[] = $line = trim($each_line);
 1908             $bbox = ImageTTFBBox($font_size, 0, $font_file, $line);
 1909             $line_widths[] = $width = $bbox[2] - $bbox[0];
 1910             if ($width > $total_width) $total_width = $width;
 1911             $lastline_descent = 1 + $bbox[1];
 1912         }
 1913         $n_lines = count($lines);
 1914         $total_height = $n_lines * $font_height + ($n_lines - 1) * $line_spacing;
 1915 
 1916         // Calculate the rotation matrix for the text's angle. Remember that GD points Y down,
 1917         // so the sin() terms change sign.
 1918         $theta = deg2rad($angle);
 1919         $cos_t = cos($theta);
 1920         $sin_t = sin($theta);
 1921         $r00 = $cos_t;    $r01 = $sin_t;
 1922         $r10 = -$sin_t;   $r11 = $cos_t;
 1923 
 1924         // Make a bounding box of the right size, with upper left corner at (0,0).
 1925         // By convention, the point order is: LL, LR, UR, UL.
 1926         // Note this is still working with the text at 0 degrees.
 1927         // When sizing text (SizeText), use the overall size with descenders.
 1928         //   This tells the caller how much room to leave for the text.
 1929         // When drawing text (DrawText), use the size without descenders - that
 1930         //   is, down to the baseline. This is for accurate positioning.
 1931         $b[0] = 0;
 1932         if ($draw_it) {
 1933             $b[1] = $total_height;
 1934         } else {
 1935             $b[1] = $total_height + $lastline_descent;
 1936         }
 1937         $b[2] = $total_width;  $b[3] = $b[1];
 1938         $b[4] = $total_width;  $b[5] = 0;
 1939         $b[6] = 0;             $b[7] = 0;
 1940 
 1941         // Rotate the bounding box, then offset to the reference point:
 1942         for ($i = 0; $i < 8; $i += 2) {
 1943             $x_b = $b[$i];
 1944             $y_b = $b[$i+1];
 1945             $c[$i]   = $x + $r00 * $x_b + $r01 * $y_b;
 1946             $c[$i+1] = $y + $r10 * $x_b + $r11 * $y_b;
 1947         }
 1948 
 1949         // Get an orthogonal (aligned with X and Y axes) bounding box around it, by
 1950         // finding the min and max X and Y:
 1951         $bbox_ref_x = $bbox_max_x = $c[0];
 1952         $bbox_ref_y = $bbox_max_y = $c[1];
 1953         for ($i = 2; $i < 8; $i += 2) {
 1954             $x_b = $c[$i];
 1955             if ($x_b < $bbox_ref_x) $bbox_ref_x = $x_b;
 1956             elseif ($bbox_max_x < $x_b) $bbox_max_x = $x_b;
 1957             $y_b = $c[$i+1];
 1958             if ($y_b < $bbox_ref_y) $bbox_ref_y = $y_b;
 1959             elseif ($bbox_max_y < $y_b) $bbox_max_y = $y_b;
 1960         }
 1961         $bbox_width = $bbox_max_x - $bbox_ref_x;
 1962         $bbox_height = $bbox_max_y - $bbox_ref_y;
 1963 
 1964         if (!$draw_it) {
 1965             // Return the bounding box, rounded up (so it always contains the text):
 1966             return array((int)ceil($bbox_width), (int)ceil($bbox_height));
 1967         }
 1968 
 1969         $interline_step = $font_height + $line_spacing; // Line-to-line step
 1970 
 1971         // Calculate the offsets from the supplied reference point to the
 1972         // upper-left corner of the text.
 1973         // Start at the reference point at the upper left corner of the bounding
 1974         // box (bbox_ref_x, bbox_ref_y) then adjust it for the 9 point alignment.
 1975         // h,v_factor are 0,0 for top,left, .5,.5 for center,center, 1,1 for bottom,right.
 1976         //    $off_x = $bbox_ref_x + $bbox_width * $h_factor - $x;
 1977         //    $off_y = $bbox_ref_y + $bbox_height * $v_factor - $y;
 1978         // Then use that offset to calculate back to the supplied reference point x, y
 1979         // to get the text base point.
 1980         //    $qx = $x - $off_x;
 1981         //    $qy = $y - $off_y;
 1982         // Reduces to:
 1983         $qx = 2 * $x - $bbox_ref_x - $bbox_width * $h_factor;
 1984         $qy = 2 * $y - $bbox_ref_y - $bbox_height * $v_factor;
 1985 
 1986         // Check for debug callback. Don't calculate bounding box unless it is wanted.
 1987         if ($this->GetCallback('debug_textbox')) {
 1988             // Calculate the orthogonal bounding box coordinates for debug testing.
 1989 
 1990             // qx, qy is upper left corner relative to the text.
 1991             // Calculate px,py: upper left corner (absolute) of the bounding box.
 1992             // There are 4 equation sets for this, depending on the quadrant:
 1993             if ($sin_t > 0) {
 1994                 if ($cos_t > 0) {
 1995                     // Quadrant: 0d - 90d:
 1996                     $px = $qx; $py = $qy - $total_width * $sin_t;
 1997                 } else {
 1998                     // Quadrant: 90d - 180d:
 1999                    $px = $qx + $total_width * $cos_t; $py = $qy - $bbox_height;
 2000                 }
 2001             } else {
 2002                 if ($cos_t < 0) {
 2003                     // Quadrant: 180d - 270d:
 2004                     $px = $qx - $bbox_width; $py = $qy + $total_height * $cos_t;
 2005                 } else {
 2006                     // Quadrant: 270d - 360d:
 2007                     $px = $qx + $total_height * $sin_t; $py = $qy;
 2008                 }
 2009             }
 2010             $this->DoCallback('debug_textbox', $px, $py, $bbox_width, $bbox_height);
 2011         }
 2012 
 2013         // Since alignment is applied after rotation, which parameter is used
 2014         // to control alignment of each line within the text box varies with
 2015         // the angle.
 2016         //   Angle (degrees):       Line alignment controlled by:
 2017         //  -45 < angle <= 45          h_align
 2018         //   45 < angle <= 135         reversed v_align
 2019         //  135 < angle <= 225         reversed h_align
 2020         //  225 < angle <= 315         v_align
 2021         if ($cos_t >= $sin_t) {
 2022             if ($cos_t >= -$sin_t) $line_align_factor = $h_factor;
 2023             else $line_align_factor = $v_factor;
 2024         } else {
 2025             if ($cos_t >= -$sin_t) $line_align_factor = 1-$v_factor;
 2026             else $line_align_factor = 1-$h_factor;
 2027         }
 2028 
 2029         // Now we have the start point, spacing and in-line alignment factor.
 2030         // We are finally ready to start drawing the text, line by line.
 2031         for ($i = 0; $i < $n_lines; $i++) {
 2032 
 2033             // For drawing TTF text, the reference point is the left edge of the
 2034             // text baseline (not the lower left corner of the bounding box).
 2035             // The following also adjusts for horizontal (relative to
 2036             // the text) alignment of the current line within the box.
 2037             // What is happening is rotation of this vector by the text angle:
 2038             //    (x = (total_width - line_width) * factor, y = font_height)
 2039 
 2040             $width_factor = ($total_width - $line_widths[$i]) * $line_align_factor;
 2041             $rx = $qx + $r00 * $width_factor + $r01 * $font_height;
 2042             $ry = $qy + $r10 * $width_factor + $r11 * $font_height;
 2043 
 2044             // Finally, draw the text:
 2045             ImageTTFText($this->img, $font_size, $angle, $rx, $ry, $color, $font_file, $lines[$i]);
 2046 
 2047             // Step to position of next line.
 2048             // This is a rotation of (x=0,y=height+line_spacing) by $angle:
 2049             $qx += $r01 * $interline_step;
 2050             $qy += $r11 * $interline_step;
 2051         }
 2052         return TRUE;
 2053     }
 2054 
 2055     /**
 2056      * Draws or returns the size of a text string
 2057      *
 2058      * This is intended for use by DrawText() and SizeText() exclusively. It hides the
 2059      * differences between GD and TTF text from those and higher level functions. It uses
 2060      * either ProcessTextTTF() or ProcessTextGD() to do the actual drawing or sizing.
 2061      *
 2062      * @param bool $draw_it  True to draw the text, False to just return the orthogonal width and height
 2063      * @param string|array|null $font_id  Text element name, empty or NULL for 'generic', or font array
 2064      * @param float $angle  Text angle in degrees
 2065      * @param int $x  Reference point X coordinate for the text (ignored if $draw_it is False)
 2066      * @param int $y  Reference point Y coordinate for the text (ignored if $draw_it is False)
 2067      * @param int $color  GD color index to use for drawing the text (ignored if $draw_it is False)
 2068      * @param string $text  The text to draw or size (can have newlines \n within)
 2069      * @param string $halign  Horizontal alignment: left |  center | right (ignored if $draw_it is False)
 2070      * @param string $valign  Vertical alignment: top | center | bottom (ignored if $draw_it is False)
 2071      * @return bool|int[]  True, if drawing text; an array of ($width, $height) if not.
 2072      */
 2073     protected function ProcessText($draw_it, $font_id, $angle, $x, $y, $color, $text, $halign, $valign)
 2074     {
 2075         // Empty text case:
 2076         if ($text === '') {
 2077             if ($draw_it) return TRUE;
 2078             return array(0, 0);
 2079         }
 2080 
 2081         // Calculate width and height offset factors using the alignment args:
 2082         if ($valign == 'top') $v_factor = 0;
 2083         elseif ($valign == 'center') $v_factor = 0.5;
 2084         else $v_factor = 1.0; // 'bottom'
 2085         if ($halign == 'left') $h_factor = 0;
 2086         elseif ($halign == 'center') $h_factor = 0.5;
 2087         else $h_factor = 1.0; // 'right'
 2088 
 2089         // Preferred usage for $font_id is a text element name (see SetFont()), but for compatibility
 2090         // accept a font array too. For external (callback) usage, support a default font.
 2091         if (is_array($font_id)) $font = $font_id; // Use supplied array; deprecated
 2092         elseif (!empty($font_id) && isset($this->fonts[$font_id])) $font = $this->fonts[$font_id];
 2093         else $font = $this->fonts['generic']; // Fallback default, or font_id is empty.
 2094 
 2095         if ($font['ttf']) {
 2096             return $this->ProcessTextTTF($draw_it, $font, $angle, $x, $y, $color, $text,
 2097                                          $h_factor, $v_factor);
 2098         }
 2099         return $this->ProcessTextGD($draw_it, $font, $angle, $x, $y, $color, $text, $h_factor, $v_factor);
 2100     }
 2101 
 2102     /**
 2103      * Draws a string of text on the plot
 2104      *
 2105      * Note: This function is mostly for internal use, and is not documented
 2106      * for public use. But it can be legitimately used from a PHPlot callback.
 2107      *
 2108      * @param string|array|null $which_font  Text element name, empty or NULL for 'generic', or font array
 2109      * @param float $which_angle  Text angle in degrees
 2110      * @param int $which_xpos  Reference point X coordinate for the text
 2111      * @param int $which_ypos  Reference point Y coordinate for the text
 2112      * @param int $which_color  GD color index to use for drawing the text
 2113      * @param string $which_text  The text to draw, with newlines (\n) between lines
 2114      * @param string $which_halign  Horizontal alignment (relative to the image): left | center | right
 2115      * @param string $which_valign  Vertical alignment (relative to the image): top | center | bottom
 2116      * @return bool  True always
 2117      */
 2118     function DrawText($which_font, $which_angle, $which_xpos, $which_ypos, $which_color, $which_text,
 2119                       $which_halign = 'left', $which_valign = 'bottom')
 2120     {
 2121         return $this->ProcessText(TRUE,
 2122                            $which_font, $which_angle, $which_xpos, $which_ypos,
 2123                            $which_color, $which_text, $which_halign, $which_valign);
 2124     }
 2125 
 2126     /**
 2127      * Calculates the size of block of text
 2128      *
 2129      * The returned size is the orthogonal width and height of a bounding
 2130      * box aligned with the X and Y axes of the text. Only for angle=0 is
 2131      * this the actual width and height of the text block, but for any angle
 2132      * it is the amount of space needed to contain the text.
 2133      * Note: This function is mostly for internal use, and is not documented
 2134      * for public use. But it can be legitimately used from a PHPlot callback.
 2135      *
 2136      * @param string|array|null $which_font  Text element name, empty or NULL for 'generic', or font array
 2137      * @param float $which_angle  Text angle in degrees
 2138      * @param string $which_text  The text to calculate size of
 2139      * @return int[]  A two element array ($width, $height) of the text
 2140      */
 2141     function SizeText($which_font, $which_angle, $which_text)
 2142     {
 2143         // Color, position, and alignment are not used when calculating the size.
 2144         return $this->ProcessText(FALSE,
 2145                            $which_font, $which_angle, 0, 0, 1, $which_text, '', '');
 2146     }
 2147 
 2148 /////////////////////////////////////////////
 2149 ///////////            INPUT / OUTPUT CONTROL
 2150 /////////////////////////////////////////////
 2151 
 2152     /**
 2153      * Selects the graphic image format generated by DrawGraph()
 2154      *
 2155      * @param string $format  The format to use: jpg | png | gif | wbmp
 2156      * @return bool  True (False on error if an error handler returns True)
 2157      */
 2158     function SetFileFormat($format)
 2159     {
 2160         $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__);
 2161         if (!$asked) return FALSE;
 2162         switch ($asked) {
 2163         case 'jpg':
 2164             $format_test = IMG_JPG;
 2165             break;
 2166         case 'png':
 2167             $format_test = IMG_PNG;
 2168             break;
 2169         case 'gif':
 2170             $format_test = IMG_GIF;
 2171             break;
 2172         case 'wbmp':
 2173             $format_test = IMG_WBMP;
 2174             break;
 2175         }
 2176         if (!(imagetypes() & $format_test)) {
 2177             return $this->PrintError("SetFileFormat(): File format '$format' not supported");
 2178         }
 2179         $this->file_format = $asked;
 2180         return TRUE;
 2181     }
 2182 
 2183     /**
 2184      * Sets an image file to be used as the image background
 2185      *
 2186      * @param string $input_file  Path to the file to be used (jpeg, png or gif)
 2187      * @param string $mode   Optional method for the background: centeredtile | tile | scale
 2188      * @return bool  True (False on error if an error handler returns True)
 2189      */
 2190     function SetBgImage($input_file, $mode='centeredtile')
 2191     {
 2192         $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
 2193         $this->bgimg  = $input_file;
 2194         return (boolean)$this->bgmode;
 2195     }
 2196 
 2197     /**
 2198      * Sets an image file to be used as the plot area background
 2199      *
 2200      * @param string $input_file  Path to the file to be used (jpeg, png or gif)
 2201      * @param string $mode   Optional method for the background: centeredtile | tile | scale
 2202      * @return bool  True (False on error if an error handler returns True)
 2203      */
 2204     function SetPlotAreaBgImage($input_file, $mode='tile')
 2205     {
 2206         $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
 2207         $this->plotbgimg  = $input_file;
 2208         return (boolean)$this->plotbgmode;
 2209     }
 2210 
 2211     /**
 2212      * Redirects PHPlot output to a file
 2213      *
 2214      * Note: Output file has no effect unless SetIsInline(TRUE) is called.
 2215      *
 2216      * @param string $which_output_file  Pathname of the file to write the image data into
 2217      * @return bool  True always
 2218      */
 2219     function SetOutputFile($which_output_file)
 2220     {
 2221         if (isset($which_output_file) && $which_output_file !== '')
 2222             $this->output_file = $which_output_file;
 2223         else
 2224             $this->output_file = NULL;
 2225         return TRUE;
 2226     }
 2227 
 2228     /**
 2229      * Sets the output image to be inline, without HTTP headers
 2230      *
 2231      * This will suppress the Content-Type headers that would otherwise be sent.
 2232      *
 2233      * @param bool $which_ii  True to suppress HTTP headers, False to include the headers
 2234      * @return bool  True always
 2235      */
 2236     function SetIsInline($which_ii)
 2237     {
 2238         $this->is_inline = (bool)$which_ii;
 2239         return TRUE;
 2240     }
 2241 
 2242     /**
 2243      * Gets the MIME type and GD output function name for the current file type
 2244      *
 2245      * @param string $mime_type  Reference variable where the MIME type is returned
 2246      * @param string $output_f  Reference variable where the GD output function name is returned
 2247      * @return bool  True (False on error if an error handler returns True)
 2248      * @since 5.5.0
 2249      */
 2250     protected function GetImageType(&$mime_type, &$output_f)
 2251     {
 2252         switch ($this->file_format) {
 2253         case 'png':
 2254             $mime_type = 'image/png';
 2255             $output_f = 'imagepng';
 2256             break;
 2257         case 'jpg':
 2258             $mime_type = 'image/jpeg';
 2259             $output_f = 'imagejpeg';
 2260             break;
 2261         case 'gif':
 2262             $mime_type = 'image/gif';
 2263             $output_f = 'imagegif';
 2264             break;
 2265         case 'wbmp':
 2266             $mime_type = 'image/wbmp';
 2267             $output_f = 'imagewbmp';
 2268             break;
 2269         default:
 2270             // Report the error on PrintImage, because that is where this code used to be.
 2271             return $this->PrintError('PrintImage(): Please select an image type!');
 2272         }
 2273         return TRUE;
 2274     }
 2275 
 2276     /**
 2277      * Tells the browser not to cache the image
 2278      *
 2279      * This is used by PrintImage, depending on SetBrowserCache(). It sends
 2280      * HTTP headers that discourage browser-side caching.
 2281      * Originally submitted by Thiemo Nagel. Modified to add more options based on mjpg-streamer.
 2282      *
 2283      * @return bool  True always
 2284      * @since 5.8.0
 2285      */
 2286     protected function DisableCaching()
 2287     {
 2288         header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
 2289         header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
 2290         header('Cache-Control: no-store, no-cache, must-revalidate, pre-check=0, post-check=0, max-age=0');
 2291         header('Pragma: no-cache');
 2292         return TRUE;
 2293     }
 2294 
 2295     /**
 2296      * Outputs the generated image to standard output or to a file
 2297      *
 2298      * This is automatically called by DrawGraph(), unless SetPrintImage(False) was used.
 2299      *
 2300      * @return bool  True always
 2301      */
 2302     function PrintImage()
 2303     {
 2304         if (!$this->browser_cache && !$this->is_inline)
 2305             $this->DisableCaching();
 2306 
 2307         // Get MIME type and GD output function name:
 2308         if (!$this->GetImageType($mime_type, $output_f)) return FALSE;
 2309 
 2310         if (!$this->is_inline) {
 2311             Header("Content-type: $mime_type");
 2312         }
 2313         if ($this->is_inline && isset($this->output_file)) {
 2314             $output_f($this->img, $this->output_file);
 2315         } else {
 2316             $output_f($this->img);
 2317         }
 2318         return TRUE;
 2319     }
 2320 
 2321     /**
 2322      * Returns the image data as raw data, base64 encoded, or data URL (see RFC2397)
 2323      *
 2324      * @param string $encoding  Optional encoding to use: dataurl (default) | raw | base64
 2325      * @return string  Encoded image data (False on error if error handler returns True)
 2326      * @since 5.5.0
 2327      */
 2328     function EncodeImage($encoding = 'dataurl')
 2329     {
 2330         $enc = $this->CheckOption($encoding, 'dataurl, raw, base64', __FUNCTION__);
 2331         if (!$enc || !$this->GetImageType($mime_type, $output_f)) return FALSE;
 2332         ob_start();
 2333         $output_f($this->img);
 2334         switch ($enc) {
 2335         case 'raw':
 2336             return ob_get_clean();
 2337         case 'base64':
 2338             return base64_encode(ob_get_clean());
 2339         default:  // 'dataurl', checked above.
 2340             return "data:$mime_type;base64,\n" . chunk_split(base64_encode(ob_get_clean()));
 2341         }
 2342     }
 2343 
 2344     /**
 2345      * Draws a text message on the image, replacing the plot
 2346      *
 2347      * This is used for PHPlot error handling, and is available for
 2348      * application-level error handling or other special purposes.
 2349      * Options (keys in $options) are: draw_background draw_border force_print
 2350      *   reset_font text_color text_wrap wrap_width.
 2351      * Default option values were chosen for fail-safe error-handling.
 2352      *
 2353      * @param string $text  Text of the message to display in the image
 2354      * @param string[] $options  Optional associative array of control options
 2355      * @return bool  True always
 2356      * @since 5.7.0
 2357      */
 2358     function DrawMessage($text, $options = NULL)
 2359     {
 2360         // Merge options with defaults, and set as local variables:
 2361         extract( array_merge( array(
 2362             'draw_background' => FALSE,  // Draw image background per SetBgImage(), SetBackgroundColor()
 2363             'draw_border' => FALSE,      // Draw image border as set with SetBorder*()
 2364             'force_print' => TRUE,       // Ignore SetPrintImage() setting and always output
 2365             'reset_font' => TRUE,        // Reset fonts (to avoid possible TTF error)
 2366             'text_color' => '',          // If not empty, text color specification
 2367             'text_wrap' => TRUE,         // Wrap the message text with wordwrap()
 2368             'wrap_width' => 75,          // Width in characters for wordwrap()
 2369                 ), (array)$options));
 2370 
 2371         // Do colors, background, and border:
 2372         if ($draw_border && !isset($this->ndx_i_border) || $draw_background && !isset($this->ndx_bg_color))
 2373             $this->SetBgColorIndexes();
 2374         if ($draw_background) {  // User-specified background
 2375             $this->DrawBackground(TRUE);  // TRUE means force overwriting of background
 2376         } else {  // Default to plain white background
 2377             $bgcolor = imagecolorresolve($this->img, 255, 255, 255);
 2378             ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, $bgcolor);
 2379         }
 2380         if ($draw_border) $this->DrawImageBorder(TRUE);
 2381         if (empty($text_color)) $rgb = array(0, 0, 0);
 2382         else $rgb = $this->SetRGBColor($text_color);
 2383         $ndx_text_color = imagecolorresolve($this->img, $rgb[0], $rgb[1], $rgb[2]);
 2384 
 2385         // Error images should reset fonts, to avoid chance of a TTF error when displaying an error.
 2386         if ($reset_font) $this->SetUseTTF(FALSE);
 2387 
 2388         // Determine the space needed for the text, and center the text box within the image:
 2389         if ($text_wrap) $text = wordwrap($text, $wrap_width);
 2390         list($text_width, $text_height) = $this->SizeText('generic', 0, $text);
 2391         $x = max($this->safe_margin, ($this->image_width - $text_width) / 2);
 2392         $y = max($this->safe_margin, ($this->image_height - $text_height) / 2);
 2393         $this->DrawText('generic', 0, $x, $y, $ndx_text_color, $text, 'left', 'top');
 2394         if ($force_print || $this->print_image) $this->PrintImage();
 2395         return TRUE;
 2396     }
 2397 
 2398     /**
 2399      * Handles a fatal error in PHPlot
 2400      *
 2401      * When a fatal error occurs (most often a parameter error in a function call),
 2402      * PHPlot will normally produce an image containing the error message,
 2403      * output the image, and then trigger a user-level error with the message.
 2404      * (The error message has to be presented as an image, because the browser is
 2405      * expecting an image.)
 2406      * If no error handler is set up, PHP will log the message and exit.
 2407      * If there is an error handler, and it returns TRUE, PrintError returns FALSE,
 2408      * and this will be passed back up to the calling code as a FALSE return.
 2409      *
 2410      * @param string $error_message  Text of the error message
 2411      * @return bool  False, but only if there is an error handler that returned TRUE.
 2412      */
 2413     protected function PrintError($error_message)
 2414     {
 2415         // Be sure not to loop recursively, e.g. PrintError - PrintImage - PrintError.
 2416         if (isset($this->in_error)) return FALSE;
 2417         $this->in_error = TRUE;
 2418 
 2419         // Output an image containing the error message:
 2420         if (!$this->suppress_error_image) {
 2421             // img will be empty if the error occurs very early - e.g. when allocating the image.
 2422             if (!empty($this->img)) {
 2423                 $this->DrawMessage($error_message);
 2424             } elseif (!$this->is_inline) {
 2425                 Header('HTTP/1.0 500 Internal Server Error');
 2426             }
 2427         }
 2428         trigger_error($error_message, E_USER_ERROR);
 2429         // This is only reached if the error handler returns TRUE
 2430         unset($this->in_error);
 2431         return FALSE;
 2432     }
 2433 
 2434     /**
 2435      * Enables or disables error image production on failure
 2436      *
 2437      * @param bool $error_image  True to enable the error image, False to disable it.
 2438      * @return bool  True always
 2439      * @since 5.5.0
 2440      */
 2441     function SetFailureImage($error_image)
 2442     {
 2443         $this->suppress_error_image = !$error_image;
 2444         return TRUE;
 2445     }
 2446 
 2447     /**
 2448      * Begins a Motion-JPEG (or other type) plot stream
 2449      *
 2450      * @return bool  True always
 2451      * @since 5.8.0
 2452      */
 2453     function StartStream()
 2454     {
 2455         $this->GetImageType($mime_type, $this->stream_output_f);
 2456         $this->stream_boundary = "PHPlot-Streaming-Frame"; // Arbitrary MIME boundary
 2457         $this->stream_frame_header = "\r\n--$this->stream_boundary\r\nContent-Type: $mime_type\r\n";
 2458         $this->DisableCaching();  // Send headers to disable browser-side caching
 2459         header("Content-type: multipart/x-mixed-replace; boundary=\"$this->stream_boundary\"");
 2460         return TRUE;
 2461     }
 2462 
 2463     /**
 2464      * Ends a Motion-JPEG (or other type) plot stream
 2465      *
 2466      * @return bool  True always
 2467      * @since 5.8.0
 2468      */
 2469     function EndStream()
 2470     {
 2471         echo "\r\n--$this->stream_boundary--\r\n";
 2472         flush();
 2473         return TRUE;
 2474     }
 2475 
 2476     /**
 2477      * Outputs the generated plot as one frame in a plot stream
 2478      *
 2479      * @return bool  True always
 2480      * @since 5.8.0
 2481      */
 2482     function PrintImageFrame()
 2483     {
 2484         ob_start();
 2485         call_user_func($this->stream_output_f, $this->img);
 2486         $size = ob_get_length();
 2487         $frame = ob_get_clean();
 2488         echo $this->stream_frame_header, "Content-Length: $size\r\n\r\n", $frame, "\r\n";
 2489         flush();
 2490         // This gets the next DrawGraph() to do background and titles again.
 2491         $this->done = array();
 2492         return TRUE;
 2493     }
 2494 
 2495 /////////////////////////////////////////////
 2496 ///////////                            LABELS
 2497 /////////////////////////////////////////////
 2498 
 2499     /**
 2500      * Positions and controls X data labels
 2501      *
 2502      * For vertical plots, these are X axis data labels, showing label strings from the data array.
 2503      * Accepted positions are: plotdown, plotup, both, none.
 2504      * For horizontal plots, these are X data value labels, show the data values.
 2505      * Accepted positions are: plotin, plotstack, none.
 2506      *
 2507      * @param string $which_xdlp  Desired label position: plotdown | plotup | both | none | plotin | plotstack
 2508      * @return bool  True (False on error if an error handler returns True)
 2509      */
 2510     function SetXDataLabelPos($which_xdlp)
 2511     {
 2512         $which_xdlp = $this->CheckOption($which_xdlp, 'plotdown, plotup, both, none, plotin, plotstack',
 2513                                          __FUNCTION__);
 2514         if (!$which_xdlp) return FALSE;
 2515         $this->x_data_label_pos = $which_xdlp;
 2516 
 2517         return TRUE;
 2518     }
 2519 
 2520     /**
 2521      * Positions and controls Y data labels
 2522      *
 2523      * For vertical plots, these are Y data value labels, showing the data values.
 2524      * Accepted positions are: plotin, plotstack, none.
 2525      * For horizontal plots, these are Y axis data labels, showing label strings from the data array.
 2526      * Accepted positions are: plotleft, plotright, both, none.
 2527      *
 2528      * @param string $which_ydlp  Desired label position: plotleft | plotright | both | none | ...
 2529      * @return bool  True (False on error if an error handler returns True)
 2530      */
 2531     function SetYDataLabelPos($which_ydlp)
 2532     {
 2533         $which_ydlp = $this->CheckOption($which_ydlp, 'plotleft, plotright, both, none, plotin, plotstack',
 2534                                           __FUNCTION__);
 2535         if (!$which_ydlp) return FALSE;
 2536         $this->y_data_label_pos = $which_ydlp;
 2537 
 2538         return TRUE;
 2539     }
 2540 
 2541     /**
 2542      * Positions the X tick labels
 2543      *
 2544      * @param string $which_xtlp  Desired label position: plotdown | plotup | both | none | xaxis
 2545      * @return bool  True (False on error if an error handler returns True)
 2546      */
 2547     function SetXTickLabelPos($which_xtlp)
 2548     {
 2549         $which_xtlp = $this->CheckOption($which_xtlp, 'plotdown, plotup, both, xaxis, none',
 2550                                          __FUNCTION__);
 2551         if (!$which_xtlp) return FALSE;
 2552         $this->x_tick_label_pos = $which_xtlp;
 2553 
 2554         return TRUE;
 2555     }
 2556 
 2557     /**
 2558      * Positions the Y tick labels
 2559      *
 2560      * @param string $which_ytlp  Desired label position: plotleft | plotright | both | none | yaxis
 2561      * @return bool  True (False on error if an error handler returns True)
 2562      */
 2563     function SetYTickLabelPos($which_ytlp)
 2564     {
 2565         $which_ytlp = $this->CheckOption($which_ytlp, 'plotleft, plotright, both, yaxis, none',
 2566                                          __FUNCTION__);
 2567         if (!$which_ytlp) return FALSE;
 2568         $this->y_tick_label_pos = $which_ytlp;
 2569 
 2570         return TRUE;
 2571     }
 2572 
 2573     /**
 2574      * Sets the formatting type for tick, data, or pie chart labels
 2575      *
 2576      * This implements SetXLabelType(), SetYLabelType(), SetXYDataLabelType(), SetYDataLabelType(),
 2577      * and part of SetPieLabelType(). Those functions combine their arguments into an array and
 2578      * pass the array to this function as $args.
 2579      *
 2580      * $mode='x' or 'y' set the type for tick labels, and the default type for data labels
 2581      * if they are not separately configured.  $mode='xd' or 'yd' set the type for data labels.
 2582      * $mode='p' sets the type for pie chart labels.
 2583      *
 2584      * $args[0] sets the formatting type: 'data' | 'time' | 'printf' | 'custom' | empty.
 2585      *     If this is missing or empty, the default formatting for $mode is restored.
 2586      * Additional values in $args[] depend on the formatting type. All further paramters except
 2587      * for the callback are optional.
 2588      * For type='data': $args[1] = precision, $args[2] = prefix, $args[3] = suffix.
 2589      * For type 'time': $args[1] = format string for strftime().
 2590      * For type 'printf': $args[1:3] = 1, 2, or 3 format strings for sprintf().
 2591      * For type 'custom': $args[1] = the callback (required), $args[2] = pass-through argument.
 2592      *
 2593      * @param string $mode  Which label type to configure: x | y | xd | yd | p
 2594      * @param array $args  Additional arguments controlling the format type
 2595      * @return bool  True (False on error if an error handler returns True)
 2596      */
 2597     protected function SetLabelType($mode, $args)
 2598     {
 2599         if (!$this->CheckOption($mode, 'x, y, xd, yd, p', __FUNCTION__))
 2600             return FALSE;
 2601 
 2602         $type = isset($args[0]) ? $args[0] : '';
 2603         $format = &$this->label_format[$mode];  // Shorthand reference to format storage variables
 2604         switch ($type) {
 2605         case 'data':
 2606             if (isset($args[1]))
 2607                 $format['precision'] = $args[1];
 2608             elseif (!isset($format['precision']))
 2609                 $format['precision'] = 1;
 2610             $format['prefix'] = isset($args[2]) ? $args[2] : '';
 2611             $format['suffix'] = isset($args[3]) ? $args[3] : '';
 2612             break;
 2613 
 2614         case 'time':
 2615             if (isset($args[1]))
 2616                 $format['time_format'] = $args[1];
 2617             elseif (!isset($format['time_format']))
 2618                 $format['time_format'] = '%H:%M:%S';
 2619             break;
 2620 
 2621         case 'printf':
 2622             if (isset($args[1]))
 2623                 // Accept 1, 2, or 3 format strings (see FormatLabel)
 2624                 $format['printf_format'] = array_slice($args, 1, 3);
 2625             elseif (!isset($format['printf_format']))
 2626                 $format['printf_format'] = '%e';
 2627             break;
 2628 
 2629         case 'custom':
 2630             if (isset($args[1])) {
 2631                 $format['custom_callback'] = $args[1];
 2632                 $format['custom_arg'] = isset($args[2]) ? $args[2] : NULL;
 2633             } else {
 2634                 $type = ''; // Error, 'custom' without a function, set to no-format mode.
 2635             }
 2636             break;
 2637 
 2638         case '':
 2639         case 'title':   // Retained for backwards compatibility?
 2640             break;
 2641 
 2642         default:
 2643             $this->CheckOption($type, 'data, time, printf, custom', __FUNCTION__);
 2644             $type = '';
 2645         }
 2646         $format['type'] = $type;
 2647         return (boolean)$type;
 2648     }
 2649 
 2650     /**
 2651      * Sets formatting mode for X tick labels, and default for X data labels
 2652      *
 2653      * @param string $type  Formatting mode: data | time | printf | custom, or '' to reset to default
 2654      * @param mixed $varargs  One or more additional arguments that depend on $type
 2655      * @return bool  True (False on error if an error handler returns True)
 2656      */
 2657     function SetXLabelType($type=NULL, $varargs=NULL)  // Variable arguments
 2658     {
 2659         $args = func_get_args();
 2660         return $this->SetLabelType('x', $args);
 2661     }
 2662 
 2663     /**
 2664      * Sets formatting mode for X data labels (overriding any mode set with SetXLabelType)
 2665      *
 2666      * @param string $type  Formatting mode: data | time | printf | custom, or '' to reset to default
 2667      * @param mixed $varargs  One or more additional arguments that depend on $type
 2668      * @return bool  True (False on error if an error handler returns True)
 2669      * @since 5.1.0
 2670      */
 2671     function SetXDataLabelType($type=NULL, $varargs=NULL)  // Variable arguments, named params are unused
 2672     {
 2673         $args = func_get_args();
 2674         return $this->SetLabelType('xd', $args);
 2675     }
 2676 
 2677     /**
 2678      * Sets formatting mode for Y tick labels, and default for Y data labels
 2679      *
 2680      * @param string $type  Formatting mode: data | time | printf | custom, or '' to reset to default
 2681      * @param mixed $varargs  One or more additional arguments that depend on $type
 2682      * @return bool  True (False on error if an error handler returns True)
 2683      */
 2684     function SetYLabelType($type=NULL, $varargs=NULL)  // Variable arguments, named params are unused
 2685     {
 2686         $args = func_get_args();
 2687         return $this->SetLabelType('y', $args);
 2688     }
 2689 
 2690     /**
 2691      * Sets formatting mode for Y data labels (overriding any mode set with SetYLabelType)
 2692      *
 2693      * @param string $type  Formatting mode: data | time | printf | custom, or '' to reset to default
 2694      * @param mixed $varargs  One or more additional arguments that depend on $type
 2695      * @return bool  True (False on error if an error handler returns True)
 2696      * @since 5.1.0
 2697      */
 2698     function SetYDataLabelType($type=NULL, $varargs=NULL)  // Variable arguments, named params are unused
 2699     {
 2700         $args = func_get_args();
 2701         return $this->SetLabelType('yd', $args);
 2702     }
 2703 
 2704     /**
 2705      * Sets type and formatting mode for pie chart labels.
 2706      *
 2707      * @param string|string[] $source_  Label source keyword or array: percent | value | label | index | ''
 2708      * @param string $type  Optional formatting mode: data | printf | custom
 2709      * @param mixed $varargs  Zero or more additional arguments telling how to format the label
 2710      * @return bool  True (False on error if an error handler returns True)
 2711      * @since 5.6.0
 2712      */
 2713     function SetPieLabelType($source_, $type=NULL, $varargs=NULL)  // Variable arguments, named params unused
 2714     {
 2715         $args = func_get_args();
 2716         $source = array_shift($args);
 2717         if (empty($source)) {
 2718             $this->pie_label_source = NULL; // Restore default
 2719             $args = array(''); // See below - tells SetLabelType to do no formatting or default.
 2720         } else {
 2721             $this->pie_label_source = $this->CheckOptionArray($source, 'percent, value, label, index',
 2722                                                               __FUNCTION__);
 2723             if (empty($this->pie_label_source)) return FALSE;
 2724         }
 2725         return $this->SetLabelType('p', $args);
 2726     }
 2727 
 2728     /**
 2729      * Sets the date/time formatting string for X labels
 2730      *
 2731      * This does not enable date/time formatting. For that you need to use
 2732      * SetXLabelType('time'). But since you can pass the formatting string
 2733      * to SetXLabelType() also, there is no need to use SetXTimeFormat().
 2734      *
 2735      * @param string $which_xtf  Formatting string to use (see PHP function strftime())
 2736      * @return bool  True always
 2737      */
 2738     function SetXTimeFormat($which_xtf)
 2739     {
 2740         $this->label_format['x']['time_format'] = $which_xtf;
 2741         return TRUE;
 2742     }
 2743 
 2744     /**
 2745      * Sets the date/time formatting string for Y labels
 2746      *
 2747      * This does not enable date/time formatting. For that you need to use
 2748      * SetYLabelType('time'). But since you can pass the formatting string
 2749      * to SetYLabelType() also, there is no need to use SetYTimeFormat().
 2750      *
 2751      * @param string $which_ytf  Formatting string (see PHP function strftime())
 2752      * @return bool  True always
 2753      */
 2754     function SetYTimeFormat($which_ytf)
 2755     {
 2756         $this->label_format['y']['time_format'] = $which_ytf;
 2757         return TRUE;
 2758     }
 2759 
 2760     /**
 2761      * Sets the separators used when formatting number labels
 2762      *
 2763      * Separators are used with 'data' label formatting. The defaults
 2764      * are set from the system locale (if available).
 2765      *
 2766      * @param string $decimal_point  The character to use as a decimal point
 2767      * @param string $thousands_sep  The character to use as a thousands grouping separator
 2768      * @return bool  True always
 2769      * @since 5.0.4
 2770      */
 2771     function SetNumberFormat($decimal_point, $thousands_sep)
 2772     {
 2773         $this->decimal_point = $decimal_point;
 2774         $this->thousands_sep = $thousands_sep;
 2775         return TRUE;
 2776     }
 2777 
 2778     /**
 2779      * Sets the text angle for X tick labels, and the default angle for X data labels
 2780      *
 2781      * @param float $which_xla  Desired angle for label text, in degrees
 2782      * @return bool  True always
 2783      */
 2784     function SetXLabelAngle($which_xla)
 2785     {
 2786         $this->x_label_angle = $which_xla;
 2787         return TRUE;
 2788     }
 2789 
 2790     /**
 2791      * Sets the text angle for Y tick labels
 2792      *
 2793      * Note: Unlike SetXLabelAngle(), this does not also apply to data labels.
 2794      *
 2795      * @param float $which_yla  Desired angle for label text, in degrees
 2796      * @return bool  True always
 2797      */
 2798     function SetYLabelAngle($which_yla)
 2799     {
 2800         $this->y_label_angle = $which_yla;
 2801         return TRUE;
 2802     }
 2803 
 2804     /**
 2805      * Sets the text angle for X data labels (overriding an angle set with SetXLabelAngle)
 2806      *
 2807      * @param float $which_xdla  Desired angle for label text, in degrees
 2808      * @return bool  True always
 2809      * @since 5.1.0
 2810      */
 2811     function SetXDataLabelAngle($which_xdla)
 2812     {
 2813         $this->x_data_label_angle_u = $which_xdla;
 2814         return TRUE;
 2815     }
 2816 
 2817     /**
 2818      * Sets the angle for Y data labels
 2819      *
 2820      * @param float $which_ydla  Desired angle for label text, in degrees
 2821      * @return bool  True always
 2822      * @since 5.1.0
 2823      */
 2824     function SetYDataLabelAngle($which_ydla)
 2825     {
 2826         $this->y_data_label_angle = $which_ydla;
 2827         return TRUE;
 2828     }
 2829 
 2830 /////////////////////////////////////////////
 2831 ///////////                              MISC
 2832 /////////////////////////////////////////////
 2833 
 2834     /**
 2835      * Checks the validity of an option (a multiple-choice function parameter)
 2836      *
 2837      * @param string $which_opt  String to check, usually the provided value of a function argument
 2838      * @param string $which_acc  String of accepted choices, lower-case, choices separated by comma space
 2839      * @param string $which_func  Name of the calling function, for error messages, usually __FUNCTION__
 2840      * @return string|null  Downcased/trimmed option value if valid; NULL on error if error handler returns
 2841      */
 2842     protected function CheckOption($which_opt, $which_acc, $which_func)
 2843     {
 2844         $asked = strtolower(trim($which_opt));
 2845 
 2846         // Look for the supplied value in a comma/space separated list.
 2847         if (strpos(", $which_acc,", ", $asked,") !== FALSE)
 2848             return $asked;
 2849 
 2850         $this->PrintError("$which_func(): '$which_opt' not in available choices: '$which_acc'.");
 2851         return NULL;
 2852     }
 2853 
 2854     /**
 2855      * Checks the validity of an array of options (multiple-choice function parameters)
 2856      *
 2857      * This is used to validate arguments to functions that accept either a single value
 2858      * or an array of values (example: SetPlotBorderType).
 2859      *
 2860      * @param string|string[] $opt  String or array of strings to check
 2861      * @param string $acc  String of accepted choices, lower-case, choices separated by comma space
 2862      * @param string $func  Name of the calling function, for error messages, usually __FUNCTION__
 2863      * @return string[]|null  Downcased/trimmed option values if valid; NULL on error if error handler returns
 2864      * @since 5.1.2
 2865      */
 2866     protected function CheckOptionArray($opt, $acc, $func)
 2867     {
 2868         $opt_array = (array)$opt;
 2869         $result = array();
 2870         foreach ($opt_array as $option) {
 2871             $choice = $this->CheckOption($option, $acc, $func);
 2872             if (is_null($choice)) return NULL; // In case CheckOption error handler returns
 2873             $result[] = $choice;
 2874         }
 2875         return $result;
 2876     }
 2877 
 2878     /**
 2879      * Checks for compatibility of the plot type and data type
 2880      *
 2881      * This is called by the plot-type-specific drawing functions, such as DrawBars().
 2882      * It checks to make sure that the current data type is supported by the
 2883      * drawing function.
 2884      *
 2885      * @param string $valid_types  Valid data types, separated by comma space
 2886      * @return bool  True if data type is valid; False on error if an error handler returns True
 2887      * @since 5.1.2
 2888      */
 2889     protected function CheckDataType($valid_types)
 2890     {
 2891         if (strpos(", $valid_types,", ", $this->data_type,") !== FALSE)
 2892             return TRUE;
 2893 
 2894         $this->PrintError("Data type '$this->data_type' is not valid for '$this->plot_type' plots."
 2895                . " Supported data type(s): '$valid_types'");
 2896         return FALSE;
 2897     }
 2898 
 2899     /**
 2900      * Decodes the data type into variables used to determine how to process a data array
 2901      *
 2902      * This sets the 'datetype_*' class variables, which are used by other functions
 2903      * that need to access the data array. This keeps the information about the meaning
 2904      * of each data type in a single place (the datatypes static array).
 2905      * @since 5.1.2
 2906      */
 2907     protected function DecodeDataType()
 2908     {
 2909         $v = &self::$datatypes[$this->data_type]; // Shortcut reference, already validated in SetDataType()
 2910         $this->datatype_implied =    !empty($v['implied']);     // X (Y for horizontal plots) is implied
 2911         $this->datatype_error_bars = !empty($v['error_bars']);  // Has +error, -error values
 2912         $this->datatype_pie_single = !empty($v['pie_single']);  // Single-row pie chart
 2913         $this->datatype_swapped_xy = !empty($v['swapped_xy']);  // Horizontal plot with swapped X:Y
 2914         $this->datatype_yz =         !empty($v['yz']);          // Has a Z value for each Y
 2915     }
 2916 
 2917     /**
 2918      * Validates the data array, making sure it is properly structured
 2919      *
 2920      * This function is used by DrawGraph() to make sure the data array is populated
 2921      * and each row is structured correctly, according to the data type.
 2922      *
 2923      * It also calculates data_columns, which is the maximum number of dependent
 2924      * variable values (usually Y) in the data array rows.  (For pie charts, this is
 2925      * the number of slices.) It depends on the data_type, unlike records_per_group
 2926      * (which was previously used to pad style arrays, but is not accurate).
 2927      *
 2928      * Note error messages refer to the caller, the public DrawGraph().
 2929      *
 2930      * @return bool  True (False on error if an error handler returns True)
 2931      * @since 5.1.3
 2932      */
 2933     protected function CheckDataArray()
 2934     {
 2935         // Test for missing image, which really should never happen.
 2936         if (!$this->img) {
 2937             return $this->PrintError('DrawGraph(): No image resource allocated');
 2938         }
 2939 
 2940         // Test for missing or empty data array:
 2941         if (empty($this->data) || !is_array($this->data)) {
 2942             return $this->PrintError("DrawGraph(): No data array");
 2943         }
 2944         if ($this->total_records == 0) {
 2945             return $this->PrintError('DrawGraph(): Empty data set');
 2946         }
 2947 
 2948         // Decode the data type into functional flags.
 2949         $this->DecodeDataType();
 2950 
 2951         // Calculate the maximum number of dependent values per independent value
 2952         // (e.g. Y for each X), or the number of pie slices. Also validate the rows.
 2953         $skip = $this->datatype_implied ? 1 : 2; // Skip factor for data label and independent variable
 2954         if ($this->datatype_error_bars) {
 2955             $this->data_columns = (int)(($this->records_per_group - $skip) / 3);
 2956             // Validate the data array for error plots: (label, X, then groups of Y, +err, -err):
 2957             for ($i = 0; $i < $this->num_data_rows; $i++) {
 2958                 if ($this->num_recs[$i] < $skip || ($this->num_recs[$i] - $skip) % 3 != 0)
 2959                     return $this->PrintError("DrawGraph(): Invalid $this->data_type data array (row $i)");
 2960             }
 2961         } elseif ($this->datatype_pie_single) {
 2962             $this->data_columns = $this->num_data_rows; // Special case for this type of pie chart.
 2963             // Validate the data array for text-data-single pie charts. Requires 1 value per row.
 2964             for ($i = 0; $i < $this->num_data_rows; $i++) {
 2965                 if ($this->num_recs[$i] != 2)
 2966                     return $this->PrintError("DrawGraph(): Invalid $this->data_type data array (row $i)");
 2967             }
 2968         } elseif ($this->datatype_yz) {
 2969             $this->data_columns = (int)(($this->records_per_group - $skip) / 2); //  (y, z) pairs
 2970             // Validate the data array for plots using X, Y, Z: (label, X, then pairs of Y, Z)
 2971             for ($i = 0; $i < $this->num_data_rows; $i++) {
 2972                 if ($this->num_recs[$i] < $skip || ($this->num_recs[$i] - $skip) % 2 != 0)
 2973                     return $this->PrintError("DrawGraph(): Invalid $this->data_type data array (row $i)");
 2974             }
 2975         } else {
 2976             $this->data_columns = $this->records_per_group - $skip;
 2977             // Validate the data array for non-error plots:
 2978             for ($i = 0; $i < $this->num_data_rows; $i++) {
 2979                 if ($this->num_recs[$i] < $skip)
 2980                     return $this->PrintError("DrawGraph(): Invalid $this->data_type data array (row $i)");
 2981             }
 2982         }
 2983         return TRUE;
 2984     }
 2985 
 2986     /**
 2987      * Controls browser-side image caching
 2988      *
 2989      * @param bool $which_browser_cache  True to allow the browser to cache the image, false to not allow
 2990      * @return bool  True always
 2991      */
 2992     function SetBrowserCache($which_browser_cache)
 2993     {
 2994         $this->browser_cache = $which_browser_cache;
 2995         return TRUE;
 2996     }
 2997 
 2998     /**
 2999      * Determines whether or not DrawGraph() automatically outputs the image when the plot is drawn
 3000      *
 3001      * @param bool $which_pi  True to have DrawGraph() call PrintImage() when done, false to not output
 3002      * @return bool  True always
 3003      */
 3004     function SetPrintImage($which_pi)
 3005     {
 3006         $this->print_image = $which_pi;
 3007         return TRUE;
 3008     }
 3009 
 3010     /**
 3011      * Sets how much of a border is drawn around the plot area
 3012      *
 3013      * The argument can be a single value, or any array of values, indicating
 3014      * which sides should get a border. 'full' means all 4 sides.
 3015      *
 3016      * @param string|string[] $pdt  Border control keyword(s):  left|right|top|bottom|sides|none|full
 3017      * @return bool  True (False on error if an error handler returns True)
 3018      */
 3019     function SetPlotBorderType($pbt)
 3020     {
 3021         $this->plot_border_type = $this->CheckOptionArray($pbt, 'left, right, top, bottom, sides, none, full',
 3022                                                           __FUNCTION__);
 3023         return !empty($this->plot_border_type);
 3024     }
 3025 
 3026     /**
 3027      * Sets the type of border drawn around the image
 3028      *
 3029      * @param string $sibt  Border type: raised | solid | plain | none
 3030      * @return bool  True (False on error if an error handler returns True)
 3031      */
 3032     function SetImageBorderType($sibt)
 3033     {
 3034         $this->image_border_type = $this->CheckOption($sibt, 'raised, plain, solid, none', __FUNCTION__);
 3035         return (boolean)$this->image_border_type;
 3036     }
 3037 
 3038     /**
 3039      * Sets the width of the image border, if enabled
 3040      *
 3041      * @param int $width  Image border width in pixels
 3042      * @return bool  True always
 3043      * @since 5.1.2
 3044      */
 3045     function SetImageBorderWidth($width)
 3046     {
 3047         $this->image_border_width = $width;
 3048         return TRUE;
 3049     }
 3050 
 3051     /**
 3052      * Enables or disables drawing of the plot area background color
 3053      *
 3054      * @param bool $dpab  True to draw the plot area background color, false to not draw it
 3055      * @return bool  True always
 3056      */
 3057     function SetDrawPlotAreaBackground($dpab)
 3058     {
 3059         $this->draw_plot_area_background = (bool)$dpab;
 3060         return TRUE;
 3061     }
 3062 
 3063     /**
 3064      * Enables or disables drawing of the X (vertical) grid lines
 3065      *
 3066      * @param bool $dxg  True to draw the X grid lines, false to not draw them; or NULL to restore default
 3067      * @return bool  True always
 3068      */
 3069     function SetDrawXGrid($dxg = NULL)
 3070     {
 3071         $this->draw_x_grid = $dxg;
 3072         return TRUE;
 3073     }
 3074 
 3075     /**
 3076      * Enables or disables drawing of the Y grid lines
 3077      *
 3078      * @param bool $dyg  True to draw the Y grid lines, false to not draw them; or NULL to restore default
 3079      * @return bool  True always
 3080      */
 3081     function SetDrawYGrid($dyg = NULL)
 3082     {
 3083         $this->draw_y_grid = $dyg;
 3084         return TRUE;
 3085     }
 3086 
 3087     /**
 3088      * Selects dashed or solid grid lines
 3089      *
 3090      * @param bool $ddg  True to draw the grid with dashed lines, false to use solid lines
 3091      * @return bool  True always
 3092      */
 3093     function SetDrawDashedGrid($ddg)
 3094     {
 3095         $this->dashed_grid = (bool)$ddg;
 3096         return TRUE;
 3097     }
 3098 
 3099     /**
 3100      * Enables or disables drawing of X data label lines
 3101      *
 3102      * @param bool $dxdl  True to draw the X data label lines, false to not draw them
 3103      * @return bool  True always
 3104      */
 3105     function SetDrawXDataLabelLines($dxdl)
 3106     {
 3107         $this->draw_x_data_label_lines = (bool)$dxdl;
 3108         return TRUE;
 3109     }
 3110 
 3111     /**
 3112      * Enables or disables drawing of Y data label Lines (horizontal plots only)
 3113      *
 3114      * @param bool $dydl  True to draw the Y data label lines, false to not draw them
 3115      * @return bool  True always
 3116      * @since 6.0.0
 3117      */
 3118     function SetDrawYDataLabelLines($dydl)
 3119     {
 3120         $this->draw_y_data_label_lines = (bool)$dydl;
 3121         return TRUE;
 3122     }
 3123 
 3124     /**
 3125      * Enables or disables drawing of borders around pie chart segments
 3126      *
 3127      * @param bool $dpb  True to draw the pie chart segment borders, false to not draw them
 3128      * @return bool  True always
 3129      * @since 6.0.0
 3130      */
 3131     function SetDrawPieBorders($dpb)
 3132     {
 3133         $this->draw_pie_borders = (bool)$dpb;
 3134         return TRUE;
 3135     }
 3136 
 3137     /**
 3138      * Enables or disables drawing of data borders for plot types that support them
 3139      *
 3140      * For plot types which support data borders, such as 'bars', this enables
 3141      * or disables them. The default depends on the plot type.
 3142      *
 3143      * @param bool $ddb  True to draw the data borders, false to not draw them
 3144      * @return bool  True always
 3145      * @since 6.0.0
 3146      */
 3147     function SetDrawDataBorders($ddb)
 3148     {
 3149         $this->draw_data_borders = (bool)$ddb;
 3150         return TRUE;
 3151     }
 3152 
 3153     /**
 3154      * Sets the main title text for the plot
 3155      *
 3156      * @param string $which_title  The text to use for the main plot title. Can contain multiple lines
 3157      * @return bool  True always
 3158      */
 3159     function SetTitle($which_title)
 3160     {
 3161         $this->title_txt = $which_title;
 3162         return TRUE;
 3163     }
 3164 
 3165     /**
 3166      * Sets the X axis title, and optionally its position
 3167      *
 3168      * Sets the text to be displayed as the X axis title. Optionally, it
 3169      * also sets the position of the title and the axis itself: below the
 3170      * graph (the usual place), above the graph, both, or neither.
 3171      *
 3172      * @param string $which_xtitle The text string to use for the X axis title. Can contain multiple lines
 3173      * @param string $which_xpos   Optional position for the X axis and title: plotdown plotup both none
 3174      * @return bool  True (False on error if an error handler returns True)
 3175      */
 3176     function SetXTitle($which_xtitle, $which_xpos = 'plotdown')
 3177     {
 3178         if (!($which_xpos = $this->CheckOption($which_xpos, 'plotdown, plotup, both, none', __FUNCTION__)))
 3179             return FALSE;
 3180         if (($this->x_title_txt = $which_xtitle) === '')
 3181             $this->x_title_pos = 'none';
 3182         else
 3183             $this->x_title_pos = $which_xpos;
 3184         return TRUE;
 3185     }
 3186 
 3187     /**
 3188      * Sets the Y axis title, and optionally its position
 3189      *
 3190      * Sets the text to be displayed as the Y axis title. Optionally, it
 3191      * also sets the position of the title and the axis itself: on the left
 3192      * side of the graph (the usual place), on the right side, both, or neither.
 3193      *
 3194      * @param string $which_ytitle The text string to use for the Y axis title. Can contain multiple lines
 3195      * @param string $which_ypos   Optional position for the X axis and title: plotleft plotright both none
 3196      * @return bool  True (False on error if an error handler returns True)
 3197      */
 3198     function SetYTitle($which_ytitle, $which_ypos = 'plotleft')
 3199     {
 3200         if (!($which_ypos = $this->CheckOption($which_ypos, 'plotleft, plotright, both, none', __FUNCTION__)))
 3201             return FALSE;
 3202         if (($this->y_title_txt = $which_ytitle) === '')
 3203             $this->y_title_pos = 'none';
 3204         else
 3205             $this->y_title_pos = $which_ypos;
 3206         return TRUE;
 3207     }
 3208 
 3209     /**
 3210      * Sets the size of the drop shadow for bar and pie charts
 3211      *
 3212      * Sets the size in pixels of the drop shadow used to give bar and pie
 3213      * charts a 3-D look. The 3-D look can be disabled by setting the shading
 3214      * to zero.
 3215      *
 3216      * @param int $which_s Size of the drop shadow in pixels
 3217      * @return bool  True always
 3218      */
 3219     function SetShading($which_s)
 3220     {
 3221         $this->shading = (int)$which_s;
 3222         return TRUE;
 3223     }
 3224 
 3225     /**
 3226      * Selects the type of plot - how the data will be graphed
 3227      *
 3228      * @param string $which_pt  The plot type, such as bars, lines, pie, ...
 3229      * @return bool  True (False on error if an error handler returns True)
 3230      */
 3231     function SetPlotType($which_pt)
 3232     {
 3233         $avail_plot_types = implode(', ', array_keys(self::$plots)); // List of known plot types
 3234         $this->plot_type = $this->CheckOption($which_pt, $avail_plot_types, __FUNCTION__);
 3235         return (boolean)$this->plot_type;
 3236     }
 3237 
 3238     /**
 3239      * Sets the position of the X axis
 3240      *
 3241      * @param float $pos  Axis position as an integer Y world coordinate; '' or omit for default
 3242      * @return bool  True always
 3243      */
 3244     function SetXAxisPosition($pos='')
 3245     {
 3246         $this->x_axis_position = ($pos === '') ? NULL : (int)$pos;
 3247         return TRUE;
 3248     }
 3249 
 3250     /**
 3251      * Sets the position of the Y axis
 3252      *
 3253      * @param float $pos  Axis position as an integer X world coordinate; '' or omit for default
 3254      * @return bool  True always
 3255      */
 3256     function SetYAxisPosition($pos='')
 3257     {
 3258         $this->y_axis_position = ($pos === '') ? NULL : (int)$pos;
 3259         return TRUE;
 3260     }
 3261 
 3262     /**
 3263      * Enables or disables drawing of the X axis line
 3264      *
 3265      * Note: This controls drawing of the axis line only, and not the ticks, labels, or grid.
 3266      *
 3267      * @param bool $draw  True to draw the axis line, False to not draw it
 3268      * @return bool  True always
 3269      * @since 5.3.0
 3270      */
 3271     function SetDrawXAxis($draw)
 3272     {
 3273         $this->suppress_x_axis = !$draw; // See DrawXAxis()
 3274         return TRUE;
 3275     }
 3276 
 3277     /**
 3278      * Enables or disables drawing of the Y axis line
 3279      *
 3280      * Note: This controls drawing of the axis line only, and not the ticks, labels, or grid.
 3281      *
 3282      * @param bool $draw  True to draw the axis line, False to not draw it
 3283      * @return bool  True always
 3284      * @since 5.3.0
 3285      */
 3286     function SetDrawYAxis($draw)
 3287     {
 3288         $this->suppress_y_axis = !$draw; // See DrawYAxis()
 3289         return TRUE;
 3290     }
 3291 
 3292     /**
 3293      * Selects linear or logarithmic scale for the X axis
 3294      *
 3295      * @param string $which_xst  The scale type: linear | log
 3296      * @return bool  True (False on error if an error handler returns True)
 3297      */
 3298     function SetXScaleType($which_xst)
 3299     {
 3300         $this->xscale_type = $this->CheckOption($which_xst, 'linear, log', __FUNCTION__);
 3301         return (boolean)$this->xscale_type;
 3302     }
 3303 
 3304     /**
 3305      * Selects linear or logarithmic scale for the Y axis
 3306      *
 3307      * @param string $which_yst  The scale type: linear | log
 3308      * @return bool  True (False on error if an error handler returns True)
 3309      */
 3310     function SetYScaleType($which_yst)
 3311     {
 3312         $this->yscale_type = $this->CheckOption($which_yst, 'linear, log',  __FUNCTION__);
 3313         return (boolean)$this->yscale_type;
 3314     }
 3315 
 3316     /**
 3317      * Sets the precision for numerically formatted X labels
 3318      *
 3319      * Note: Use of the equivalent SetXLabelType('data', $which_prec) is preferred.
 3320      *
 3321      * @param int $which_prec  Number of digits to display
 3322      * @return bool  True (False on error if an error handler returns True)
 3323      */
 3324     function SetPrecisionX($which_prec)
 3325     {
 3326         return $this->SetXLabelType('data', $which_prec);
 3327     }
 3328 
 3329     /**
 3330      * Sets the precision for numerically formatted Y labels
 3331      *
 3332      * Note: Use of the equivalent SetYLabelType('data', $which_prec) is preferred.
 3333      *
 3334      * @param int $which_prec  Number of digits to display
 3335      * @return bool  True (False on error if an error handler returns True)
 3336      */
 3337     function SetPrecisionY($which_prec)
 3338     {
 3339         return $this->SetYLabelType('data', $which_prec);
 3340     }
 3341 
 3342     /**
 3343      * Sets the line width used for error bars
 3344      *
 3345      * @param int $which_seblw  Desired width in pixels of the lines used to draw error bars
 3346      * @return bool  True always
 3347      */
 3348     function SetErrorBarLineWidth($which_seblw)
 3349     {
 3350         $this->error_bar_line_width = $which_seblw;
 3351         return TRUE;
 3352     }
 3353 
 3354     /**
 3355      * Sets the position for pie chart labels
 3356      *
 3357      * @param float $which_blp  Label position factor (0 <= blp <= 1); 0 or False for no labels
 3358      * @return bool  True always
 3359      */
 3360     function SetLabelScalePosition($which_blp)
 3361     {
 3362         $this->label_scale_position = $which_blp;
 3363         return TRUE;
 3364     }
 3365 
 3366     /**
 3367      * Sets the size of the error bar tee
 3368      *
 3369      * @param int $which_ebs  Length in pixels of the error bar "T"
 3370      * @return bool  True always
 3371      */
 3372     function SetErrorBarSize($which_ebs)
 3373     {
 3374         $this->error_bar_size = $which_ebs;
 3375         return TRUE;
 3376     }
 3377 
 3378     /**
 3379      * Selects the shape of the error bars
 3380      *
 3381      * @param string $which_ebs  Error bar shape: tee | line
 3382      * @return bool  True (False on error if an error handler returns True)
 3383      */
 3384     function SetErrorBarShape($which_ebs)
 3385     {
 3386         $this->error_bar_shape = $this->CheckOption($which_ebs, 'tee, line', __FUNCTION__);
 3387         return (boolean)$this->error_bar_shape;
 3388     }
 3389 
 3390     /**
 3391      * Synchronizes the lengths of the point shapes and point sizes arrays
 3392      *
 3393      * This pads the smaller of $point_shapes[] and $point_sizes[], making them the same size.
 3394      * It is called just before drawing any plot that needs 'points'.
 3395      * @since 5.1.0
 3396      */
 3397     protected function CheckPointParams()
 3398     {
 3399         $ps = count($this->point_sizes);
 3400         $pt = count($this->point_shapes);
 3401 
 3402         if ($ps < $pt) {
 3403             $this->pad_array($this->point_sizes, $pt);
 3404             $this->point_counts = $pt;
 3405         } elseif ($ps > $pt) {
 3406             $this->pad_array($this->point_shapes, $ps);
 3407             $this->point_counts = $ps;
 3408         } else {
 3409             $this->point_counts = $ps;
 3410         }
 3411     }
 3412 
 3413     /**
 3414      * Selects the point shape for each data set
 3415      *
 3416      * Valid point shapes are known here and in DrawShape(). Includes: circle | dot | diamond | ...
 3417      * The point shape and point sizes arrays are synchronized before drawing a graph
 3418      * that uses points. See CheckPointParams()
 3419      *
 3420      * @param string|string[] $which_pt  Array (or single value) of valid point shapes
 3421      * @return bool  True (False on error if an error handler returns True)
 3422      */
 3423     function SetPointShapes($which_pt)
 3424     {
 3425         $this->point_shapes = $this->CheckOptionArray($which_pt, 'halfline, line, plus, cross, rect,'
 3426                        . ' circle, dot, diamond, triangle, trianglemid, delta, yield, star, hourglass,'
 3427                        . ' bowtie, target, box, home, up, down, none', __FUNCTION__);
 3428         return !empty($this->point_shapes);
 3429     }
 3430 
 3431     /**
 3432      * Sets the point size for each data set
 3433      *
 3434      * The point shape and point sizes arrays are synchronized before drawing a graph
 3435      * that uses points. See CheckPointParams()
 3436      *
 3437      * @param string[]|string $which_ps  Array (or single value) of point sizes in pixels
 3438      * @return bool  True always
 3439      */
 3440     function SetPointSizes($which_ps)
 3441     {
 3442         if (is_array($which_ps)) {
 3443             // Use provided array:
 3444             $this->point_sizes = $which_ps;
 3445         } elseif (!is_null($which_ps)) {
 3446             // Make the single value into an array:
 3447             $this->point_sizes = array($which_ps);
 3448         }
 3449         return TRUE;
 3450     }
 3451 
 3452     /**
 3453      * Sets whether lines should be broken at missing data, for 'lines' and 'squared' plots.
 3454      *
 3455      * @param bool $bl  True to break the lines, false to connect around missing data
 3456      * @return bool  True always
 3457      */
 3458     function SetDrawBrokenLines($bl)
 3459     {
 3460         $this->draw_broken_lines = (bool)$bl;
 3461         return TRUE;
 3462     }
 3463 
 3464     /**
 3465      * Sets the data type, which defines the structure of the data array
 3466      *
 3467      * For a list of available data types, see the static arrays $datatypes and $datatypes_map.
 3468      *
 3469      * @param string $which_dt  The data array format type: text-data | data-data | ...
 3470      * @return bool  True (False on error if an error handler returns True)
 3471      */
 3472     function SetDataType($which_dt)
 3473     {
 3474         // Handle data type aliases - mostly for backward compatibility:
 3475         if (isset(self::$datatypes_map[$which_dt]))
 3476             $which_dt = self::$datatypes_map[$which_dt];
 3477         // Validate the datatype argument against the available data types:
 3478         $valid_data_types = implode(', ', array_keys(self::$datatypes));
 3479         $this->data_type = $this->CheckOption($which_dt, $valid_data_types, __FUNCTION__);
 3480         return (boolean)$this->data_type;
 3481     }
 3482 
 3483     /**
 3484      * Sets the data array for plotting
 3485      *
 3486      * This copy the array of data values, converting rows to numerical indexes.
 3487      * It also validates that the array uses 0-based sequential integer indexes,
 3488      * and that each array value (row) is another array. Other validation is
 3489      * deferred to CheckDataArray().
 3490      *
 3491      * @param array $which_dv  The data array, an array of row arrays, interpreted per SetDataType()
 3492      * @return bool  True (False on error if an error handler returns True)
 3493      */
 3494     function SetDataValues($which_dv)
 3495     {
 3496         $this->num_data_rows = count($which_dv);
 3497         $this->total_records = 0;
 3498         $this->data = array();
 3499         $this->num_recs = array();
 3500         for ($i = 0; $i < $this->num_data_rows; $i++) {
 3501             if (!isset($which_dv[$i]) || !is_array($which_dv[$i])) {
 3502                 return $this->PrintError("SetDataValues(): Invalid data array (row $i)");
 3503             }
 3504             $this->data[$i] = array_values($which_dv[$i]);   // convert to numerical indices.
 3505 
 3506             // Count size of each row, and total for the array.
 3507             $this->total_records += $this->num_recs[$i] = count($this->data[$i]);
 3508         }
 3509         // This is the size of the widest row in the data array
 3510         // Note records_per_group isn't used much anymore. See data_columns in CheckDataArray()
 3511         $this->records_per_group = empty($this->num_recs) ? 0 : max($this->num_recs);
 3512         return TRUE;
 3513     }
 3514 
 3515     /**
 3516      * Pads the style arrays so they are large enough for the number of data sets
 3517      *
 3518      * The style arrays to be padded are line_widths, line_styles, data_colors, data_border_colors.
 3519      * This ensures they have at least $data_columns entries (maximum number of data sets), which
 3520      * simplifies the plot drawing functions.
 3521      * Other data color arrays are handled in the Need*Colors() functions instead (if needed).
 3522      *
 3523      * @return bool  True always
 3524      */
 3525     protected function PadArrays()
 3526     {
 3527         $this->pad_array($this->line_widths, $this->data_columns);
 3528         $this->pad_array($this->line_styles, $this->data_columns);
 3529         $this->pad_array($this->ndx_data_colors, $this->data_columns);
 3530         $this->pad_array($this->ndx_data_border_colors, $this->data_columns);
 3531         return TRUE;
 3532     }
 3533 
 3534     /**
 3535      * Pads an array with copies of itself until it reaches the given size
 3536      *
 3537      * pad_array only works on 0-based sequential integer indexed arrays. It also
 3538      * accepts a scalar, which is first converted to a single element array.
 3539      * Elements of the array are appended until it reaches the given size.
 3540      *
 3541      * @param array|mixed $arr  Reference variable for the array (or scalar) to pad
 3542      * @param int $size  Minimum size of the resulting array
 3543      */
 3544     protected function pad_array(&$arr, $size)
 3545     {
 3546         if (! is_array($arr)) {
 3547             $arr = array($arr);
 3548         }
 3549         $n = count($arr);
 3550         $base = 0;
 3551         while ($n < $size) $arr[$n++] = $arr[$base++];
 3552     }
 3553 
 3554     /**
 3555      * Formats a floating point number, with decimal separator and thousands groups separator
 3556      *
 3557      * This is like PHP's number_format, but uses class variables for separators, and tries to
 3558      * get the separators from the current locale.
 3559      * Note: The locale is saved and reset after getting the values. This is needed due to an issue with
 3560      * PHP (see PHP bug 45365 and others): It uses a locale-specific decimal separator when converting
 3561      * numbers to strings, but fails to convert back if the separator is other than dot. This would cause
 3562      * pie chart labels to fail with "A non well formed numeric value encountered".
 3563      *
 3564      * @param float $number  A floating point number to format
 3565      * @param int $decimals  Number of decimal places in the result
 3566      * @return string  The formatted result
 3567      */
 3568     protected function number_format($number, $decimals=0)
 3569     {
 3570         // Try to get the proper decimal and thousands separators if they are not already set.
 3571         if (!isset($this->decimal_point, $this->thousands_sep)) {
 3572             // Load locale-specific values from environment, unless disabled (for testing):
 3573             if (!$this->locale_override) {
 3574                 $save_locale = @setlocale(LC_NUMERIC, '0');
 3575                 @setlocale(LC_NUMERIC, '');
 3576             }
 3577             // Fetch locale settings:
 3578             $locale = @localeconv();
 3579             // Restore locale. (See note above.)
 3580             if (!empty($save_locale)) @setlocale(LC_NUMERIC, $save_locale);
 3581             if (isset($locale['decimal_point'], $locale['thousands_sep'])) {
 3582                 $this->decimal_point = $locale['decimal_point'];
 3583                 $this->thousands_sep = $locale['thousands_sep'];
 3584             } else {
 3585                 // Locale information not available.
 3586                 $this->decimal_point = '.';
 3587                 $this->thousands_sep = ',';
 3588             }
 3589         }
 3590         return number_format($number, $decimals, $this->decimal_point, $this->thousands_sep);
 3591     }
 3592 
 3593     /**
 3594      * Registers a callback (hook) function
 3595      *
 3596      * See the $callbacks array (class property) for the available callback names
 3597      *
 3598      * @param string $reason  A pre-defined name where a callback can be defined
 3599      * @param callback $function  Function or method to register for callback
 3600      * @param mixed $arg  Optional opaque argument to supply to the callback function when called
 3601      * @return bool  True if the callback reason is valid, else False
 3602      * @since 5.0.4
 3603      */
 3604     function SetCallback($reason, $function, $arg = NULL)
 3605     {
 3606         // Use array_key_exists because valid reason keys have NULL as value.
 3607         if (!array_key_exists($reason, $this->callbacks))
 3608             return FALSE;
 3609         $this->callbacks[$reason] = array($function, $arg);
 3610         return TRUE;
 3611     }
 3612 
 3613     /**
 3614      * Returns the current callback function registered for the given reason
 3615      *
 3616      * Note you can safely test the return value with a simple 'if', as no valid
 3617      * function name evaluates to false. PHPlot uses if (GetCallback(...)) to
 3618      * to avoid preparing arguments to an unused callback.
 3619      *
 3620      * @param string $reason  A pre-defined name where a callback can be defined
 3621      * @return callback|false  The current callback for the reason; False if none or invalid
 3622      * @since 5.0.4
 3623      */
 3624     function GetCallback($reason)
 3625     {
 3626         if (isset($this->callbacks[$reason]))
 3627             return $this->callbacks[$reason][0];
 3628         return FALSE;
 3629     }
 3630 
 3631     /**
 3632      * Un-registers any callback registered for the given reason
 3633      *
 3634      * Note: A True return means $reason is valid; it does not mean a callback
 3635      * was actually registered for that reason.
 3636      *
 3637      * @param string $reason  A pre-defined name where a callback can be defined
 3638      * @return bool  True if it was a valid callback reason, else False
 3639      * @since 5.0.4
 3640      */
 3641     function RemoveCallback($reason)
 3642     {
 3643         if (!array_key_exists($reason, $this->callbacks))
 3644             return FALSE;
 3645         $this->callbacks[$reason] = NULL;
 3646         return TRUE;
 3647     }
 3648 
 3649     /**
 3650      * Invokes a callback function, if one is registered
 3651      *
 3652      * Callbacks are called like this: callback_function($image, $passthru, [$arg,...])
 3653      * Here $passthru is the argument specified when the callback was defined by SetCallback()
 3654      * and is under control of the application. $arg... is zero or more additional arguments
 3655      * specified when then callback is called. These are under control of PHPlot itself.
 3656      *
 3657      * @param string $reason  A string naming one of the pre-defined callback reasons
 3658      * @param mixed $varargs  Zero or more additional arguments to be passed to the callback
 3659      * @return mixed  Whatever value is returned by the callback function (if any)
 3660      */
 3661     protected function DoCallback($reason, $varargs = NULL)
 3662     {
 3663         if (!isset($this->callbacks[$reason]))
 3664             return;
 3665         $args = func_get_args();
 3666         // Make the argument vector $args[] look like:  $image, $passthru, [$arg...]
 3667         list($function, $args[0]) = $this->callbacks[$reason];
 3668         array_unshift($args, $this->img);
 3669         return call_user_func_array($function, $args);
 3670     }
 3671 
 3672     /**
 3673      * Allocates colors for the image background and image border
 3674      *
 3675      * This is separate from SetColorIndexes() below so that DrawMessage can use it.
 3676      *
 3677      * @since 5.7.0
 3678      */
 3679     protected function SetBgColorIndexes()
 3680     {
 3681         $this->ndx_bg_color = $this->GetColorIndex($this->bg_color); // Background first
 3682         $this->ndx_plot_bg_color = $this->GetColorIndex($this->plot_bg_color);
 3683         if ($this->image_border_type != 'none') {
 3684             $this->ndx_i_border = $this->GetColorIndex($this->i_border);
 3685             $this->ndx_i_border_dark = $this->GetDarkColorIndex($this->i_border);
 3686         }
 3687     }
 3688 
 3689     /**
 3690      * Allocates all the colors needed for the plot
 3691      *
 3692      * This is called by DrawGraph to allocate the colors needed for the plot.  Each selectable
 3693      * color has already been validated, parsed into an array (r,g,b,a), and stored into a member
 3694      * variable. Now the GD color indexes are assigned and stored into the ndx_*_color variables.
 3695      * This is deferred here to avoid allocating unneeded colors and to avoid order dependencies,
 3696      * especially with the transparent color.
 3697      *
 3698      * For drawing data elements, only the main data colors and border colors are allocated here.
 3699      * Dark colors and error bar colors are allocated by Need*Color() functions.
 3700      * (Data border colors default to just black, so there is no cost to always allocating.)
 3701      *
 3702      * Data color allocation works as follows. If there is a data_color callback, then allocate all
 3703      * defined data colors (because the callback can use them however it wants). Otherwise, only allocate
 3704      * the number of colors that will be used. This is the larger of the number of data sets and the
 3705      * number of legend lines.
 3706      *
 3707      * @since 5.2.0
 3708      */
 3709     protected function SetColorIndexes()
 3710     {
 3711         $this->SetBgColorIndexes(); // Background and border colors
 3712 
 3713         // Handle defaults for X and Y title colors.
 3714         $this->ndx_title_color   = $this->GetColorIndex($this->title_color);
 3715         $this->ndx_x_title_color = $this->GetColorIndex($this->x_title_color, $this->ndx_title_color);
 3716         $this->ndx_y_title_color = $this->GetColorIndex($this->y_title_color, $this->ndx_title_color);
 3717 
 3718         // General text color, which is the default color for tick and data labels unless overridden.
 3719         $this->ndx_text_color      = $this->GetColorIndex($this->text_color);
 3720         $this->ndx_ticklabel_color = $this->GetColorIndex($this->ticklabel_color, $this->ndx_text_color);
 3721         $this->ndx_datalabel_color = $this->GetColorIndex($this->datalabel_color, $this->ndx_text_color);
 3722         $this->ndx_dvlabel_color   = $this->GetColorIndex($this->dvlabel_color, $this->ndx_datalabel_color);
 3723 
 3724         $this->ndx_grid_color       = $this->GetColorIndex($this->grid_color);
 3725         $this->ndx_light_grid_color = $this->GetColorIndex($this->light_grid_color);
 3726         $this->ndx_tick_color       = $this->GetColorIndex($this->tick_color);
 3727         // Pie label and border colors default to grid color, for historical reasons (PHPlot <= 5.6.1)
 3728         $this->ndx_pielabel_color   = $this->GetColorIndex($this->pielabel_color, $this->ndx_grid_color);
 3729         $this->ndx_pieborder_color  = $this->GetColorIndex($this->pieborder_color, $this->ndx_grid_color);
 3730 
 3731         // Maximum number of data & border colors to allocate:
 3732         if ($this->GetCallback('data_color')) {
 3733             $n_data = count($this->data_colors); // Need all of them
 3734             $n_border = count($this->data_border_colors);
 3735         } else {
 3736             $n_data = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
 3737             $n_border = $n_data; // One border color per data color
 3738         }
 3739 
 3740         // Allocate main data colors. For other colors used for data, see the functions which follow.
 3741         $this->ndx_data_colors = $this->GetColorIndexArray($this->data_colors, $n_data);
 3742         $this->ndx_data_border_colors = $this->GetColorIndexArray($this->data_border_colors, $n_border);
 3743 
 3744         // Legend colors: background defaults to image background, text defaults to general text color.
 3745         $this->ndx_legend_bg_color = $this->GetColorIndex($this->legend_bg_color, $this->ndx_bg_color);
 3746         $this->ndx_legend_text_color = $this->GetColorIndex($this->legend_text_color, $this->ndx_text_color);
 3747 
 3748         // Set up a color as transparent, if SetTransparentColor was used.
 3749         if (!empty($this->transparent_color)) {
 3750             imagecolortransparent($this->img, $this->GetColorIndex($this->transparent_color));
 3751         }
 3752     }
 3753 
 3754     /**
 3755      * Allocates dark-shade data colors used for shading
 3756      *
 3757      * This is called if needed by graph drawing functions that need shading.
 3758      *
 3759      * @since 5.2.0
 3760      */
 3761     protected function NeedDataDarkColors()
 3762     {
 3763         // This duplicates the calculation in SetColorIndexes() for number of data colors to allocate.
 3764         if ($this->GetCallback('data_color')) {
 3765             $n_data = count($this->data_colors);
 3766         } else {
 3767             $n_data = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
 3768         }
 3769         $this->ndx_data_dark_colors = $this->GetDarkColorIndexArray($this->data_colors, $n_data);
 3770         $this->pad_array($this->ndx_data_dark_colors, $this->data_columns);
 3771     }
 3772 
 3773     /**
 3774      * Allocates error bar colors
 3775      *
 3776      * This is called if needed by graph drawing functions that draw error bars.
 3777      *
 3778      * @since 5.2.0
 3779      */
 3780     protected function NeedErrorBarColors()
 3781     {
 3782         // This is similar to the calculation in SetColorIndexes() for number of data colors to allocate.
 3783         if ($this->GetCallback('data_color')) {
 3784             $n_err = count($this->error_bar_colors);
 3785         } else {
 3786             $n_err = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
 3787         }
 3788         $this->ndx_error_bar_colors = $this->GetColorIndexArray($this->error_bar_colors, $n_err);
 3789         $this->pad_array($this->ndx_error_bar_colors, $this->data_columns);
 3790     }
 3791 
 3792     /**
 3793      * Selects the best alignment for text, based on its vector angle from a point
 3794      *
 3795      * This picks one of 8 alignments (horizontal = left, center, or right; vertical = top, center
 3796      * or bottom; but never center/center) for text that needs to be close to a point but directed
 3797      * away from the point. The angle of the vector from the reference point determines the alignment.
 3798      *
 3799      * How it works: Picture a unit circle with 16 slices of 22.5 degrees each.
 3800      * Draw horizontal lines at the 22.5 degree and -22.5 degree positions on the circle.
 3801      * Text above the upper line will have 'bottom' vertical alignment; below the lower line will
 3802      * have 'top' vertical alignment, and between the lines will have 'center' vertical alignment.
 3803      * Horizontal alignment is similar, using +/- 22.5 degrees from vertical.
 3804      *
 3805      * @param float $sin_t  sin() of the angle of the text offset from a reference point
 3806      * @param float $cos_t  cos() of the angle of the text offset from a reference point
 3807      * @param string $h_align  Reference variable to get the horizontal alignment, for DrawText()
 3808      * @param string $v_align  Reference variable to get the vertical alignment, for DrawText()
 3809      * @param bool $reverse  True to reverse the alignment, e.g. for text inside an ellipse
 3810      * @since 5.6.0
 3811      */
 3812     protected function GetTextAlignment($sin_t, $cos_t, &$h_align, &$v_align, $reverse = FALSE)
 3813     {
 3814         if ($reverse) {   // Return the opposite alignment, align(T-180) vs align(T)
 3815             $sin_t = -$sin_t;   // sin(T-180) = -sin(T)
 3816             $cos_t = -$cos_t;   // cos(T-180) = -cos(T)
 3817         }
 3818         if ($sin_t >= 0.383) $v_align = 'bottom';       // 0.383 = sin(22.5 degrees)
 3819         elseif ($sin_t >= -0.383) $v_align = 'center';
 3820         else $v_align = 'top';
 3821         if ($cos_t >= 0.383) $h_align = 'left';         // 0.383 = cos(90 - 22.5 degrees)
 3822         elseif ($cos_t >= -0.383) $h_align = 'center';
 3823         else $h_align = 'right';
 3824     }
 3825 
 3826     /**
 3827      * Determines if and where to draw Data Value Labels
 3828      *
 3829      * This is called by plot drawing functions that support Data Value Labels (other
 3830      * than bars and stackedbars, which have their own way of doing it). If those
 3831      * labels are on, it returns True and sets 4 keys in $dvl[] which are used by
 3832      * DrawDataValueLabel to position the label: x_offset, y_offset = pixel offsets
 3833      * for the label; h_align, v_align = text alignment choices.
 3834      * It uses two member variables: data_value_label_angle and data_value_label_distance
 3835      * to define the vector to the label.
 3836      *
 3837      * @param string $label_control  Label position control; either $x_data_label_pos or $y_data_label_pos
 3838      * @param array $dvl  Reference argument for result parameters for DrawDataValueLabel()
 3839      * @return bool  False if data value labels are off; True if on and $dvl is set
 3840      * @since 5.3.0
 3841      */
 3842     protected function CheckDataValueLabels($label_control, &$dvl)
 3843     {
 3844         if ($label_control != 'plotin')
 3845             return FALSE; // No data value labels
 3846         $angle = deg2rad($this->data_value_label_angle);
 3847         $cos = cos($angle);
 3848         $sin = sin($angle);
 3849         $dvl['x_offset'] = (int)($this->data_value_label_distance * $cos);
 3850         $dvl['y_offset'] = -(int)($this->data_value_label_distance * $sin); // Y is reversed (device coords)
 3851 
 3852         // Choose text alignment based on angle:
 3853         $this->GetTextAlignment($sin, $cos, $dvl['h_align'], $dvl['v_align']);
 3854         return TRUE;
 3855     }
 3856 
 3857     /**
 3858      * Enables or disables automatic pie chart size calculation
 3859      *
 3860      * If autosize is disabled, PHPlot uses the full plot area (as PHPlot-5.5.0
 3861      * and earlier always did). Note the flag pie_full_size is unset by default,
 3862      * and stores the complement of $enable.
 3863      *
 3864      * @param bool $enable  True to enable automatic size calculation, False to use the maximum area
 3865      * @return bool  True always
 3866      * @since 5.6.0
 3867      */
 3868     function SetPieAutoSize($enable)
 3869     {
 3870         $this->pie_full_size = !$enable;
 3871         return TRUE;
 3872     }
 3873 
 3874     /**
 3875      * Sets the starting angle for pie chart segments
 3876      *
 3877      * @param float $angle  Starting angle in degrees
 3878      * @return bool  True always
 3879      * @since 6.0.0
 3880      */
 3881     function SetPieStartAngle($angle)
 3882     {
 3883         $this->pie_start_angle = $angle;
 3884         return TRUE;
 3885     }
 3886 
 3887     /**
 3888      * Sets the direction for pie chart segments
 3889      *
 3890      * @param string $which  Direction for pie segments: clockwise | cw | counterclockwise | ccw
 3891      * @return bool  True (False on error if an error handler returns True)
 3892      * @since 6.0.0
 3893      */
 3894     function SetPieDirection($which)
 3895     {
 3896         $control = $this->CheckOption($which, 'clockwise, cw, counterclockwise, ccw', __FUNCTION__);
 3897         if (empty($control)) return FALSE;
 3898         $this->pie_direction_cw = ($control == 'clockwise' || $control == 'cw');
 3899         return TRUE;
 3900     }
 3901 
 3902 //////////////////////////////////////////////////////////
 3903 ///////////         DATA ANALYSIS, SCALING AND TRANSLATION
 3904 //////////////////////////////////////////////////////////
 3905 
 3906     /**
 3907      * Analyzes the data array and calculates the minimum and maximum values
 3908      *
 3909      * In this function, IV refers to the independent variable, and DV the dependent
 3910      * variable.  For vertical plots (most common), IV is X and DV is Y. For
 3911      * horizontal plots (swapped X/Y), IV is Y and DV is X.  At the end of the
 3912      * function, IV and DV ranges get assigned into X or Y variables.
 3913      *
 3914      * The data type mostly determines the data array structure, but some plot types
 3915      * do special things such as sum the values in a row. This information is in the
 3916      * plots[] array.
 3917      *
 3918      * This calculates min_x, max_x, min_y, and max_y. It also calculates two arrays
 3919      * data_min[] and data_max[] with per-row min and max values. These are used for
 3920      * data label lines. For vertical plots, these are the Y range for each X. For
 3921      * vertical (swapped X/Y) plots, they are the X range for each Y.  For X/Y/Z
 3922      * plots, it also calculates min_z and max_z.
 3923      *
 3924      * @return bool  True always
 3925      */
 3926     protected function FindDataLimits()
 3927     {
 3928         // Does this plot type need special processing of the data values?
 3929         $sum_vals = !empty(self::$plots[$this->plot_type]['sum_vals']); // Add up values in each row
 3930         $abs_vals = !empty(self::$plots[$this->plot_type]['abs_vals']); // Take absolute values
 3931 
 3932         // Initialize arrays which track the min/max per-row dependent values:
 3933         $this->data_min = array();
 3934         $this->data_max = array();
 3935 
 3936         // Independent values are in the data array or assumed?
 3937         if ($this->datatype_implied) {
 3938             // Range for text-data is 0.5 to num_data_rows-0.5. Add 0.5 fixed margins on each side.
 3939             $all_iv = array(0, $this->num_data_rows);
 3940         } else {
 3941             $all_iv = array(); // Calculated below
 3942         }
 3943         // For X/Y/Z plots, make sure these are not left over from a previous plot.
 3944         if ($this->datatype_yz)
 3945             $this->min_z = $this->max_z = NULL;
 3946 
 3947         // Process all rows of data:
 3948         for ($i = 0; $i < $this->num_data_rows; $i++) {
 3949             $n_vals = $this->num_recs[$i];
 3950             $j = 1; // Skips label at [0]
 3951 
 3952             if (!$this->datatype_implied) {
 3953                 $all_iv[] = (double)$this->data[$i][$j++];
 3954             }
 3955 
 3956             if ($sum_vals) {
 3957                 $all_dv = array(0, 0); // One limit is 0, other calculated below
 3958             } else {
 3959                 $all_dv = array();
 3960             }
 3961             while ($j < $n_vals) {
 3962                 if (is_numeric($val = $this->data[$i][$j++])) {
 3963 
 3964                     if ($this->datatype_error_bars) {
 3965                         $all_dv[] = $val + (double)$this->data[$i][$j++];
 3966                         $all_dv[] = $val - (double)$this->data[$i][$j++];
 3967                     } else {
 3968                         if ($abs_vals) {
 3969                             $val = abs($val); // Use absolute values
 3970                         }
 3971                         if ($sum_vals) {
 3972                             $all_dv[1] += $val;  // Sum of values
 3973                         } else {
 3974                             $all_dv[] = $val; // List of all values
 3975                         }
 3976                         if ($this->datatype_yz) {
 3977                             $z = $this->data[$i][$j++]; // Note Z is required if Y is present.
 3978                             if (!isset($this->min_z) || $z < $this->min_z) $this->min_z = $z;
 3979                             if (!isset($this->max_z) || $z > $this->max_z) $this->max_z = $z;
 3980                         }
 3981                     }
 3982                 } else {    // Missing DV value
 3983                   if ($this->datatype_error_bars) $j += 2;
 3984                   elseif ($this->datatype_yz) $j++;
 3985                 }
 3986             }
 3987             if (!empty($all_dv)) {
 3988                 $this->data_min[$i] = min($all_dv);  // Store per-row DV range
 3989                 $this->data_max[$i] = max($all_dv);
 3990             }
 3991         }
 3992 
 3993         if ($this->datatype_swapped_xy) {
 3994             // Assign min and max for swapped X/Y plots: IV=Y and DV=X
 3995             $this->min_y = min($all_iv);
 3996             $this->max_y = max($all_iv);
 3997             if (empty($this->data_min)) { // Guard against regressive case: No X at all
 3998                 $this->min_x = 0;
 3999                 $this->max_x = 0;
 4000             } else {
 4001                 $this->min_x = min($this->data_min);  // Store global X range
 4002                 $this->max_x = max($this->data_max);
 4003             }
 4004         } else {
 4005             // Assign min and max for normal plots: IV=X and DV=Y
 4006             $this->min_x = min($all_iv);
 4007             $this->max_x = max($all_iv);
 4008             if (empty($this->data_min)) { // Guard against regressive case: No Y at all
 4009                 $this->min_y = 0;
 4010                 $this->max_y = 0;
 4011             } else {
 4012                 $this->min_y = min($this->data_min);  // Store global Y range
 4013                 $this->max_y = max($this->data_max);
 4014             }
 4015         }
 4016         // For X/Y/Z plots, make sure these are set. If there are no valid data values,
 4017         // they will be unset, so set them here to prevent undefined property warnings.
 4018         if ($this->datatype_yz && !isset($this->min_z)) {   // Means max_z is also unset
 4019             $this->max_z = $this->min_z = 0; // Actual values do not matter.
 4020         }
 4021 
 4022         if ($this->GetCallback('debug_scale')) {
 4023             $this->DoCallback('debug_scale', __FUNCTION__, array(
 4024                 'min_x' => $this->min_x, 'min_y' => $this->min_y,
 4025                 'max_x' => $this->max_x, 'max_y' => $this->max_y,
 4026                 'min_z' => isset($this->min_z) ? $this->min_z : '',
 4027                 'max_z' => isset($this->max_z) ? $this->max_z : ''));
 4028         }
 4029         return TRUE;
 4030     }
 4031 
 4032     /**
 4033      * Calculates the plot area margin size and related positions and offsets
 4034      *
 4035      * Calculates the title sizes: title_height, x_title_height, and y_title_width.
 4036      * These are local, not class variables, since they are only used here.
 4037      * The X and Y title size variables are 0 if there is no corresponding title.
 4038      *
 4039      * Calculates the tick label and axis data label offsets, relative to the plot
 4040      * area: x_label_top_offset, x_label_bot_offset, x_label_axis_offset,
 4041      * y_label_left_offset, y_label_right_offset, and y_label_axis_offset.
 4042      *
 4043      * Calculates the title position offsets, relative to the plot area:
 4044      * x_title_top_offset, x_title_bot_offset, y_title_left_offset, and
 4045      * y_title_left_offset. Also calculates the main title offset, which is relative
 4046      * to the top of the image.
 4047      *
 4048      * Finally, calculates the plot area margins using the above to figure out how
 4049      * much space outside the plot area is needed. The class variables are:
 4050      * y_top_margin, y_bot_margin, x_left_margin, and x_right_margin.  All 4 margins
 4051      * are calculated, but only those not set with SetPlotAreaPixels() or
 4052      * SetMarginsPixels() are stored into the class variables.
 4053      *
 4054      * A plot with $minimize True, such as a pie chart, does not have an X or Y axis
 4055      * or X/Y titles, and so can use more of the image space.
 4056      *
 4057      * A picture of the locations of elements and spacing can be found in the
 4058      * PHPlot Reference Manual.
 4059      *
 4060      * @param bool $maximize  If True, use full image area (less margins and title space)
 4061      * @return bool  True always
 4062      */
 4063     protected function CalcMargins($maximize)
 4064     {
 4065         // This is the line-to-line or line-to-text spacing:
 4066         $gap = $this->safe_margin;
 4067         // Initial margin on each side takes into account a possible image border.
 4068         // For compatibility, if border is 1 or 2, don't increase the margins.
 4069         $base_margin = max($gap, $this->GetImageBorderWidth() + 3);
 4070         $this->title_offset = $base_margin;  // For use in DrawTitle
 4071 
 4072         // Minimum margin on each side. This reduces the chance that the
 4073         // right-most tick label (for example) will run off the image edge
 4074         // if there are no titles on that side.
 4075         $min_margin = 2 * $gap + $base_margin;
 4076 
 4077         // Calculate the title sizes (main here, axis titles below):
 4078         list($unused, $title_height) = $this->SizeText('title', 0, $this->title_txt);
 4079 
 4080         // Special case for maximum area usage with no X/Y titles or labels, only main title:
 4081         if ($maximize) {
 4082             if (!isset($this->x_left_margin))
 4083                 $this->x_left_margin = $base_margin;
 4084             if (!isset($this->x_right_margin))
 4085                 $this->x_right_margin = $base_margin;
 4086             if (!isset($this->y_top_margin)) {
 4087                 $this->y_top_margin = $base_margin;
 4088                 if ($title_height > 0)
 4089                     $this->y_top_margin += $title_height + $gap;
 4090             }
 4091             if (!isset($this->y_bot_margin))
 4092                 $this->y_bot_margin = $base_margin;
 4093 
 4094             return TRUE;
 4095         }
 4096 
 4097         list($unused, $x_title_height) = $this->SizeText('x_title', 0, $this->x_title_txt);
 4098         list($y_title_width, $unused) = $this->SizeText('y_title', 90, $this->y_title_txt);
 4099 
 4100         // For X/Y tick and label position of 'xaxis' or 'yaxis', determine if the axis happens to be
 4101         // on an edge of a plot. If it is, we need to account for the margins there.
 4102         if ($this->x_axis_position <= $this->plot_min_y)
 4103             $x_axis_pos = 'bottom';
 4104         elseif ($this->x_axis_position >= $this->plot_max_y)
 4105             $x_axis_pos = 'top';
 4106         else
 4107             $x_axis_pos = 'none';
 4108         if ($this->y_axis_position <= $this->plot_min_x)
 4109             $y_axis_pos = 'left';
 4110         elseif ($this->y_axis_position >= $this->plot_max_x)
 4111             $y_axis_pos = 'right';
 4112         else
 4113             $y_axis_pos = 'none';
 4114 
 4115         // Calculate the heights for X tick and data labels, and the max (used if they are overlaid):
 4116         $x_data_label_height = ($this->x_data_label_pos == 'none') ? 0 : $this->CalcMaxDataLabelSize('x');
 4117         $x_tick_label_height = ($this->x_tick_label_pos == 'none') ? 0 : $this->CalcMaxTickLabelSize('x');
 4118         $x_max_label_height = max($x_data_label_height, $x_tick_label_height);
 4119 
 4120         // Calculate the space needed above and below the plot for X tick and X data labels:
 4121 
 4122         // Above the plot:
 4123         $tick_labels_above = ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both'
 4124                           || ($this->x_tick_label_pos == 'xaxis' && $x_axis_pos == 'top'));
 4125         $data_labels_above = ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both');
 4126         if ($tick_labels_above) {
 4127             if ($data_labels_above) {
 4128                 $label_height_above = $x_max_label_height;
 4129             } else {
 4130                 $label_height_above = $x_tick_label_height;
 4131             }
 4132         } elseif ($data_labels_above) {
 4133             $label_height_above = $x_data_label_height;
 4134         } else {
 4135             $label_height_above = 0;
 4136         }
 4137 
 4138         // Below the plot:
 4139         $tick_labels_below = ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both'
 4140                           || ($this->x_tick_label_pos == 'xaxis' && $x_axis_pos == 'bottom'));
 4141         $data_labels_below = ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both');
 4142         if ($tick_labels_below) {
 4143             if ($data_labels_below) {
 4144                 $label_height_below = $x_max_label_height;
 4145             } else {
 4146                 $label_height_below = $x_tick_label_height;
 4147             }
 4148         } elseif ($data_labels_below) {
 4149             $label_height_below = $x_data_label_height;
 4150         } else {
 4151             $label_height_below = 0;
 4152         }
 4153 
 4154         // Calculate the width for Y tick and data labels, if on, and the max:
 4155         // Note CalcMaxDataLabelSize('y') returns 0 except for swapped X/Y plots.
 4156         $y_data_label_width = ($this->y_data_label_pos == 'none') ? 0 : $this->CalcMaxDataLabelSize('y');
 4157         $y_tick_label_width = ($this->y_tick_label_pos == 'none') ? 0 : $this->CalcMaxTickLabelSize('y');
 4158         $y_max_label_width = max($y_data_label_width, $y_tick_label_width);
 4159 
 4160         // Calculate the space needed left and right of the plot for Y tick and Y data labels:
 4161         // (Y data labels here are for swapped X/Y plots such has horizontal bars)
 4162 
 4163         // Left of the plot:
 4164         $tick_labels_left = ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both'
 4165                          || ($this->y_tick_label_pos == 'yaxis' && $y_axis_pos == 'left'));
 4166         $data_labels_left = ($this->y_data_label_pos == 'plotleft' || $this->y_data_label_pos == 'both');
 4167         if ($tick_labels_left) {
 4168             if ($data_labels_left) {
 4169                 $label_width_left = $y_max_label_width;
 4170             } else {
 4171                 $label_width_left = $y_tick_label_width;
 4172             }
 4173         } elseif ($data_labels_left) {
 4174             $label_width_left = $y_data_label_width;
 4175         } else {
 4176             $label_width_left = 0;
 4177         }
 4178 
 4179         // Right of the plot:
 4180         $tick_labels_right = ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both'
 4181                           || ($this->y_tick_label_pos == 'yaxis' && $y_axis_pos == 'right'));
 4182         $data_labels_right = ($this->y_data_label_pos == 'plotright' || $this->y_data_label_pos == 'both');
 4183         if ($tick_labels_right) {
 4184             if ($data_labels_right) {
 4185                 $label_width_right = $y_max_label_width;
 4186             } else {
 4187                 $label_width_right = $y_tick_label_width;
 4188             }
 4189         } elseif ($data_labels_right) {
 4190             $label_width_right = $y_data_label_width;
 4191         } else {
 4192             $label_width_right = 0;
 4193         }
 4194 
 4195         ///////// Calculate margins:
 4196 
 4197         // Calculating Top and Bottom margins:
 4198         // y_top_margin: Main title, Upper X title, X ticks and tick labels, and X data labels:
 4199         // y_bot_margin: Lower title, ticks and tick labels, and data labels:
 4200         $top_margin = $base_margin;
 4201         $bot_margin = $base_margin;
 4202         $this->x_title_top_offset = $gap;
 4203         $this->x_title_bot_offset = $gap;
 4204 
 4205         // Space for main title?
 4206         if ($title_height > 0)
 4207             $top_margin += $title_height + $gap;
 4208 
 4209         // Reserve space for X title, above and/or below as needed:
 4210         if ($x_title_height > 0 && ($pos = $this->x_title_pos) != 'none') {
 4211             if ($pos == 'plotup' || $pos == 'both')
 4212                 $top_margin += $x_title_height + $gap;
 4213             if ($pos == 'plotdown' || $pos == 'both')
 4214                 $bot_margin += $x_title_height + $gap;
 4215         }
 4216 
 4217         // Space for X Labels above the plot?
 4218         if ($label_height_above > 0) {
 4219             $top_margin += $label_height_above + $gap;
 4220             $this->x_title_top_offset += $label_height_above + $gap;
 4221         }
 4222 
 4223         // Space for X Labels below the plot?
 4224         if ($label_height_below > 0) {
 4225             $bot_margin += $label_height_below + $gap;
 4226             $this->x_title_bot_offset += $label_height_below + $gap;
 4227         }
 4228 
 4229         // Space for X Ticks above the plot?
 4230         if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both'
 4231            || ($this->x_tick_pos == 'xaxis' && $x_axis_pos == 'top')) {
 4232             $top_margin += $this->x_tick_length;
 4233             $this->x_label_top_offset = $this->x_tick_length + $gap;
 4234             $this->x_title_top_offset += $this->x_tick_length;
 4235         } else {
 4236             // No X Ticks above the plot:
 4237             $this->x_label_top_offset = $gap;
 4238         }
 4239 
 4240         // Space for X Ticks below the plot?
 4241         if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both'
 4242            || ($this->x_tick_pos == 'xaxis' && $x_axis_pos == 'bottom')) {
 4243             $bot_margin += $this->x_tick_length;
 4244             $this->x_label_bot_offset = $this->x_tick_length + $gap;
 4245             $this->x_title_bot_offset += $this->x_tick_length;
 4246         } else {
 4247             // No X Ticks below the plot:
 4248             $this->x_label_bot_offset = $gap;
 4249         }
 4250         // Label offsets for on-axis ticks:
 4251         if ($this->x_tick_pos == 'xaxis') {
 4252             $this->x_label_axis_offset = $this->x_tick_length + $gap;
 4253         } else {
 4254             $this->x_label_axis_offset = $gap;
 4255         }
 4256 
 4257         // Calculating Left and Right margins:
 4258         // x_left_margin: Left Y title, Y ticks and tick labels:
 4259         // x_right_margin: Right Y title, Y ticks and tick labels:
 4260         $left_margin = $base_margin;
 4261         $right_margin = $base_margin;
 4262         $this->y_title_left_offset = $gap;
 4263         $this->y_title_right_offset = $gap;
 4264 
 4265         // Reserve space for Y title, on left and/or right as needed:
 4266         if ($y_title_width > 0 && ($pos = $this->y_title_pos) != 'none') {
 4267             if ($pos == 'plotleft' || $pos == 'both')
 4268                 $left_margin += $y_title_width + $gap;
 4269             if ($pos == 'plotright' || $pos == 'both')
 4270                 $right_margin += $y_title_width + $gap;
 4271         }
 4272 
 4273         // Space for Y Labels left of the plot?
 4274         if ($label_width_left > 0) {
 4275             $left_margin += $label_width_left + $gap;
 4276             $this->y_title_left_offset += $label_width_left + $gap;
 4277         }
 4278 
 4279         // Space for Y Labels right of the plot?
 4280         if ($label_width_right > 0) {
 4281             $right_margin += $label_width_right + $gap;
 4282             $this->y_title_right_offset += $label_width_right + $gap;
 4283         }
 4284 
 4285         // Space for Y Ticks left of plot?
 4286         if ($this->y_tick_pos == 'plotleft' || $this->y_tick_pos == 'both'
 4287            || ($this->y_tick_pos == 'yaxis' && $y_axis_pos == 'left')) {
 4288             $left_margin += $this->y_tick_length;
 4289             $this->y_label_left_offset = $this->y_tick_length + $gap;
 4290             $this->y_title_left_offset += $this->y_tick_length;
 4291         } else {
 4292             // No Y Ticks left of plot:
 4293             $this->y_label_left_offset = $gap;
 4294         }
 4295 
 4296         // Space for Y Ticks right of plot?
 4297         if ($this->y_tick_pos == 'plotright' || $this->y_tick_pos == 'both'
 4298            || ($this->y_tick_pos == 'yaxis' && $y_axis_pos == 'right')) {
 4299             $right_margin += $this->y_tick_length;
 4300             $this->y_label_right_offset = $this->y_tick_length + $gap;
 4301             $this->y_title_right_offset += $this->y_tick_length;
 4302         } else {
 4303             // No Y Ticks right of plot:
 4304             $this->y_label_right_offset = $gap;
 4305         }
 4306 
 4307         // Label offsets for on-axis ticks:
 4308         if ($this->x_tick_pos == 'yaxis') {
 4309             $this->y_label_axis_offset = $this->y_tick_length + $gap;
 4310         } else {
 4311             $this->y_label_axis_offset = $gap;
 4312         }
 4313 
 4314         // Apply the minimum margins and store in the object.
 4315         // Do not set margins which were user-defined (see note at top of function).
 4316         if (!isset($this->y_top_margin))
 4317             $this->y_top_margin = max($min_margin, $top_margin);
 4318         if (!isset($this->y_bot_margin))
 4319             $this->y_bot_margin = max($min_margin, $bot_margin);
 4320         if (!isset($this->x_left_margin))
 4321             $this->x_left_margin = max($min_margin, $left_margin);
 4322         if (!isset($this->x_right_margin))
 4323             $this->x_right_margin = max($min_margin, $right_margin);
 4324 
 4325         if ($this->GetCallback('debug_scale')) {
 4326             // (Too bad compact() doesn't work on class member variables...)
 4327             $this->DoCallback('debug_scale', __FUNCTION__, array(
 4328                 'label_height_above' => $label_height_above,
 4329                 'label_height_below' => $label_height_below,
 4330                 'label_width_left' => $label_width_left,
 4331                 'label_width_right' => $label_width_right,
 4332                 'x_tick_length' => $this->x_tick_length,
 4333                 'y_tick_length' => $this->y_tick_length,
 4334                 'x_left_margin' => $this->x_left_margin,
 4335                 'x_right_margin' => $this->x_right_margin,
 4336                 'y_top_margin' => $this->y_top_margin,
 4337                 'y_bot_margin' => $this->y_bot_margin,
 4338                 'x_label_top_offset' => $this->x_label_top_offset,
 4339                 'x_label_bot_offset' => $this->x_label_bot_offset,
 4340                 'y_label_left_offset' => $this->y_label_left_offset,
 4341                 'y_label_right_offset' => $this->y_label_right_offset,
 4342                 'x_title_top_offset' => $this->x_title_top_offset,
 4343                 'x_title_bot_offset' => $this->x_title_bot_offset,
 4344                 'y_title_left_offset' => $this->y_title_left_offset,
 4345                 'y_title_right_offset' => $this->y_title_right_offset));
 4346         }
 4347 
 4348         return TRUE;
 4349     }
 4350 
 4351     /**
 4352      * Calculates the plot area (in pixels) from the margins
 4353      *
 4354      * This does the deferred calculation of class variables plot_area,
 4355      * plot_area_width, and plot_area_height. The margins might come
 4356      * from SetMarginsPixels(), SetPlotAreaPixels(), or CalcMargins().
 4357      *
 4358      * @return bool  True always
 4359      * @since 5.0.5
 4360      */
 4361     protected function CalcPlotAreaPixels()
 4362     {
 4363         $this->plot_area = array($this->x_left_margin, $this->y_top_margin,
 4364                                  $this->image_width - $this->x_right_margin,
 4365                                  $this->image_height - $this->y_bot_margin);
 4366         $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0];
 4367         $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1];
 4368 
 4369         $this->DoCallback('debug_scale', __FUNCTION__, $this->plot_area);
 4370         return TRUE;
 4371     }
 4372 
 4373     /**
 4374      * Sets the margins around the plot area
 4375      *
 4376      * This determines the plot area, equivalent to SetPlotAreaPixels()
 4377      * but instead of giving the plot area size, you give the margin sizes.
 4378      *
 4379      * @param int $which_lm  Left margin in pixels; omit or NULL to auto-calculate
 4380      * @param int $which_rm  Right margin in pixels; omit or NULL to auto-calculate
 4381      * @param int $which_tm  Top margin in pixels; omit or NULL to auto-calculate
 4382      * @param int $which_bm  Bottom margin in pixels; omit or NULL to auto-calculate
 4383      * @return bool  True always
 4384      */
 4385     function SetMarginsPixels($which_lm = NULL, $which_rm = NULL, $which_tm = NULL, $which_bm = NULL)
 4386     {
 4387         $this->x_left_margin = $which_lm;
 4388         $this->x_right_margin = $which_rm;
 4389         $this->y_top_margin = $which_tm;
 4390         $this->y_bot_margin = $which_bm;
 4391         return TRUE;
 4392     }
 4393 
 4394     /**
 4395      * Sets the limits for the plot area, in device coordinates
 4396      *
 4397      * This determines the plot area, equivalent to SetMarginsPixels()
 4398      * but instead of giving the margin sizes, you give the plot area size,
 4399      *
 4400      * This stores the margins, not the area. That may seem odd, but the idea is
 4401      * to make SetPlotAreaPixels and SetMarginsPixels two ways to accomplish the
 4402      * same thing, and the deferred calculations in CalcMargins and
 4403      * CalcPlotAreaPixels don't need to know which was used.
 4404      *
 4405      * @param int $x1  Top left corner X coordinate in pixels; omit or NULL to auto-calculate
 4406      * @param int $y1  Top left corner Y coordinate in pixels; omit or NULL to auto-calculate
 4407      * @param int $x2  Bottom right corner X coordinate in pixels; omit or NULL to auto-calculate
 4408      * @param int $y2  Bottom right corner Y coordinate in pixels; omit or NULL to auto-calculate
 4409      * @return bool  True always
 4410      */
 4411     function SetPlotAreaPixels($x1 = NULL, $y1 = NULL, $x2 = NULL, $y2 = NULL)
 4412     {
 4413         $this->x_left_margin = $x1;
 4414         $this->x_right_margin = isset($x2) ? $this->image_width - $x2 : NULL;
 4415         $this->y_top_margin = $y1;
 4416         $this->y_bot_margin = isset($y2) ? $this->image_height - $y2 : NULL;
 4417         return TRUE;
 4418     }
 4419 
 4420     /**
 4421      * Calculates an appropriate tick increment in 'decimal' mode
 4422      *
 4423      * This is used by CalcStep().  It returns the largest value that is 1, 2, or 5
 4424      * times an integer power of 10, and which divides the data range into no fewer
 4425      * than min_ticks tick intervals.
 4426      *
 4427      * @param float $range  The data range (max - min), already checked as > 0
 4428      * @param int $min_ticks  Smallest number of intervals allowed, already checked > 0
 4429      * @return float  The calculated tick increment
 4430      * @since 6.0.0
 4431      */
 4432     protected function CalcStep125($range, $min_ticks)
 4433     {
 4434         $v = log10($range / $min_ticks);
 4435         $vi = (int)floor($v);
 4436         $tick_step = pow(10, $vi);
 4437         $f = $v - $vi;
 4438         if ($f > 0.69897) $tick_step *= 5;   // Note 0.69897 = log10(5)
 4439         elseif ($f > 0.301) $tick_step *= 2;  // Note 0.30103 = log10(2), truncated to fix edge cases
 4440         return $tick_step;
 4441     }
 4442 
 4443     /**
 4444      * Calculates an appropriate tick increment in 'date/time' mode
 4445      *
 4446      * This is used by CalcStep() when the axis values use date/time units.
 4447      * Unlike in CalcStep125(), there is no equation to compute this, so an array
 4448      * $datetime_steps is used.  The values are "natural" time intervals, keeping
 4449      * the ratio between adjacent entries <= 2.5 (so that max_ticks <= 2.5*min_ticks).
 4450      * For seconds or minutes, it uses: 1 2 5 10 15 30. For hours, it uses 1 2 4 8 12
 4451      * 24 48 96 and 168 (=7 days). Above that, it falls back to CalcStep125 using days.
 4452      *
 4453      * @param float $range  The data range (max - min), already checked as > 0
 4454      * @param int $min_ticks  Smallest number of intervals allowed, already checked > 0
 4455      * @return int  The calculated tick increment. This will always be >= 1 second
 4456      * @since 6.0.0
 4457      */
 4458     protected function CalcStepDatetime($range, $min_ticks)
 4459     {
 4460         static $datetime_steps = array(1, 2, 5, 10, 15, 30, 60, 120, 300, 600, 900, 1800, 3600,
 4461                                     7200, 14400, 28800, 43200, 86400, 172800, 345600, 604800);
 4462         static $datetime_limit = 1512000;  // 1 week times 2.5, in seconds.
 4463 
 4464         if ($range < $min_ticks) {
 4465             $tick_step = 1;    // Range is too small; minimum interval is 1 second.
 4466         } elseif (($tick_limit = $range / $min_ticks) <= $datetime_limit) {
 4467             // Find the biggest value in the table <= tick_limit (which is the
 4468             // exact interval which would give min_ticks steps):
 4469             foreach ($datetime_steps as $v) {
 4470                 if ($v <= $tick_limit) $tick_step = $v;
 4471                 else break;
 4472             }
 4473         } else {
 4474             // Use the numeric-mode algorithm to find a 1,2,5*10**n solution, in units of days.
 4475             $tick_step = $this->CalcStep125($range / 86400, $min_ticks) * 86400;
 4476         }
 4477         return $tick_step;
 4478     }
 4479 
 4480     /**
 4481      * Calculates an appropriate tick increment in 'binary' mode
 4482      *
 4483      * This is used by CalcStep(). It returns the largest power of 2 that divides
 4484      * the range into at least min_ticks intervals.
 4485      * Note: This contains an ugly work-around to a round-off problem. Using
 4486      * floor(log2(2^N)) should produce N for all integer N, but with glibc, it turns
 4487      * out that log2(8) is slightly < 3, and similar for a few other values (64, 4096,
 4488      * 8192). So they truncate to N-1. Other tested values, and all negative values,
 4489      * and all values on Windows, were found to truncate with floor() in the right
 4490      * direction. The adjustment below makes all values N=-50 to 50 truncate correctly.
 4491      *
 4492      * @param float $range  The data range (max - min), already checked as > 0
 4493      * @param int $min_ticks  Smallest number of intervals allowed, already checked > 0
 4494      * @return float  The calculated tick increment
 4495      * @since 6.0.0
 4496      */
 4497     protected function CalcStepBinary($range, $min_ticks)
 4498     {
 4499         $log2 = log($range / $min_ticks, 2);
 4500         if ($log2 > 0) $log2 *= 1.000001; // See note above
 4501         return pow(2, (int)floor($log2));
 4502     }
 4503 
 4504     /**
 4505      * Calculates an ideal tick increment for a given range
 4506      *
 4507      * This is only used when neither Set[XY]TickIncrement nor SetNum[XY]Ticks was used.
 4508      *
 4509      * @param string $which  Which axis to calculate for. Must be 'x' or 'y'
 4510      * @param float $range  The data range (max - min), already checked as > 0
 4511      * @return float  The tick increment, using one of 3 methods depending on the 'tick_mode'
 4512      * @since 6.0.0
 4513      */
 4514     protected function CalcStep($which, $range)
 4515     {
 4516         // Get tick control variables: min_ticks, tick_mode, tick_inc_integer.
 4517         extract($this->tickctl[$which]);
 4518 
 4519         // If tick_mode is null, default to decimal mode unless the axis uses date/time formatting:
 4520         if (!isset($tick_mode)) {
 4521             if (isset($this->label_format[$which]['type']) && $this->label_format[$which]['type'] == 'time')
 4522                 $tick_mode = 'date';
 4523             else
 4524                 $tick_mode = 'decimal';
 4525         }
 4526 
 4527         // Use proper mode to calculate the tick increment, with integer override option.
 4528         if ($tick_mode == 'date') {
 4529             $tick_step = $this->CalcStepDatetime($range, $min_ticks);
 4530         } elseif ($tick_inc_integer && $range <= $min_ticks) {
 4531             $tick_step = 1;  // Whole integer ticks selected but range is too small.
 4532         } elseif ($tick_mode == 'binary') {
 4533             $tick_step = $this->CalcStepBinary($range, $min_ticks);
 4534         } else {
 4535             $tick_step = $this->CalcStep125($range, $min_ticks);
 4536         }
 4537         return $tick_step;
 4538     }
 4539 
 4540     /**
 4541      * Initializes range variables for CalcPlotRange()
 4542      *
 4543      * This is a helper for CalcPlotRange(), which calls it 4 times, to initialize
 4544      * each end of the range for each axis. It also sets flags to indicate
 4545      * whether automatic adjustment of the range end is needed.
 4546      *
 4547      * @param float $plot_limit  Reference to (possibly unset) plot_min_[xy] or plot_max_[xy]
 4548      * @param bool $implied  True if this is the implied variable (X for vertical plots)
 4549      * @param float $data_limit  Actual data limit at this end: one of min_x, max_x,  etc.
 4550      * @return array  Array with (initial value of the range limit, adjustment flag)
 4551      * @since 6.0.0
 4552      */
 4553     protected function CalcRangeInit(&$plot_limit, $implied, $data_limit)
 4554     {
 4555         if (isset($plot_limit) && $plot_limit !== '') {
 4556             // Use the user-supplied value, and do no further adjustments.
 4557             return array($plot_limit, FALSE);
 4558         }
 4559         // Start with the actual data range. Set adjustment flag TRUE unless the range was implied.
 4560         return array($data_limit, !$implied);
 4561     }
 4562 
 4563     /**
 4564      * Checks for a positive plot area range, and adjust if necessary
 4565      *
 4566      * This makes sure that the X or Y plot range is positive. The tick increment and
 4567      * other calculations cannot handle negative or zero range, so we need to do
 4568      * something to prevent it. There are 2 general cases: 1) automatic range, and
 4569      * data is 'flat' (all same value). 2) One side of range given in
 4570      * SetPlotAreaWorld(), and all the data is on the wrong side of that.
 4571      *
 4572      * Note that values specified in SetPlotAreaWorld() are never adjusted, even if it
 4573      * means an empty plot (because all the data is outside the range).
 4574      *
 4575      * Called by CalcPlotRange() after CalcRangeInit() applies the defaults.
 4576      *
 4577      * @param string $which  Which axis: 'x' or 'y', used only for reporting
 4578      * @param float $plot_min  Reference variable for the low end limit, changed if necessary
 4579      * @param float $plot_max  Reference variable for the high end limit, changed if necessary
 4580      * @param bool $adjust_min  True means $plot_min was auto-calculated, and may be adjusted
 4581      * @param bool $adjust_max  True means $plot_max was auto-calculated, and may be adjusted
 4582      * @return bool  True (False on error if an error handler returns True)
 4583      * @since 6.0.0
 4584      */
 4585     protected function CheckPlotRange($which, &$plot_min, &$plot_max, $adjust_min, $adjust_max)
 4586     {
 4587         if ($plot_min < $plot_max)
 4588             return TRUE; // No adjustment needed.
 4589 
 4590         // Bad range, plot_min >= plot_max, needs fixing.
 4591         if ($adjust_max && $adjust_min) {
 4592             // Both min and max are calculated, so either or both can be adjusted.
 4593             // It should not be possible that plot_min > plot_max here, but check it to be safe:
 4594             if ($plot_max != $plot_min)
 4595                 return $this->PrintError("SetPlotAreaWorld(): Inverse auto $which range error");
 4596 
 4597             if ($plot_max == 0.0) {
 4598                 // All 0. Use the arbitrary range 0:10
 4599                 $plot_max = 10;
 4600             } elseif ($plot_min > 0) {
 4601                 // All same positive value. Use the range 0:10 (or larger).
 4602                 $plot_min = 0;
 4603                 $plot_max = max($plot_max, 10);
 4604             } else {
 4605                 // All same negative value. Use the range -10:0 (or larger).
 4606                 $plot_min = min($plot_min, -10);
 4607                 $plot_max = 0;
 4608             }
 4609         } elseif ($adjust_max) {  // Equivalent to: ($adjust_max && !$adjust_min)
 4610             // Max is calculated, min was set, so adjust max
 4611             if ($plot_min < 0) $plot_max = 0;
 4612             else $plot_max = $plot_min + 10;
 4613 
 4614         } elseif ($adjust_min) {  // Equivalent to: (!$adjust_max && $adjust_min)
 4615             // Min is calculated, max was set, so adjust min
 4616             if ($plot_max > 0) $plot_min = 0;
 4617             else $plot_min = $plot_max - 10;
 4618 
 4619         } else {  // Equivalent to: (!$adjust_max && !$adjust_min)
 4620             // Both limits are set. This should never happen, since SetPlotAreaWorld stops it.
 4621             return $this->PrintError("SetPlotAreaWorld(): Inverse $which range error");
 4622         }
 4623         return TRUE;
 4624     }
 4625 
 4626     /**
 4627      * Gets the plot range end adjustment factor
 4628      *
 4629      * This is a helper for CalcPlotRange().  If $adjust is already set, it is left
 4630      * alone, otherwise it uses the current plot type to apply a default setting.
 4631      *
 4632      * @param string $which  Which axis to calculate for. Must be 'x' or 'y'
 4633      * @param float $adjust  Reference variable for the range end adjustment factor, NULL or already set
 4634      * @since 6.0.0
 4635      */
 4636     protected function GetRangeEndAdjust($which, &$adjust)
 4637     {
 4638         if (isset($adjust)) return;  // Already set, nothing to do
 4639 
 4640         // The plot type can customize how an end adjustment is applied:
 4641         if (empty(self::$plots[$this->plot_type]['adjust_type'])) {
 4642             // Default (adjust_type missing or 0) means pad the dependent variable axis only.
 4643             $adjust = ($which == 'x' XOR $this->datatype_swapped_xy) ? 0 : 0.03;
 4644         } else {
 4645             switch (self::$plots[$this->plot_type]['adjust_type']) {
 4646             case 1:
 4647                 // Adjust type = 1 means add extra padding to both X and Y axis ends
 4648                 $adjust = 0.03;
 4649                 break;
 4650             case 2:
 4651                 // Adjust type = 2 means do not add extra padding to either axis.
 4652                 $adjust = 0;
 4653                 break;
 4654             }
 4655         }
 4656     }
 4657 
 4658     /**
 4659      * Calculates the plot range and tick increment for X or Y
 4660      *
 4661      * This is a helper for CalcPlotAreaWorld(). It returns the plot range (plot_min
 4662      * and plot_max), and the tick increment, for the X or Y axis. Priority is given
 4663      * to user-set values with SetPlotAreaWorld() and Set[XY]TickIncrement(), but if
 4664      * these were defaulted then values are automatically calculated.
 4665      *
 4666      * @param string $which  Which axis to calculate for. Must be 'x' or 'y'
 4667      * @return float[]  Array of (tick_increment, plot_min, plot_max) or FALSE on handled error
 4668      * @since 6.0.0
 4669      */
 4670     protected function CalcPlotRange($which)
 4671     {
 4672         // Independent variable is X in the usual vertical plots; Y in horizontal plots:
 4673         $independent_variable = ($this->datatype_swapped_xy XOR $which == 'x');
 4674         // 'implied' means this is a non-explicitly given independent variable, e.g. X in 'text-data'.
 4675         $implied = $this->datatype_implied && $independent_variable;
 4676 
 4677         // Initialize the range (plot_min : plot_max) and get the adjustment flags:
 4678         list($plot_min, $adjust_min) =
 4679             $this->CalcRangeInit($this->{"plot_min_$which"}, $implied, $this->{"min_$which"});
 4680         list($plot_max, $adjust_max) =
 4681             $this->CalcRangeInit($this->{"plot_max_$which"}, $implied, $this->{"max_$which"});
 4682 
 4683         // Get local copies of variables used for range adjustment: adjust_{mode,amount} zero_magnet
 4684         extract($this->rangectl[$which]);
 4685         $this->GetRangeEndAdjust($which, $adjust_amount);  // Apply default to $adjust_amount if needed
 4686 
 4687         // Get local copies of other variables for X or Y:
 4688         if ($which == 'x') {
 4689             $num_ticks = $this->num_x_ticks;
 4690             $tick_inc = $this->x_tick_inc_u;
 4691             // Tick anchor is only used in 'T' adjust mode, where no tick anchor means anchor at 0.
 4692             $tick_anchor = isset($this->x_tick_anchor) ? $this->x_tick_anchor : 0;
 4693         } else {
 4694             $num_ticks = $this->num_y_ticks;
 4695             $tick_inc = $this->y_tick_inc_u;
 4696             $tick_anchor = isset($this->y_tick_anchor) ? $this->y_tick_anchor : 0;
 4697         }
 4698 
 4699         // Validate the range, which must be positive. Adjusts plot_min and plot_max if necessary.
 4700         if (!$this->CheckPlotRange($which, $plot_min, $plot_max, $adjust_min, $adjust_max))
 4701             return FALSE;
 4702 
 4703         // Adjust the min and max values, if flagged above for adjustment.
 4704 
 4705         // Notes: (zero_magnet / (1 - zero_magnet)) is just a way to map a control with range 0:1
 4706         // into the range 0:infinity (with zero_magnet==1 handled as a special case).
 4707         //    (plot_max / (plot_max - plot_min)) compares the range after plot_min would be set
 4708         // to zero with the original range. Similar for negative data: (plot_min / (plot_min - plot_max))
 4709         // compares the range with plot_max=0 to the original range.
 4710 
 4711         $range = $plot_max - $plot_min;
 4712 
 4713         // When all data > 0, test to see if the zero magnet is strong enough to pull the min down to zero:
 4714         if ($adjust_min && $plot_min > 0 && $zero_magnet > 0 && ($zero_magnet == 1.0 ||
 4715                  $plot_max / $range < $zero_magnet / (1 - $zero_magnet))) {
 4716             $plot_min = 0;
 4717             $range = $plot_max;
 4718         }
 4719 
 4720         // Similar to above, but for negative data: zero magnet pulls max up to zero:
 4721         if ($adjust_max && $plot_max < 0 && $zero_magnet > 0 && ($zero_magnet == 1.0 ||
 4722                  -$plot_min / $range < $zero_magnet / (1 - $zero_magnet))) {
 4723             $plot_max = 0;
 4724             $range = 0 - $plot_min;
 4725         }
 4726 
 4727         // Calculate the tick increment, if it wasn't set using Set[XY]TickIncrement().
 4728         $num_ticks_override = FALSE;
 4729         if (empty($tick_inc)) {
 4730             if (empty($num_ticks)) {
 4731                 // Calculate a reasonable tick increment, based on the current plot area limits
 4732                 $tick_inc = $this->CalcStep($which, $range);
 4733             } else {
 4734                 // Number of ticks was provided: use exactly that.
 4735                 // Adjustment is below, after range is calculated using mode 'R':
 4736                 $adjust_mode = 'R';
 4737                 $num_ticks_override = TRUE;
 4738             }
 4739         }
 4740 
 4741         // Adjust the lower bound, if necessary, using one of 3 modes:
 4742         if ($adjust_min && $plot_min != 0) {
 4743             // Mode 'R' and basis for other modes: Extend the limit by a percentage of the
 4744             // plot range, but only when the data is negative (to leave room below for labels).
 4745             if ($plot_min < 0)
 4746                 $plot_min -= $adjust_amount * $range;
 4747 
 4748             if ($adjust_mode == 'T') {
 4749                 // Mode 'T': Adjust to previous tick mark, taking tick anchor into account.
 4750                 $pm = $tick_anchor + $tick_inc * floor(($plot_min - $tick_anchor) / $tick_inc);
 4751                 // Don't allow the tick anchor adjustment to result in the range end moving across 0:
 4752                 $plot_min = (($plot_min >= 0) === ($pm >= 0)) ? $pm : 0;
 4753             } elseif ($adjust_mode == 'I') {
 4754                 // Mode 'I': Adjust to previous integer:
 4755                 $plot_min = floor($plot_min);
 4756             }
 4757         }
 4758 
 4759         // Adjust the upper bound, if necessary, using one of 3 modes:
 4760         if ($adjust_max && $plot_max != 0) {
 4761             // Mode 'R' and basis for other modes: Extend the limit by a percentage of the
 4762             // plot range, but only when the max is positive (leaves room above for labels).
 4763             if ($plot_max > 0)
 4764                 $plot_max += $adjust_amount * $range;
 4765 
 4766             if ($adjust_mode == 'T') {
 4767                 // Mode 'T': Adjust to next tick mark, taking tick anchor into account:
 4768                 $pm = $tick_anchor + $tick_inc * ceil(($plot_max - $tick_anchor) / $tick_inc);
 4769                 // Don't allow the tick anchor adjustment to result in the range end moving across 0:
 4770                 $plot_max = (($plot_max >= 0) === ($pm >= 0)) ? $pm : 0;
 4771             } elseif ($adjust_mode == 'I') {
 4772                 // Mode 'I': Adjustment to next higher integer.
 4773                 $plot_max = ceil($plot_max);
 4774             }
 4775         }
 4776 
 4777         // Calculate the tick increment for the case where number of ticks was given:
 4778         if ($num_ticks_override) {
 4779             $tick_inc = ($plot_max - $plot_min) / $num_ticks;
 4780         }
 4781 
 4782         // Check log scale range - plot_min and plot_max must be > 0.
 4783         if ($which == 'y' && $this->yscale_type == 'log' || $which == 'x' && $this->xscale_type == 'log') {
 4784             if ($plot_min <= 0) $plot_min = 1;
 4785             if ($plot_max <= 0) {
 4786                 // Note: Error message names the public function, not this function.
 4787                 return $this->PrintError("SetPlotAreaWorld(): Invalid $which range for log scale");
 4788             }
 4789         }
 4790         // Final error check to ensure the range is positive.
 4791         if ($plot_min >= $plot_max) $plot_max = $plot_min + 1;
 4792 
 4793         // Return the calculated values. (Note these get stored back into class variables.)
 4794         return array($tick_inc, $plot_min, $plot_max);
 4795     }
 4796 
 4797     /**
 4798      * Calculates the World Coordinate limits of the plot area, and the tick increments
 4799      *
 4800      * This is called by DrawGraph(), after FindDataLimits() determines the data
 4801      * limits, to calculate the plot area scale and tick increments.  The plot range
 4802      * and increment are related, which is why they are calculated together.
 4803      *
 4804      * The plot range variables (plot_min_x, plot_max_x, plot_min_y, plot_max_y) are
 4805      * calculated if necessary, then stored back into the object ('sticky' for
 4806      * multiple plots on an image).
 4807      *
 4808      * The tick increments (x_tick_inc, y_tick_inc) are calculated. These default to
 4809      * the user-set values (x_tick_inc_u, y_tick_inc_u) but we keep the effective
 4810      * values separate so that they can be recalculated for a second plot (which may
 4811      * have a different data range).
 4812      *
 4813      * @return bool  True, unless an handled error occurred in a called function
 4814      * @since 5.0.5
 4815      */
 4816     protected function CalcPlotAreaWorld()
 4817     {
 4818         list($this->x_tick_inc, $this->plot_min_x, $this->plot_max_x) = $this->CalcPlotRange('x');
 4819         list($this->y_tick_inc, $this->plot_min_y, $this->plot_max_y) = $this->CalcPlotRange('y');
 4820         if ($this->GetCallback('debug_scale')) {
 4821             $this->DoCallback('debug_scale', __FUNCTION__, array(
 4822                 'plot_min_x' => $this->plot_min_x, 'plot_min_y' => $this->plot_min_y,
 4823                 'plot_max_x' => $this->plot_max_x, 'plot_max_y' => $this->plot_max_y,
 4824                 'x_tick_inc' => $this->x_tick_inc, 'y_tick_inc' => $this->y_tick_inc));
 4825         }
 4826         return isset($this->x_tick_inc, $this->y_tick_inc); // Pass thru FALSE return from CalcPlotRange()
 4827     }
 4828 
 4829     /**
 4830      * Overrides automatic data scaling to device coordinates
 4831      *
 4832      * This fixes one or more ends of the range of the plot to specific
 4833      * value(s), given in World Coordinates.
 4834      * Any limit not set or set to NULL will be calculated in
 4835      * CalcPlotAreaWorld().  If both ends of either X or Y range are
 4836      * supplied, the range is validated to ensure min < max.
 4837      *
 4838      * @param float $xmin  X data range minimum; omit or NULL to auto-calculate
 4839      * @param float $ymin  Y data range minimum; omit or NULL to auto-calculate
 4840      * @param float $xmax  X data range maximum; omit or NULL to auto-calculate
 4841      * @param float $ymax  Y data range maximum; omit or NULL to auto-calculate
 4842      * @return bool  True (False on error if an error handler returns True)
 4843      */
 4844     function SetPlotAreaWorld($xmin=NULL, $ymin=NULL, $xmax=NULL, $ymax=NULL)
 4845     {
 4846         $this->plot_min_x = $xmin;
 4847         $this->plot_max_x = $xmax;
 4848         $this->plot_min_y = $ymin;
 4849         $this->plot_max_y = $ymax;
 4850         if (isset($xmin) && isset($xmax) && $xmin >= $xmax) $bad = 'X';
 4851         elseif (isset($ymin) && isset($ymax) && $ymin >= $ymax) $bad = 'Y';
 4852         else return TRUE;
 4853         return $this->PrintError("SetPlotAreaWorld(): $bad range error - min >= max");
 4854     }
 4855 
 4856     /**
 4857      * Calculates sizes of bars for bars and stackedbars plots
 4858      *
 4859      * This calculates the following class variables, which control the size and
 4860      * spacing of bars in vertical and horizontal 'bars' and 'stackedbars' plots:
 4861      * record_bar_width (allocated width for each bar, including gaps),
 4862      * actual_bar_width (actual drawn width of each bar), and
 4863      * bar_adjust_gap (gap on each side of each bar, 0 if they touch).
 4864      *
 4865      * Note that when $verticals is False, the bars are horizontal, but the same
 4866      * variable names are used.  Think of "bar_width" as being the width if you
 4867      * are standing on the Y axis looking towards positive X.
 4868      *
 4869      * @param bool $stacked  If true, this is a stacked bar plot (1 bar per group)
 4870      * @param bool $verticals  If false, this is a horizontal bar plot
 4871      * @return bool  True always
 4872      */
 4873     protected function CalcBarWidths($stacked, $verticals)
 4874     {
 4875         // group_width is the width of a group, including padding
 4876         if ($verticals) {
 4877             $group_width = $this->plot_area_width / $this->num_data_rows;
 4878         } else {
 4879             $group_width = $this->plot_area_height / $this->num_data_rows;
 4880         }
 4881 
 4882         // Number of bar spaces in the group, including actual bars and bar_extra_space extra:
 4883         if ($stacked) {
 4884             $num_spots = 1 + $this->bar_extra_space;
 4885         } else {
 4886             $num_spots = $this->data_columns + $this->bar_extra_space;
 4887         }
 4888 
 4889         // record_bar_width is the width of each bar's allocated area.
 4890         // If bar_width_adjust=1 this is the width of the bar, otherwise
 4891         // the bar is centered inside record_bar_width.
 4892         // The equation is:
 4893         //   group_frac_width * group_width = record_bar_width * num_spots
 4894         $this->record_bar_width = $this->group_frac_width * $group_width / $num_spots;
 4895 
 4896         // Note that the extra space due to group_frac_width and bar_extra_space will be
 4897         // evenly divided on each side of the group: the drawn bars are centered in the group.
 4898 
 4899         // Within each bar's allocated space, if bar_width_adjust=1 the bar fills the
 4900         // space, otherwise it is centered.
 4901         // This is the actual drawn bar width:
 4902         $this->actual_bar_width = $this->record_bar_width * $this->bar_width_adjust;
 4903         // This is the gap on each side of the bar (0 if bar_width_adjust=1):
 4904         $this->bar_adjust_gap = ($this->record_bar_width - $this->actual_bar_width) / 2;
 4905 
 4906         if ($this->GetCallback('debug_scale')) {
 4907             $this->DoCallback('debug_scale', __FUNCTION__, array(
 4908                 'record_bar_width' => $this->record_bar_width,
 4909                 'actual_bar_width' => $this->actual_bar_width,
 4910                 'bar_adjust_gap' => $this->bar_adjust_gap));
 4911         }
 4912         return TRUE;
 4913     }
 4914 
 4915     /**
 4916      * Calculates the X and Y axis positions in world coordinates
 4917      *
 4918      * This validates or calculates the class variables x_axis_position and
 4919      * y_axis_position. Note x_axis_position is the Y value where the X axis
 4920      * goes; y_axis_position is the X value where the Y axis goes.
 4921      * If they were set by the user, they are used as-is, unless they are
 4922      * outside the data range. If they are not set, they are calculated here.
 4923      *
 4924      * For vertical plots, the X axis defaults to Y=0 if that is inside the plot
 4925      * range, else whichever of the top or bottom that has the smallest absolute
 4926      * value (that is, the value closest to 0).  The Y axis defaults to the left
 4927      * edge. For horizontal plots, the axis roles and defaults are switched.
 4928      *
 4929      * @return bool  True always
 4930      * @since 5.0.5
 4931      */
 4932     protected function CalcAxisPositions()
 4933     {
 4934         // Validate user-provided X axis position, or calculate a default if not provided:
 4935         if (isset($this->x_axis_position)) {
 4936             // Force user-provided X axis position to be within the plot range:
 4937             $this->x_axis_position = min(max($this->plot_min_y, $this->x_axis_position), $this->plot_max_y);
 4938         } elseif ($this->yscale_type == 'log') {
 4939             // Always use 1 for X axis position on log scale plots.
 4940             $this->x_axis_position = 1;
 4941         } elseif ($this->datatype_swapped_xy || $this->plot_min_y > 0) {
 4942             // Horizontal plot, or Vertical Plot with all Y > 0: Place X axis on the bottom.
 4943             $this->x_axis_position = $this->plot_min_y;
 4944         } elseif ($this->plot_max_y < 0) {
 4945             // Vertical plot with all Y < 0, so place the X axis at the top.
 4946             $this->x_axis_position = $this->plot_max_y;
 4947         } else {
 4948             // Vertical plot range includes Y=0, so place X axis at 0.
 4949             $this->x_axis_position = 0;
 4950         }
 4951 
 4952         // Validate user-provided Y axis position, or calculate a default if not provided:
 4953         if (isset($this->y_axis_position)) {
 4954             // Force user-provided Y axis position to be within the plot range:
 4955             $this->y_axis_position = min(max($this->plot_min_x, $this->y_axis_position), $this->plot_max_x);
 4956         } elseif ($this->xscale_type == 'log') {
 4957             // Always use 1 for Y axis position on log scale plots.
 4958             $this->y_axis_position = 1;
 4959         } elseif (!$this->datatype_swapped_xy || $this->plot_min_x > 0) {
 4960             // Vertical plot, or Horizontal Plot with all X > 0: Place Y axis on left side.
 4961             $this->y_axis_position = $this->plot_min_x;
 4962         } elseif ($this->plot_max_x < 0) {
 4963             // Horizontal plot with all X < 0, so place the Y axis on the right side.
 4964             $this->y_axis_position = $this->plot_max_x;
 4965         } else {
 4966             // Horizontal plot range includes X=0: place Y axis at 0.
 4967             $this->y_axis_position = 0;
 4968         }
 4969 
 4970         if ($this->GetCallback('debug_scale')) {
 4971             $this->DoCallback('debug_scale', __FUNCTION__, array(
 4972                 'x_axis_position' => $this->x_axis_position,
 4973                 'y_axis_position' => $this->y_axis_position));
 4974         }
 4975 
 4976         return TRUE;
 4977     }
 4978 
 4979     /**
 4980      * Calculates parameters for transforming world coordinates to pixel coordinates
 4981      *
 4982      * This calculates xscale, yscale, plot_origin_x, and plot_origin_y, which map
 4983      * world coordinate space into the plot area. These are used by xtr() and ytr()
 4984      *
 4985      * @return bool  True always
 4986      */
 4987     protected function CalcTranslation()
 4988     {
 4989         if ($this->plot_max_x - $this->plot_min_x == 0) { // Check for div by 0
 4990             $this->xscale = 0;
 4991         } else {
 4992             if ($this->xscale_type == 'log') {
 4993                 $this->xscale = $this->plot_area_width /
 4994                                 (log10($this->plot_max_x) - log10($this->plot_min_x));
 4995             } else {
 4996                 $this->xscale = $this->plot_area_width / ($this->plot_max_x - $this->plot_min_x);
 4997             }
 4998         }
 4999 
 5000         if ($this->plot_max_y - $this->plot_min_y == 0) { // Check for div by 0
 5001             $this->yscale = 0;
 5002         } else {
 5003             if ($this->yscale_type == 'log') {
 5004                 $this->yscale = $this->plot_area_height /
 5005                                 (log10($this->plot_max_y) - log10($this->plot_min_y));
 5006             } else {
 5007                 $this->yscale = $this->plot_area_height / ($this->plot_max_y - $this->plot_min_y);
 5008             }
 5009         }
 5010         // GD defines x = 0 at left and y = 0 at TOP so -/+ respectively
 5011         if ($this->xscale_type == 'log') {
<