Overview

Packages

  • None
  • PieChart

Classes

  • PieChart
  • PieChartColor
  • PieChartGD
  • PieChartImagick
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: include 'PieChart.php';
  3: include 'lib/imageSmoothArc.php';
  4: 
  5: /**
  6:  * A lightweight class for drawing pie charts, using the GD library.
  7:  * @author    Sam Christy <sam_christy@hotmail.co.uk>
  8:  * @licence   GNU GPL v3.0 <http://www.gnu.org/licenses/gpl-3.0.html>
  9:  * @copyright © Sam Christy 2013
 10:  * @package   PieChart
 11:  * @version   v1.2
 12:  */
 13: class PieChartGD extends PieChart {
 14:     public function destroy() {
 15:         imageDestroy($this->canvas);
 16:     }
 17: 
 18:     public function draw() {
 19:         $this->canvas = imageCreateTrueColor($this->width, $this->height);
 20: 
 21:         // Set anti-aliasing for the pie chart.
 22:         imageAntiAlias($this->canvas, true);
 23: 
 24:         imageFilledRectangle($this->canvas, 0, 0, $this->width, $this->height,
 25:                 $this->_convertColor($this->backgroundColor));
 26:         
 27:         $total = 0;
 28:         $sliceStart = 90;  // Start at 12 o'clock.
 29: 
 30:         $titleHeight = $this->_drawTitle();
 31:         $legendWidth = $this->_drawLegend($titleHeight);
 32: 
 33:         // Account for the space occupied by the legend and its padding.
 34:         $pieCentreX = ($this->width - $legendWidth) / 2;
 35: 
 36:         // Account for the space occupied by the title.
 37:         $pieCentreY = $titleHeight + ($this->height - $titleHeight) / 2;
 38: 
 39:         // 10% padding on the top and bottom of the pie.
 40:         $pieDiameter = round(
 41:                 min($this->width - $legendWidth, $this->height - $titleHeight) * 0.85
 42:         );
 43: 
 44:         foreach ($this->slices as $slice)
 45:             $total += $slice['value'];
 46: 
 47:         // Draw the slices.
 48:         foreach (array_reverse($this->slices) as $slice) {
 49:             $sliceWidth = 360 * $slice['value'] / $total;
 50: 
 51:             // Skip slices that are too small to draw / be visible.
 52:             if ($sliceWidth == 0)
 53:                 continue;
 54: 
 55:             $sliceEnd = $sliceStart + $sliceWidth;
 56:             
 57:             imageSmoothArc(
 58:                 $this->canvas,
 59:                 $pieCentreX,
 60:                 $pieCentreY,
 61:                 $pieDiameter,
 62:                 $pieDiameter,
 63:                 array($slice['color']->r, $slice['color']->g, $slice['color']->b, 0),
 64:                 deg2rad($sliceStart),
 65:                 deg2rad($sliceEnd)
 66:             );
 67: 
 68:             // Move along to the next slice.
 69:             $sliceStart = $sliceEnd;
 70:         }
 71:     }
 72:     
 73:     protected function _output($method, $format, $filename) {
 74:         switch ($format) {
 75:             case parent::FORMAT_GIF:
 76:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 77:                     return imageGIF($this->canvas);
 78:                 }
 79:                 else if ($method == parent::OUTPUT_SAVE) {
 80:                     return imageGIF($this->canvas, $filename);
 81:                 }
 82:                 break;
 83:                 
 84:             case parent::FORMAT_JPEG:
 85:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 86:                     return imageJPEG($this->canvas, NULL, $this->quality);
 87:                 }
 88:                 else if ($method == parent::OUTPUT_SAVE) {
 89:                     return imageJPEG($this->canvas, $filename, $this->quality);
 90:                 }
 91:                 break;
 92:             
 93:             case parent::FORMAT_PNG:
 94:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 95:                     return imagePNG($this->canvas);
 96:                 }
 97:                 else if ($method == parent::OUTPUT_SAVE) {
 98:                     return imagePNG($this->canvas, $filename);
 99:                 }
100:                 break;
101:         }
102:         
103:         return false;  // The output method or format is missing!
104:     }
105: 
106:     /**
107:      * Draws the legend for the pieChart, if $this->hasLegend is true.
108:      * @param int $legendOffset The number of pixels the legend is offset by the title.
109:      * @return int The width of the legend and its padding.
110:      */
111:     protected function _drawLegend($legendOffset) {
112:         if (!$this->hasLegend)
113:             return 0;
114: 
115:         // Determine the ideal font size for the legend text;
116:         $legendFontSize = $this->width * 0.022;
117: 
118:         // If the legend's font size is too small, we won't bother drawing it.
119:         if (ceil($legendFontSize) < 8)
120:             return 0;
121: 
122:         // Calculate the size and padding for the color squares.
123:         $squareSize    = $this->height * 0.060;
124:         $squarePadding = $this->height * 0.025;
125:         $labelPadding  = $this->height * 0.025;
126: 
127:         $sliceCount = count($this->slices);
128: 
129:         $legendPadding = 0.05 * $this->width;
130: 
131:         // Determine the width and height of the legend.
132:         $legendWidth = $squareSize + $labelPadding + $this->_maxLabelWidth($legendFontSize);
133:         $legendHeight = $sliceCount * ($squareSize + $squarePadding) - $squarePadding;
134: 
135:         // If the legend and its padding occupy too much space, we will not draw it.        
136:         if ($legendWidth + $legendPadding * 2 > $this->width / 2)  // Too wide.
137:             return 0;
138: 
139:         if ($legendHeight > $this->height - $legendOffset - $legendPadding * 2)  // Too high.
140:             return 0;
141: 
142:         $legendX = $this->width - $legendWidth - $legendPadding;
143:         $legendY = ($this->height - $legendOffset) / 2 + $legendOffset - $legendHeight / 2;
144: 
145:         $i = 0;
146:         foreach ($this->slices as $sliceName => $slice) {
147:             // Move down...
148:             $OffsetY = $i++ * ($squareSize + $squarePadding);
149: 
150:             $this->_drawLegendKey(
151:                 $legendX,
152:                 $legendY + $OffsetY,
153:                 $slice['color'],
154:                 $sliceName,
155:                 $squareSize,
156:                 $labelPadding,
157:                 $legendFontSize
158:             );
159:         }
160: 
161:         return $legendWidth + $legendPadding * 2;
162:     }
163: 
164:     /**
165:      * Draws the legend key at the specific location.
166:      * @param int $x The x coordinate for the key's top, left corner.
167:      * @param int $y The y coordinate for the key's top, left corner.
168:      * @param object $color The GD colour identifier, created with imageColorAllocate().
169:      * @param string $label
170:      * @param int $squareSize The size of the square, in pixels.
171:      * @param int $labelPadding
172:      * @param int $fontSize
173:      */
174:     protected function _drawLegendKey($x, $y, $color, $label, $squareSize, $labelPadding,
175:             $fontSize) {
176:         $labelX = $x + $squareSize + $labelPadding;
177: 
178:         // Centre the label vertically to the square.
179:         $labelBBox = imageTTFBBox($fontSize, 0, $this->legendFont, $label);
180:         $labelHeight = abs($labelBBox[7] - $labelBBox[1]);
181: 
182:         $labelY = $y + $squareSize / 2 - $labelHeight / 2;
183:         
184:         imageFilledRectangle(
185:            $this->canvas, $x, $y, $x + $squareSize, $y + $squareSize, $this->_convertColor($color)
186:         );
187: 
188:         imageTTFText(
189:             $this->canvas,
190:             $fontSize,
191:             0,
192:             $labelX + abs($labelBBox[0]), // Eliminate left overhang.
193:             $labelY + abs($labelBBox[7]), // Eliminate area above the baseline.
194:             $this->_convertColor($this->textColor),
195:             $this->legendFont,
196:             $label
197:         );
198:     }
199: 
200:     /**
201:      * Returns the width, in pixels, of the chart's widest label.
202:      * @return int
203:      */
204:     protected function _maxLabelWidth($fontSize) {
205:         $widestLabelWidth = 0;
206: 
207:         foreach ($this->slices as $sliceName => $slice) {
208:             // Measure the label.
209:             $boundingBox = imageTTFBBox($fontSize, 0, $this->legendFont, $sliceName);
210:             $labelWidth = $boundingBox[2] - $boundingBox[0];
211: 
212:             if ($labelWidth > $widestLabelWidth)
213:                 $widestLabelWidth = $labelWidth;
214:         }
215: 
216:         return $widestLabelWidth;
217:     }
218: 
219:     /**
220:      * Draws and returns the height of the title and its padding (in pixels). If no title is 
221:      * specified, then nothing is drawn and 0 is returned.
222:      * @var float x location
223:      * @var float y location
224:      * @return int The height of the title + padding.
225:      */
226:     protected function _drawTitle($x = 0, $y = 0) {
227:         if (!$this->title)
228:             return 0;
229: 
230:         $titleColor = $this->_convertColor($this->textColor);
231: 
232:         // Determine ideal font size for the title.
233:         $titleSize = 0.0675 * $this->height;  // The largest sensible value.
234:         $minTitleSize = 10;                   // The smallest legible value.
235: 
236:         do {
237:             $titleBBox = imageTTFBBox($titleSize, 0, $this->titleFont, $this->title);
238:             $titleWidth = $titleBBox[2] - $titleBBox[0];
239: 
240:             // If we can fit the title in, with 5% padding on each side, then we can 
241:             // draw it.
242:             if ($titleWidth <= ($this->width * 0.9))
243:                 break;
244: 
245:             $titleSize -= 0.5; // Try a smaller font size.
246:         } while ($titleSize >= $minTitleSize);
247: 
248:         // If the title is simply too long to be drawn legibly, then we will simply not 
249:         // draw it.
250:         if ($titleSize < $minTitleSize)
251:             return 0;
252: 
253:         $titleHeight = abs($titleBBox[7] - $titleBBox[1]);
254: 
255:         // Give the title 7.5% top padding.
256:         $titleTopPadding = 0.075 * $this->height;
257: 
258:         // Centre the title.
259:         $x = $this->width / 2 - $titleWidth / 2;
260:         $y = $titleTopPadding;
261: 
262:         imageTtfText(
263:             $this->canvas, $titleSize, 
264:             0,
265:             $x + abs($titleBBox[0]),  // Account for left overhang.
266:             $y + abs($titleBBox[7]),  // Account for the area above the baseline.
267:             $titleColor, 
268:             $this->titleFont, 
269:             $this->title
270:         );
271: 
272:         return $titleHeight + $titleTopPadding;
273:     }
274:     
275:     /**
276:      * A convenience function for converting PieChartColor objects to the format
277:      * that GD requires.
278:      */
279:     private function _convertColor(PieChartColor $color) {
280:         // Interestingly, GD uses the ARGB format internally, so 
281:         // PieChartColor::toInt() would actually work for everything but GIFs...
282:         return imageColorAllocate($this->canvas, $color->r, $color->g, $color->b);
283:     }
284: }
PieChart API documentation generated by ApiGen 2.8.0