]> git.sur5r.net Git - bacula/bacula/commitdiff
bacula-web: Upgraded phplot from version 5.2.0 to 5.3.1
authorDavide Franco <bacula-dev@dflc.ch>
Wed, 19 Jan 2011 11:27:26 +0000 (12:27 +0100)
committerEric Bollengier <eric@eb.homelinux.org>
Thu, 3 Mar 2011 09:39:30 +0000 (10:39 +0100)
gui/bacula-web/external_packages/phplot/ChangeLog
gui/bacula-web/external_packages/phplot/NEWS.txt
gui/bacula-web/external_packages/phplot/README.txt
gui/bacula-web/external_packages/phplot/phplot.php

index ace1b3bfc7ab5faf5b1d4964287518f7c7bf91ff..af66bd1cf82c65609ac387ed6a1192235f2fb6e6 100644 (file)
@@ -2,6 +2,125 @@ This is the Change Log for PHPlot.
 The project home page is http://sourceforge.net/projects/phplot/
 -----------------------------------------------------------------------------
 
 The project home page is http://sourceforge.net/projects/phplot/
 -----------------------------------------------------------------------------
 
+2011-01-15 (lbayuk)     ===== Released as 5.3.1 =====
+    * phplot.php: Updated version
+    * README.txt: Updated for new release
+    * NEWS.txt: Add text for new release
+
+2011-01-09
+    * Fixed some style / indent errors, and 1 redundant test.
+
+2011-01-03
+    * For bug 3143586 "Multiple plots per image - fixes & docs":
+      Make sure there is a documented way to reset PHPlot feature
+      settings, especially those for which the default settings result
+      in automatic calculated values. Where possible, calling a Set*()
+      function with no arguments should reset the feature to defaults.
+
+      + Changed SetLegendPixels() arguments to be optional with default
+        NULL meaning reset to automatic positioning.
+
+      + Fixed SetXAxisPosition() and SetYAxisPosition() to accept empty
+        string '' to mean reset to default automatic positioning.
+        Make arguments optional, defaulting to empty string.
+
+      + Changed SetNumXTicks() and SetNumYTicks() arguments to be
+        optional with default empty string '' meaning reset to
+        of automatic calculation.
+
+    * Changed SetPointShapes() to use CheckOptionArray(). This
+      simplifies the function with no change in operation.
+
+    * Extend copyright years to 2011.
+
+2010-12-30
+    * Fix for bug 3147397 "Data colors missing with multiple plots":
+      + Do not truncate the data_colors and related arrays, so the full
+        set of colors will be available for subsequent plots on the image.
+        (Color indexes are still allocated in the image only as needed.)
+      + New internal functions GetColorIndexArray() and
+        GetDarkColorIndexArray(), replacing previous use of array_map().
+      + Removed internal function truncate_array() - no longer used.
+      + Changed SetColorIndexes(), NeedDataDarkColors(), and
+        NeedErrorBarColors() to only allocate the color indexes that will
+        be needed (instead of allocating all colors in the truncated color
+        descriptor arrays).
+
+2010-12-28
+    * Instead of throwing an error, SetLegend(NULL) now clears the legend
+      array. This can be useful with multiple plots on an image. Before
+      this change, only SetLegend(array()) did that (possibly by accident).
+
+2010-12-27
+    * Do not have SetDefaultStyles() call deprecated SetLabelColor().
+
+    * Fixes for bug 3143586 "Multiple plots per image - fixes & docs":
+      + Fix DrawLegend so it doesn't forget that the legend position
+        was specified in world coordinates. This fixes the legend
+        position for plots after the first.
+      + Don't draw the image border more than once (although this would
+        probably have no impact on the resulting image). This parallels
+        the behavior for the main plot title and the image background.
+        Replaced member variables background_done and title_done with a new
+        member array done[] which will track when these elements were done.
+
+2010-12-06
+    * Fix comments above CalcPlotAreaWorld(). Deleted incorrect information
+      from before data-data-yx existed, and before DecodeDataType rewrite.
+
+2010-12-04 (lbayuk)     ===== Released as 5.3.0 =====
+    * phplot.php: Updated version
+    * README.txt: Updated for new release
+    * NEWS.txt: Add text for new release
+
+2010-12-03
+    * Feature request 3127005 "Ability to suppress X/Y axis lines":
+      Added SetDrawXAxis() and SetDrawYAxis() to control flags which
+      will suppress drawing the X or Y axis lines. (These lines were
+      probably the only PHPlot elements that could not be turned off.)
+      Changed DrawXAxis() and DrawYAxis() to conditionally draw the
+      axis lines.
+
+2010-11-28
+    * Feature request 3117873 "Data value labels in more plot types":
+      Implemented Data Value Labels for plot types points, lines,
+      linepoints, and squared.  Added 2 class variables which can be
+      set to control the distance and angle of the labels from points.
+      New internal function CheckDataValueLabels() calculates position
+      and text alignment for these labels.
+
+    * Updated comments for Set[XY]DataLabelPos to match the text in
+      the manual, which was rewritten to clarify label types.
+
+2010-11-23
+    * Code cleanup. Moved some functions around to group "plot drawing
+      helpers" separately from "plot drawing". No changes to operation.
+
+2010-11-21
+    * Feature request 3111166 "Control legend colorbox width":
+      Added a class variable legend_colorbox_width which can be changed
+      to make the colorboxes wider or narrower.
+
+2010-11-16
+    * Feature request 3093483 "Investing support chart types":
+      Added 3 new plot types: Basic OHLC (Open/High/Low/Close), Candlesticks,
+      and Filled Candlesticks. Implemented with one new function to handle the
+      3 new plot types: ohlc, candlesticks, and candlesticks2.
+
+2010-11-11
+    * Moved information about plot types into a new static member array
+      plots[]. (This is an internal change with no impact on usage, but will
+      make it easier to add new plot types.) SetPlotType() no longer needs a
+      list of plot types to check, FindDataLimits() does not need to check for
+      specific plot types to to process the data array, and DrawGraph() uses
+      data from the array rather than knowing about all the plot types.
+
+2010-10-31
+    * Changed internal CalcBarWidths() to take two arguments which indicate
+      how it should calculate bar widths, rather than having it check the
+      plot_type directly. (Taken from another, experimental change. This
+      minimizes places where plot_type is directly used.)
+
 2010-10-03 (lbayuk)     ===== Released as 5.2.0 =====
     * phplot.php: Updated version
     * README.txt: Updated for new release
 2010-10-03 (lbayuk)     ===== Released as 5.2.0 =====
     * phplot.php: Updated version
     * README.txt: Updated for new release
index 0efb1bc5c9e78e9fc81f0ee6d6ddf7a3bfcc744e..fdd99fe18a898645efdb1cdd0db391575efa73fb 100644 (file)
@@ -4,6 +4,78 @@ The project home page is http://phplot.sourceforge.net/
 Refer the the ChangeLog file for detailed source changes.
 -----------------------------------------------------------------------------
 
 Refer the the ChangeLog file for detailed source changes.
 -----------------------------------------------------------------------------
 
+2011-01-15 Release 5.3.1
+
+Overview:
+
+This is the current stable release of PHPlot. This release focuses on
+providing better support and documentation for creating multiple plots on a
+single image.
+
+The PHPlot reference manual has been updated to match this release. Some
+new material has been added, including a new section on multiple plots per
+image, and a new example of overlay plots.
+
+
+Bugs Fixed in 5.3.1:
+
+#3143586 "Multiple plots per image - fixes & docs":
+  The reference manual now contains a section on multiple plots, and a
+  new example. A bug was fixed with SetLegendWorld and multiple plots.
+  Image border will now be drawn at most once. It is now possible to
+  restore the default 'automatic' behavior for axis positioning. Other
+  functions were changed to make arguments optional, so when called with
+  no arguments they reset to the default. The reference manual has been
+  updated with these changes too.
+
+#3147397 "Data colors missing with multiple plots":
+  The fix for bug #3049726 "Optimize color allocation" caused a problem
+  with multiple plots. This has been fixed. PHPlot will no longer truncate
+  the data color table at each plot. It will still only allocate data colors
+  as needed, but all of the pre-set or configured data colors will be
+  available for each plot.
+
+
+-----------------------------------------------------------------------------
+
+2010-12-04 Release 5.3.0
+
+Overview:
+
+This is the current stable release of PHPlot. This release includes new
+plot types and some new features.
+
+The PHPlot reference manual has been updated to match this release. Some of
+the sections have been moved, there are new examples for the new plot types,
+and a new section on tunable parameters has been added.
+
+
+New features in 5.3.0:
+
+#3093483 "Investing support chart types":
+  Added 3 new plot types: Basic OHLC (Open/High/Low/Close), Candlesticks,
+  and Filled Candlesticks. These are variations of plots that show the
+  performance of a stock or other financial security.
+
+#3111166 "Control legend colorbox width":
+  It is now possible to control the width of the color boxes in the
+  legend, using a class variable which is documented in the manual.
+
+#3117873 "Data value labels in more plot types":
+  Data value labels, which show the dependent variable values near the
+  data points, are now available for more plot types: lines, linepoints,
+  points, and squared. (These were previously available only for bars and
+  stackedbars plots.)
+
+#3127005 "Ability to suppress X/Y axis lines":
+  New functions SetDrawXAxis() and SetDrawYAxis() were added to control
+  display of the X and Y axis lines. (These lines were probably the only
+  PHPlot elements that could not be turned off.) In special cases, these
+  can be used to produce a "bare" plot image.
+
+
+-----------------------------------------------------------------------------
+
 2010-10-03 Release 5.2.0
 
 Overview:
 2010-10-03 Release 5.2.0
 
 Overview:
index 748522651075c69572266bb066d1a085be2ac063..fa81bcf42eea021d8c87548539f1566c84a9c969 100644 (file)
@@ -1,5 +1,5 @@
 This is the README file for PHPlot
 This is the README file for PHPlot
-Last updated for PHPlot-5.2.0 on 2010-10-03
+Last updated for PHPlot-5.3.1 on 2011-01-15
 The project web site is http://sourceforge.net/projects/phplot/
 The project home page is http://phplot.sourceforge.net/
 -----------------------------------------------------------------------------
 The project web site is http://sourceforge.net/projects/phplot/
 The project home page is http://phplot.sourceforge.net/
 -----------------------------------------------------------------------------
@@ -13,7 +13,8 @@ complete information, download the PHPlot Reference Manual from the
 Sourceforge project web site. You can also view the manual online at
 http://phplot.sourceforge.net
 
 Sourceforge project web site. You can also view the manual online at
 http://phplot.sourceforge.net
 
-For important changes in this release, see the NEWS.txt file.
+For information about changes in this release, including any possible
+incompatibilities, see the NEWS.txt file.
 
 
 CONTENTS:
 
 
 CONTENTS:
@@ -30,8 +31,8 @@ CONTENTS:
 REQUIREMENTS:
 
 You need a recent version of PHP5, and you are advised to use the latest
 REQUIREMENTS:
 
 You need a recent version of PHP5, and you are advised to use the latest
-stable release.  This version of PHPlot has been tested with PHP-5.3.3 and
-PHP-5.2.14 on Linux, and with PHP-5.3.3 on Windows/XP.
+stable release.  This version of PHPlot has been tested with PHP-5.3.5 and
+PHP-5.2.17 on Linux, and with PHP-5.3.5 on Windows XP.
 
 Use of PHP-5.3.2 or PHP-5.2.13 is not recommended, if you are using
 TrueType Font (TTF) text. A bug with TTF rendering in those versions
 
 Use of PHP-5.3.2 or PHP-5.2.13 is not recommended, if you are using
 TrueType Font (TTF) text. A bug with TTF rendering in those versions
@@ -72,6 +73,10 @@ KNOWN ISSUES:
 Here are some of the problems we know about in PHPlot. See the bug tracker
 on the PHPlot project web site for more information.
 
 Here are some of the problems we know about in PHPlot. See the bug tracker
 on the PHPlot project web site for more information.
 
+#3142124 Clip plot elements to plot area
+  Plot elements are not currently clipped to the plot area, and may extend
+  beyond. PHP does not currently support the GD clipping control.
+
 #1795969 The automatic range calculation for Y values needs to be rewritten.  
   This is especially a problem with small offset ranges (e.g. Y=[999:1001]).
   You can use SetPlotAreaWorld to set a specific range instead.
 #1795969 The automatic range calculation for Y values needs to be rewritten.  
   This is especially a problem with small offset ranges (e.g. Y=[999:1001]).
   You can use SetPlotAreaWorld to set a specific range instead.
@@ -146,7 +151,7 @@ graph, check your web server error log for more information.
 
 COPYRIGHT and LICENSE:
 
 
 COPYRIGHT and LICENSE:
 
-PHPlot is Copyright (C) 1998-2010 Afan Ottenheimer
+PHPlot is Copyright (C) 1998-2011 Afan Ottenheimer
 
 This is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
 
 This is free software; you can redistribute it and/or
 modify it under the terms of the GNU Lesser General Public
index c7f323c38db97808c0dbe78910879803ed9228f7..4abb7498bf0484deb04ec054df088de945dcb33f 100644 (file)
@@ -1,13 +1,13 @@
 <?php
 <?php
-/* $Id: phplot.php,v 1.201 2010/10/03 21:57:09 lbayuk Exp $ */
+/* $Id: phplot.php,v 1.216 2011/01/16 01:19:55 lbayuk Exp $ */
 /*
 /*
- * PHPLOT Version 5.2.0
+ * PHPLOT Version 5.3.1
  *
  * A PHP class for creating scientific and business charts
  * Visit http://sourceforge.net/projects/phplot/
  * for PHPlot documentation, downloads, and discussions.
  * ---------------------------------------------------------------------
  *
  * A PHP class for creating scientific and business charts
  * Visit http://sourceforge.net/projects/phplot/
  * for PHPlot documentation, downloads, and discussions.
  * ---------------------------------------------------------------------
- * Copyright (C) 1998-2010 Afan Ottenheimer
+ * Copyright (C) 1998-2011 Afan Ottenheimer
  *
  * This is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
  *
  * This is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Lesser General Public
@@ -47,7 +47,6 @@ class PHPlot
     public $browser_cache = FALSE;         // FALSE = Sends headers for browser to not cache the image,
                                            // (only if is_inline = FALSE also)
     public $print_image = TRUE;            // DrawGraph calls PrintImage. See SetPrintImage
     public $browser_cache = FALSE;         // FALSE = Sends headers for browser to not cache the image,
                                            // (only if is_inline = FALSE also)
     public $print_image = TRUE;            // DrawGraph calls PrintImage. See SetPrintImage
-    public $background_done = FALSE;       // TRUE after background image is drawn once
 
     public $safe_margin = 5;               // Extra margin used in several places, in pixels
 
 
     public $safe_margin = 5;               // Extra margin used in several places, in pixels
 
@@ -75,7 +74,7 @@ class PHPlot
 
 //Data
     public $data_type = 'text-data';       // Structure of the data array
 
 //Data
     public $data_type = 'text-data';       // Structure of the data array
-    public $plot_type= 'linepoints';       // bars, lines, linepoints, area, points, pie, thinbarline, squared
+    public $plot_type = 'linepoints';      // See $plots[] below
 
     public $label_scale_position = 0.5;    // Shifts data labels in pie charts. 1 = top, 0 = bottom
     public $group_frac_width = 0.7;        // Bars use this fraction (0 to 1) of a group's space
 
     public $label_scale_position = 0.5;    // Shifts data labels in pie charts. 1 = top, 0 = bottom
     public $group_frac_width = 0.7;        // Bars use this fraction (0 to 1) of a group's space
@@ -204,6 +203,65 @@ class PHPlot
         'debug_scale' => NULL,    // For testing/debugging scale setup
     );
 
         'debug_scale' => NULL,    // For testing/debugging scale setup
     );
 
+    // Defined plot types static array:
+    // Array key is the plot type. (Upper case letters are not allowed due to CheckOption)
+    // Value is an array with these keys:
+    //   draw_method (required) : Class method to call to draw the plot.
+    //   draw_arg : Optional array of arguments to pass to draw_method.
+    //   draw_axes : If FALSE, do not draw X/Y axis lines, labels, ticks, grid, titles.
+    //   abs_vals, sum_vals : Data array processing flags. See FindDataLimits().
+    static protected $plots = array(
+        'area' => array(
+            'draw_method' => 'DrawArea',
+            'abs_vals' => TRUE,
+        ),
+        'bars' => array(
+            'draw_method' => 'DrawBars',
+        ),
+        'candlesticks' => array(
+            'draw_method' => 'DrawOHLC',
+            'draw_arg' => array(TRUE, FALSE), // Draw candlesticks, only fill if "closed down"
+        ),
+        'candlesticks2' => array(
+            'draw_method' => 'DrawOHLC',
+            'draw_arg' => array(TRUE, TRUE), // Draw candlesticks, fill always
+        ),
+        'linepoints' => array(
+            'draw_method' => 'DrawLinePoints',
+        ),
+        'lines' => array(
+            'draw_method' => 'DrawLines',
+        ),
+        'ohlc' => array(
+            'draw_method' => 'DrawOHLC',
+            'draw_arg' => array(FALSE), // Don't draw candlesticks
+        ),
+        'pie' => array(
+            'draw_method' => 'DrawPieChart',
+            'draw_axes' => FALSE,
+            'abs_vals' => TRUE,
+        ),
+        'points' => array(
+            'draw_method' => 'DrawDots',
+        ),
+        'squared' => array(
+            'draw_method' => 'DrawSquared',
+        ),
+        'stackedarea' => array(
+            'draw_method' => 'DrawArea',
+            'draw_arg' => array(TRUE), // Tells DrawArea to draw stacked area plot
+            'sum_vals' => TRUE,
+            'abs_vals' => TRUE,
+        ),
+        'stackedbars' => array(
+            'draw_method' => 'DrawStackedBars',
+            'sum_vals' => TRUE,
+        ),
+        'thinbarline' => array(
+            'draw_method' => 'DrawThinBarLines',
+        ),
+    );
+
 //////////////////////////////////////////////////////
 //BEGIN CODE
 //////////////////////////////////////////////////////
 //////////////////////////////////////////////////////
 //BEGIN CODE
 //////////////////////////////////////////////////////
@@ -296,7 +354,7 @@ class PHPlot
         $this->img = $im;
 
         // Do not overwrite the input file with the background color.
         $this->img = $im;
 
         // Do not overwrite the input file with the background color.
-        $this->background_done = TRUE;
+        $this->done['background'] = TRUE;
 
         return TRUE;
     }
 
         return TRUE;
     }
@@ -318,6 +376,38 @@ class PHPlot
         return imagecolorresolvealpha($this->img, $r, $g, $b, $a);
     }
 
         return imagecolorresolvealpha($this->img, $r, $g, $b, $a);
     }
 
+    /*
+     * Allocate an array of GD color indexes for an array of color specifications.
+     * This is used for the data_colors array, for example.
+     *  $color_array : Array of color specifications, each an array of R,G,B,A components.
+     *     This must use 0-based sequential integer indexes.
+     *  $max_colors : Limit color allocation to no more than this.
+     * Returns an array of GD color indexes.
+     */
+    protected function GetColorIndexArray($color_array, $max_colors)
+    {
+        $n = min(count($color_array), $max_colors);
+        $result = array();
+        for ($i = 0; $i < $n; $i++)
+            $result[] = $this->GetColorIndex($color_array[$i]);
+        return $result;
+    }
+
+    /*
+     * Allocate an array of GD color indexes for darker shades of an array of color specifications.
+     *  $color_array : Array of color specifications, each an array of R,G,B,A components.
+     *  $max_colors : Limit color allocation to this many colors from the array.
+     * Returns an array of GD color indexes.
+     */
+    protected function GetDarkColorIndexArray($color_array, $max_colors)
+    {
+        $n = min(count($color_array), $max_colors);
+        $result = array();
+        for ($i = 0; $i < $n; $i++)
+            $result[] = $this->GetDarkColorIndex($color_array[$i]);
+        return $result;
+    }
+
     /*
      * Allocate a GD color index for a darker shade of a color specified by a 4 component array.
      * See notes for GetColorIndex() above.
     /*
      * Allocate a GD color index for a darker shade of a color specified by a 4 component array.
      * See notes for GetColorIndex() above.
@@ -342,7 +432,6 @@ class PHPlot
         $this->SetImageBorderColor(array(194, 194, 194));
         $this->SetPlotBgColor('white');
         $this->SetBackgroundColor('white');
         $this->SetImageBorderColor(array(194, 194, 194));
         $this->SetPlotBgColor('white');
         $this->SetBackgroundColor('white');
-        $this->SetLabelColor('black');
         $this->SetTextColor('black');
         $this->SetGridColor('black');
         $this->SetLightGridColor('gray');
         $this->SetTextColor('black');
         $this->SetGridColor('black');
         $this->SetLightGridColor('gray');
@@ -407,7 +496,7 @@ class PHPlot
     }
 
     /*
     }
 
     /*
-     * Do not use. Use SetTitleColor instead.
+     * Deprecated. Use SetTitleColor()
      */
     function SetLabelColor($which_color)
     {
      */
     function SetLabelColor($which_color)
     {
@@ -1595,10 +1684,10 @@ class PHPlot
 /////////////////////////////////////////////
 
     /*
 /////////////////////////////////////////////
 
     /*
-     * Sets position for X data labels. For most plot types, these are
-     * labels along the X axis (but different from X tick labels).
+     * Sets position for X data labels.
+     * For vertical plots, these are X axis data labels, showing label strings from the data array.
      *    Accepted positions are: plotdown, plotup, both, none.
      *    Accepted positions are: plotdown, plotup, both, none.
-     * For horizontal bar charts, these are the labels right (or left) of the bars.
+     * For horizontal plots (bar, stackedbar only), these are X data value labels, show the data values.
      *    Accepted positions are: plotin, plotstack, none.
      */
     function SetXDataLabelPos($which_xdlp)
      *    Accepted positions are: plotin, plotstack, none.
      */
     function SetXDataLabelPos($which_xdlp)
@@ -1613,9 +1702,9 @@ class PHPlot
 
     /*
      * Sets position for Y data labels.
 
     /*
      * Sets position for Y data labels.
-     * For bars and stackedbars, these are labels above the bars with the Y values.
+     * For vertical plots (where available), these are Y data value labels, showing the data values.
      *    Accepted positions are: plotin, plotstack, none.
      *    Accepted positions are: plotin, plotstack, none.
-     * For horizontal bar charts, these are the labels along the Y axis.
+     * For horizontal plots, these are Y axis data labels, showing label strings from the data array.
      *    Accepted positions are: plotleft, plotright, both, none.
      */
     function SetYDataLabelPos($which_ydlp)
      *    Accepted positions are: plotleft, plotright, both, none.
      */
     function SetYDataLabelPos($which_ydlp)
@@ -1987,15 +2076,16 @@ class PHPlot
      * Set text to display in the graph's legend.
      *   $which_leg : Array of strings for the complete legend, or a single string
      *                to be appended to the legend.
      * Set text to display in the graph's legend.
      *   $which_leg : Array of strings for the complete legend, or a single string
      *                to be appended to the legend.
+     *                Or NULL (or an empty array) to cancel the legend.
      */
     function SetLegend($which_leg)
     {
      */
     function SetLegend($which_leg)
     {
-        if (is_array($which_leg)) {             // use array
+        if (is_array($which_leg)) {           // use array (or cancel, if empty array)
             $this->legend = $which_leg;
             $this->legend = $which_leg;
-        } elseif (! is_null($which_leg)) {     // append string
+        } elseif (!is_null($which_leg)) {     // append string
             $this->legend[] = $which_leg;
         } else {
             $this->legend[] = $which_leg;
         } else {
-            return $this->PrintError("SetLegend(): argument must not be null.");
+            $this->legend = '';  // Reinitialize to empty, meaning no legend.
         }
         return TRUE;
     }
         }
         return TRUE;
     }
@@ -2003,8 +2093,9 @@ class PHPlot
     /*
      * Specifies the position of the legend's upper/leftmost corner,
      * in pixel (device) coordinates.
     /*
      * Specifies the position of the legend's upper/leftmost corner,
      * in pixel (device) coordinates.
+     * Both X and Y must be provided, or both omitted (or use NULL) to restore auto-positioning.
      */
      */
-    function SetLegendPixels($which_x, $which_y)
+    function SetLegendPixels($which_x=NULL, $which_y=NULL)
     {
         $this->legend_x_pos = $which_x;
         $this->legend_y_pos = $which_y;
     {
         $this->legend_x_pos = $which_x;
         $this->legend_y_pos = $which_y;
@@ -2176,9 +2267,8 @@ class PHPlot
      */
     function SetPlotType($which_pt)
     {
      */
     function SetPlotType($which_pt)
     {
-        $this->plot_type = $this->CheckOption($which_pt, 'bars, stackedbars, lines, linepoints,'
-                            . ' area, points, pie, thinbarline, squared, stackedarea',
-                            __FUNCTION__);
+        $avail_plot_types = implode(', ', array_keys(PHPlot::$plots)); // List of known plot types
+        $this->plot_type = $this->CheckOption($which_pt, $avail_plot_types, __FUNCTION__);
         return (boolean)$this->plot_type;
     }
 
         return (boolean)$this->plot_type;
     }
 
@@ -2186,9 +2276,9 @@ class PHPlot
      * Set the position of the X axis.
      *  $pos : Axis position in world coordinates (as an integer).
      */
      * Set the position of the X axis.
      *  $pos : Axis position in world coordinates (as an integer).
      */
-    function SetXAxisPosition($pos)
+    function SetXAxisPosition($pos='')
     {
     {
-        $this->x_axis_position = (int)$pos;
+        $this->x_axis_position = ($pos === '') ? $pos : (int)$pos;
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -2196,9 +2286,31 @@ class PHPlot
      * Set the position of the Y axis.
      *  $pos : Axis position in world coordinates (as an integer).
      */
      * Set the position of the Y axis.
      *  $pos : Axis position in world coordinates (as an integer).
      */
-    function SetYAxisPosition($pos)
+    function SetYAxisPosition($pos='')
     {
     {
-        $this->y_axis_position = (int)$pos;
+        $this->y_axis_position = ($pos === '') ? $pos : (int)$pos;
+        return TRUE;
+    }
+
+    /*
+     * Enable or disable drawing of the X axis line.
+     *  $draw : True to draw the axis (default if not called), False to suppress it.
+     * This controls drawing of the axis line only, and not the ticks, labels, or grid.
+     */
+    function SetDrawXAxis($draw)
+    {
+        $this->suppress_x_axis = !$draw; // See DrawXAxis()
+        return TRUE;
+    }
+
+    /*
+     * Enable or disable drawing of the Y axis line.
+     *  $draw : True to draw the axis (default if not called), False to suppress it.
+     * This controls drawing of the axis line only, and not the ticks, labels, or grid.
+     */
+    function SetDrawYAxis($draw)
+    {
+        $this->suppress_y_axis = !$draw; // See DrawYAxis()
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -2307,29 +2419,16 @@ class PHPlot
 
     /*
      * Set the point shape for each data set.
 
     /*
      * Set the point shape for each data set.
-     *   $which_pt : Array (or single value) of valid point shapes.
+     *   $which_pt : Array (or single value) of valid point shapes. See also DrawDot() for valid shapes.
      * The point shape and point sizes arrays are synchronized before drawing a graph
      * that uses points. See CheckPointParams()
      */
     function SetPointShapes($which_pt)
     {
      * The point shape and point sizes arrays are synchronized before drawing a graph
      * that uses points. See CheckPointParams()
      */
     function SetPointShapes($which_pt)
     {
-        if (is_array($which_pt)) {
-            // Use provided array:
-            $this->point_shapes = $which_pt;
-        } elseif (!is_null($which_pt)) {
-            // Make the single value into an array:
-            $this->point_shapes = array($which_pt);
-        }
-
-        // Validate all the shapes. This list must agree with DrawDot().
-        foreach ($this->point_shapes as $shape)
-        {
-            if (!$this->CheckOption($shape, 'halfline, line, plus, cross, rect, circle, dot,'
-                       . ' diamond, triangle, trianglemid, delta, yield, star, hourglass,'
-                       . ' bowtie, target, box, home, up, down, none', __FUNCTION__))
-                return FALSE;
-        }
-        return TRUE;
+        $this->point_shapes = $this->CheckOptionArray($which_pt, 'halfline, line, plus, cross, rect,'
+                       . ' circle, dot, diamond, triangle, trianglemid, delta, yield, star, hourglass,'
+                       . ' bowtie, target, box, home, up, down, none', __FUNCTION__);
+        return !empty($this->point_shapes);
     }
 
     /*
     }
 
     /*
@@ -2446,17 +2545,6 @@ class PHPlot
         while ($n < $size) $arr[$n++] = $arr[$base++];
     }
 
         while ($n < $size) $arr[$n++] = $arr[$base++];
     }
 
-    /*
-     * Truncate an array to a maximum size.
-     * This only works on 0-based sequential integer indexed arrays.
-     *    $arr : The array to truncate.
-     *    $size : Maximum size of the resulting array.
-     */
-    protected function truncate_array(&$arr, $size)
-    {
-        for ($n = count($arr) - 1; $n >= $size; $n--) unset($arr[$n]);
-    }
-
     /*
      * Format a floating-point number.
      *   $number : A floating point number to format
     /*
      * Format a floating-point number.
      *   $number : A floating point number to format
@@ -2473,14 +2561,13 @@ class PHPlot
                 @setlocale(LC_ALL, '');
             // Fetch locale settings:
             $locale = @localeconv();
                 @setlocale(LC_ALL, '');
             // Fetch locale settings:
             $locale = @localeconv();
-            if (!empty($locale) && isset($locale['decimal_point']) &&
-                    isset($locale['thousands_sep'])) {
-              $this->decimal_point = $locale['decimal_point'];
-              $this->thousands_sep = $locale['thousands_sep'];
+            if (isset($locale['decimal_point']) && isset($locale['thousands_sep'])) {
+                $this->decimal_point = $locale['decimal_point'];
+                $this->thousands_sep = $locale['thousands_sep'];
             } else {
             } else {
-              // Locale information not available.
-              $this->decimal_point = '.';
-              $this->thousands_sep = ',';
+                // Locale information not available.
+                $this->decimal_point = '.';
+                $this->thousands_sep = ',';
             }
         }
         return number_format($number, $decimals, $this->decimal_point, $this->thousands_sep);
             }
         }
         return number_format($number, $decimals, $this->decimal_point, $this->thousands_sep);
@@ -2567,9 +2654,9 @@ class PHPlot
      * (Data border colors default to just black, so there is no cost to always allocating.)
      *
      * Data color allocation works as follows. If there is a data_color callback, then allocate all
      * (Data border colors default to just black, so there is no cost to always allocating.)
      *
      * Data color allocation works as follows. If there is a data_color callback, then allocate all
-     * defined data colors (because the callback can use them however it wants). Otherwise, truncate
-     * the array to the number of colors that will be used. This is the larger of the number of data
-     * sets and the number of legend lines.
+     * defined data colors (because the callback can use them however it wants). Otherwise, only allocate
+     * the number of colors that will be used. This is the larger of the number of data sets and the
+     * number of legend lines.
      */
     protected function SetColorIndexes()
     {
      */
     protected function SetColorIndexes()
     {
@@ -2598,18 +2685,18 @@ class PHPlot
         $this->ndx_light_grid_color = $this->GetColorIndex($this->light_grid_color);
         $this->ndx_tick_color       = $this->GetColorIndex($this->tick_color);
 
         $this->ndx_light_grid_color = $this->GetColorIndex($this->light_grid_color);
         $this->ndx_tick_color       = $this->GetColorIndex($this->tick_color);
 
-        // If no data_color callback is being used, only allocate needed colors.
-        if (!$this->GetCallback('data_color')) {
-            $data_colors_needed = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
-            $this->truncate_array($this->data_colors, $data_colors_needed);
-            $this->truncate_array($this->data_border_colors, $data_colors_needed);
-            $this->truncate_array($this->error_bar_colors, $data_colors_needed);
+        // Maximum number of data & border colors to allocate:
+        if ($this->GetCallback('data_color')) {
+            $n_data = count($this->data_colors); // Need all of them
+            $n_border = count($this->data_border_colors);
+        } else {
+            $n_data = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
+            $n_border = $n_data; // One border color per data color
         }
 
         // Allocate main data colors. For other colors used for data, see the functions which follow.
         }
 
         // Allocate main data colors. For other colors used for data, see the functions which follow.
-        $getcolor_cb = array($this, 'GetColorIndex');
-        $this->ndx_data_colors = array_map($getcolor_cb, $this->data_colors);
-        $this->ndx_data_border_colors = array_map($getcolor_cb, $this->data_border_colors);
+        $this->ndx_data_colors = $this->GetColorIndexArray($this->data_colors, $n_data);
+        $this->ndx_data_border_colors = $this->GetColorIndexArray($this->data_border_colors, $n_border);
 
         // Set up a color as transparent, if SetTransparentColor was used.
         if (!empty($this->transparent_color)) {
 
         // Set up a color as transparent, if SetTransparentColor was used.
         if (!empty($this->transparent_color)) {
@@ -2622,8 +2709,13 @@ class PHPlot
      */
     protected function NeedDataDarkColors()
     {
      */
     protected function NeedDataDarkColors()
     {
-        $getdarkcolor_cb = array($this, 'GetDarkColorIndex');
-        $this->ndx_data_dark_colors = array_map($getdarkcolor_cb, $this->data_colors);
+        // This duplicates the calculation in SetColorIndexes() for number of data colors to allocate.
+        if ($this->GetCallback('data_color')) {
+            $n_data = count($this->data_colors);
+        } else {
+            $n_data = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
+        }
+        $this->ndx_data_dark_colors = $this->GetDarkColorIndexArray($this->data_colors, $n_data);
         $this->pad_array($this->ndx_data_dark_colors, $this->data_columns);
     }
 
         $this->pad_array($this->ndx_data_dark_colors, $this->data_columns);
     }
 
@@ -2632,11 +2724,48 @@ class PHPlot
      */
     protected function NeedErrorBarColors()
     {
      */
     protected function NeedErrorBarColors()
     {
-        $getcolor_cb = array($this, 'GetColorIndex');
-        $this->ndx_error_bar_colors = array_map($getcolor_cb, $this->error_bar_colors);
+        // This is similar to the calculation in SetColorIndexes() for number of data colors to allocate.
+        if ($this->GetCallback('data_color')) {
+            $n_err = count($this->error_bar_colors);
+        } else {
+            $n_err = max($this->data_columns, empty($this->legend) ? 0 : count($this->legend));
+        }
+        $this->ndx_error_bar_colors = $this->GetColorIndexArray($this->error_bar_colors, $n_err);
         $this->pad_array($this->ndx_error_bar_colors, $this->data_columns);
     }
 
         $this->pad_array($this->ndx_error_bar_colors, $this->data_columns);
     }
 
+    /*
+     * Determine if, and where, to draw Data Value Labels.
+     *   $label_control : Label position control. Either x_data_label_pos or y_data_label_pos.
+     *   &$x_adj, &$y_adj : Returns X,Y adjustments (offset in pixels) to the text position.
+     *   &$h_align, &$v_align : Returns horizontal and vertical alignment for the label.
+     *      The above 4 argument values should be passed to DrawDataValueLabel()
+     * Returns True if data value labels should be drawn (based on $label_control), else False.
+     * This is used for plot types other than bars/stackedbars (which have their own way of doing it).
+     * It uses two member variables (unset by default): data_value_label_angle and data_value_label_distance
+     * to define the vector to the label. Default is 90 degrees at 5 pixels.
+     */
+    protected function CheckDataValueLabels($label_control, &$x_adj, &$y_adj, &$h_align, &$v_align)
+    {
+        if ($label_control != 'plotin')
+            return FALSE; // No data value labels
+        $angle = deg2rad(isset($this->data_value_label_angle) ? $this->data_value_label_angle : 90);
+        $radius = isset($this->data_value_label_distance) ? $this->data_value_label_distance : 5;
+        $cos = cos($angle);
+        $sin = sin($angle);
+        $x_adj = (int)($radius * $cos);
+        $y_adj = -(int)($radius * $sin); // Y is reversed in device coordinates
+
+        // Choose from 8 (not 9, center/center can't happen) text alignments based on angle:
+        if ($sin >= 0.383) $v_align = 'bottom'; // 0.383 = sin(360deg / 16)
+        elseif ($sin >= -0.383) $v_align = 'center';
+        else $v_align = 'top';
+        if ($cos >= 0.383) $h_align = 'left';
+        elseif ($cos >= -0.383) $h_align = 'center';
+        else $h_align = 'right';
+        return TRUE;
+    }
+
 //////////////////////////////////////////////////////////
 ///////////         DATA ANALYSIS, SCALING AND TRANSLATION
 //////////////////////////////////////////////////////////
 //////////////////////////////////////////////////////////
 ///////////         DATA ANALYSIS, SCALING AND TRANSLATION
 //////////////////////////////////////////////////////////
@@ -2647,8 +2776,8 @@ class PHPlot
      * For most plots, IV is X and DV is Y. For swapped X/Y plots, IV is Y and DV is X.
      * At the end of the function, IV and DV ranges get assigned into X or Y.
      *
      * For most plots, IV is X and DV is Y. For swapped X/Y plots, IV is Y and DV is X.
      * At the end of the function, IV and DV ranges get assigned into X or Y.
      *
-     * This has to know how certain plot types use the data. 'area' and 'pie' use absolute
-     * values, 'stackedbars' sums values, and 'stackedarea' sums absolute values.
+     * The data type mostly determines the data array structure, but some plot types do special
+     * things such as sum the values in a row. This information is in the plots[] array.
      *
      * This calculates min_x, max_x, min_y, and max_y. It also calculates two arrays
      * data_min[] and data_max[] with per-row min and max values. These are used for
      *
      * This calculates min_x, max_x, min_y, and max_y. It also calculates two arrays
      * data_min[] and data_max[] with per-row min and max values. These are used for
@@ -2657,10 +2786,9 @@ class PHPlot
      */
     protected function FindDataLimits()
     {
      */
     protected function FindDataLimits()
     {
-        // Special case processing for certain plot types:
-        $sum_abs = ($this->plot_type == 'stackedarea'); // Sum of absolute values
-        $sum_val = ($this->plot_type == 'stackedbars'); // Sum of values
-        $abs_val = ($this->plot_type == 'area' || $this->plot_type == 'pie'); // Absolute values
+        // Does this plot type need special processing of the data values?
+        $sum_vals = !empty(PHPlot::$plots[$this->plot_type]['sum_vals']); // Add up values in each row
+        $abs_vals = !empty(PHPlot::$plots[$this->plot_type]['abs_vals']); // Take absolute values
 
         // These need to be initialized in case there are multiple plots and missing data points.
         $this->data_min = array();
 
         // These need to be initialized in case there are multiple plots and missing data points.
         $this->data_min = array();
@@ -2682,7 +2810,7 @@ class PHPlot
                 $all_iv[] = (double)$this->data[$i][$j++];
             }
 
                 $all_iv[] = (double)$this->data[$i][$j++];
             }
 
-            if ($sum_abs || $sum_val) {
+            if ($sum_vals) {
                 $all_dv = array(0, 0); // One limit is 0, other calculated below
             } else {
                 $all_dv = array();
                 $all_dv = array(0, 0); // One limit is 0, other calculated below
             } else {
                 $all_dv = array();
@@ -2694,14 +2822,15 @@ class PHPlot
                     if ($this->datatype_error_bars) {
                         $all_dv[] = $val + (double)$this->data[$i][$j++];
                         $all_dv[] = $val - (double)$this->data[$i][$j++];
                     if ($this->datatype_error_bars) {
                         $all_dv[] = $val + (double)$this->data[$i][$j++];
                         $all_dv[] = $val - (double)$this->data[$i][$j++];
-                    } elseif ($sum_abs) {
-                        $all_dv[1] += abs($val); // Sum of absolute values
-                    } elseif ($sum_val) {
-                        $all_dv[1] += $val;  // Sum of values
-                    } elseif ($abs_val) {
-                        $all_dv[] = abs($val); // List of all absolute values
                     } else {
                     } else {
-                        $all_dv[] = $val; // List of all values
+                        if ($abs_vals) {
+                            $val = abs($val); // Use absolute values
+                        }
+                        if ($sum_vals) {
+                            $all_dv[1] += $val;  // Sum of values
+                        } else {
+                            $all_dv[] = $val; // List of all values
+                        }
                     }
                 } else {    // Missing DV value
                   $j++;
                     }
                 } else {    // Missing DV value
                   $j++;
@@ -3146,10 +3275,6 @@ class PHPlot
      * Pre-requisites: FindDataLimits() calculates min_x, max_x, min_y, max_y
      * which are the limits of the data to be plotted.
      *
      * Pre-requisites: FindDataLimits() calculates min_x, max_x, min_y, max_y
      * which are the limits of the data to be plotted.
      *
-     * Note: $implied_y and $swapped_xy are currently equivalent, but in the
-     * future there may be a data type with swapped X/Y and explicit Y values.
-     * The 4 code blocks below for plot_min_x, plot_max_x, plot_min_y, and
-     * plot_max_y already contain logic for this case.
      * The general method is this:
      *   If any part of the range is user-defined (via SetPlotAreaWorld),
      *      use the user-defined value.
      * The general method is this:
      *   If any part of the range is user-defined (via SetPlotAreaWorld),
      *      use the user-defined value.
@@ -3236,7 +3361,6 @@ class PHPlot
                 'plot_min_x' => $this->plot_min_x, 'plot_min_y' => $this->plot_min_y,
                 'plot_max_x' => $this->plot_max_x, 'plot_max_y' => $this->plot_max_y));
         }
                 'plot_min_x' => $this->plot_min_x, 'plot_min_y' => $this->plot_min_y,
                 'plot_max_x' => $this->plot_max_x, 'plot_max_y' => $this->plot_max_y));
         }
-
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -3256,6 +3380,8 @@ class PHPlot
 
     /*
      * Calculate the width (or height) of bars for bar plots.
 
     /*
      * Calculate the width (or height) of bars for bar plots.
+     *   $stacked : If true, this is a stacked bar plot (1 bar per group).
+     *   $verticals : If false, this is a horizontal bar plot.
      * This calculates:
      *    record_bar_width : Allocated width for each bar (including gaps)
      *    actual_bar_width : Actual drawn width of each bar
      * This calculates:
      *    record_bar_width : Allocated width for each bar (including gaps)
      *    actual_bar_width : Actual drawn width of each bar
@@ -3264,7 +3390,7 @@ class PHPlot
      * but the same variable names are used. Think of "bar_width" as being
      * the width if you are standing on the Y axis looking towards positive X.
      */
      * but the same variable names are used. Think of "bar_width" as being
      * the width if you are standing on the Y axis looking towards positive X.
      */
-    protected function CalcBarWidths($verticals = TRUE)
+    protected function CalcBarWidths($stacked, $verticals)
     {
         // group_width is the width of a group, including padding
         if ($verticals) {
     {
         // group_width is the width of a group, including padding
         if ($verticals) {
@@ -3275,10 +3401,10 @@ class PHPlot
 
         // Actual number of bar spaces in the group. This includes the drawn bars, and
         // 'bar_extra_space'-worth of extra bars.
 
         // Actual number of bar spaces in the group. This includes the drawn bars, and
         // 'bar_extra_space'-worth of extra bars.
-        if ($this->plot_type == 'stackedbars') {
-          $num_spots = 1 + $this->bar_extra_space;
+        if ($stacked) {
+            $num_spots = 1 + $this->bar_extra_space;
         } else {
         } else {
-          $num_spots = $this->data_columns + $this->bar_extra_space;
+            $num_spots = $this->data_columns + $this->bar_extra_space;
         }
 
         // record_bar_width is the width of each bar's allocated area.
         }
 
         // record_bar_width is the width of each bar's allocated area.
@@ -3497,7 +3623,7 @@ class PHPlot
             $skip_lo = $this->skip_bottom_tick;
             $skip_hi = $this->skip_top_tick;
         } else {
             $skip_lo = $this->skip_bottom_tick;
             $skip_hi = $this->skip_top_tick;
         } else {
-          return $this->PrintError("CalcTicks: Invalid usage ($which)");
+            return $this->PrintError("CalcTicks: Invalid usage ($which)");
         }
 
         if (!empty($tick_inc)) {
         }
 
         if (!empty($tick_inc)) {
@@ -3537,13 +3663,13 @@ class PHPlot
         list($tick_start, $tick_end, $tick_step) = $this->CalcTicks($which);
 
         if ($which == 'x') {
         list($tick_start, $tick_end, $tick_step) = $this->CalcTicks($which);
 
         if ($which == 'x') {
-          $font = $this->fonts['x_label'];
-          $angle = $this->x_label_angle;
+            $font = $this->fonts['x_label'];
+            $angle = $this->x_label_angle;
         } elseif ($which == 'y') {
         } elseif ($which == 'y') {
-          $font = $this->fonts['y_label'];
-          $angle = $this->y_label_angle;
+            $font = $this->fonts['y_label'];
+            $angle = $this->y_label_angle;
         } else {
         } else {
-          return $this->PrintError("CalcMaxTickLabelSize: Invalid usage ($which)");
+            return $this->PrintError("CalcMaxTickLabelSize: Invalid usage ($which)");
         }
 
         $max_width = 0;
         }
 
         $max_width = 0;
@@ -3822,7 +3948,7 @@ class PHPlot
      * Set the number of X tick marks.
      * Use either this or SetXTickIncrement(), not both, to control the X tick marks.
      */
      * Set the number of X tick marks.
      * Use either this or SetXTickIncrement(), not both, to control the X tick marks.
      */
-    function SetNumXTicks($which_nt)
+    function SetNumXTicks($which_nt='')
     {
         $this->num_x_ticks = $which_nt;
         if (!empty($which_nt)) {
     {
         $this->num_x_ticks = $which_nt;
         if (!empty($which_nt)) {
@@ -3835,7 +3961,7 @@ class PHPlot
      * Set the number of Y tick marks.
      * Use either this or SetYTickIncrement(), not both, to control the Y tick marks.
      */
      * Set the number of Y tick marks.
      * Use either this or SetYTickIncrement(), not both, to control the Y tick marks.
      */
-    function SetNumYTicks($which_nt)
+    function SetNumYTicks($which_nt='')
     {
         $this->num_y_ticks = $which_nt;
         if (!empty($which_nt)) {
     {
         $this->num_y_ticks = $which_nt;
         if (!empty($which_nt)) {
@@ -3952,14 +4078,14 @@ class PHPlot
     protected function DrawBackground()
     {
         // Don't draw this twice if drawing two plots on one image
     protected function DrawBackground()
     {
         // Don't draw this twice if drawing two plots on one image
-        if (! $this->background_done) {
+        if (empty($this->done['background'])) {
             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);
             }
             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;
+            $this->done['background'] = TRUE;
         }
         return TRUE;
     }
         }
         return TRUE;
     }
@@ -4051,8 +4177,9 @@ class PHPlot
      */
     protected function DrawImageBorder()
     {
      */
     protected function DrawImageBorder()
     {
-        if ($this->image_border_type == 'none')
-            return TRUE; // Early test for default case.
+        // Do nothing if already drawn, or if no border has been set.
+        if ($this->image_border_type == 'none' || !empty($this->done['border']))
+            return TRUE;
         $width = $this->GetImageBorderWidth();
         $color1 = $this->ndx_i_border;
         $color2 = $this->ndx_i_border_dark;
         $width = $this->GetImageBorderWidth();
         $color1 = $this->ndx_i_border;
         $color2 = $this->ndx_i_border_dark;
@@ -4081,6 +4208,7 @@ class PHPlot
             return $this->PrintError(
                           "DrawImageBorder(): unknown image_border_type: '$this->image_border_type'");
         }
             return $this->PrintError(
                           "DrawImageBorder(): unknown image_border_type: '$this->image_border_type'");
         }
+        $this->done['border'] = TRUE; // Border should only be drawn once per image.
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -4091,7 +4219,7 @@ class PHPlot
      */
     protected function DrawTitle()
     {
      */
     protected function DrawTitle()
     {
-        if (isset($this->title_done) || $this->title_txt === '')
+        if (!empty($this->done['title']) || $this->title_txt === '')
             return TRUE;
 
         // Center of the image:
             return TRUE;
 
         // Center of the image:
@@ -4103,7 +4231,7 @@ class PHPlot
         $this->DrawText($this->fonts['title'], 0, $xpos, $ypos,
                         $this->ndx_title_color, $this->title_txt, 'center', 'top');
 
         $this->DrawText($this->fonts['title'], 0, $xpos, $ypos,
                         $this->ndx_title_color, $this->title_txt, 'center', 'top');
 
-        $this->title_done = TRUE;
+        $this->done['title'] = TRUE;
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -4166,10 +4294,11 @@ class PHPlot
         // Draw ticks, labels and grid
         $this->DrawXTicks();
 
         // Draw ticks, labels and grid
         $this->DrawXTicks();
 
-        //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);
-
+        //Draw X Axis at Y = x_axis_y_pixels, unless suppressed (See SetXAxisPosition)
+        if (empty($this->suppress_x_axis)) {
+            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;
     }
 
         return TRUE;
     }
 
@@ -4179,13 +4308,14 @@ class PHPlot
      */
     protected function DrawYAxis()
     {
      */
     protected function DrawYAxis()
     {
-        // Draw ticks, labels and grid, if any
+        // Draw ticks, labels and grid
         $this->DrawYTicks();
 
         $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);
-
+        // Draw Y axis at X = y_axis_x_pixels, unless suppressed (See SetYAxisPosition)
+        if (empty($this->suppress_y_axis)) {
+            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;
     }
 
         return TRUE;
     }
 
@@ -4399,8 +4529,8 @@ class PHPlot
 
     /*
      * Draw the data value label associated with a point in the plot.
 
     /*
      * Draw the data value label associated with a point in the plot.
-     * This is used for bar and stacked bar charts. These are the labels above,
-     * to the right, or within the bars, not the axis labels.
+     * These are labels that show the value (dependent variable, usually Y) of the data point,
+     * and are drawn within the plot area (not to be confused with axis data labels).
      *
      *    $x_or_y : Specify 'x' or 'y' labels. This selects font, angle, and formatting.
      *    $x_world, $y_world : World coordinates of the text (see also x/y_adjustment).
      *
      *    $x_or_y : Specify 'x' or 'y' labels. This selects font, angle, and formatting.
      *    $x_world, $y_world : World coordinates of the text (see also x/y_adjustment).
@@ -4409,7 +4539,6 @@ class PHPlot
      *    $x_adjustment, $y_adjustment : Text position offsets, in device coordinates.
      *    $min_width, $min_height : If supplied, suppress the text if it will not fit.
      * Returns True, if the text was drawn, or False, if it will not fit.
      *    $x_adjustment, $y_adjustment : Text position offsets, in device coordinates.
      *    $min_width, $min_height : If supplied, suppress the text if it will not fit.
      * Returns True, if the text was drawn, or False, if it will not fit.
-     *
      */
     protected function DrawDataValueLabel($x_or_y, $x_world, $y_world, $text, $halign, $valign,
                       $x_adjustment=0, $y_adjustment=0, $min_width=NULL, $min_height=NULL)
      */
     protected function DrawDataValueLabel($x_or_y, $x_world, $y_world, $text, $halign, $valign,
                       $x_adjustment=0, $y_adjustment=0, $min_width=NULL, $min_height=NULL)
@@ -4440,7 +4569,7 @@ class PHPlot
     }
 
     /*
     }
 
     /*
-     * Draws the data label associated with a point in the plot.
+     * Draws the axis 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.
      * 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.
@@ -4471,7 +4600,7 @@ class PHPlot
 
     /*
      * Draw a data label along the Y axis or side.
 
     /*
      * Draw a data label along the Y axis or side.
-     * This is only used by horizontal bar charts.
+     * This is used by horizontal plots.
      */
     protected function DrawYDataLabel($ylab, $ypos)
     {
      */
     protected function DrawYDataLabel($ylab, $ypos)
     {
@@ -4526,7 +4655,7 @@ class PHPlot
 
     /*
      * Draws the graph legend
 
     /*
      * Draws the graph legend
-     *
+     * This is called by DrawGraph only if $this->legend is not empty.
      * Base code submitted by Marlin Viss
      */
     protected function DrawLegend()
      * Base code submitted by Marlin Viss
      */
     protected function DrawLegend()
@@ -4552,14 +4681,17 @@ class PHPlot
         // Sizing parameters:
         $v_margin = $char_h/2;                   // Between vertical borders and labels
         $dot_height = $char_h + $line_spacing;   // Height of the small colored boxes
         // Sizing parameters:
         $v_margin = $char_h/2;                   // Between vertical borders and labels
         $dot_height = $char_h + $line_spacing;   // Height of the small colored boxes
+        // Color boxes are $char_w wide, but can be adjusted using legend_colorbox_width:
+        $colorbox_width = $char_w;
+        if (isset($this->legend_colorbox_width))
+            $colorbox_width *= $this->legend_colorbox_width;
+
         // Overall legend box width e.g.: | space colorbox space text space |
         // Overall legend box width e.g.: | space colorbox space text space |
-        // where colorbox and each space are 1 char width.
-        if ($colorbox_align != 'none') {
-            $width = $max_width + 4 * $char_w;
-            $draw_colorbox = TRUE;
+        // where each space adds $char_w, and colorbox adds $char_w * its width adjustment.
+        if (($draw_colorbox = ($colorbox_align != 'none'))) {
+            $width = $max_width + 3 * $char_w + $colorbox_width;
         } else {
             $width = $max_width + 2 * $char_w;
         } else {
             $width = $max_width + 2 * $char_w;
-            $draw_colorbox = FALSE;
         }
 
         //////// Calculate box position
         }
 
         //////// Calculate box position
@@ -4572,7 +4704,6 @@ class PHPlot
             // User-defined position in world-coordinates (See SetLegendWorld).
             $box_start_x = $this->xtr($this->legend_x_pos);
             $box_start_y = $this->ytr($this->legend_y_pos);
             // User-defined position in world-coordinates (See SetLegendWorld).
             $box_start_x = $this->xtr($this->legend_x_pos);
             $box_start_y = $this->ytr($this->legend_y_pos);
-            unset($this->legend_xy_world);
         } else {
             // User-defined position in pixel coordinates.
             $box_start_x = $this->legend_x_pos;
         } else {
             // User-defined position in pixel coordinates.
             $box_start_x = $this->legend_x_pos;
@@ -4600,14 +4731,14 @@ class PHPlot
                 $x_pos = $box_end_x - $char_w;
         } elseif ($colorbox_align == 'left') {
             $dot_left_x = $box_start_x + $char_w;
                 $x_pos = $box_end_x - $char_w;
         } elseif ($colorbox_align == 'left') {
             $dot_left_x = $box_start_x + $char_w;
-            $dot_right_x = $dot_left_x + $char_w;
+            $dot_right_x = $dot_left_x + $colorbox_width;
             if ($text_align == 'left')
             if ($text_align == 'left')
-                $x_pos = $dot_left_x + 2 * $char_w;
+                $x_pos = $dot_right_x + $char_w;
             else
                 $x_pos = $box_end_x - $char_w;
             else
                 $x_pos = $box_end_x - $char_w;
-        } else {
-            $dot_left_x = $box_end_x - 2 * $char_w;
-            $dot_right_x = $dot_left_x + $char_w;
+        } else {      // $colorbox_align == 'right'
+            $dot_right_x = $box_end_x - $char_w;
+            $dot_left_x = $dot_right_x - $colorbox_width;
             if ($text_align == 'left')
                 $x_pos = $box_start_x + $char_w;
             else
             if ($text_align == 'left')
                 $x_pos = $box_start_x + $char_w;
             else
@@ -4641,150 +4772,9 @@ class PHPlot
     }
 
 /////////////////////////////////////////////
     }
 
 /////////////////////////////////////////////
-////////////////////             PLOT DRAWING
+////////////////////     PLOT DRAWING HELPERS
 /////////////////////////////////////////////
 
 /////////////////////////////////////////////
 
-    /*
-     * Draws a pie chart. Data is 'text-data', 'data-data', or 'text-data-single'.
-     *
-     *  For text-data-single, the data array contains records with an ignored label,
-     *  and one Y value. Each record defines a sector of the pie, as a portion of
-     *  the sum of all Y values.
-     *
-     *  For text-data and data-data, the data array contains records with an ignored label,
-     *  an ignored X value (for data-data only), and N (N>=1) Y values per record.
-     *  The pie chart will be produced with N segments. The relative size of the first
-     *  sector of the pie is the sum of the first Y data value in each record, etc.
-     *
-     *  Note: With text-data-single, the data labels could be used, but are not currently.
-     *
-     *  If there are no valid data points > 0 at all, just draw nothing. It may seem more correct to
-     *  raise an error, but all of the other plot types handle it this way implicitly. DrawGraph
-     *  checks for an empty data array, but this is different: a non-empty data array with no Y values,
-     *  or all Y=0.
-     */
-    protected function DrawPieChart()
-    {
-        if (!$this->CheckDataType('text-data, text-data-single, data-data'))
-            return FALSE;
-
-        // Allocate dark colors only if they will be used for shading.
-        if ($this->shading > 0)
-            $this->NeedDataDarkColors();
-
-        $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;
-
-        $num_slices = $this->data_columns;  // See CheckDataArray which calculates this for us.
-        if ($num_slices < 1) return TRUE;   // Give up early if there is no data at all.
-        $sumarr = array_fill(0, $num_slices, 0);
-
-        if ($this->datatype_pie_single) {
-            // text-data-single: One data column per row, one pie slice per row.
-            for ($i = 0; $i < $num_slices; $i++) {
-                // $legend[$i] = $this->data[$i][0];                // Note: Labels are not used yet
-                if (is_numeric($this->data[$i][1]))
-                    $sumarr[$i] = abs($this->data[$i][1]);
-            }
-        } else {
-            // text-data: Sum each column (skipping label), one pie slice per column.
-            // data-data: Sum each column (skipping X value and label), one pie slice per column.
-            $skip = ($this->datatype_implied) ? 1 : 2; // Leading values to skip in each row.
-            for ($i = 0; $i < $this->num_data_rows; $i++) {
-                for ($j = $skip; $j < $this->num_recs[$i]; $j++) {
-                    if (is_numeric($this->data[$i][$j]))
-                        $sumarr[$j-$skip] += abs($this->data[$i][$j]);
-                }
-            }
-        }
-
-        $total = array_sum($sumarr);
-
-        if ($total == 0) {
-            // There are either no valid data points, or all are 0.
-            // See top comment about why not to make this an error.
-            return TRUE;
-        }
-
-        if ($this->shading) {
-            $diam2 = $diameter / 2;
-        } else {
-            $diam2 = $diameter;
-        }
-        $max_data_colors = count($this->ndx_data_colors);
-
-        // Use the Y label format precision, with default value:
-        if (isset($this->label_format['y']['precision']))
-            $precision = $this->label_format['y']['precision'];
-        else
-            $precision = 1;
-
-        for ($h = $this->shading; $h >= 0; $h--) {
-            $color_index = 0;
-            $start_angle = 0;
-            $end_angle = 0;
-            for ($j = 0; $j < $num_slices; $j++) {
-                $val = $sumarr[$j];
-
-                // 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 = $this->number_format(($val / $total * 100), $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()
-                // Also note ImageFilledArc only takes angles in integer degrees, and if the
-                // the start and end angles match then you get a full circle not a zero-width
-                // pie. This is bad. So skip any zero-size wedge. On the other hand, we cannot
-                // let cumulative error from rounding to integer result in missing wedges. So
-                // keep the running total as a float, and round the angles. It should not
-                // be necessary to check that the last wedge ends at 360 degrees.
-                $start_angle = $end_angle;
-                $end_angle += $val;
-                // This method of conversion to integer - truncate after reversing it - was
-                // chosen to match the implicit method of PHPlot<=5.0.4 to get the same slices.
-                $arc_start_angle = (int)(360 - $start_angle);
-                $arc_end_angle = (int)(360 - $end_angle);
-
-                if ($arc_start_angle > $arc_end_angle) {
-                    $mid_angle = deg2rad($end_angle - ($val / 2));
-
-                    // Draw the slice
-                    ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
-                                   $arc_end_angle, $arc_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,
-                                           $arc_end_angle, $arc_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->fonts['generic'], 0, $label_x, $label_y, $this->ndx_grid_color,
-                                        $label_txt, 'center', 'center');
-                    }
-                }
-                if (++$color_index >= $max_data_colors)
-                    $color_index = 0;
-            }   // end for
-        }   // end for
-        return TRUE;
-    }
-
     /*
      * Get data color to use for plotting.
      *   $row, $idx : Index arguments for the current data point.
     /*
      * Get data color to use for plotting.
      *   $row, $idx : Index arguments for the current data point.
@@ -4800,7 +4790,7 @@ class PHPlot
             $num_data_colors = count($this->ndx_data_colors);
             $vars = compact('custom_color', 'num_data_colors');
         } else {
             $num_data_colors = count($this->ndx_data_colors);
             $vars = compact('custom_color', 'num_data_colors');
         } else {
-          extract($vars);
+            extract($vars);
         }
 
         // Select the colors.
         }
 
         // Select the colors.
@@ -4830,7 +4820,7 @@ class PHPlot
             $num_error_colors = count($this->ndx_error_bar_colors);
             $vars = compact('custom_color', 'num_data_colors', 'num_error_colors');
         } else {
             $num_error_colors = count($this->ndx_error_bar_colors);
             $vars = compact('custom_color', 'num_data_colors', 'num_error_colors');
         } else {
-          extract($vars);
+            extract($vars);
         }
 
         // Select the colors.
         }
 
         // Select the colors.
@@ -4845,24 +4835,377 @@ class PHPlot
     }
 
     /*
     }
 
     /*
-     * Draw the points and errors bars for an error plot of types points and linepoints
-     * Supports only data-data-error format, with each row of the form
-     *   array("title", x, y1, error1+, error1-, y2, error2+, error2-, ...)
-     * This is called from DrawDots, with data type already checked.
-     *   $paired is true for linepoints error plots, to make sure elements are
-     *       only drawn once.  If true, data labels are drawn by DrawLinesError, and error
-     *       bars are drawn by DrawDotsError. (This choice is for backwards compatibility.)
+     * Get colors to use for a bar chart. There is a data color, and either a border color
+     * or a shading color (data dark color).
+     *   $row, $idx : Index arguments for the current bar.
+     *   &$vars : Variable storage. Caller makes an empty array, and this function uses it.
+     *   &$data_color : Returned result - Color index for the data (bar fill).
+     *   &$alt_color : Returned result - Color index for the shading or outline.
      */
      */
-    protected function DrawDotsError($paired = FALSE)
+    protected function GetBarColors($row, $idx, &$vars, &$data_color, &$alt_color)
     {
     {
-        // Adjust the point shapes and point sizes arrays:
-        $this->CheckPointParams();
+        // Initialize or extract variables:
+        if (empty($vars)) {
+            if ($this->shading > 0)    // This plot needs dark colors if shading is on.
+                $this->NeedDataDarkColors();
+            $custom_color = (bool)$this->GetCallback('data_color');
+            $num_data_colors = count($this->ndx_data_colors);
+            $num_border_colors = count($this->ndx_data_border_colors);
+            $vars = compact('custom_color', 'num_data_colors', 'num_border_colors');
+        } else {
+            extract($vars);
+        }
 
 
-        $gcvars = array(); // For GetDataErrorColors, which initializes and uses this.
-        // Special flag for data color callback to indicate the 'points' part of 'linepoints':
-        $alt_flag = $paired ? 1 : 0;
+        // Select the colors.
+        if ($custom_color) {
+            $col_i = $this->DoCallback('data_color', $row, $idx); // Custom color index
+            $i_data = $col_i % $num_data_colors; // Index for data colors and dark colors
+            $i_border = $col_i % $num_border_colors; // Index for data borders (if used)
+        } else {
+            $i_data = $i_border = $idx;
+        }
+        $data_color = $this->ndx_data_colors[$i_data];
+        if ($this->shading > 0) {
+            $alt_color = $this->ndx_data_dark_colors[$i_data];
+        } else {
+            $alt_color = $this->ndx_data_border_colors[$i_border];
+        }
+    }
 
 
-        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
+    /*
+     * Draws a styled dot. Uses world coordinates.
+     * The list of supported shapes can also be found in SetPointShapes().
+     * All shapes are drawn using a 3x3 grid, centered on the data point.
+     * The center is (x_mid, y_mid) and the corners are (x1, y1) and (x2, y2).
+     *   $record is the 0-based index that selects the shape and size.
+     */
+    protected function DrawDot($x_world, $y_world, $record, $color)
+    {
+        $index = $record % $this->point_counts;
+        $point_size = $this->point_sizes[$index];
+
+        $half_point = (int)($point_size / 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[$index]) {
+        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 'circle':
+            ImageArc($this->img, $x_mid, $y_mid, $point_size, $point_size, 0, 360, $color);
+            break;
+        case 'dot':
+            ImageFilledEllipse($this->img, $x_mid, $y_mid, $point_size, $point_size, $color);
+            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;
+        case 'yield':
+            $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y2);
+            ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+            break;
+        case 'delta':
+            $arrpoints = array( $x1, $y2, $x2, $y2, $x_mid, $y1);
+            ImageFilledPolygon($this->img, $arrpoints, 3, $color);
+            break;
+        case 'star':
+            ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
+            ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
+            ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
+            ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
+            break;
+        case 'hourglass':
+            $arrpoints = array( $x1, $y1, $x2, $y1, $x1, $y2, $x2, $y2);
+            ImageFilledPolygon($this->img, $arrpoints, 4, $color);
+            break;
+        case 'bowtie':
+            $arrpoints = array( $x1, $y1, $x1, $y2, $x2, $y1, $x2, $y2);
+            ImageFilledPolygon($this->img, $arrpoints, 4, $color);
+            break;
+        case 'target':
+            ImageFilledRectangle($this->img, $x1, $y1, $x_mid, $y_mid, $color);
+            ImageFilledRectangle($this->img, $x_mid, $y_mid, $x2, $y2, $color);
+            ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+            break;
+        case 'box':
+            ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+            break;
+        case 'home': /* As in: "home plate" (baseball), also looks sort of like a house. */
+            $arrpoints = array( $x1, $y2, $x2, $y2, $x2, $y_mid, $x_mid, $y1, $x1, $y_mid);
+            ImageFilledPolygon($this->img, $arrpoints, 5, $color);
+            break;
+        case 'up':
+            ImagePolygon($this->img, array($x_mid, $y1, $x2, $y2, $x1, $y2), 3, $color);
+            break;
+        case 'down':
+            ImagePolygon($this->img, array($x_mid, $y2, $x1, $y1, $x2, $y1), 3, $color);
+            break;
+        case 'none': /* Special case, no point shape here */
+            break;
+        default: /* Also 'rect' */
+            ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
+            break;
+        }
+        return TRUE;
+    }
+
+    /*
+     * Draw a bar (or segment of a bar), with optional shading or border.
+     * This is used by the bar and stackedbar plots, vertical and horizontal.
+     *   $x1, $y1 : One corner of the bar.
+     *   $x2, $y2 : Other corner of the bar.
+     *   $data_color : Color index to use for the bar fill.
+     *   $alt_color : Color index to use for the shading (if shading is on), else for the border.
+     *      Note the same color is NOT used for shading and border - just the same argument.
+     *      See GetBarColors() for where these arguments come from.
+     *   $shade_top : Shade the top? (Suppressed for downward stack segments except first.)
+     *   $shade_side : Shade the right side? (Suppressed for leftward stack segments except first.)
+     *      Only one of $shade_top or $shade_side can be FALSE. Both default to TRUE.
+     */
+    protected function DrawBar($x1, $y1, $x2, $y2, $data_color, $alt_color,
+            $shade_top = TRUE, $shade_side = TRUE)
+    {
+        // Sort the points so x1,y1 is upper left and x2,y2 is lower right. This
+        // is needed in order to get the shading right, and imagerectangle may require it.
+        if ($x1 > $x2) {
+            $t = $x1; $x1 = $x2; $x2 = $t;
+        }
+        if ($y1 > $y2) {
+            $t = $y1; $y1 = $y2; $y2 = $t;
+        }
+
+        // Draw the bar
+        ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $data_color);
+
+        // Draw a shade, or a border.
+        if (($shade = $this->shading) > 0) {
+            if ($shade_top && $shade_side) {
+                $npts = 6;
+                $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade,
+                             $x2 + $shade, $y2 - $shade, $x2, $y2, $x2, $y1);
+            } else {
+                $npts = 4;
+                if ($shade_top) { // Suppress side shading
+                    $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade, $x2, $y1);
+                } else { // Suppress top shading
+                    $pts = array($x2, $y2, $x2, $y1, $x2 + $shade, $y1 - $shade, $x2 + $shade, $y2 - $shade);
+                }
+            }
+            ImageFilledPolygon($this->img, $pts, $npts, $alt_color);
+        } else {
+            ImageRectangle($this->img, $x1, $y1, $x2,$y2, $alt_color);
+        }
+    }
+
+    /*
+     *  Draw an Error Bar set. Used by DrawDotsError and DrawLinesError
+     */
+    protected function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color)
+    {
+        $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);
+        if ($error_bar_type == 'tee') {
+            ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
+        }
+        ImageSetThickness($this->img, 1);
+        return TRUE;
+    }
+
+/////////////////////////////////////////////
+////////////////////             PLOT DRAWING
+/////////////////////////////////////////////
+
+    /*
+     * Draws a pie chart. Data is 'text-data', 'data-data', or 'text-data-single'.
+     *
+     *  For text-data-single, the data array contains records with an ignored label,
+     *  and one Y value. Each record defines a sector of the pie, as a portion of
+     *  the sum of all Y values.
+     *
+     *  For text-data and data-data, the data array contains records with an ignored label,
+     *  an ignored X value (for data-data only), and N (N>=1) Y values per record.
+     *  The pie chart will be produced with N segments. The relative size of the first
+     *  sector of the pie is the sum of the first Y data value in each record, etc.
+     *
+     *  Note: With text-data-single, the data labels could be used, but are not currently.
+     *
+     *  If there are no valid data points > 0 at all, just draw nothing. It may seem more correct to
+     *  raise an error, but all of the other plot types handle it this way implicitly. DrawGraph
+     *  checks for an empty data array, but this is different: a non-empty data array with no Y values,
+     *  or all Y=0.
+     */
+    protected function DrawPieChart()
+    {
+        if (!$this->CheckDataType('text-data, text-data-single, data-data'))
+            return FALSE;
+
+        // Allocate dark colors only if they will be used for shading.
+        if ($this->shading > 0)
+            $this->NeedDataDarkColors();
+
+        $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;
+
+        $num_slices = $this->data_columns;  // See CheckDataArray which calculates this for us.
+        if ($num_slices < 1) return TRUE;   // Give up early if there is no data at all.
+        $sumarr = array_fill(0, $num_slices, 0);
+
+        if ($this->datatype_pie_single) {
+            // text-data-single: One data column per row, one pie slice per row.
+            for ($i = 0; $i < $num_slices; $i++) {
+                // $legend[$i] = $this->data[$i][0];                // Note: Labels are not used yet
+                if (is_numeric($this->data[$i][1]))
+                    $sumarr[$i] = abs($this->data[$i][1]);
+            }
+        } else {
+            // text-data: Sum each column (skipping label), one pie slice per column.
+            // data-data: Sum each column (skipping X value and label), one pie slice per column.
+            $skip = ($this->datatype_implied) ? 1 : 2; // Leading values to skip in each row.
+            for ($i = 0; $i < $this->num_data_rows; $i++) {
+                for ($j = $skip; $j < $this->num_recs[$i]; $j++) {
+                    if (is_numeric($this->data[$i][$j]))
+                        $sumarr[$j-$skip] += abs($this->data[$i][$j]);
+                }
+            }
+        }
+
+        $total = array_sum($sumarr);
+
+        if ($total == 0) {
+            // There are either no valid data points, or all are 0.
+            // See top comment about why not to make this an error.
+            return TRUE;
+        }
+
+        if ($this->shading) {
+            $diam2 = $diameter / 2;
+        } else {
+            $diam2 = $diameter;
+        }
+        $max_data_colors = count($this->ndx_data_colors);
+
+        // Use the Y label format precision, with default value:
+        if (isset($this->label_format['y']['precision']))
+            $precision = $this->label_format['y']['precision'];
+        else
+            $precision = 1;
+
+        for ($h = $this->shading; $h >= 0; $h--) {
+            $color_index = 0;
+            $start_angle = 0;
+            $end_angle = 0;
+            for ($j = 0; $j < $num_slices; $j++) {
+                $val = $sumarr[$j];
+
+                // 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 = $this->number_format(($val / $total * 100), $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()
+                // Also note ImageFilledArc only takes angles in integer degrees, and if the
+                // the start and end angles match then you get a full circle not a zero-width
+                // pie. This is bad. So skip any zero-size wedge. On the other hand, we cannot
+                // let cumulative error from rounding to integer result in missing wedges. So
+                // keep the running total as a float, and round the angles. It should not
+                // be necessary to check that the last wedge ends at 360 degrees.
+                $start_angle = $end_angle;
+                $end_angle += $val;
+                // This method of conversion to integer - truncate after reversing it - was
+                // chosen to match the implicit method of PHPlot<=5.0.4 to get the same slices.
+                $arc_start_angle = (int)(360 - $start_angle);
+                $arc_end_angle = (int)(360 - $end_angle);
+
+                if ($arc_start_angle > $arc_end_angle) {
+                    $mid_angle = deg2rad($end_angle - ($val / 2));
+
+                    // Draw the slice
+                    ImageFilledArc($this->img, $xpos, $ypos+$h, $diameter, $diam2,
+                                   $arc_end_angle, $arc_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,
+                                           $arc_end_angle, $arc_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->fonts['generic'], 0, $label_x, $label_y, $this->ndx_grid_color,
+                                        $label_txt, 'center', 'center');
+                    }
+                }
+                if (++$color_index >= $max_data_colors)
+                    $color_index = 0;
+            }   // end for
+        }   // end for
+        return TRUE;
+    }
+
+    /*
+     * Draw the points and errors bars for an error plot of types points and linepoints
+     * Supports only data-data-error format, with each row of the form
+     *   array("title", x, y1, error1+, error1-, y2, error2+, error2-, ...)
+     * This is called from DrawDots, with data type already checked.
+     *   $paired is true for linepoints error plots, to make sure elements are
+     *       only drawn once.  If true, data labels are drawn by DrawLinesError, and error
+     *       bars are drawn by DrawDotsError. (This choice is for backwards compatibility.)
+     */
+    protected function DrawDotsError($paired = FALSE)
+    {
+        // Adjust the point shapes and point sizes arrays:
+        $this->CheckPointParams();
+
+        $gcvars = array(); // For GetDataErrorColors, which initializes and uses this.
+        // Special flag for data color callback to indicate the 'points' part of 'linepoints':
+        $alt_flag = $paired ? 1 : 0;
+
+        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $record = 1;                                // Skip record #0 (title)
 
             $x_now = $this->data[$row][$record++];  // Read it, advance record index
             $record = 1;                                // Skip record #0 (title)
 
             $x_now = $this->data[$row][$record++];  // Read it, advance record index
@@ -4918,6 +5261,10 @@ class PHPlot
         // Special flag for data color callback to indicate the 'points' part of 'linepoints':
         $alt_flag = $paired ? 1 : 0;
 
         // Special flag for data color callback to indicate the 'points' part of 'linepoints':
         $alt_flag = $paired ? 1 : 0;
 
+        // Data Value Labels? (Skip if doing the points from a linepoints plot)
+        $do_dvls = !$paired && $this->CheckDataValueLabels($this->y_data_label_pos,
+                      $dvl_x_off, $dvl_y_off, $dvl_h_align, $dvl_v_align);
+
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $rec = 1;                    // Skip record #0 (data label)
 
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $rec = 1;                    // Skip record #0 (data label)
 
@@ -4929,17 +5276,24 @@ class PHPlot
             $x_now_pixels = $this->xtr($x_now);
 
             // Draw X Data labels?
             $x_now_pixels = $this->xtr($x_now);
 
             // Draw X Data labels?
-            if ($this->x_data_label_pos != 'none' && !$paired)
+            if (!$paired && $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->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
+                    $y_now = (double)$this->data[$row][$rec];
 
                     // Select the color:
                     $this->GetDataColor($row, $idx, $gcvars, $data_color, $alt_flag);
                     // Draw the marker:
 
                     // Select the color:
                     $this->GetDataColor($row, $idx, $gcvars, $data_color, $alt_flag);
                     // Draw the marker:
-                    $this->DrawDot($x_now, $this->data[$row][$rec], $idx, $data_color);
+                    $this->DrawDot($x_now, $y_now, $idx, $data_color);
+
+                    // Draw data value labels?
+                    if ($do_dvls) {
+                        $this->DrawDataValueLabel('y', $x_now, $y_now, $y_now, $dvl_h_align, $dvl_v_align,
+                                                  $dvl_x_off, $dvl_y_off);
+                    }
                 }
             }
         }
                 }
             }
         }
@@ -4985,150 +5339,28 @@ class PHPlot
             }
 
             // Proceed with dependent values
             }
 
             // Proceed with dependent values
-            for ($idx = 0; $rec < $this->num_recs[$row]; $rec++, $idx++) {
-                if (is_numeric($this->data[$row][$rec])) {              // Allow for missing data
-                    $dv = $this->data[$row][$rec];
-                    ImageSetThickness($this->img, $this->line_widths[$idx]);
-
-                    // Select the color:
-                    $this->GetDataColor($row, $idx, $gcvars, $data_color);
-
-                    if ($this->datatype_swapped_xy) {
-                        // Draw a line from user defined y axis position right (or left) to xtr($dv)
-                        ImageLine($this->img, $this->y_axis_x_pixels, $y_now_pixels,
-                                              $this->xtr($dv), $y_now_pixels, $data_color);
-                    } else {
-                        // Draw a line from user defined x axis position up (or down) to ytr($dv)
-                        ImageLine($this->img, $x_now_pixels, $this->x_axis_y_pixels,
-                                              $x_now_pixels, $this->ytr($dv), $data_color);
-                   }
-                }
-            }
-        }
-
-        ImageSetThickness($this->img, 1);
-        return TRUE;
-    }
-
-    /*
-     *  Draw an Error Bar set. Used by DrawDotsError and DrawLinesError
-     */
-    protected function DrawYErrorBar($x_world, $y_world, $error_height, $error_bar_type, $color)
-    {
-        $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);
-        if ($error_bar_type == 'tee') {
-            ImageLine($this->img, $x1-$this->error_bar_size, $y2, $x1+$this->error_bar_size, $y2, $color);
-        }
-        ImageSetThickness($this->img, 1);
-        return TRUE;
-    }
-
-    /*
-     * Draws a styled dot. Uses world coordinates.
-     * The list of supported shapes can also be found in SetPointShapes().
-     * All shapes are drawn using a 3x3 grid, centered on the data point.
-     * The center is (x_mid, y_mid) and the corners are (x1, y1) and (x2, y2).
-     *   $record is the 0-based index that selects the shape and size.
-     */
-    protected function DrawDot($x_world, $y_world, $record, $color)
-    {
-        $index = $record % $this->point_counts;
-        $point_size = $this->point_sizes[$index];
-
-        $half_point = (int)($point_size / 2);
-
-        $x_mid = $this->xtr($x_world);
-        $y_mid = $this->ytr($y_world);
+            for ($idx = 0; $rec < $this->num_recs[$row]; $rec++, $idx++) {
+                if (is_numeric($this->data[$row][$rec])) {              // Allow for missing data
+                    $dv = $this->data[$row][$rec];
+                    ImageSetThickness($this->img, $this->line_widths[$idx]);
 
 
-        $x1 = $x_mid - $half_point;
-        $x2 = $x_mid + $half_point;
-        $y1 = $y_mid - $half_point;
-        $y2 = $y_mid + $half_point;
+                    // Select the color:
+                    $this->GetDataColor($row, $idx, $gcvars, $data_color);
 
 
-        switch ($this->point_shapes[$index]) {
-        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 'circle':
-            ImageArc($this->img, $x_mid, $y_mid, $point_size, $point_size, 0, 360, $color);
-            break;
-        case 'dot':
-            ImageFilledEllipse($this->img, $x_mid, $y_mid, $point_size, $point_size, $color);
-            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;
-        case 'yield':
-            $arrpoints = array( $x1, $y1, $x2, $y1, $x_mid, $y2);
-            ImageFilledPolygon($this->img, $arrpoints, 3, $color);
-            break;
-        case 'delta':
-            $arrpoints = array( $x1, $y2, $x2, $y2, $x_mid, $y1);
-            ImageFilledPolygon($this->img, $arrpoints, 3, $color);
-            break;
-        case 'star':
-            ImageLine($this->img, $x1, $y_mid, $x2, $y_mid, $color);
-            ImageLine($this->img, $x_mid, $y1, $x_mid, $y2, $color);
-            ImageLine($this->img, $x1, $y1, $x2, $y2, $color);
-            ImageLine($this->img, $x1, $y2, $x2, $y1, $color);
-            break;
-        case 'hourglass':
-            $arrpoints = array( $x1, $y1, $x2, $y1, $x1, $y2, $x2, $y2);
-            ImageFilledPolygon($this->img, $arrpoints, 4, $color);
-            break;
-        case 'bowtie':
-            $arrpoints = array( $x1, $y1, $x1, $y2, $x2, $y1, $x2, $y2);
-            ImageFilledPolygon($this->img, $arrpoints, 4, $color);
-            break;
-        case 'target':
-            ImageFilledRectangle($this->img, $x1, $y1, $x_mid, $y_mid, $color);
-            ImageFilledRectangle($this->img, $x_mid, $y_mid, $x2, $y2, $color);
-            ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
-            break;
-        case 'box':
-            ImageRectangle($this->img, $x1, $y1, $x2, $y2, $color);
-            break;
-        case 'home': /* As in: "home plate" (baseball), also looks sort of like a house. */
-            $arrpoints = array( $x1, $y2, $x2, $y2, $x2, $y_mid, $x_mid, $y1, $x1, $y_mid);
-            ImageFilledPolygon($this->img, $arrpoints, 5, $color);
-            break;
-        case 'up':
-            ImagePolygon($this->img, array($x_mid, $y1, $x2, $y2, $x1, $y2), 3, $color);
-            break;
-        case 'down':
-            ImagePolygon($this->img, array($x_mid, $y2, $x1, $y1, $x2, $y1), 3, $color);
-            break;
-        case 'none': /* Special case, no point shape here */
-            break;
-        default: /* Also 'rect' */
-            ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $color);
-            break;
+                    if ($this->datatype_swapped_xy) {
+                        // Draw a line from user defined y axis position right (or left) to xtr($dv)
+                        ImageLine($this->img, $this->y_axis_x_pixels, $y_now_pixels,
+                                              $this->xtr($dv), $y_now_pixels, $data_color);
+                    } else {
+                        // Draw a line from user defined x axis position up (or down) to ytr($dv)
+                        ImageLine($this->img, $x_now_pixels, $this->x_axis_y_pixels,
+                                              $x_now_pixels, $this->ytr($dv), $data_color);
+                   }
+                }
+            }
         }
         }
+
+        ImageSetThickness($this->img, 1);
         return TRUE;
     }
 
         return TRUE;
     }
 
@@ -5244,6 +5476,10 @@ class PHPlot
 
         $gcvars = array(); // For GetDataColor, which initializes and uses this.
 
 
         $gcvars = array(); // For GetDataColor, which initializes and uses this.
 
+        // Data Value Labels?
+        $do_dvls = $this->CheckDataValueLabels($this->y_data_label_pos,
+                      $dvl_x_off, $dvl_y_off, $dvl_h_align, $dvl_v_align);
+
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $record = 1;                                    // Skip record #0 (data label)
 
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $record = 1;                                    // Skip record #0 (data label)
 
@@ -5261,16 +5497,16 @@ class PHPlot
                 if (($line_style = $this->line_styles[$idx]) == 'none')
                     continue; //Allow suppressing entire line, useful with linepoints
                 if (is_numeric($this->data[$row][$record])) {           //Allow for missing Y data
                 if (($line_style = $this->line_styles[$idx]) == 'none')
                     continue; //Allow suppressing entire line, useful with linepoints
                 if (is_numeric($this->data[$row][$record])) {           //Allow for missing Y data
-
-                    // Select the color:
-                    $this->GetDataColor($row, $idx, $gcvars, $data_color);
-
-                    $y_now_pixels = $this->ytr($this->data[$row][$record]);
+                    $y_now = (double)$this->data[$row][$record];
+                    $y_now_pixels = $this->ytr($y_now);
 
                     if ($start_lines[$idx]) {
                         // Set line width, revert it to normal at the end
                         ImageSetThickness($this->img, $this->line_widths[$idx]);
 
 
                     if ($start_lines[$idx]) {
                         // Set line width, revert it to normal at the end
                         ImageSetThickness($this->img, $this->line_widths[$idx]);
 
+                        // Select the color:
+                        $this->GetDataColor($row, $idx, $gcvars, $data_color);
+
                         if ($line_style == 'dashed') {
                             $this->SetDashedStyle($data_color);
                             $data_color = IMG_COLOR_STYLED;
                         if ($line_style == 'dashed') {
                             $this->SetDashedStyle($data_color);
                             $data_color = IMG_COLOR_STYLED;
@@ -5278,6 +5514,13 @@ class PHPlot
                         ImageLine($this->img, $x_now_pixels, $y_now_pixels,
                                   $lastx[$idx], $lasty[$idx], $data_color);
                     }
                         ImageLine($this->img, $x_now_pixels, $y_now_pixels,
                                   $lastx[$idx], $lasty[$idx], $data_color);
                     }
+
+                    // Draw data value labels?
+                    if ($do_dvls) {
+                        $this->DrawDataValueLabel('y', $x_now, $y_now, $y_now, $dvl_h_align, $dvl_v_align,
+                                                  $dvl_x_off, $dvl_y_off);
+                    }
+
                     $lasty[$idx] = $y_now_pixels;
                     $lastx[$idx] = $x_now_pixels;
                     $start_lines[$idx] = TRUE;
                     $lasty[$idx] = $y_now_pixels;
                     $lastx[$idx] = $x_now_pixels;
                     $start_lines[$idx] = TRUE;
@@ -5401,6 +5644,10 @@ class PHPlot
 
         $gcvars = array(); // For GetDataColor, which initializes and uses this.
 
 
         $gcvars = array(); // For GetDataColor, which initializes and uses this.
 
+        // Data Value Labels?
+        $do_dvls = $this->CheckDataValueLabels($this->y_data_label_pos,
+                      $dvl_x_off, $dvl_y_off, $dvl_h_align, $dvl_v_align);
+
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $record = 1;                                    // Skip record #0 (data label)
 
         for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
             $record = 1;                                    // Skip record #0 (data label)
 
@@ -5417,7 +5664,8 @@ class PHPlot
             // Draw Lines
             for ($idx = 0; $record < $this->num_recs[$row]; $record++, $idx++) {
                 if (is_numeric($this->data[$row][$record])) {               // Allow for missing Y data
             // Draw Lines
             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]);
+                    $y_now = (double)$this->data[$row][$record];
+                    $y_now_pixels = $this->ytr($y_now);
 
                     if ($start_lines[$idx]) {
                         // Set line width, revert it to normal at the end
 
                     if ($start_lines[$idx]) {
                         // Set line width, revert it to normal at the end
@@ -5435,6 +5683,13 @@ class PHPlot
                         ImageLine($this->img, $x_now_pixels, $lasty[$idx],
                                   $x_now_pixels, $y_now_pixels, $data_color);
                     }
                         ImageLine($this->img, $x_now_pixels, $lasty[$idx],
                                   $x_now_pixels, $y_now_pixels, $data_color);
                     }
+
+                    // Draw data value labels?
+                    if ($do_dvls) {
+                        $this->DrawDataValueLabel('y', $x_now, $y_now, $y_now, $dvl_h_align, $dvl_v_align,
+                                                  $dvl_x_off, $dvl_y_off);
+                    }
+
                     $lastx[$idx] = $x_now_pixels;
                     $lasty[$idx] = $y_now_pixels;
                     $start_lines[$idx] = TRUE;
                     $lastx[$idx] = $x_now_pixels;
                     $lasty[$idx] = $y_now_pixels;
                     $start_lines[$idx] = TRUE;
@@ -5448,92 +5703,6 @@ class PHPlot
         return TRUE;
     }
 
         return TRUE;
     }
 
-    /*
-     * Draw a bar (or segment of a bar), with optional shading or border.
-     * This is used by the bar and stackedbar plots, vertical and horizontal.
-     *   $x1, $y1 : One corner of the bar.
-     *   $x2, $y2 : Other corner of the bar.
-     *   $data_color : Color index to use for the bar fill.
-     *   $alt_color : Color index to use for the shading (if shading is on), else for the border.
-     *      Note the same color is NOT used for shading and border - just the same argument.
-     *      See GetBarColors() for where these arguments come from.
-     *   $shade_top : Shade the top? (Suppressed for downward stack segments except first.)
-     *   $shade_side : Shade the right side? (Suppressed for leftward stack segments except first.)
-     *      Only one of $shade_top or $shade_side can be FALSE. Both default to TRUE.
-     */
-    protected function DrawBar($x1, $y1, $x2, $y2, $data_color, $alt_color,
-            $shade_top = TRUE, $shade_side = TRUE)
-    {
-        // Sort the points so x1,y1 is upper left and x2,y2 is lower right. This
-        // is needed in order to get the shading right, and imagerectangle may require it.
-        if ($x1 > $x2) {
-            $t = $x1; $x1 = $x2; $x2 = $t;
-        }
-        if ($y1 > $y2) {
-            $t = $y1; $y1 = $y2; $y2 = $t;
-        }
-
-        // Draw the bar
-        ImageFilledRectangle($this->img, $x1, $y1, $x2, $y2, $data_color);
-
-        // Draw a shade, or a border.
-        if (($shade = $this->shading) > 0) {
-            if ($shade_top && $shade_side) {
-                $npts = 6;
-                $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade,
-                             $x2 + $shade, $y2 - $shade, $x2, $y2, $x2, $y1);
-            } else {
-                $npts = 4;
-                if ($shade_top) { // Suppress side shading
-                    $pts = array($x1, $y1, $x1 + $shade, $y1 - $shade, $x2 + $shade, $y1 - $shade, $x2, $y1);
-                } else { // Suppress top shading
-                    $pts = array($x2, $y2, $x2, $y1, $x2 + $shade, $y1 - $shade, $x2 + $shade, $y2 - $shade);
-                }
-            }
-            ImageFilledPolygon($this->img, $pts, $npts, $alt_color);
-        } else {
-            ImageRectangle($this->img, $x1, $y1, $x2,$y2, $alt_color);
-        }
-    }
-
-    /*
-     * Get colors to use for a bar chart. There is a data color, and either a border color
-     * or a shading color (data dark color).
-     *   $row, $idx : Index arguments for the current bar.
-     *   &$vars : Variable storage. Caller makes an empty array, and this function uses it.
-     *   &$data_color : Returned result - Color index for the data (bar fill).
-     *   &$alt_color : Returned result - Color index for the shading or outline.
-     */
-    protected function GetBarColors($row, $idx, &$vars, &$data_color, &$alt_color)
-    {
-        // Initialize or extract variables:
-        if (empty($vars)) {
-            if ($this->shading > 0)    // This plot needs dark colors if shading is on.
-                $this->NeedDataDarkColors();
-            $custom_color = (bool)$this->GetCallback('data_color');
-            $num_data_colors = count($this->ndx_data_colors);
-            $num_border_colors = count($this->ndx_data_border_colors);
-            $vars = compact('custom_color', 'num_data_colors', 'num_border_colors');
-        } else {
-          extract($vars);
-        }
-
-        // Select the colors.
-        if ($custom_color) {
-            $col_i = $this->DoCallback('data_color', $row, $idx); // Custom color index
-            $i_data = $col_i % $num_data_colors; // Index for data colors and dark colors
-            $i_border = $col_i % $num_border_colors; // Index for data borders (if used)
-        } else {
-            $i_data = $i_border = $idx;
-        }
-        $data_color = $this->ndx_data_colors[$i_data];
-        if ($this->shading > 0) {
-            $alt_color = $this->ndx_data_dark_colors[$i_data];
-        } else {
-            $alt_color = $this->ndx_data_border_colors[$i_border];
-        }
-    }
-
     /*
      * Draw a Bar chart
      * Supports text-data format, with each row in the form array(label, y1, y2, y3, ...)
     /*
      * Draw a Bar chart
      * Supports text-data format, with each row in the form array(label, y1, y2, y3, ...)
@@ -5545,7 +5714,7 @@ class PHPlot
             return FALSE;
         if ($this->datatype_swapped_xy)
             return $this->DrawHorizBars();
             return FALSE;
         if ($this->datatype_swapped_xy)
             return $this->DrawHorizBars();
-        $this->CalcBarWidths();
+        $this->CalcBarWidths(FALSE, TRUE); // Calculate bar widths for unstacked, vertical
 
         // This is the X offset from the bar group's label center point to the left side of the first bar
         // in the group. See also CalcBarWidths above.
 
         // This is the X offset from the bar group's label center point to the left side of the first bar
         // in the group. See also CalcBarWidths above.
@@ -5612,7 +5781,7 @@ class PHPlot
      */
     protected function DrawHorizBars()
     {
      */
     protected function DrawHorizBars()
     {
-        $this->CalcBarWidths(FALSE); // Calculate bar sizes for horizontal plots
+        $this->CalcBarWidths(FALSE, FALSE); // Calculate bar widths for unstacked, vertical
 
         // This is the Y offset from the bar group's label center point to the bottom of the first bar
         // in the group. See also CalcBarWidths above.
 
         // This is the Y offset from the bar group's label center point to the bottom of the first bar
         // in the group. See also CalcBarWidths above.
@@ -5686,7 +5855,7 @@ class PHPlot
             return FALSE;
         if ($this->datatype_swapped_xy)
             return $this->DrawHorizStackedBars();
             return FALSE;
         if ($this->datatype_swapped_xy)
             return $this->DrawHorizStackedBars();
-        $this->CalcBarWidths();
+        $this->CalcBarWidths(TRUE, TRUE); // Calculate bar widths for stacked, vertical
 
         // This is the X offset from the bar's label center point to the left side of the bar.
         $x_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap;
 
         // This is the X offset from the bar's label center point to the left side of the bar.
         $x_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap;
@@ -5777,7 +5946,7 @@ class PHPlot
      */
     protected function DrawHorizStackedBars()
     {
      */
     protected function DrawHorizStackedBars()
     {
-        $this->CalcBarWidths(FALSE); // Calculate bar sizes for horizontal plots
+        $this->CalcBarWidths(TRUE, FALSE); // Calculate bar widths for stacked, horizontal
 
         // This is the Y offset from the bar's label center point to the bottom of the bar
         $y_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap;
 
         // This is the Y offset from the bar's label center point to the bottom of the bar
         $y_first_bar = $this->record_bar_width / 2 - $this->bar_adjust_gap;
@@ -5859,6 +6028,128 @@ class PHPlot
         return TRUE;
     }
 
         return TRUE;
     }
 
+    /*
+     * Draw a financial "Open/High/Low/Close" (OHLC) plot, including candlestick plots.
+     * Data format can be text-data (label, Yo, Yh, Yl, Yc) or data-data (label, X, Yo, Yh, Yl, Yc).
+     * Yo="Opening price", Yc="Closing price", Yl="Low price", Yh="High price".
+     * Each row must have exactly 4 Y values. No multiple data sets, no missing values.
+     * There are 3 subtypes, selected by $draw_candles and $always_fill.
+     *   $draw_candles  $always_fill  Description:
+     *    FALSE          N/A          A basic OHLC chart with a vertical line for price range, horizontal
+     *                                tick marks on left for opening price and right for closing price.
+     *    TRUE           FALSE        A candlestick plot with filled body indicating close down, outline
+     *                                for closing up, and vertical wicks for low and high prices.
+     *    TRUE           TRUE         A candlestick plot where the candle bodies are always filled.
+     * These map to 3 plot types per the $plots[] array.
+     *
+     * Data color usage:                        If closes down:   If closes up or unchanged:
+     *    Candlestick body, ohlc range line:      color 0           color 1
+     *    Candlestick wicks, ohlc tick marks:     color 2           color 3
+     * There are three member variables that control the width (candlestick body or tick marks):
+     *     ohlc_max_width, ohlc_min_width, ohlc_frac_width
+     * (There is no API to change them at this time.)
+     */
+    protected function DrawOHLC($draw_candles, $always_fill = FALSE)
+    {
+        if (!$this->CheckDataType('text-data, data-data'))
+            return FALSE;
+
+        // Assign name of GD function to draw candlestick bodies for stocks that close up.
+        $draw_body_close_up = $always_fill ? 'imagefilledrectangle' : 'imagerectangle';
+
+        // These 3 variables control the calculation of the half-width of the candle body, or length of
+        // the tick marks. This is scaled based on the plot density, but within tight limits.
+        $min_width = isset($this->ohlc_min_width) ? $this->ohlc_min_width : 2;
+        $max_width = isset($this->ohlc_max_width) ? $this->ohlc_max_width : 8;
+        $width_factor = isset($this->ohlc_frac_width) ? $this->ohlc_frac_width : 0.3;
+        $dw = max($min_width, min($max_width,
+                     (int)($width_factor * $this->plot_area_width / $this->num_data_rows)));
+
+        // Get line widths to use: index 0 for body/stroke, 1 for wick/tick.
+        list($body_thickness, $wick_thickness) = $this->line_widths;
+
+        $gcvars = array(); // For GetDataColor, which initializes and uses this.
+
+        for ($row = 0, $cnt = 0; $row < $this->num_data_rows; $row++) {
+            $record = 1;                                    // Skip record #0 (data label)
+
+            if ($this->datatype_implied)                    // Implied X values?
+                $x_now = 0.5 + $cnt++;                      // Place text-data at X = 0.5, 1.5, 2.5, etc...
+            else
+                $x_now = $this->data[$row][$record++];      // Read it, advance record index
+
+            $x_now_pixels = $this->xtr($x_now);             // Convert X to device coordinates
+            $x_left = $x_now_pixels - $dw;
+            $x_right = $x_now_pixels + $dw;
+
+            if ($this->x_data_label_pos != 'none')          // Draw X Data labels?
+                $this->DrawXDataLabel($this->data[$row][0], $x_now_pixels, $row);
+
+            // Require and use 4 numeric values in each row.
+            if ($this->num_recs[$row] - $record != 4
+                    || !is_numeric($yo = $this->data[$row][$record++])
+                    || !is_numeric($yh = $this->data[$row][$record++])
+                    || !is_numeric($yl = $this->data[$row][$record++])
+                    || !is_numeric($yc = $this->data[$row][$record++])) {
+                return $this->PrintError("DrawOHLC: row $row must have 4 values.");
+            }
+
+            // Set device coordinates for each value and direction flag:
+            $yh_pixels = $this->ytr($yh);
+            $yl_pixels = $this->ytr($yl);
+            $yc_pixels = $this->ytr($yc);
+            $yo_pixels = $this->ytr($yo);
+            $closed_up = $yc >= $yo;
+
+            // Get data colors and line thicknesses:
+            if ($closed_up) {
+                $this->GetDataColor($row, 1, $gcvars, $body_color); // Color 1 for body, closing up
+                $this->GetDataColor($row, 3, $gcvars, $ext_color);  // Color 3 for wicks/ticks
+            } else {
+                $this->GetDataColor($row, 0, $gcvars, $body_color); // Color 0 for body, closing down
+                $this->GetDataColor($row, 2, $gcvars, $ext_color);  // Color 2 for wicks/ticks
+            }
+            imagesetthickness($this->img, $body_thickness);
+
+            if ($draw_candles) {
+                // Note: Unlike ImageFilledRectangle, ImageRectangle 'requires' its arguments in
+                // order with upper left corner first.
+                if ($closed_up) {
+                    $yb1_pixels = $yc_pixels; // Upper body Y
+                    $yb2_pixels = $yo_pixels; // Lower body Y
+                    $draw_body = $draw_body_close_up;
+                    // Avoid a PHP/GD bug resulting in "T"-shaped ends to zero height unfilled rectangle:
+                    if ($yb1_pixels == $yb2_pixels)
+                        $draw_body = 'imagefilledrectangle';
+                } else {
+                    $yb1_pixels = $yo_pixels;
+                    $yb2_pixels = $yc_pixels;
+                    $draw_body = 'imagefilledrectangle';
+                }
+
+                // Draw candle body
+                $draw_body($this->img, $x_left, $yb1_pixels, $x_right, $yb2_pixels, $body_color);
+
+                // Draw upper and lower wicks, if they have height. (In device coords, that's dY<0)
+                imagesetthickness($this->img, $wick_thickness);
+                if ($yh_pixels < $yb1_pixels) {
+                    imageline($this->img, $x_now_pixels, $yb1_pixels, $x_now_pixels, $yh_pixels, $ext_color);
+                }
+                if ($yl_pixels > $yb2_pixels) {
+                    imageline($this->img, $x_now_pixels, $yb2_pixels, $x_now_pixels, $yl_pixels, $ext_color);
+                }
+            } else {
+                // Basic OHLC
+                imageline($this->img, $x_now_pixels, $yl_pixels, $x_now_pixels, $yh_pixels, $body_color);
+                imagesetthickness($this->img, $wick_thickness);
+                imageline($this->img, $x_left, $yo_pixels, $x_now_pixels, $yo_pixels, $ext_color);
+                imageline($this->img, $x_right, $yc_pixels, $x_now_pixels, $yc_pixels, $ext_color);
+            }
+            imagesetthickness($this->img, 1);
+        }
+        return TRUE;
+    }
+
     /*
      * Draw the graph.
      * This is the function that performs the actual drawing, after all
     /*
      * Draw the graph.
      * This is the function that performs the actual drawing, after all
@@ -5872,13 +6163,14 @@ class PHPlot
         if (!$this->CheckDataArray())
             return FALSE; // Error message already reported.
 
         if (!$this->CheckDataArray())
             return FALSE; // Error message already reported.
 
+        // Set defaults then import plot type configuration:
+        $draw_axes = TRUE;
+        $draw_arg = array(); // Default is: no arguments to the drawing function
+        extract(PHPlot::$plots[$this->plot_type]);
+
         // Allocate colors for the plot:
         $this->SetColorIndexes();
 
         // Allocate colors for the plot:
         $this->SetColorIndexes();
 
-        // For pie charts: don't draw grid or border or axes, and maximize area usage.
-        // These controls can be split up in the future if needed.
-        $draw_axes = ($this->plot_type != 'pie');
-
         // Get maxima and minima for scaling:
         if (!$this->FindDataLimits())
             return FALSE;
         // Get maxima and minima for scaling:
         if (!$this->FindDataLimits())
             return FALSE;
@@ -5930,39 +6222,8 @@ class PHPlot
             $this->DoCallback('draw_axes');
         }
 
             $this->DoCallback('draw_axes');
         }
 
-        switch ($this->plot_type) {
-        case 'thinbarline':
-            $this->DrawThinBarLines();
-            break;
-        case 'area':
-            $this->DrawArea();
-            break;
-        case 'squared':
-            $this->DrawSquared();
-            break;
-        case 'lines':
-            $this->DrawLines();
-            break;
-        case 'linepoints':
-            $this->DrawLinePoints();
-            break;
-        case 'points';
-            $this->DrawDots();
-            break;
-        case 'pie':
-            $this->DrawPieChart();
-            break;
-        case 'stackedbars':
-            $this->DrawStackedBars();
-            break;
-        case 'stackedarea':
-            $this->DrawArea(TRUE);
-            break;
-        // case 'bars':
-        default:
-            $this->DrawBars();
-            break;
-        }   // end switch
+        // Call the plot-type drawing method:
+        call_user_func_array(array($this, $draw_method), $draw_arg);
         $this->DoCallback('draw_graph', $this->plot_area);
 
         if ($draw_axes && $this->grid_at_foreground) {   // Usually one wants grids to go back, but...
         $this->DoCallback('draw_graph', $this->plot_area);
 
         if ($draw_axes && $this->grid_at_foreground) {   // Usually one wants grids to go back, but...