Overview

Namespaces

  • SamChristy
    • PieChart

Classes

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