0

plotting data coming from a live database using mysql in a webpage using php/html.

thanks in advacne.^^

2
Contributors
4
Replies
5
Views
7 Years
Discussion Span
Last Post by xuexue
0

Could you please be more specific. How much data are you talking about, what kind of data, and what type of graph should be plotted?
What have you written (please post your code here) and what is going wrong?

0

we are talking about precised data here..lets say the maximum value for y = 3.4025 and lowest is 3.4000..data varies around taht range only. i already have the code but it does not work for minute amount.

0

So, all you need is to make some changes in the code to get it working. And we can always help, but .... where is the code? Or is 6000 lines long?

What does the code do if you multiply your data with 10 or 100 so your lowest value will be 34 or 34000 and the highest 34099? If your code can handle results like that, the problem would be solved.

Edited by colweb: n/a

0

here is the code sir..

im using PHPlot in plotting graphs, here is the code which makes the graph..

i deleted the fonts, colors and ticks code..if ever you needed the full code i can send it thru mail. thanks, thanks, i will really appreciate
if you could help me debug this one. the data we are talking ranges from e.g. (5.7041 - 5.7009)

thanks thansk.

phplot.php

<?php

if (! defined(__FUNCTION__))
    define(__FUNCTION__, '__FUNCTION__ Requires at least PHP 4.3.0.');

define ('MINY', -1);        // Indexes in $data (for DrawXDataLine())
define ('MAXY', -2);
define ('TOTY', -3);

error_reporting(E_ALL);

class PHPlot 
{
    //////////////// CONFIG PARAMETERS //////////////////////

    var $is_inline = FALSE;             // FALSE = Sends headers, TRUE = sends just raw image data
    var $browser_cache = FALSE;         // FALSE = Sends headers for browser to not cache the image,
                                        // (only if is_inline = FALSE also)

    var $safe_margin = 5;               // Extra margin used in several places. In pixels

    var $x_axis_position = '';          // Where to draw both axis (world coordinates),
    var $y_axis_position = '';          // leave blank for X axis at 0 and Y axis at left of plot.

    var $xscale_type = 'linear';        // linear, log
    var $yscale_type = 'linear';

//Fonts
    var $use_ttf  = FALSE;                  // Use True Type Fonts?
    var $ttf_path = '.';                    // Default path to look in for TT Fonts.
    var $default_ttfont = 'benjamingothic.ttf';
    var $line_spacing = 4;                  // Pixels between lines.

// Font angles: 0 or 90 degrees for fixed fonts, any for TTF
    var $x_label_angle = 0;                 // For labels on X axis (tick and data)
    var $y_label_angle = 0;                 // For labels on Y axis (tick and data)
    var $x_title_angle = 0;                 // Don't change this if you don't want to screw things up!
    var $y_title_angle = 90;                // Nor this.
    var $title_angle = 0;                   // Or this.

//Formats
    var $file_format = 'png';
    var $output_file = '';                  // For output to a file instead of stdout

//Data
    var $data_type = 'text-data';           // text-data, data-data-error, data-data, text-data-single
    var $plot_type= 'linepoints';           // bars, lines, linepoints, area, points, pie, thinbarline, squared

    var $label_scale_position = 0.5;        // Shifts data labes in pie charts. 1 = top, 0 = bottom
    var $group_frac_width = 0.7;            // value from 0 to 1 = width of bar groups
    var $bar_width_adjust = 1;              // 1 = bars of normal width, must be > 0

    var $y_precision = 1;
    var $x_precision = 1;

    var $data_units_text = '';              // Units text for 'data' labels (i.e: 'ยค', '$', etc.)

// Titles
    var $title_txt = 'X-Bar R Chart';

    var $x_title_txt = '';
    var $x_title_pos = 'plotdown';          // plotdown, plotup, both, none

    var $y_title_txt = '';
    var $y_title_pos = 'plotleft';          // plotleft, plotright, both, none


//Labels
    // There are two types of labels in PHPlot:
    //    Tick labels: they follow the grid, next to ticks in axis.   (DONE)
    //                 they are drawn at grid drawing time, by DrawXTicks() and DrawYTicks()
    //    Data labels: they follow the data points, and can be placed on the axis or the plot (x/y)  (TODO)
    //                 they are drawn at graph plotting time, by Draw*DataLabel(), called by DrawLines(), etc.
    //                 Draw*DataLabel() also draws H/V lines to datapoints depending on draw_*_data_label_lines

    // Tick Labels
    var $x_tick_label_pos = 'plotdown';     // plotdown, plotup, both, xaxis, none
    var $y_tick_label_pos = 'plotleft';     // plotleft, plotright, both, yaxis, none

    // Data Labels:
    var $x_data_label_pos = 'plotdown';     // plotdown, plotup, both, plot, all, none
    var $y_data_label_pos = 'plotleft';     // plotleft, plotright, both, plot, all, none

    var $draw_x_data_label_lines = FALSE;   // Draw a line from the data point to the axis?
    var $draw_y_data_label_lines = FALSE;   // TODO

    // Label types: (for tick, data and plot labels)
    var $x_label_type = '';                 // data, time. Leave blank for no formatting.
    var $y_label_type = '';                 // data, time. Leave blank for no formatting.
    var $x_time_format = '%H:%m:%s';        // See http://www.php.net/manual/html/function.strftime.html
    var $y_time_format = '%H:%m:%s';        // SetYTimeFormat() too...

    // Skipping labels
    var $x_label_inc = 1;                   // Draw a label every this many (1 = all) (TODO)
    var $y_label_inc = 1;
    var $_x_label_cnt = 0;                  // internal count FIXME: work in progress

    // Legend
    var $legend = '';                       // An array with legend titles
    var $legend_x_pos = '';
    var $legend_y_pos = '';


//Ticks
    var $x_tick_length = 5;                 // tick length in pixels for upper/lower axis
    var $y_tick_length = 5;                 // tick length in pixels for left/right axis

    var $x_tick_cross = 3;                  // ticks cross x axis this many pixels
    var $y_tick_cross = 3;                  // ticks cross y axis this many pixels

    var $x_tick_pos = 'plotdown';           // plotdown, plotup, both, xaxis, none
    var $y_tick_pos = 'plotleft';           // plotright, plotleft, both, yaxis, none

    var $num_x_ticks = '';
    var $num_y_ticks = '';

    var $x_tick_inc = '';                   // Set num_x_ticks or x_tick_inc, not both.
    var $y_tick_inc = '';                   // Set num_y_ticks or y_tick_inc, not both.

    var $skip_top_tick = FALSE;
    var $skip_bottom_tick = FALSE;
    var $skip_left_tick = FALSE;
    var $skip_right_tick = FALSE;

//Grid Formatting
    var $draw_x_grid = FALSE;
    var $draw_y_grid = TRUE;

    var $dashed_grid = TRUE;
    var $grid_at_foreground = FALSE;        // Chooses whether to draw the grid below or above the graph

//Colors and styles       (all colors can be array (R,G,B) or named color)
    var $color_array = 'small';             // 'small', 'large' or array (define your own colors)
                                            // See rgb.inc.php and SetRGBArray()
    var $i_border = array(194, 194, 194);
    var $plot_bg_color = 'white';
    var $bg_color = 'white';
    var $label_color = 'black';
    var $text_color = 'black';
    var $grid_color = 'black';
    var $light_grid_color = 'gray';
    var $tick_color = 'black';
    var $title_color = 'black';
    var $data_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1');
    var $error_bar_colors = array('SkyBlue', 'green', 'orange', 'blue', 'orange', 'red', 'violet', 'azure1');
    var $data_border_colors = array('black');

    var $line_widths = 1;                  // single value or array
    var $line_styles = array('solid', 'solid', 'dashed');   // single value or array
    var $dashed_style = '2-4';              // colored dots-transparent dots

    var $point_sizes = array(5,5,3);         // single value or array
    var $point_shapes = array('dot');   // rect, circle, diamond, triangle, dot, line, halfline, cross

    var $error_bar_size = 5;                // right and left size of tee
    var $error_bar_shape = 'tee';           // 'tee' or 'line'
    var $error_bar_line_width = 1;          // single value (or array TODO)

    var $plot_border_type = 'sides';        // left, sides, none, full
    var $image_border_type = 'none';        // 'raised', 'plain', 'none'

    var $shading = 5;                       // 0 for no shading, > 0 is size of shadows in pixels

    var $draw_plot_area_background = FALSE;
    var $draw_broken_lines = FALSE;          // Tells not to draw lines for missing Y data.


//////////////////////////////////////////////////////
//BEGIN CODE
//////////////////////////////////////////////////////

    /*!
     * Constructor: Setup img resource, colors and size of the image, and font sizes.
     *
     * \param which_width       int    Image width in pixels.
     * \param which_height      int    Image height in pixels.
     * \param which_output_file string Filename for output.
     * \param which_input_fule  string Path to a file to be used as background.
     */
    function PHPlot($which_width=600, $which_height=400, $which_output_file=NULL, $which_input_file=NULL)
    {
        /*
         * Please see http://www.php.net/register_shutdown_function
         * PLEASE NOTE: register_shutdown_function() will take a copy of the object rather than a reference
         * so we put an ampersand. However, the function registered will work on the object as it
         * was upon registration. To solve this, one of two methods can be used:
         *      $obj = new object();
         *      register_shutdown_function(array(&$obj,'shutdown'));
         * OR
         *      $obj = &new object();
         * HOWEVER, as the second statement assigns $obj a reference to the current object, it might be that
         * several instances mess things up... (CHECK THIS)
         *
         * AND
         *    as $this->img is set upon construction of the object, problems will not arise for us (for the
         *    moment maybe, so I put all this here just in case)
         */
        register_shutdown_function(array(&$this, '_PHPlot'));

        $this->SetRGBArray($this->color_array);

        $this->background_done = FALSE;     // Set to TRUE after background image is drawn once

        if ($which_output_file)
            $this->SetOutputFile($which_output_file);

        if ($which_input_file)
            $this->SetInputFile($which_input_file);
        else 
		{
            $this->image_width = $which_width;
            $this->image_height = $which_height;

            $this->img = ImageCreate($this->image_width, $this->image_height);
            if (! $this->img)
                $this->PrintError('PHPlot(): Could not create image resource.');
		}

        $this->SetDefaultStyles();
        $this->SetDefaultFonts();

        $this->SetTitle('');
        $this->SetXTitle('');
        $this->SetYTitle('');

        $this->print_image = TRUE;      // Use for multiple plots per image (TODO: automatic)
    }

    /*!
     * Destructor. Image resources not deallocated can be memory hogs, I think
     * it is safer to automatically call imagedestroy upon script termination than
     * do it ourselves.
     * See notes in the constructor code.
     */
    function _PHPlot ()
    {
        ImageDestroy($this->img);
        return;
    }


/////////////////////////////////////////////
///////////            INPUT / OUTPUT CONTROL
/////////////////////////////////////////////

    /*!
     * Sets output file format.
     */
    function SetFileFormat($format)
    {
        $asked = $this->CheckOption($format, 'jpg, png, gif, wbmp', __FUNCTION__);

        switch ($asked) 
		{
			case 'jpg':
				if (imagetypes() & IMG_JPG)
					$this->file_format = 'jpg';
					return TRUE;
				break;
			case 'png':
				if (imagetypes() & IMG_PNG)
					$this->file_format = 'png';
					return TRUE;
				break;
			case 'gif':
				if (imagetypes() & IMG_GIF)
					$this->file_format = 'gif';
					return TRUE;
				break;
			case 'wbmp':
				if (imagetypes() & IMG_WBMP)
					$this->file_format = 'wbmp';
					return TRUE;
				break;
			default:
				$this->PrintError("SetFileFormat():File format '$format' not supported");
				return FALSE;
        }
    }


    /*!
     * Selects an input file to be used as graph background and scales or tiles this image
     * to fit the sizes.
     *  \param input_file string Path to the file to be used (jpeg, png and gif accepted)
     *  \param mode       string 'centeredtile', 'tile', 'scale' (the image to the graph's size)
     */
    function SetBgImage($input_file, $mode='centeredtile')
    {
        $this->bgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
        $this->bgimg  = $input_file;
    }

    /*!
     * Selects an input file to be used as plot area background and scales or tiles this image
     * to fit the sizes.
     *  \param input_file string Path to the file to be used (jpeg, png and gif accepted)
     *  \param mode       string 'centeredtile', 'tile', 'scale' (the image to the graph's size)
     */
    function SetPlotAreaBgImage($input_file, $mode='tile')
    {
        $this->plotbgmode = $this->CheckOption($mode, 'tile, centeredtile, scale', __FUNCTION__);
        $this->plotbgimg  = $input_file;
    }


    /*!
     * Sets the name of the file to be used as output file.
     */
    function SetOutputFile($which_output_file)
    {
        $this->output_file = $which_output_file;
        return TRUE;
    }

    /*!
     * Sets the output image as 'inline', that is: no Content-Type headers are sent
     * to the browser. Needed if you want to embed the images.
     */
    function SetIsInline($which_ii)
    {
        $this->is_inline = (bool)$which_ii;
        return TRUE;
    }


    /*!
     * Performs the actual outputting of the generated graph, and
     * destroys the image resource.
     */
    function PrintImage()
    {
        // Browser cache stuff submitted by Thiemo Nagel
        if ( (! $this->browser_cache) && (! $this->is_inline)) 
		{
            header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
            header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . 'GMT');
            header('Cache-Control: no-cache, must-revalidate');
            header('Pragma: no-cache');
        }

        switch($this->file_format) 
		{
			case 'png':
				if (! $this->is_inline) 
				{
					Header('Content-type: image/png');
				}
				if ($this->is_inline && $this->output_file != '') 
				{
					ImagePng($this->img, $this->output_file);
				} 
				else 
				{
					ImagePng($this->img);
				}
				break;
			case 'jpg':
				if (! $this->is_inline) 
				{
					Header('Content-type: image/jpeg');
				}
				if ($this->is_inline && $this->output_file != '') 
				{
					ImageJPEG($this->img, $this->output_file);
				} 
				else 
				{
					ImageJPEG($this->img);
				}
				break;
			case 'gif':
				if (! $this->is_inline) 
				{
					Header('Content-type: image/gif');
				}
				if ($this->is_inline && $this->output_file != '') 
				{
					ImageGIF($this->img, $this->output_file);
				} 
				else 
				{
					ImageGIF($this->img);
				}
	
				break;
			case 'wbmp':        // wireless bitmap, 2 bit.
				if (! $this->is_inline) 
				{
					Header('Content-type: image/wbmp');
				}
				if ($this->is_inline && $this->output_file != '') 
				{
					ImageWBMP($this->img, $this->output_file);
				} 
				else 
				{
					ImageWBMP($this->img);
				}
				break;
			default:
				$this->PrintError('PrintImage(): Please select an image type!');
				break;
			}
        return TRUE;
    }

    /*! 
     * Prints an error message to stdout and dies 
     */
    function PrintError($error_message) 
    {
        echo "<p><b>Fatal error</b>: $error_message<p>";
        die;
    }

    /*!
     * Prints an error message inline into the generated image and draws it centered
     * around the given coordinates (defaults to center of the image)
     *   \param error_message Message to be drawn
     *   \param where_x       X coordinate
     *   \param where_y       Y coordinate
     */
    function DrawError($error_message, $where_x = NULL, $where_y = NULL) 
    {
        if (! $this->img)
            $this->PrintError('_DrawError(): Warning, no image resource allocated. '. 'The message to be written was: '.$error_message);

        $ypos = (! $where_y) ? $this->image_height/2 : $where_y;
        $xpos = (! $where_x) ? $this->image_width/2 : $where_x;
        ImageRectangle($this->img, 0, 0, $this->image_width, $this->image_height, ImageColorAllocate($this->img, 255, 255, 255));

        $this->DrawText($this->generic_font, 0, $xpos, $ypos, ImageColorAllocate($this->img, 0, 0, 0), $error_message, 'center', 'center');

        $this->PrintImage();
        exit;
//        return TRUE;
    }


/////////////////////////////////////////////
///////////                              MISC
/////////////////////////////////////////////

    /*!
     * Checks the valididy of an option.
     *  \param which_opt  String to check.
     *  \param which_acc  String of accepted choices.
     *  \param which_func Name of the calling function, for error messages.
     *  \note If checking everywhere for correctness slows things down, we could provide a
     *        child class overriding every Set...() method which uses CheckOption(). Those new
     *        methods could proceed in the unsafe but faster way.
     */
    function CheckOption($which_opt, $which_acc, $which_func)
    {
        $asked = trim($which_opt);

        // FIXME: this for backward compatibility, as eregi() fails with empty strings.
        if ($asked == '')
            return '';

        $asked = strtolower($asked);
        if (@ eregi($asked, $which_acc)) 
		{
            return $asked;
        } 
		else 
		{
            $this->DrawError("$which_func(): '$which_opt' not in available choices: '$which_acc'.");
            return NULL;
        }
    }


    /*!
     *  \note Submitted by Thiemo Nagel
     */
    function SetBrowserCache($which_browser_cache)
    {
        $this->browser_cache = $which_browser_cache;
        return TRUE;
    }

    /*!
     * Whether to show the final image or not
     */
    function SetPrintImage($which_pi)
    {
        $this->print_image = $which_pi;
        return TRUE;
    }

    /*!
     * Sets the graph's legend. If argument is not an array, appends it to the legend.
     */
    function SetLegend($which_leg)
    {
        if (is_array($which_leg)) 
		{             // use array
            $this->legend = $which_leg;
            return TRUE;
        } 
		else if (! is_null($which_leg)) 
		{     // append string
            $this->legend[] = $which_leg;
            return TRUE;
        } 
		else 
		{
            $this->DrawError("SetLegend(): argument must not be null.");
            return FALSE;
        }
    }

    /*!
     * Specifies the absolute (relative to image's up/left corner) position
     * of the legend's upper/leftmost corner.
     *  $which_type not yet used (TODO)
     */
    function SetLegendPixels($which_x, $which_y, $which_type=NULL) 
    { 
        $this->legend_x_pos = $which_x;
        $this->legend_y_pos = $which_y;

        return TRUE;
    }

    /*!
     * Specifies the relative (to graph's origin) position of the legend's
     * upper/leftmost corner. MUST be called after scales are set up.
     *   $which_type not yet used (TODO)
     */
    function SetLegendWorld($which_x, $which_y, $which_type=NULL) 
    { 
        if (! isset($this->scale_is_set))
            $this->CalcTranslation();

        $this->legend_x_pos = $this->xtr($which_x);
        $this->legend_y_pos = $this->ytr($which_y);

        return TRUE;
    }

    /*!
     * Accepted values are: left, sides, none, full
     */
    function SetPlotBorderType($pbt)
    {
        $this->plot_border_type = $this->CheckOption($pbt, 'left, sides, none, full', __FUNCTION__);
    }

    /*!
     * Accepted values are: raised, plain
     */
    function SetImageBorderType($sibt) 
    {
        $this->image_border_type = $this->CheckOption($sibt, 'raised, plain', __FUNCTION__);
    }


    /*!
     * \param dpab bool
     */
    function SetDrawPlotAreaBackground($dpab)
    {
        $this->draw_plot_area_background = (bool)$dpab;
    }


    /*!
     * \param dyg bool 
     */
    function SetDrawYGrid($dyg) 
    {
        $this->draw_y_grid = (bool)$dyg;
        return TRUE;
    }


    /*!
     * \param dxg bool
     */
    function SetDrawXGrid($dxg) 
    {
        $this->draw_x_grid = (bool)$dxg;
        return TRUE;
    }


    /*!
     * \param ddg bool 
     */
    function SetDrawDashedGrid($ddg) 
    {
        $this->dashed_grid = (bool)$ddg;
        return TRUE;
    }


    /*!
     * \param dxdl bool
     */
    function SetDrawXDataLabelLines($dxdl)
    {
        $this->draw_x_data_label_lines = (bool)$dxdl;
        return TRUE;

    }

    
    /*!
     * TODO: draw_y_data_label_lines not implemented.
     * \param dydl bool
     */
    function SetDrawYDataLabelLines($dydl)
    {
        $this->draw_y_data_label_lines = $dydl;
        return TRUE;
    }
    
    /*!
     * Sets the graph's title.
     * TODO: add parameter to choose title placement: left, right, centered=
     */
    function SetTitle($which_title) 
    {
        $this->title_txt = $which_title;

        if ($which_title == '') 
		{
            $this->title_height = 0;
            return TRUE;
        }            

        $str = split("\n", $which_title);
        $lines = count($str);
        $spacing = $this->line_spacing * ($lines - 1);

        if ($this->use_ttf) 
		{
            $size = $this->TTFBBoxSize($this->title_font['size'], 0, $this->title_font['font'], $which_title);
            $this->title_height = $size[1] * $lines;
        } 
		else 
		{
            $this->title_height = ($this->title_font['height'] + $spacing) * $lines;
        }   
        return TRUE;
    }

    /*!
     * Sets the X axis title and position.
     */
    function SetXTitle($which_xtitle, $which_xpos = 'plotdown') 
    {
        if ($which_xtitle == '')
            $which_xpos = 'none';

        $this->x_title_pos = $this->CheckOption($which_xpos, 'plotdown, plotup, both, none', __FUNCTION__);

        $this->x_title_txt = $which_xtitle;

        $str = split("\n", $which_xtitle);
        $lines = count($str);
        $spacing = $this->line_spacing * ($lines - 1);

        if ($this->use_ttf) 
		{
            $size = $this->TTFBBoxSize($this->x_title_font['size'], 0, $this->x_title_font['font'], $which_xtitle);
            $this->x_title_height = $size[1] * $lines;
        } 
		else 
		{
            $this->x_title_height = ($this->y_title_font['height'] + $spacing) * $lines;
        }

        return TRUE;
    }


    /*!
     * Sets the Y axis title and position.
     */
    function SetYTitle($which_ytitle, $which_ypos = 'plotleft') 
    {
        if ($which_ytitle == '')
            $which_ypos = 'none';

        $this->y_title_pos = $this->CheckOption($which_ypos, 'plotleft, plotright, both, none', __FUNCTION__);

        $this->y_title_txt = $which_ytitle;

        $str = split("\n", $which_ytitle);
        $lines = count($str);
        $spacing = $this->line_spacing * ($lines - 1);

        if ($this->use_ttf) 
		{
            $size = $this->TTFBBoxSize($this->y_title_font['size'], 90, $this->y_title_font['font'],  $which_ytitle);
            $this->y_title_width = $size[0] * $lines;
        } 
		else 
		{
            $this->y_title_width = ($this->y_title_font['height'] + $spacing) * $lines;
        }

        return TRUE;
    }

   /*!
     * Sets the position of Y axis.
     * \param pos int Position in world coordinates.
     */
    function SetYAxisPosition($pos)
    {
        $this->y_axis_position = (int)$pos;
        if (isset($this->scale_is_set)) 
		{
            $this->CalcTranslation();
        }
        return TRUE;
    }
    
    /*!
     * Sets the position of X axis.
     * \param pos int Position in world coordinates. 
     */
    function SetXAxisPosition($pos)
    {
        $this->x_axis_position = (int)$pos;
        if (isset($this->scale_is_set)) 
		{
            $this->CalcTranslation();
        }
        return TRUE;
    }

    function SetXScaleType($which_xst)
    {
        $this->xscale_type = $this->CheckOption($which_xst, 'linear, log', __FUNCTION__);
        return TRUE;
    }
	/*
    function SetYScaleType($which_yst)
    {
        $this->yscale_type = $this->CheckOption($which_yst, 'linear, log',  __FUNCTION__);
        return TRUE;
    }
	
    function SetPrecisionX($which_prec)
    {
        $this->x_precision = $which_prec;
        $this->SetXLabelType('data');
        return TRUE;
    }
	*/
    function SetPrecisionY($which_prec)		
    {
        $this->y_precision = $which_prec;
        $this->SetYLabelType('data');
        return TRUE;
    }
	

    function SetErrorBarLineWidth($which_seblw)
    {
        $this->error_bar_line_width = $which_seblw;
        return TRUE;
    }

    function SetLabelScalePosition($which_blp)
    {
        //0 to 1
        $this->label_scale_position = $which_blp;
        return TRUE;
    }

    function SetErrorBarSize($which_ebs)
    {
        //in pixels
        $this->error_bar_size = $which_ebs;
        return TRUE;
    }

    /*!
     * Can be one of: 'tee', 'line'
     */
    function SetErrorBarShape($which_ebs)
    {
        $this->error_bar_shape = $this->CheckOption($which_ebs, 'tee, line', __FUNCTION__);
    }

    /*!
     * Sets point shape for each data set via an array.
     * Shape can be one of: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot',
     * 'diamond', 'triangle', 'trianglemid'
     */
    function SetPointShapes($which_pt)
    {
        if (is_null($which_pt)) 
		{
            // Do nothing, use default value.
        } 
		else if (is_array($which_pt)) 
		{
            // Did we get an array with point shapes?
            $this->point_shapes = $which_pt;
        } 
		else 
		{
            // Single value into array
            $this->point_shapes = array($which_pt);
        }

        foreach ($this->point_shapes as $shape)
        {
            // TODO, better check, per element rectification
            $this->CheckOption($shape, 'halfline, line, plus, cross, rect, circle, dot, diamond, triangle, trianglemid', __FUNCTION__);
        }

        // Make both point_shapes and point_sizes same size.
        $ps = count($this->point_sizes);
        $pt = count($this->point_shapes);

        if ($ps < $pt) 
		{
            array_pad_array($this->point_sizes, $pt);
        } 
		else if ($pt > $ps) 
		{
            array_pad_array($this->point_shapes, $ps);
        }
        return TRUE;
    }

    /*!
     * Sets the point size for point plots.
     * \param ps int Size in pixels.
     * \note Test this more extensively
     */
    function SetPointSizes($which_ps)
    {
        if (is_null($which_ps)) 
		{
            // Do nothing, use default value.
        } 
		else if (is_array($which_ps)) 
		{
            // Did we get an array with point sizes?
            $this->point_sizes = $which_ps;
        }
		else 
		{
            // Single value into array
            $this->point_sizes = array($which_ps);
        }

        // Make both point_shapes and point_sizes same size.
        $ps = count($this->point_sizes);
        $pt = count($this->point_shapes);

        if ($ps < $pt) 
		{
            array_pad_array($this->point_sizes, $pt);
        } 
		else if ($pt > $ps) 
		{
            array_pad_array($this->point_shapes, $ps);
        }

        // Fix odd point sizes for point shapes which need it
        for ($i = 0; $i < $pt; $i++) 
		{
            if ($this->point_shapes[$i] == 'diamond' or $this->point_shapes[$i] == 'triangle') 
			{
                if ($this->point_sizes[$i] % 2 != 0) 
				{
                    $this->point_sizes[$i]++;
                }
            }
        }
        return TRUE;
    }


    /*!
     * Tells not to draw lines for missing Y data. Only works with 'lines' and 'squared' plots.
     * \param bl bool
     */
    function SetDrawBrokenLines($bl)
    {
        $this->draw_broken_lines = (bool)$bl;
    }


    /*!
     *  text-data: ('label', y1, y2, y3, ...)
     *  text-data-single: ('label', data), for some pie charts.
     *  data-data: ('label', x, y1, y2, y3, ...)
     *  data-data-error: ('label', x1, y1, e1+, e2-, y2, e2+, e2-, y3, e3+, e3-, ...)
     */
    function SetDataType($which_dt)
    {
        //The next four lines are for past compatibility.
        if ($which_dt == 'text-linear') { $which_dt = 'text-data'; };
        if ($which_dt == 'linear-linear') { $which_dt = 'data-data'; };
        if ($which_dt == 'linear-linear-error') { $which_dt = 'data-data-error'; };
        if ($which_dt == 'text-data-pie') { $which_dt = 'text-data-single'; }

        $this->data_type = $this->CheckOption($which_dt, 'text-data, text-data-single, '.'data-data, data-data-error', __FUNCTION__);
        return TRUE;
    }
																			
    /*!
     * Copy the array passed as data values. We convert to numerical indexes, for its
     * use for (or while) loops, which sometimes are faster. Performance improvements		***********************************************************************************
     * vary from 28% in DrawLines() to 49% in DrawArea() for plot drawing functions.
     */
    function SetDataValues(&$which_dv)
    {
        unset ($this->data_limits_done);        // Reset this for every new data_set
        $this->num_data_rows = count($which_dv);
        $this->total_records = 0;               // Perform some useful calculations.
        $this->records_per_group = 1;           
        for ($i = 0, $recs = 0; $i < $this->num_data_rows; $i++) 
		{
            // Copy
            $this->data[$i] = array_values($which_dv[$i]);   // convert to numerical indices.

            // Compute some values
            $recs = count($this->data[$i]); 
            $this->total_records += $recs;

            if ($recs > $this->records_per_group)
                $this->records_per_group = $recs;

            $this->num_recs[$i] = $recs;
        }
    }

    /*!
     * Pad styles arrays for later use by plot drawing functions:
     * This removes the need for $max_data_colors, etc. and $color_index = $color_index % $max_data_colors
     * in DrawBars(), DrawLines(), etc.
     */
    function PadArrays()
    {
        array_pad_array($this->line_widths, $this->records_per_group);
        array_pad_array($this->line_styles, $this->records_per_group);

        array_pad_array($this->data_colors, $this->records_per_group);
        array_pad_array($this->data_border_colors, $this->records_per_group);
        array_pad_array($this->error_bar_colors, $this->records_per_group);

        $this->SetDataColors();
        $this->SetDataBorderColors();
        $this->SetErrorBarColors();

        return TRUE;
    }


//////////////////////////////////////////////////////////
///////////         DATA ANALYSIS, SCALING AND TRANSLATION
//////////////////////////////////////////////////////////

    /*!
     * Analizes data and sets up internal maxima and minima
     * Needed by: CalcMargins(), ...
     *   Text-Data is different than data-data graphs. For them what
     *   we have, instead of X values, is # of records equally spaced on data.
     *   text-data is passed in as $data[] = (title, y1, y2, y3, y4, ...)
     *   data-data is passed in as $data[] = (title, x, y1, y2, y3, y4, ...)
     */
	 
    function FindDataLimits()
    {
        // Set some default min and max values before running through the data
        switch ($this->data_type) 
		{
        	case 'text-data':		$minx = 0;				
									$maxx = 23;
									$miny = 2.4896;
									$maxy = 2.4904;
									break;
	        default:            	break;
        }
        $minminy = $miny;
        $maxmaxy = $maxy;

        if ($this->plot_type == 'stackedbars') 
		{
			$maxmaxy = $minminy = 0; 
		}

        // Process each row of data
        for ($i=0; $i < $this->num_data_rows; $i++) 
		{
            $j=0;
            // Extract maximum text label length
            $val = @ strlen($this->data[$i][$j++]);
            $maxt = ($val > $maxt) ? $val : $maxt;

            if ($this->plot_type == 'stackedbars') 
			{ 
				$maxy = $miny = 0; 
			}

            switch ($this->data_type) 
			{
				case 'text-data':           // Data is passed in as (title, y1, y2, y3, ...)
				case 'text-data-single':    // This one is for some pie charts
					// $numrecs = @ count($this->data[$i]);
					$miny = $maxy = (double)$this->data[$i][$j];
					for (; $j < $this->num_recs[$i]; $j++) 
					{
						$val = (double)$this->data[$i][$j];
						if ($this->plot_type == 'stackedbars') 
						{
							$maxy += abs($val);      // only positive values for the moment
						} 
						else 
						{
							$maxy = ($val > $maxy) ? $val : $maxy;
							$miny = ($val < $miny) ? $val : $miny;
						}
					}
					break;
				case 'data-data':           // Data is passed in as (title, x, y, y2, y3, ...)
					// X value:
					$val = (double)$this->data[$i][$j++];
					$maxx = ($val > $maxx) ? $val : $maxx;
					$minx = ($val < $minx) ? $val : $minx;
	
					$miny = $maxy = (double)$this->data[$i][$j];
					// $numrecs = @ count($this->data[$i]);
					for (; $j < $this->num_recs[$i]; $j++) 
					{
						$val = (double)$this->data[$i][$j];
						$maxy = ($val > $maxy) ? $val : $maxy;
						$miny = ($val < $miny) ? $val : $miny;
					}
					break;
				case 'data-data-error':     // Data is passed in as (title, x, y, err+, err-, y2, err2+, err2-,...)
					// X value:
					$val = (double)$this->data[$i][$j++];
					$maxx = ($val > $maxx) ? $val : $maxx;
					$minx = ($val < $minx) ? $val : $minx;
	
					$miny = $maxy = (double)$this->data[$i][$j];
					// $numrecs = @ count($this->data[$i]);
					for (; $j < $this->num_recs[$i];) 
					{
						// Y value:
						$val = (double)$this->data[$i][$j++];
						$maxy = ($val > $maxy) ? $val : $maxy;
						$miny = ($val < $miny) ? $val : $miny;
						// Error +:
						$val = (double)$this->data[$i][$j++];
						$maxe = ($val > $maxe) ? $val : $maxe;
						// Error -:
						$val = (double)$this->data[$i][$j++];
						$mine = ($val > $mine) ? $val : $mine;
					}
					$maxy = $maxy + $maxe;
					$miny = $miny - $mine;      // assume error bars are always > 0
					break;
				default:
					$this->PrintError("FindDataLimits(): Unknown data type '$data_type'.");
				break;
            }
            $this->data[$i][MINY] = $miny;      // This row's min Y, for DrawXDataLine()
            $this->data[$i][MAXY] = $maxy;      // This row's max Y, for DrawXDataLine()

            $minminy = ($miny < $minminy) ? $miny : $minminy;   // global min
            $maxmaxy = ($maxy > $maxmaxy) ? $maxy : $maxmaxy;   // global max
        }

        $this->min_x = $minx;
        $this->max_x = $maxx;
        $this->min_y = $minminy;
        $this->max_y = $maxmaxy;
        $this->max_t = $maxt;

        $this->data_limits_done = TRUE;

        return TRUE;
    }


    /*!
     * Calculates image margins on the fly from title positions and sizes,
     * and tick labels positions and sizes.
     *
     * FIXME: fix x_data_label_pos behaviour. Now we are leaving room for it AND x_tick_label_pos
     *        maybe it shouldn't be so...
     *
     * FIXME: y_data_label_pos is not yet used...
     *
     * TODO: add x_tick_label_width and y_tick_label_height and use them to calculate
     *       max_x_labels and max_y_labels, to be used by drawing functions to avoid overlapping.
     */
    function CalcMargins()
    {
        // Temporary variables for label size calculation
        $xlab = $this->FormatLabel('x', $this->max_x);
        $ylab = $this->FormatLabel('y', $this->max_y);

        // dirty fix:
        // max_t is the maximum data label length (from column 0 of each data row).
        if ($this->max_t > strlen ($xlab))
            $xlab = sprintf ("%{$this->max_t}s","_");

        //////// Calculate maximum X/Y axis label height and width:

        // TTFonts:
        if ($this->use_ttf) 
		{
            // Maximum X axis label height
            $size = $this->TTFBBoxSize($this->x_label_font['size'], $this->x_label_angle, $this->x_label_font['font'], $xlab);
            $this->x_tick_label_height = $size[1];

            // Maximum Y axis label width
            $size = $this->TTFBBoxSize($this->y_label_font['size'], $this->y_label_angle, $this->y_label_font['font'], $ylab);
            $this->y_tick_label_width = $size[0];
        }
        // Fixed fonts:
        else 
		{

            // Maximum X axis label height
            if ($this->x_label_angle == 90)
                $this->x_tick_label_height = strlen($xlab) * $this->x_label_font['width'];
            else
                $this->x_tick_label_height = $this->x_label_font['height'];

            // Maximum Y axis label width
            $this->y_tick_label_width = strlen($ylab) * $this->y_label_font['width'];
        }

        ///////// Calculate margins:

        // Upper title, ticks and tick labels, and data labels:
        $this->y_top_margin = $this->title_height + $this->safe_margin * 2;

        if ($this->x_title_pos == 'plotup' || $this->x_title_pos == 'both')
            $this->y_top_margin += $this->x_title_height + $this->safe_margin;

        if ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both')
            $this->y_top_margin += $this->x_tick_label_height;

        if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both')
            $this->y_top_margin += $this->x_tick_length * 2;

        if ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both')
            $this->y_top_margin += $this->x_tick_label_height;

        // Lower title, ticks and tick labels, and data labels:
        $this->y_bot_margin = $this->safe_margin * 2;

        if ($this->x_title_pos == 'plotdown' || $this->x_title_pos == 'both')
            $this->y_bot_margin += $this->x_title_height;

        if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both')
            $this->y_bot_margin += $this->x_tick_length * 2;

        if ($this->x_tick_pos == 'xaxis' && ($this->x_axis_position == '' || $this->x_axis_position == 0))
            $this->y_bot_margin += $this->x_tick_length * 2;

        if ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both')            
            $this->y_bot_margin += $this->x_tick_label_height;

        if ($this->x_tick_label_pos == 'xaxis' && ($this->x_axis_position == '' || $this->x_axis_position == 0))
            $this->y_bot_margin += $this->x_tick_label_height;

        if ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both')
            $this->y_bot_margin += $this->x_tick_label_height;

        // Left title, ticks and tick labels:
        $this->x_left_margin = $this->safe_margin * 2;

        if ($this->y_title_pos == 'plotleft' || $this->y_title_pos == 'both')
            $this->x_left_margin += $this->y_title_width + $this->safe_margin;

        if ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both')
            $this->x_left_margin += $this->y_tick_label_width;

        if ($this->y_tick_pos == 'plotleft' || $this->y_tick_pos == 'both')
            $this->x_left_margin += $this->y_tick_length * 2 ;

        // Right title, ticks and tick labels:
        $this->x_right_margin = $this->safe_margin * 2;

        if ($this->y_title_pos == 'plotright' || $this->y_title_pos == 'both')
            $this->x_right_margin += $this->y_title_width + $this->safe_margin;

        if ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both')
            $this->x_right_margin += $this->y_tick_label_width;

        if ($this->y_tick_pos == 'plotright' || $this->y_tick_pos == 'both')
            $this->x_right_margin += $this->y_tick_length * 2;

        $this->x_tot_margin = $this->x_left_margin + $this->x_right_margin;
        $this->y_tot_margin = $this->y_top_margin + $this->y_bot_margin;

        return;
    }

    /*!
     * Set the margins in pixels (left, right, top, bottom)
     */
    function SetMarginsPixels($which_lm, $which_rm, $which_tm, $which_bm)
    { 
        $this->x_left_margin = $which_lm;
        $this->x_right_margin = $which_rm;
        $this->x_tot_margin = $which_lm + $which_rm;

        $this->y_top_margin = $which_tm;
        $this->y_bot_margin = $which_bm;
        $this->y_tot_margin = $which_tm + $which_bm;

        $this->SetPlotAreaPixels();

        return;
    }

    /*!
     * Sets the limits for the plot area. If no arguments are supplied, uses
     * values calculated from CalcMargins();
     * Like in GD, (0,0) is upper left
     *
     * This resets the scale if SetPlotAreaWorld() was already called
     */
    function SetPlotAreaPixels($x1=NULL, $y1=NULL, $x2=NULL, $y2=NULL) 
    {
        if ($x2 && $y2) 
		{
            $this->plot_area = array($x1, $y1, $x2, $y2);
        } 
		else 
		{
            if (! isset($this->x_tot_margin))
                $this->CalcMargins();

            $this->plot_area = array($this->x_left_margin, $this->y_top_margin, $this->image_width - $this->x_right_margin, $this->image_height - $this->y_bot_margin);
        }
        $this->plot_area_width = $this->plot_area[2] - $this->plot_area[0];
        $this->plot_area_height = $this->plot_area[3] - $this->plot_area[1];

        // Reset the scale with the new plot area.
        if (isset($this->plot_max_x))
            $this->CalcTranslation();

        return TRUE;

    }

    /*!
     * Sets minimum and maximum x and y values in the plot using FindDataLimits()
     * or from the supplied parameters, if any.
     *
     * This resets the scale if SetPlotAreaPixels() was already called
     */
    function SetPlotAreaWorld($xmin=NULL, $ymin=NULL, $xmax=NULL, $ymax=NULL) 
    {
        if (! isset($this->data_limits_done)) 
		{ // For automatic setting of data we need data limits
            $this->FindDataLimits() ;
        }
 
        if ($xmin === NULL || $xmin === '') 
		{
            if ($this->data_type == 'text-data')  // Valid for data without X values only.
                $xmin = 0;
            else
                $xmin = $this->min_x;
        }
        if ($xmax === NULL || $xmax === '') 
		{
            if ($this->data_type == 'text-data')  // Valid for data without X values only.
                $xmax = $this->max_x + 1;
            else
                $xmax = $this->max_x;
        }

        // Leave room above and below the highest and lowest data points.
        
        if ($ymin === NULL || $ymin === '') 
		{
            if ($this->min_y < 0)
                $ymin = ceil($this->min_y * 1.1);
            else
                $ymin = floor($this->min_y * 0.9);
        }    
        if ($ymax === NULL || $ymax === '') 
		{
            if ($this->max_y < 0)
                $ymax = floor($this->max_y * 0.9);
            else
                $ymax = ceil($this->max_y * 1.1);
        }
        
        // Error checking
        
        if ($ymin == $ymax)     // Minimum height
            $ymax += 1;

        if ($this->yscale_type == 'log') 
		{
            if ($ymin <= 0) 
			{ 
                $ymin = 1;
            }
            if ($ymax <= 0) {
                $this->PrintError('SetPlotAreaWorld(): Log plots need data greater than 0');
                return FALSE;
            }
        }
        
        if ($ymax <= $ymin) 
		{
            $this->DrawError('SetPlotAreaWorld(): Error in data - max not greater than min');
            return FALSE;
        }
       
        // Reset (if it was already set) the scale with the new maxs and mins
      
        $this->plot_min_x = $xmin;
        $this->plot_max_x = $xmax;
        $this->plot_min_y = $ymin;
        $this->plot_max_y = $ymax;

        if (isset($this->plot_area_width)) 
		{
            $this->CalcTranslation();
        }

        return TRUE;
    } //function SetPlotAreaWorld


    /*!
     * For bar plots, which have equally spaced x variables.
     */
    function CalcBarWidths() 
    {
        $group_width = ($this->plot_area[2] - $this->plot_area[0]) / $this->num_data_rows * $this->group_frac_width;
        if ($this->plot_type == 'bars') 
		{
            $this->record_bar_width = $group_width / $this->records_per_group;
        } 
		else if ($this->plot_type == 'stackedbars') 
		{
            $this->record_bar_width = $group_width;
        }            
        $this->data_group_space = $group_width / 2;
        return TRUE;
    }

    /*!
     * Calculates scaling stuff...
     */
    function CalcTranslation()
    {
        if ($this->plot_max_x - $this->plot_min_x == 0) 
		{ // Check for div by 0
            $this->xscale = 0;
        } 
		else 
		{
            if ($this->xscale_type == 'log') 
			{
                $this->xscale = ($this->plot_area_width)/(log10($this->plot_max_x) - log10($this->plot_min_x));
            } 
			else 
			{
                $this->xscale = ($this->plot_area_width)/($this->plot_max_x - $this->plot_min_x);
            }
        }

        if ($this->plot_max_y - $this->plot_min_y == 0) 
		{ // Check for div by 0
            $this->yscale = 0;
        } 
		else 
		{
            if ($this->yscale_type == 'log') 
			{
                $this->yscale = ($this->plot_area_height)/(log10($this->plot_max_y) - log10($this->plot_min_y));
            } 
			else 
			{
                $this->yscale = ($this->plot_area_height)/($this->plot_max_y - $this->plot_min_y);
            }
        }
        // GD defines x = 0 at left and y = 0 at TOP so -/+ respectively
        if ($this->xscale_type == 'log') 
		{
            $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * log10($this->plot_min_x) );
        } 
		else 
		{
            $this->plot_origin_x = $this->plot_area[0] - ($this->xscale * $this->plot_min_x);
        }
        if ($this->yscale_type == 'log') 
		{
            $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * log10($this->plot_min_y));
        } 
		else 
		{ 
            $this->plot_origin_y = $this->plot_area[3] + ($this->yscale * $this->plot_min_y);
        }

        $this->scale_is_set = TRUE;

        /************** FIXME?? *************/
        // There should be a better place for this.

        // User provided y axis position?
        if ($this->y_axis_position != '') 
		{
            // Make sure we draw our axis inside the plot
            $this->y_axis_position = ($this->y_axis_position < $this->plot_min_x)
                                     ? $this->plot_min_x : $this->y_axis_position;
            $this->y_axis_position = ($this->y_axis_position > $this->plot_max_x)
                                     ? $this->plot_max_x : $this->y_axis_position;
            $this->y_axis_x_pixels = $this->xtr($this->y_axis_position);
        } 
		else 
		{
            // Default to left axis
            $this->y_axis_x_pixels = $this->xtr($this->plot_min_x);
        }
        // User provided x axis position?
        if ($this->x_axis_position != '') 
		{
            // Make sure we draw our axis inside the plot
            $this->x_axis_position = ($this->x_axis_position < $this->plot_min_y)
                                     ? $this->plot_min_y : $this->x_axis_position;
            $this->x_axis_position = ($this->x_axis_position > $this->plot_max_y)
                                     ? $this->plot_max_y : $this->x_axis_position;
            $this->x_axis_y_pixels = $this->ytr($this->x_axis_position);
        } 
		else 
		{ 
            if ($this->yscale_type == 'log')
                $this->x_axis_y_pixels = $this->ytr(1);
            else
                // Default to axis at 0 or plot_min_y (should be 0 anyway, from SetPlotAreaWorld())
                $this->x_axis_y_pixels = ($this->plot_min_y <= 0) && (0 <= $this->plot_max_y) 
                                         ? $this->ytr(0) : $this->ytr($this->plot_min_y);
        }

    } // function CalcTranslation()


    /*!
     * Translate X world coordinate into pixel coordinate
     * Needs values calculated by _CalcTranslation()
     */
    function xtr($x_world) 
    {
        //$x_pixels =  $this->x_left_margin + ($this->image_width - $this->x_tot_margin)*
        //      (($x_world - $this->plot_min_x) / ($this->plot_max_x - $this->plot_min_x)) ;
        //which with a little bit of math reduces to ...
        if ($this->xscale_type == 'log') 
		{ 
            $x_pixels = $this->plot_origin_x + log10($x_world) * $this->xscale ;
        } 
		else 
		{ 
            $x_pixels = $this->plot_origin_x + $x_world * $this->xscale ;
        }
        return ($x_pixels);
    }


    /*!
     * Translate Y world coordinate into pixel coordinate.
     * Needs values calculated by _CalcTranslation()
     */
    function ytr($y_world) 
    {
        if ($this->yscale_type == 'log') 
		{
            //minus because GD defines y = 0 at top. doh!
            $y_pixels =  $this->plot_origin_y - log10($y_world) * $this->yscale ;
        } 
		else 
		{ 
            $y_pixels =  $this->plot_origin_y - $y_world * $this->yscale ;  
        }
        return round($y_pixels);
    }

    /*!
     * Formats a tick or data label.
     *
     * \note Time formatting suggested by Marlin Viss
     */
    function FormatLabel($which_pos, $which_lab)
    {
        switch ($which_pos) 
		{
			case 'x':
			case 'plotx':
				switch ($this->x_label_type) 
				{
					case 'title':
						$lab = @ $this->data[$which_lab][0];
						break;
					case 'data':
						$lab = number_format($which_lab, $this->x_precision, '.', ',').$this->data_units_text;
						break;
					case 'time':
						$lab = strftime($this->x_time_format, $which_lab);
						break;
					default:
						// Unchanged from whatever format it is passed in
						$lab = $which_lab;
					break;
				}
	            break;
			case 'y':
			case 'ploty':
				switch ($this->y_label_type) 
				{
					case 'data':
						$lab = number_format($which_lab, $this->y_precision, '.', ',').$this->data_units_text;
						break;
					case 'time':
						$lab = strftime($this->y_time_format, $which_lab);
						break;
					default:
						// Unchanged from whatever format it is passed in
						$lab = $which_lab;
						break;
				}
				break;
			default:
				$this->PrintError("FormatLabel(): Unknown label type $which_type");
				return NULL;
        }

        return $lab;
    } //function FormatLabel



/////////////////////////////////////////////
////////////////////          GENERIC DRAWING
/////////////////////////////////////////////

    /*!
     * Fills the background.
     */
    function DrawBackground()
    {
        // Don't draw this twice if drawing two plots on one image
        if (! $this->background_done) 
		{
            if (isset($this->bgimg)) 
			{    // If bgimg is defined, use it
                $this->tile_img($this->bgimg, 0, 0, $this->image_width, $this->image_height, $this->bgmode);
            } 
			else 
			{                        // Else use solid color
                ImageFilledRectangle($this->img, 0, 0, $this->image_width, $this->image_height, $this->ndx_bg_color);
            }
            $this->background_done = TRUE;
            return TRUE;        // Done
        }
        return FALSE;           // Nothing done
    }


    /*!
     * Fills the plot area background.
     */
    function DrawPlotAreaBackground()
    {
        if (isset($this->plotbgimg)) 
		{
            $this->tile_img($this->plotbgimg, $this->plot_area[0], $this->plot_area[1], $this->plot_area_width, $this->plot_area_height, $this->plotbgmode);
        }
        else 
		{
            if ($this->draw_plot_area_background) 
			{
                ImageFilledRectangle($this->img, $this->plot_area[0], $this->plot_area[1], $this->plot_area[2], $this->plot_area[3], $this->ndx_plot_bg_color);
            }
        }

        return TRUE;
    }


    /*!
     * Tiles an image at some given coordinates.
     *
     * \param $file   string Filename of the picture to be used as tile.
     * \param $xorig  int    X coordinate of the plot where the tile is to begin.
     * \param $yorig  int    Y coordinate of the plot where the tile is to begin.
     * \param $width  int    Width of the area to be tiled.
     * \param $height int    Height of the area to be tiled.
     * \param $mode   string One of 'centeredtile', 'tile', 'scale'.
     */
    function tile_img($file, $xorig, $yorig, $width, $height, $mode)
    {
        $size = getimagesize($file);
        $input_format = $size[2];

        switch($input_format) 
		{
			case 1:
				$im = @ imagecreatefromGIF ($file);
				if (! $im) 
				{
					$this->PrintError("tile_img:() Unable to open $file as a GIF.");
					return FALSE;
				}
				break;
			case 2:
				$im = @ imagecreatefromJPEG ($file);
				if (! $im) 
				{
					$this->PrintError("tile_img(): Unable to open $file as a JPG.");
					return FALSE;
				}
				break;
			case 3:
				$im = @ imagecreatefromPNG ($file);
				if (! $im) 
				{
					$this->PrintError("tile_img(): Unable to open $file as a PNG.");
					return FALSE;
				}
				break;
			default:
				$this->PrintError('tile_img(): Please select a gif, jpg, or png image.');
				return FALSE;
				break;
        }


        if ($mode == 'scale') 
		{
            imagecopyresized($this->img, $im, $xorig, $yorig, 0, 0, $width, $height, $size[0],$size[1]);
            return TRUE;
        } 
		else if ($mode == 'centeredtile') 
		{
            $x0 = - floor($size[0]/2);   // Make the tile look better
            $y0 = - floor($size[1]/2);
        } 
		else if ($mode = 'tile') 
		{
            $x0 = 0;
            $y0 = 0;
        }

        // Actually draw the tile

        // But first on a temporal image.
        $tmp = ImageCreate($width, $height);
        if (! $tmp)
            $this->PrintError('tile_img(): Could not create image resource.');

        for ($x = $x0; $x < $width; $x += $size[0])
            for ($y = $y0; $y < $height; $y += $size[1])
                imagecopy($tmp, $im, $x, $y, 0, 0, $size[0], $size[1]);

        // Copy the temporal image onto the final one.
        imagecopy($this->img, $tmp, $xorig, $yorig, 0,0, $width, $height);

        // Free resources
        imagedestroy($tmp);
        imagedestroy($im);

        return TRUE;
    }  // function tile_img


    /*!
     * Draws a border around the final image.
     */
    function DrawImageBorder()
    {
        switch ($this->image_border_type) 
		{
			case 'raised':
				ImageLine($this->img, 0, 0, $this->image_width-1, 0, $this->ndx_i_border);
				ImageLine($this->img, 1, 1, $this->image_width-2, 1, $this->ndx_i_border);
				ImageLine($this->img, 0, 0, 0, $this->image_height-1, $this->ndx_i_border);
				ImageLine($this->img, 1, 1, 1, $this->image_height-2, $this->ndx_i_border);
				ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1,
						  $this->image_height-1, $this->ndx_i_border_dark);
				ImageLine($this->img, 0, $this->image_height-1, $this->image_width-1,
						  $this->image_height-1, $this->ndx_i_border_dark);
				ImageLine($this->img, $this->image_width-2, 1, $this->image_width-2,
						  $this->image_height-2, $this->ndx_i_border_dark);
				ImageLine($this->img, 1, $this->image_height-2, $this->image_width-2,
						  $this->image_height-2, $this->ndx_i_border_dark);
				break;
			case 'plain':
				ImageLine($this->img, 0, 0, $this->image_width, 0, $this->ndx_i_border_dark);
				ImageLine($this->img, $this->image_width-1, 0, $this->image_width-1,
						  $this->image_height, $this->ndx_i_border_dark);
				ImageLine($this->img, $this->image_width-1, $this->image_height-1, 0, $this->image_height-1,
						  $this->ndx_i_border_dark);
				ImageLine($this->img, 0, 0, 0, $this->image_height, $this->ndx_i_border_dark);
				break;
			case 'none':
				break;
			default:
				$this->DrawError("DrawImageBorder(): unknown image_border_type: '$this->image_border_type'");
				return FALSE;
        }
        return TRUE;
    }


    /*!
     * Adds the title to the graph.
     */
    function DrawTitle() 
    {
        // Center of the plot area
        //$xpos = ($this->plot_area[0] + $this->plot_area_width )/ 2;

        // Center of the image:
        $xpos = $this->image_width / 2;

        // Place it at almost at the top
        $ypos = $this->safe_margin;

        $this->DrawText($this->title_font, $this->title_angle, $xpos, $ypos, $this->ndx_title_color, $this->title_txt, 'center', 'bottom'); 

        return TRUE; 

    }


    /*!
     * Draws the X-Axis Title
     */
    function DrawXTitle()
    {
        if ($this->x_title_pos == 'none')
            return;

        // Center of the plot
        $xpos = ($this->plot_area[2] + $this->plot_area[0]) / 2;

        // Upper title
        if ($this->x_title_pos == 'plotup' || $this->x_title_pos == 'both') 
		{
            $ypos = $this->safe_margin + $this->title_height + $this->safe_margin;
            $this->DrawText($this->x_title_font, $this->x_title_angle, $xpos, $ypos, $this->ndx_title_color, $this->x_title_txt, 'center');
        }
        // Lower title
        if ($this->x_title_pos == 'plotdown' || $this->x_title_pos == 'both') 
		{
            $ypos = $this->image_height - $this->x_title_height - $this->safe_margin;
            $this->DrawText($this->x_title_font, $this->x_title_angle, $xpos, $ypos, $this->ndx_title_color, $this->x_title_txt, 'center');
        }
        return TRUE;
    }

    /*!
     * Draws the Y-Axis Title
     */
    function DrawYTitle()
    {
        if ($this->y_title_pos == 'none')
            return;

        // Center the title vertically to the plot
        $ypos = ($this->plot_area[3] + $this->plot_area[1]) / 2;

        if ($this->y_title_pos == 'plotleft' || $this->y_title_pos == 'both') 
		{
            $xpos = $this->safe_margin;
            $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color, $this->y_title_txt, 'left', 'center');
        }
        if ($this->y_title_pos == 'plotright' || $this->y_title_pos == 'both') 
		{
            $xpos = $this->image_width - $this->safe_margin - $this->y_title_width - $this->safe_margin;
            $this->DrawText($this->y_title_font, 90, $xpos, $ypos, $this->ndx_title_color, $this->y_title_txt, 'left', 'center');
        }

        return TRUE;
    }


    /*
     * \note Horizontal grid lines overwrite horizontal axis with y=0, so call this first, then DrawXAxis()
     */
    function DrawYAxis()
    {
        // Draw ticks, labels and grid, if any
        $this->DrawYTicks();

        // Draw Y axis at X = y_axis_x_pixels
        ImageLine($this->img, $this->y_axis_x_pixels, $this->plot_area[1],
                  $this->y_axis_x_pixels, $this->plot_area[3], $this->ndx_grid_color);

        return TRUE;
    }

    /*
     *
     */
    function DrawXAxis()
    {
        // Draw ticks, labels and grid
        $this->DrawXTicks();

        /* This tick and label tend to overlap with regular Y Axis labels,
         * as Mike Pullen pointed out.
         *
        //Draw Tick and Label for X axis
        if (! $this->skip_bottom_tick) {
            $ylab =$this->FormatLabel('y', $this->x_axis_position);
            $this->DrawYTick($ylab, $this->x_axis_y_pixels);
        }
        */
        //Draw X Axis at Y = x_axis_y_pixels
        ImageLine($this->img, $this->plot_area[0]+1, $this->x_axis_y_pixels, $this->plot_area[2]-1, $this->x_axis_y_pixels, $this->ndx_grid_color);

        return TRUE;
    }

    /*!
     * Draw Just one Tick, called from DrawYTicks() and DrawXAxis()
     * TODO? Move this inside DrawYTicks() and Modify DrawXAxis() ?
     */
    function DrawYTick($which_ylab, $which_ypix)
    {
        // Ticks on Y axis
        if ($this->y_tick_pos == 'yaxis') 
		{
            ImageLine($this->img, $this->y_axis_x_pixels - $this->y_tick_length, $which_ypix, $this->y_axis_x_pixels + $this->y_tick_cross, $which_ypix, $this->ndx_tick_color);
        }

        // Labels on Y axis
        if ($this->y_tick_label_pos == 'yaxis') 
		{
            $this->DrawText($this->y_label_font, $this->y_label_angle,
                            $this->y_axis_x_pixels - $this->y_tick_length * 1.5, $which_ypix,
                            $this->ndx_text_color, $which_ylab, 'right', 'center');
        }

        // Ticks to the left of the Plot Area
        if (($this->y_tick_pos == 'plotleft') || ($this->y_tick_pos == 'both') ) 
		{
            ImageLine($this->img, $this->plot_area[0] - $this->y_tick_length,
                      $which_ypix, $this->plot_area[0] + $this->y_tick_cross,
                      $which_ypix, $this->ndx_tick_color);
        }

        // Ticks to the right of the Plot Area
        if (($this->y_tick_pos == 'plotright') || ($this->y_tick_pos == 'both') ) 
		{
            ImageLine($this->img, ($this->plot_area[2] + $this->y_tick_length),
                      $which_ypix, $this->plot_area[2] - $this->y_tick_cross,
                      $which_ypix, $this->ndx_tick_color);
        }

        // Labels to the left of the plot area
        if ($this->y_tick_label_pos == 'plotleft' || $this->y_tick_label_pos == 'both') 
		{
            $this->DrawText($this->y_label_font, $this->y_label_angle,
                            $this->plot_area[0] - $this->y_tick_length * 1.5, $which_ypix,
                            $this->ndx_text_color, $which_ylab, 'right', 'center');
        }
        // Labels to the right of the plot area
        if ($this->y_tick_label_pos == 'plotright' || $this->y_tick_label_pos == 'both') 
		{
            $this->DrawText($this->y_label_font, $this->y_label_angle,
                            $this->plot_area[2] + $this->y_tick_length * 1.5, $which_ypix,
                            $this->ndx_text_color, $which_ylab, 'left', 'center');
        }
   } // Function DrawYTick()


    /*!
     * Draws Grid, Ticks and Tick Labels along Y-Axis
     * Ticks and ticklabels can be left of plot only, right of plot only,
     * both on the left and right of plot, or crossing a user defined Y-axis
     * TODO: marks at whole numbers (-10, 10, 20, 30 ...) no matter where the plot begins (-3, 4.7, etc.)
     */
    function DrawYTicks()
    {
        // Sets the line style for IMG_COLOR_STYLED lines (grid)
        if ($this->dashed_grid) 
		{
            $this->SetDashedStyle($this->ndx_light_grid_color);
            $style = IMG_COLOR_STYLED;
        } 
		else 
		{
            $style = $this->ndx_light_grid_color;
        }

        // maxy is always > miny so delta_y is always positive
        if ($this->y_tick_inc) 
		{
            $delta_y = $this->y_tick_inc;
        } 
		elseif ($this->num_y_ticks) 
		{
            $delta_y = ($this->plot_max_y - $this->plot_min_y) / $this->num_y_ticks;
        } 
		else 
		{
            $delta_y = ($this->plot_max_y - $this->plot_min_y) / 10 ;
        }

        // NOTE: When working with floats, because of approximations when adding $delta_y,
        // $y_tmp never equals $y_end  at the for loop, so one spurious line would  get drawn where
        // not for the substraction to $y_end here.
        $y_tmp = (double)$this->plot_min_y;
        $y_end = (double)$this->plot_max_y - ($delta_y/2);

        if ($this->skip_bottom_tick)
            $y_tmp += $delta_y;

        if ($this->skip_top_tick)
            $y_end -= $delta_y;

        for (;$y_tmp < $y_end; $y_tmp += $delta_y) 
		{
            $ylab = $this->FormatLabel('y', $y_tmp);
            $y_pixels = $this->ytr($y_tmp);

            // Horizontal grid line
            if ($this->draw_y_grid) 
			{
                ImageLine($this->img, $this->plot_area[0]+1, $y_pixels, $this->plot_area[2]-1, $y_pixels, $style);
            }

            // Draw ticks
            $this->DrawYTick($ylab, $y_pixels);
        }
        return TRUE;
    } // function DrawYTicks


    /*!
     * Draws Grid, Ticks and Tick Labels along X-Axis
     * Ticks and tick labels can be down of plot only, up of plot only,
     * both on up and down of plot, or crossing a user defined X-axis 
     *
     * \note Original vertical code submitted by Marlin Viss
     */
    function DrawXTicks() 
    {
        // Sets the line style for IMG_COLOR_STYLED lines (grid)
        if ($this->dashed_grid) 
		{
            $this->SetDashedStyle($this->ndx_light_grid_color);
            $style = IMG_COLOR_STYLED;
        } 
		else 
		{
            $style = $this->ndx_light_grid_color;
        }

        // Calculate x increment between ticks
        if ($this->x_tick_inc) 
		{
            $delta_x = $this->x_tick_inc;
        } 
		elseif ($this->num_x_ticks) 
		{
            $delta_x = ($this->plot_max_x - $this->plot_min_x) / $this->num_x_ticks;
        } 
		else 
		{
            $delta_x =($this->plot_max_x - $this->plot_min_x) / 10 ;
        }

        // NOTE: When working with decimals, because of approximations when adding $delta_x,
        // $x_tmp never equals $x_end  at the for loop, so one spurious line would  get drawn where
        // not for the substraction to $x_end here.
        $x_tmp = (double)$this->plot_min_x;
        $x_end = (double)$this->plot_max_x - ($delta_x/2);

        // Should the leftmost tick be drawn?
        if ($this->skip_left_tick)
            $x_tmp += $delta_x;

        // And the rightmost?
        if (! $this->skip_right_tick)
            $x_end += $delta_x;

        for (;$x_tmp < $x_end; $x_tmp += $delta_x) 
		{
            $xlab = $this->FormatLabel('x', $x_tmp);
            $x_pixels = $this->xtr($x_tmp);

            // Vertical grid lines
            if ($this->draw_x_grid) 
			{
                ImageLine($this->img, $x_pixels, $this->plot_area[1], $x_pixels, $this->plot_area[3], $style);
            }

            // Tick on X Axis
            if ($this->x_tick_pos == 'xaxis') 
			{
				ImageLine($this->img, $x_pixels, $this->x_axis_y_pixels - $this->x_tick_cross,
                          $x_pixels, $this->x_axis_y_pixels + $this->x_tick_length, $this->ndx_tick_color);
            }

            // Label on X axis
            if ($this->x_tick_label_pos == 'xaxis') 
			{
                 $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels,
                                $this->x_axis_y_pixels + $this->x_tick_length*1.5, $this->ndx_text_color, 
                                $xlab, 'center', 'bottom');
            }              

            // Top of the plot area tick
            if ($this->x_tick_pos == 'plotup' || $this->x_tick_pos == 'both') 
			{
                ImageLine($this->img, $x_pixels, $this->plot_area[1] - $this->x_tick_length,
                          $x_pixels, $this->plot_area[1] + $this->x_tick_cross, $this->ndx_tick_color);
            }
            // Bottom of the plot area tick
            if ($this->x_tick_pos == 'plotdown' || $this->x_tick_pos == 'both') 
			{
                ImageLine($this->img, $x_pixels, $this->plot_area[3] + $this->x_tick_length,
                          $x_pixels, $this->plot_area[3] - $this->x_tick_cross, $this->ndx_tick_color);
            }

            // Top of the plot area tick label
            if ($this->x_tick_label_pos == 'plotup' || $this->x_tick_label_pos == 'both') 
			{
                $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels, 
                                $this->plot_area[1] - $this->x_tick_length*1.5, $this->ndx_text_color, 
                                $xlab, 'center', 'top');
            }

            // Bottom of the plot area tick label
            if ($this->x_tick_label_pos == 'plotdown' || $this->x_tick_label_pos == 'both') 
			{
                $this->DrawText($this->x_label_font, $this->x_label_angle, $x_pixels,
                                $this->plot_area[3] + $this->x_tick_length*1.5, $this->ndx_text_color,
                                $xlab, 'center', 'bottom');
            }
        }
        return;
    } // function DrawXTicks


    /*!
     * 
     */
    function DrawPlotBorder()
    {
        switch ($this->plot_border_type) 
		{
			case 'left':    // for past compatibility
			case 'plotleft':
				ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
						  $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
				break;
			case 'right':
			case 'plotright':
				ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y),
						  $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
				break;
			case 'both':
			case 'sides':
				 ImageLine($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
						  $this->plot_area[0], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
				ImageLine($this->img, $this->plot_area[2], $this->ytr($this->plot_min_y),
						  $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
				break;
			case 'none':
				//Draw No Border
				break;
			case 'full':
			default:
				ImageRectangle($this->img, $this->plot_area[0], $this->ytr($this->plot_min_y),
							   $this->plot_area[2], $this->ytr($this->plot_max_y), $this->ndx_grid_color);
				break;
        }
        return TRUE;
    }


    /*!
     * Draws the data label associated with a point in the plot.
     * This is different from x_labels drawn by DrawXTicks() and care
     * should be taken not to draw both, as they'd probably overlap.
     * Calling of this function in DrawLines(), etc is decided after x_data_label_pos value.
     * Leave the last parameter out, to avoid the drawing of vertical lines, no matter
     * what the setting is (for plots that need it, like DrawSquared())
     */
    function DrawXDataLabel($xlab, $xpos, $row=FALSE)
    {
        // FIXME!! not working...
        if (($this->_x_label_cnt++ % $this->x_label_inc) != 0)
            return;

        $xlab = $this->FormatLabel('x', $xlab);

        // Labels below the plot area
        if ($this->x_data_label_pos == 'plotdown' || $this->x_data_label_pos == 'both')
            $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos,
                            $this->plot_area[3] + $this->x_tick_length,
                            $this->ndx_text_color, $xlab, 'center', 'bottom');

        // Labels above the plot area
        if ($this->x_data_label_pos == 'plotup' || $this->x_data_label_pos == 'both')
            $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos,
                            $this->plot_area[1] - $this->x_tick_length ,
                            $this->ndx_text_color, $xlab, 'center', 'top');

        if ($row && $this->draw_x_data_label_lines)
            $this->DrawXDataLine($xpos, $row);
    }

    /*!
     * Draws Vertical lines from data points up and down.
     * Which lines are drawn depends on the value of x_data_label_pos,
     * and whether this is at all done or not, on draw_x_data_label_lines
     *
     * \param xpos int position in pixels of the line.
     * \param row int index of the data row being drawn.
     */
    function DrawXDataLine($xpos, $row)
    {
        // Sets the line style for IMG_COLOR_STYLED lines (grid)
        if($this->dashed_grid) 
		{
            $this->SetDashedStyle($this->ndx_light_grid_color);
            $style = IMG_COLOR_STYLED;
        } 
		else 
		{
            $style = $this->ndx_light_grid_color;
        }

        // Lines from the bottom up
        if ($this->x_data_label_pos == 'both') 
		{
            ImageLine($this->img, $xpos, $this->plot_area[3], $xpos, $this->plot_area[1], $style);
        }
        // Lines coming from the bottom of the plot
        else if ($this->x_data_label_pos == 'plotdown') 
		{
            // See FindDataLimits() to see why 'MAXY' index.
            $ypos = $this->ytr($this->data[$row][MAXY]);
            ImageLine($this->img, $xpos, $ypos, $xpos, $this->plot_area[3], $style);
        }
        // Lines coming from the top of the plot
        else if ($this->x_data_label_pos == 'plotup') 
		{
            // See FindDataLimits() to see why 'MINY' index.
            $ypos = $this->ytr($this->data[$row][MINY]);
            ImageLine($this->img, $xpos, $this->plot_area[1], $xpos, $ypos, $style);
        }
    } 
    
/*    
    function DrawPlotLabel($xlab, $xpos, $ypos) 
    {
        $this->DrawText($this->x_label_font, $this->x_label_angle, $xpos, $this
*/

    /*!
     * Draws the graph legend
     *
     * \note Base code submitted by Marlin Viss
     * FIXME: maximum label length should be calculated more accurately for TT fonts
     *        Performing a BBox calculation for every legend element, for example.
     */
    function DrawLegend($which_x1, $which_y1, $which_boxtype)
    {
        // Find maximum legend label length
        $max_len = 0;
        foreach ($this->legend as $leg) 
		{
            $len = strlen($leg);
            $max_len = ($len > $max_len) ? $len : $max_len;
        }
        $max_len += 5;          // Leave room for the boxes and margins

        /////// Calculate legend labels sizes:  FIXME - dirty hack - FIXME
        // TTF:
        if ($this->use_ttf) 
		{
            $size = $this->TTFBBoxSize($this->legend_font['size'], 0,
                                       $this->legend_font['font'], '_');
            $char_w = $size[0];

            $size = $this->TTFBBoxSize($this->legend_font['size'], 0,
                                       $this->legend_font['font'], '|');
            $char_h = $size[1];                                       
        } 
        // Fixed fonts:
        else 
		{
            $char_w = $this->legend_font['width'];
            $char_h = $this->legend_font['height'];
        }

        $v_margin = $char_h/2;                         // Between vertical borders and labels
        $dot_height = $char_h + $this->line_spacing;   // Height of the small colored boxes
        $width = $char_w * $max_len;

        //////// Calculate box size
        // upper Left
        if ( (! $which_x1) || (! $which_y1) ) 
		{
            $box_start_x = $this->plot_area[2] - $width;
            $box_start_y = $this->plot_area[1] + 5;
        } 
		else 
		{ 
            $box_start_x = $which_x1;
            $box_start_y = $which_y1;
        }

        // Lower right corner
        $box_end_y = $box_start_y + $dot_height*(count($this->legend)) + 2*$v_margin; 
        $box_end_x = $box_start_x + $width - 5;


        // Draw outer box
        ImageFilledRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_bg_color);
        ImageRectangle($this->img, $box_start_x, $box_start_y, $box_end_x, $box_end_y, $this->ndx_grid_color);

        $color_index = 0;
        $max_color_index = count($this->ndx_data_colors) - 1;

        $dot_left_x = $box_end_x - $char_w * 2;
        $dot_right_x = $box_end_x - $char_w;
        $y_pos = $box_start_y + $v_margin;

        foreach ($this->legend as $leg) 
		{
            // Text right aligned to the little box
            $this->DrawText($this->legend_font, 0, $dot_left_x - $char_w, $y_pos, 
                            $this->ndx_text_color, $leg, 'right');
            // Draw a box in the data color
            ImageFilledRectangle($this->img, $dot_left_x, $y_pos + 1, $dot_right_x,
                                 $y_pos + $dot_height-1, $this->ndx_data_colors[$color_index]);
            // Draw a rectangle around the box
            ImageRectangle($this->img, $dot_left_x, $y_pos + 1, $dot_right_x,
                           $y_pos + $dot_height-1, $this->ndx_text_color);

            $y_pos += $char_h + $this->line_spacing;

            $color_index++;
            if ($color_index > $max_color_index) 
                $color_index = 0;
        }
    } // Function DrawLegend()


    /*!
     * TODO Draws a legend over (or below) an axis of the plot.
     */
    function DrawAxisLegend()
    {
        // Calculate available room
        // Calculate length of all items (boxes included)
        // Calculate number of lines and room it would take. FIXME: this should be known in CalcMargins()
        // Draw.
    }

/////////////////////////////////////////////
////////////////////             PLOT DRAWING
/////////////////////////////////////////////


    /*!
     * Draws a pie chart. Data has to be 'text-data' type.
     *
     *  This can work in two ways: the classical, with a column for each sector
     *  (computes the column totals and draws the pie with that)
     *  OR
     *  Takes each row as a sector and uses it's first value. This has the added
     *  advantage of using the labels provided, which is not the case with the
     *  former method. This might prove useful for pie charts from GROUP BY sql queries
     */
    function DrawPieChart()
    {
        $xpos = $this->plot_area[0] + $this->plot_area_width/2;
        $ypos = $this->plot_area[1] + $this->plot_area_height/2;
        $diameter = min($this->plot_area_width, $this->plot_area_height);
        $radius = $diameter/2;

        // Get sum of each column? One pie slice per column
        if ($this->data_type === 'text-data') 
		{
            for ($i = 0; $i < $this->num_data_rows; $i++) 
			{
                for ($j = 1; $j < $this->num_recs[$i]; $j++) 
				{      // Label ($row[0]) unused in these pie charts
                    @ $sumarr[$j] += abs($this->data[$i][$j]);      // NOTE!  sum > 0 to make pie charts
                }
            }
        }
        // Or only one column per row, one pie slice per row?
        else if ($this->data_type == 'text-data-single') 
		{
            for ($i = 0; $i < $this->num_data_rows; $i++) 
			{
                $legend[$i] = $this->data[$i][0];                   // Set the legend to column labels
                $sumarr[$i] = $this->data[$i][1];
            }
        }
        else if ($this->data_type == 'data-data') 
		{
            for ($i = 0; $i < $this->num_data_rows; $i++) 
			{
                for ($j = 2; $j < $this->num_recs[$i]; $j++) 
				{
                    @ $sumarr[$j] += abs($this->data[$i][$j]);
                }
            }
        }
        else 
		{
            $this->DrawError("DrawPieChart(): Data type '$this->data_type' not supported.");
            return FALSE;
        }

        $total = array_sum($sumarr);

        if ($total == 0) 
		{
            $this->DrawError('DrawPieChart(): Empty data set');
            return FALSE;
        }

        if ($this->shading) 
		{
            $diam2 = $diameter / 2;
        } 
		else 
		{
            $diam2 = $diameter;
        }
        $max_data_colors = count ($this->data_colors);

        for ($h = $this->shading; $h >= 0; $h--) 
		{
            $color_index = 0;
            $start_angle = 0;
            $end_angle = 0;
            foreach ($sumarr as $val) 
			{
                // For shaded pies: the last one (at the top of the "stack") has a brighter color:
                if ($h == 0)
                    $slicecol = $this->ndx_data_colors[$color_index];
                else
                    $slicecol = $this->ndx_data_dark_colors[$color_index];

                $label_txt = number_format(($val / $total * 100), $this->y_precision, '.', ', ') . '%';
                $val = 360 * ($val / $total);

                // NOTE that imagefilledarc measures angles CLOCKWISE (go figure why),
                // so the pie chart would start clockwise from 3 o'clock, would it not be
                // for the reversal of start and end angles in imagefilledarc()
                $start_angle = $end_angle;
                $end_angle += $val;
                $mid_angle = deg2rad($end_angle - ($val / 2));

                // Draw the slice
                ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
                               360-$end_angle, 360-$start_angle,
                               $slicecol, IMG_ARC_PIE);

                // Draw the labels only once
                if ($h == 0) 
				{
                    // Draw the outline
                    if (! $this->shading)
                        ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
                                       360-$end_angle, 360-$start_angle,
                                       $this->ndx_grid_color, IMG_ARC_PIE | IMG_ARC_EDGED |IMG_ARC_NOFILL);


                    // The '* 1.2' trick is to get labels out of the pie chart so there are more
                    // chances they can be seen in small sectors.
                    $label_x = $xpos + ($diameter * 1.2 * cos($mid_angle)) * $this->label_scale_position;
                    $label_y = $ypos+$h - ($diam2 * 1.2 * sin($mid_angle)) * $this->label_scale_position;

                    $this->DrawText($this->generic_font, 0, $label_x, $label_y, $this->ndx_grid_color,
                                    $label_txt, 'center', 'center');
                }
                $color_index++;
                $color_index = $color_index % $max_data_colors;
            }   // end for
        }   // end for
    }


    /*!
     * Supported data formats: data-data-error, text-data-error (doesn't exist yet)
     * ( data comes in as array("title", x, y, error+, error-, y2, error2+, error2-, ...) )
     */
    function DrawDotsError()
    {
        $this->CheckOption($this->data_type, 'data-data-error', __FUNCTION__);

        for($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $record = 1;                                // Skip record #0 (title)

            // Do we have a value for X?
            if ($this->data_type == 'data-data-error')
                $x_now = $this->data[$row][$record++];  // Read it, advance record index
            else
                $x_now = 0.5 + $cnt++;                  // Place text-data at X = 0.5, 1.5, 2.5, etc...

            // Draw X Data labels?
            if ($this->x_data_label_pos != 'none')
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);

            while ($record < $this->num_recs[$row]) 
			{
                    // Y:
                    $y_now = $this->data[$row][$record];
                    $this->DrawDot($x_now, $y_now, $record, $this->ndx_data_colors[$record++]);

                    // Error +
                    $val = $this->data[$row][$record];
                    $this->DrawYErrorBar($x_now, $y_now, $val, $this->error_bar_shape,
                                         $this->ndx_error_bar_colors[$record++]);
                    // Error -
                    $val = $this->data[$row][$record];
                    $this->DrawYErrorBar($x_now, $y_now, -$val, $this->error_bar_shape,
                                         $this->ndx_error_bar_colors[$record++]);
            }
        }
    } // function DrawDotsError()


    /*
     * Supported data types:
     *  - data-data ("title", x, y1, y2, y3, ...)
     *  - text-data ("title", y1, y2, y3, ...)
     */
    function DrawDots()
    {
        $this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__);

        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $rec = 1;                    // Skip record #0 (data label)

            // Do we have a value for X?
            if ($this->data_type == 'data-data')
                $x_now = $this->data[$row][$rec++];  // Read it, advance record index
            else
                $x_now = 0.5 + $cnt++;       // Place text-data at X = 0.5, 1.5, 2.5, etc...

            $x_now_pixels = $this->xtr($x_now);

            // Draw X Data labels?
            if ($this->x_data_label_pos != 'none')
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);

            // Proceed with Y values
            for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) 
			{
                if (is_numeric($this->data[$row][$rec])) 
				{              // Allow for missing Y data
                    $this->DrawDot($x_now, $this->data[$row][$rec],
                                   $rec, $this->ndx_data_colors[$idx]);
                }
            }
        }
    } //function DrawDots


    /*!
     * A clean, fast routine for when you just want charts like stock volume charts
     */
    function DrawThinBarLines()
    {
        $this->CheckOption($this->data_type, 'text-data, data-data', __FUNCTION__);

        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $rec = 1;                    // Skip record #0 (data label)

            // Do we have a value for X?
            if ($this->data_type == 'data-data')
                $x_now = $this->data[$row][$rec++];  // Read it, advance record index
            else
                $x_now = 0.5 + $cnt++;       // Place text-data at X = 0.5, 1.5, 2.5, etc...

            $x_now_pixels = $this->xtr($x_now);

            // Draw X Data labels?
            if ($this->x_data_label_pos != 'none')
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);

            // Proceed with Y values
            for($idx = 0;$rec < $this->num_recs[$row]; $rec++, $idx++) 
			{
                if (is_numeric($this->data[$row][$rec])) 
				{              // Allow for missing Y data 
                    ImageSetThickness($this->img, $this->line_widths[$idx]);
                    // Draws a line from user defined x axis position up to ytr($val)
                    ImageLine($this->img, $x_now_pixels, $this->x_axis_y_pixels, $x_now_pixels, 
                              $this->ytr($this->data[$row][$rec]), $this->ndx_data_colors[$idx]);
                }
            }
        }

        ImageSetThickness($this->img, 1);
    }  //function DrawThinBarLines

    /*!
     *
     */
    function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color)
    {
        /* 
        // TODO: add a parameter to show datalabels next to error bars?
        // something like this:
        if ($this->x_data_label_pos == 'plot') {
            $this->DrawText($this->error_font, 90, $x1, $y2, 
                            $color, $label, 'center', 'top');
        */

        $x1 = $this->xtr($x_world);
        $y1 = $this->ytr($y_world);
        $y2 = $this->ytr($y_world+$error_height) ;

        ImageSetThickness($this->img, $this->error_bar_line_width);
        ImageLine($this->img, $x1, $y1 , $x1, $y2, $color);

        switch ($error_bar_type) 
		{
			case 'line':
				break;
			case 'tee':
				ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
				break;
			default:
				ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
				break;
        }

        ImageSetThickness($this->img, 1);
        return TRUE;
    }

    /*!
     * Draws a styled dot. Uses world coordinates.
     * Supported types: 'halfline', 'line', 'plus', 'cross', 'rect', 'circle', 'dot',
     * 'diamond', 'triangle', 'trianglemid'
     */
    function DrawDot($x_world, $y_world, $record, $color)
    {
        // TODO: optimize, avoid counting every time we are called.
        $record = $record % count ($this->point_shapes);

        $half_point = $this->point_sizes[$record] / 2;

        $x_mid = $this->xtr($x_world);
        $y_mid = $this->ytr($y_world);

        $x1 = $x_mid - $half_point;
        $x2 = $x_mid + $half_point;
        $y1 = $y_mid - $half_point;
        $y2 = $y_mid + $half_point;

        switch ($this->point_shapes[$record]) 
		{
			case 'halfline':
				ImageLine($this->img, $x1, $y_mid, $x_mid, $y_mid, $color);
				break;
			case 'line':
				ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
				break;
			case 'plus':
				ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
				ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
				break;
			case 'cross':
				ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
				ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
				break;
			case 'rect':
				ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
				break;
			case 'circle':
				ImageArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record], $this->point_sizes[$record], 0, 360, $color);
				break;
			case 'dot':
				ImageFilledArc($this->img, $x_mid, $y_mid, $this->point_sizes[$record], $this->point_sizes[$record], 0, 360,
							   $color, IMG_ARC_PIE);
				break;
			case 'diamond':
				$arrpoints = array( $x1, $y_mid, $x_mid, $y1, $x2, $y_mid, $x_mid, $y2);
				ImageFilledPolygon($this->img, $arrpoints, 4, $color);
				break;
			case 'triangle':
				$arrpoints = array( $x1, $y_mid, $x2, $y_mid, $x_mid, $y2);
				ImageFilledPolygon($this->img, $arrpoints, 3, $color);
				break;
			case 'trianglemid':
				$arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y_mid);
				ImageFilledPolygon($this->img, $arrpoints, 3, $color);
				break;
			default:
				ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
				break;
        }
        return TRUE;
    }

    /*!
     * Draw an area plot. Supported data types:
     *      'text-data'
     *      'data-data'
     * NOTE: This function used to add first and last data values even on incomplete
     *       sets. That is not the behaviour now. As for missing data in between,
     *       there are two posibilities: replace the point with one on the X axis (previous
     *       way), or forget about it and use the preceding and following ones to draw the polygon.
     *       There is the possibility to use both, we just need to add the method to set
     *       it. Something like SetMissingDataBehaviour(), for example.
     */
    function DrawArea()
    {
        $incomplete_data_defaults_to_x_axis = FALSE;        // TODO: make this configurable

        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $rec = 1;                                       // Skip record #0 (data label)

            if ($this->data_type == 'data-data')            // Do we have a value for X?
                $x_now = $this->data[$row][$rec++];         // Read it, advance record index
            else
                $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...

            $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates


            if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels);

            // Proceed with Y values
            // Create array of points for imagefilledpolygon()
            for($idx = 0; $rec < $this->num_recs[$row]; $rec++, $idx++) 
			{
                if (is_numeric($this->data[$row][$rec])) 
				{              // Allow for missing Y data 
                    $y_now_pixels = $this->ytr($this->data[$row][$rec]);

                    $posarr[$idx][] = $x_now_pixels;
                    $posarr[$idx][] = $y_now_pixels;

                    $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1;
                }
                // If there's missing data...
                else 
				{
                    if (isset ($incomplete_data_defaults_to_x_axis)) 
					{
                        $posarr[$idx][] = $x_now_pixels;
                        $posarr[$idx][] = $this->x_axis_y_pixels;
                        $num_points[$idx] = isset($num_points[$idx]) ? $num_points[$idx]+1 : 1;
                    }
                }
            }
        }   // end for

        $end = count($posarr);
        for ($i = 0; $i < $end; $i++) 
		{
            // Prepend initial points. X = first point's X, Y = x_axis_y_pixels
            $x = $posarr[$i][0];
            array_unshift($posarr[$i], $x, $this->x_axis_y_pixels);

            // Append final points. X = last point's X, Y = x_axis_y_pixels
            $x = $posarr[$i][count($posarr[$i])-2];
            array_push($posarr[$i], $x, $this->x_axis_y_pixels);

            $num_points[$i] += 2;

            // Draw the poligon
            ImageFilledPolygon($this->img, $posarr[$i], $num_points[$i], $this->ndx_data_colors[$i]);
        }

    } // function DrawArea()


    /*!
     * Draw Lines. Supported data-types:
     *      'data-data', 
     *      'text-data'
     * NOTE: Please see the note regarding incomplete data sets on DrawArea()
     */
    function DrawLines() 
    {
        // This will tell us if lines have already begun to be drawn.
        // It is an array to keep separate information for every line, with a single
        // variable we would sometimes get "undefined offset" errors and no plot...
        $start_lines = array_fill(0, $this->records_per_group, FALSE);

        if ($this->data_type == 'text-data') 
		{ 
            $lastx[0] = $this->xtr(0);
            $lasty[0] = $this->xtr(0);
        }

        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $record = 1;                                    // Skip record #0 (data label)

            if ($this->data_type == 'data-data')            // Do we have a value for X?
                $x_now = $this->data[$row][$record++];      // Read it, advance record index
            else
                $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...

            $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates

            if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);

            for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) 
			{
                if (is_numeric($this->data[$row][$record])) 
				{           //Allow for missing Y data 
                    $y_now_pixels = $this->ytr($this->data[$row][$record]);

                    if ($start_lines[$idx] == TRUE) 
					{
                        // Set line width, revert it to normal at the end
                        ImageSetThickness($this->img, $this->line_widths[$idx]);

                        if ($this->line_styles[$idx] == 'dashed') 
						{
                            $this->SetDashedStyle($this->ndx_data_colors[$idx]);
                            ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 
                                      IMG_COLOR_STYLED);
                        } 
						else 
						{
                            ImageLine($this->img, $x_now_pixels, $y_now_pixels, $lastx[$idx], $lasty[$idx], 
                                      $this->ndx_data_colors[$idx]);
                        }

                    }
                    $lasty[$idx] = $y_now_pixels;
                    $lastx[$idx] = $x_now_pixels;
                    $start_lines[$idx] = TRUE;
                } 
                // Y data missing... should we leave a blank or not?
                else if ($this->draw_broken_lines) 
				{
                    $start_lines[$idx] = FALSE;
                }
            }   // end for
        }   // end for

        ImageSetThickness($this->img, 1);       // Revert to original state for lines to be drawn later. 
    } // function DrawLines()


    /*!
     * Draw lines with error bars - data comes in as 
     *      array("label", x, y, error+, error-, y2, error2+, error2-, ...);
     */
    function DrawLinesError() 
    {
        if ($this->data_type != 'data-data-error') 
		{
            $this->DrawError("DrawLinesError(): Data type '$this->data_type' not supported.");
            return FALSE;
        }

        $start_lines = array_fill(0, $this->records_per_group, FALSE);

        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) 
		{
            $record = 1;                                    // Skip record #0 (data label)

            $x_now = $this->data[$row][$record++];          // Read X value, advance record index

            $x_now_pixels = $this->xtr($x_now);             // Absolute coordinates.
            

            if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);

            // Now go for Y, E+, E-
            for ($idx = 0; $record < $this->num_recs[$row]; $idx++) 
			{
                // Y
                $y_now = $this->data[$row][$record++];
                $y_now_pixels = $this->ytr($y_now);

                if ($start_lines[$idx] == TRUE) 
				{
                    ImageSetThickness($this->img, $this->line_widths[$idx]);

                    if ($this->line_styles[$idx] == 'dashed') 
					{
                        $this->SetDashedStyle($this->ndx_data_colo

Edited by xuexue: n/a

This topic has been dead for over six months. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.