Overview

Packages

  • None
  • PieChart

Classes

  • PieChart
  • PieChartColor
  • PieChartGD
  • PieChartImagick
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: include 'PieChart.php';
  3: 
  4: /**
  5:  * A lightweight class for drawing pie charts, using the ImageMagick library.
  6:  * @author    Sam Christy <sam_christy@hotmail.co.uk>
  7:  * @licence   GNU GPL v3.0 <http://www.gnu.org/licenses/gpl-3.0.html>
  8:  * @copyright © Sam Christy 2013
  9:  * @package   PieChart
 10:  * @version   v1.2
 11:  */
 12: class PieChartImagick extends PieChart {
 13:     public function destroy() {
 14:         $this->canvas->destroy();
 15:     }
 16: 
 17:     public function draw() {
 18:         $this->canvas = new Imagick;
 19:         $this->canvas->newImage($this->width, $this->height, $this->backgroundColor->toHex());
 20:         
 21:         $total = 0;
 22:         $sliceStart = -90;  // Start at 12 o'clock.
 23: 
 24:         $titleHeight = $this->_drawTitle();
 25:         $legendWidth = $this->_drawLegend($titleHeight);
 26:         
 27:         // Account for the space occupied by the legend and its padding.
 28:         $pieCenterX = ($this->width - $legendWidth) / 2;
 29: 
 30:         // Account for the space occupied by the title.
 31:         $pieCenterY = $titleHeight + ($this->height - $titleHeight) / 2;
 32: 
 33:         // Give the pie 7.5% padding on either side.
 34:         $pieDiameter = round(
 35:                 min($this->width - $legendWidth, $this->height - $titleHeight) * 0.85
 36:         );
 37:         
 38:         $pieRadius = $pieDiameter / 2;
 39: 
 40:         foreach ($this->slices as $slice)
 41:             $total += $slice['value'];
 42: 
 43:         // Draw the slices.
 44:         foreach ($this->slices as &$slice) {
 45:             $sliceWidth = 360 * $slice['value'] / $total;
 46: 
 47:             // Skip slices that are too small to draw / be visible.
 48:             if ($sliceWidth == 0) continue;
 49:             
 50:             $sliceEnd = $sliceStart + $sliceWidth;
 51:             
 52:             $this->_drawSlice(
 53:                 array('x' => $pieCenterX, 'y' => $pieCenterY),
 54:                 $pieRadius,
 55:                 $sliceStart,
 56:                 $sliceEnd,
 57:                 $slice['color']
 58:             );
 59: 
 60:             // Move along to the next slice.
 61:             $sliceStart = $sliceEnd;
 62:         }
 63:     }
 64:     
 65:     protected function _output($method, $format, $filename) {
 66:         switch ($format) {
 67:             case parent::FORMAT_GIF:
 68:                 $this->canvas->setImageFormat('gif');
 69:                 
 70:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 71:                     return print $this->canvas;
 72:                 }
 73:                 else if ($method == parent::OUTPUT_SAVE) {
 74:                     return $this->canvas->writeImage($filename);
 75:                 }
 76:                 break;
 77:                 
 78:             case parent::FORMAT_JPEG:
 79:                 $this->canvas->setImageFormat('jpeg');
 80:                 $this->canvas->setImageCompression(imagick::COMPRESSION_JPEG);
 81:                 $this->canvas->setImageCompressionQuality($this->quality);
 82:                 
 83:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 84:                     return print $this->canvas;
 85:                 }
 86:                 else if ($method == parent::OUTPUT_SAVE) {
 87:                     return $this->canvas->writeImage($filename);
 88:                 }
 89:                 break;
 90:             
 91:             case parent::FORMAT_PNG:
 92:                 $this->canvas->setImageFormat('png');
 93:                 
 94:                 if ($method == parent::OUTPUT_INLINE || $method == parent::OUTPUT_DOWNLOAD) {
 95:                     return print $this->canvas;
 96:                 }
 97:                 else if ($method == parent::OUTPUT_SAVE) {
 98:                     return $this->canvas->writeImage($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.0325;
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.075 * $this->width;
130:         
131:         // Get the width of the legend's widest label.
132:         $maxLabelWidth = $this->_maxLabelWidth($legendFontSize);
133:         
134:         // Determine the width and height of the legend.
135:         $legendWidth = $squareSize + $labelPadding + $maxLabelWidth;
136:         $legendHeight = $sliceCount * ($squareSize + $squarePadding) - $squarePadding;
137: 
138:         // If the legend and its padding occupy too much space, we will not draw it.        
139:         if ($legendWidth + $legendPadding * 2 > $this->width / 2)  // Too wide.
140:             return 0;
141: 
142:         if ($legendHeight > $this->height - $legendOffset - $legendPadding * 2)  // Too high.
143:             return 0;
144: 
145:         $legendX = $this->width - $legendWidth - $legendPadding;
146:         $legendY = ($this->height - $legendOffset) / 2 + $legendOffset - $legendHeight / 2;
147:         
148:         $labelSettings = new ImagickDraw;
149:         
150:         $labelSettings->setFont($this->legendFont);
151:         $labelSettings->setFillColor($this->textColor->toHex());
152:         $labelSettings->setFontSize($legendFontSize);
153:         $labelSettings->setGravity(Imagick::GRAVITY_NORTHWEST);
154:         
155:         $i = 0;
156:         foreach ($this->slices as $sliceName => $slice) {
157:             // Move down...
158:             $OffsetY = $i++ * ($squareSize + $squarePadding);
159:             
160:             $keyPosX = $legendX;
161:             $keyPosY = $legendY + $OffsetY;
162:             
163:             // 1. Draw the key's colour square.
164:             $keySquare = new ImagickDraw;
165:             $keySquare->setFillColor($slice['color']->toHex());
166:             $keySquare->rectangle(
167:                 $keyPosX,
168:                 $keyPosY,
169:                 $keyPosX + $squareSize,
170:                 $keyPosY + $squareSize
171:             );
172:             
173:             $this->canvas->drawImage($keySquare);
174:             
175:             // 2. Draw the key's label.
176:             $labelMetrics = $this->canvas->queryFontMetrics($labelSettings, $sliceName);
177:             
178:             $this->canvas->annotateImage(
179:                 $labelSettings,
180:                 $keyPosX + $squareSize + $squarePadding,
181:                 $keyPosY + $labelMetrics['descender'] / 2,
182:                 0,
183:                 $sliceName
184:             );
185:         }
186:         
187:         return $legendWidth + $legendPadding;
188:     }
189:     
190:     protected function _drawSlice($center, $radius, $start, $end, $color){
191:         // Fine tuning, for a smoother edge.
192:         $radius -= 1;
193:         $center['x'] -= 1;
194:         $center['y'] -= 1;
195: 
196:         // 1. Drawn the curved part.
197:         $arc = new ImagickDraw;
198:         $color = $color->toHex();
199:         
200:         $arc->setFillColor($color);
201:         $arc->setStrokeColor($color);
202:         $arc->setStrokeWidth(1);
203:         
204:         $arc->arc(
205:             $center['x'] - $radius,
206:             $center['y'] - $radius,
207:             $center['x'] + $radius,
208:             $center['y'] + $radius,
209:             $start,
210:             $end
211:         );
212:         
213:         $this->canvas->drawImage($arc);
214:         
215:         // 2. Draw the triangular part.
216:         $startRadians = deg2rad($start);
217:         $endRadians   = deg2rad($end);
218:         
219:         // Calculate position for start of slice.
220:         $startPosition = array(
221:             'x' => $center['x'] + $radius * cos($startRadians),
222:             'y' => $center['y'] + $radius * sin($startRadians)
223:         );
224:         
225:         // Calculate position for end of slice.
226:         $endPosition = array(
227:             'x' => $center['x'] + $radius * cos($endRadians),
228:             'y' => $center['y'] + $radius * sin($endRadians)
229:         );
230:         
231:         $trianglePoints = array($startPosition, $endPosition, $center);
232:         
233:         $triangle = new ImagickDraw;
234:         
235:         $triangle->setFillColor($color);
236:         $triangle->setStrokeColor($color);
237:         $triangle->setStrokeWidth(1);
238:         
239:         $triangle->polygon($trianglePoints);
240:         
241:         $this->canvas->drawImage($triangle);
242:     }
243: 
244:     /**
245:      * Returns the width, in pixels, of the chart's widest label.
246:      * @return int
247:      */
248:     protected function _maxLabelWidth($fontSize) {
249:         $labelSettings = new ImagickDraw;
250:         $labelSettings->setFontSize($fontSize);
251:         $widestLabelWidth = 0;
252: 
253:         foreach ($this->slices as $sliceName => $slice) {
254:             // Measure the label.
255:             $labelMetrics = $this->canvas->queryFontMetrics($labelSettings, $sliceName);
256:             $labelWidth = $labelMetrics['textWidth'];
257: 
258:             if ($labelWidth > $widestLabelWidth)
259:                 $widestLabelWidth = $labelWidth;
260:         }
261: 
262:         return $widestLabelWidth;
263:     }
264: 
265:     /**
266:      * Draws and returns the height of the title and its padding (in pixels). If no title is 
267:      * specified, then nothing is drawn and 0 is returned.
268:      * @return int The height of the title + padding.
269:      */
270:     protected function _drawTitle() {
271:         if (!$this->title) return 0;
272:         
273:         $titleSettings = new ImagickDraw;
274: 
275:         $titleSettings->setFont($this->titleFont);
276:         $titleSettings->setFillColor($this->textColor->toHex());
277:         $titleSettings->setGravity(Imagick::GRAVITY_NORTH);
278: 
279:         // Determine ideal font size for the title.
280:         $titleSize = 0.08 * $this->height;    // The largest sensible value.
281:         $minTitleSize = 10;                   // The smallest legible value.
282: 
283:         do {
284:             $titleSettings->setFontSize($titleSize);
285:             
286:             // Measure the text.
287:             $titleBBox = $this->canvas->queryFontMetrics($titleSettings, $this->title);
288:             $titleWidth = $titleBBox['textWidth'];
289: 
290:             // If we can fit the title in, with 5% padding on each side, then we can draw it.
291:             if ($titleWidth <= ($this->width * 0.9))
292:                 break;
293: 
294:             $titleSize -= 0.5; // Try a smaller font size.
295:         } while ($titleSize >= $minTitleSize);
296:         
297:         $titleHeight = $titleBBox['textHeight'];
298: 
299:         // If the title is simply too long to be drawn legibly, then we will simply not draw it.
300:         if ($titleSize < $minTitleSize) return 0;
301: 
302:         // Give the title 7.5% top padding.
303:         $titleTopPadding = 0.075 * $this->height;
304: 
305:         // Draw the title (centre-top).
306:         $this->canvas->annotateImage($titleSettings, 0, $titleTopPadding, 0, $this->title);
307: 
308:         return $titleHeight + $titleTopPadding;
309:     }
310: }
PieChart API documentation generated by ApiGen 2.8.0