1: <?php
2: namespace SamChristy\PieChart;
3:
4: /**
5: * Abstract class that is designed to be extended for drawing pie charts with
6: * different graphics libraries. Use PieChartGD or PieChartImagick to actually
7: * draw your charts.
8: * @author Sam Christy <sam_christy@hotmail.co.uk>
9: * @licence GNU GPL v3.0 <http://www.gnu.org/licenses/gpl-3.0.html>
10: * @copyright © Sam Christy 2013
11: * @package PieChart
12: * @version v2.0.0
13: */
14: abstract class PieChart {
15: const FORMAT_GIF = 1;
16: const FORMAT_JPEG = 2;
17: const FORMAT_PNG = 3;
18: const OUTPUT_DOWNLOAD = 1;
19: const OUTPUT_INLINE = 2;
20: const OUTPUT_SAVE = 3;
21:
22: protected $slices;
23: protected $width;
24: protected $height;
25: protected $title;
26: protected $hasLegend;
27: protected $titleFont;
28: protected $legendFont;
29: protected $textColor;
30: protected $backgroundColor;
31: protected $canvas;
32: protected $quality;
33:
34: /**
35: * Constructs the PieChart.
36: * @param int $width The width of the chart, in pixels.
37: * @param int $height The chart's height, in pixels.
38: * @param string [$title] The chart's title.
39: * @param string|int|array [$textColor] The colour of the title and labels.
40: * @param string|int|array [$backgroundColor] The color for the background.
41: */
42: public function __construct($width = 0, $height = 0, $title = '', $textColor = 0x222222,
43: $backgroundColor = 0xffffff) {
44: $this->width = $width;
45: $this->height = $height;
46: $this->title = $title;
47: $this->hasLegend = true;
48: $this->slices = array();
49: $this->quality = 100;
50: $this->textColor = new PieChartColor($textColor);
51: $this->backgroundColor = new PieChartColor($backgroundColor);
52: $this->titleFont = __DIR__ . '/fonts/Open_Sans/OpenSans-Semibold.ttf';
53: $this->legendFont = __DIR__ . '/fonts/Open_Sans/OpenSans-Regular.ttf';
54: }
55:
56: /**
57: * Frees the memory that was allocated to the image. Use this function to
58: * clean up after your pie chart when you're finished with it.
59: */
60: public function destroy() {}
61:
62: /**
63: * Sets the title's text. To remove the title, set it to ''.
64: * @param string $title
65: * @param string [$titleFont] The name of the font file for the title.
66: */
67: public function setTitle($title, $titleFont = NULL) {
68: $this->title = $title;
69:
70: if($titleFont)
71: $this->titleFont = $titleFont;
72: }
73:
74: /**
75: * Add or remove the chart's legend (it is displayed default).
76: * @param bool $displayLegend Specify false to remove the legend or true to
77: * add one.
78: * @param string [$legendFont] The name of the font for the legend's text.
79: */
80: public function setLegend($displayLegend, $legendFont = NULL) {
81: $this->hasLegend = $displayLegend;
82:
83: if($legendFont)
84: $this->legendFont = $legendFont;
85: }
86:
87: /**
88: * Set the quality for generating output in lossy formats.
89: * @param int $quality An integer between 0 and 100 (inclusive).
90: */
91: public function setOutputQuality($quality) {
92: $this->quality = $quality;
93: }
94:
95: /**
96: * Adds a new slice to the pie chart.
97: * @param string $name The name of the slice (used for legend label).
98: * @param float $value
99: * @param string|int|array $color The CSS colour, e.g. '#FFFFFF', 'rgb(255, 255, 255)'.
100: */
101: public function addSlice($name, $value, $color) {
102: $this->slices[$name] = array(
103: 'value' => $value,
104: 'color' => new PieChartColor($color)
105: );
106: }
107:
108: /**
109: * Removes the specified slice.
110: * @param string $name The name of the slice to be removed.
111: */
112: public function removeSlice($name) {
113: unset($this->slices[$name]);
114: }
115:
116: /**
117: * Draws the chart so that it is ready for output.
118: */
119: public function draw() {}
120:
121: /**
122: * For child classes to override, so that the output functions work.
123: * @param int $method
124: * @param int $format
125: * @param string $filename
126: */
127: protected function _output($method, $format, $filename) {}
128:
129: /**
130: * Echos the chart as a GIF and instructs the browser to display it inline.
131: * @param string [$filename] The filename for the picture.
132: * @return bool true if successful, false otherwise (implementation-dependent).
133: */
134: public function outputGIF($filename = 'pie-chart.gif') {
135: header('Content-Type: image/gif');
136: header("Content-Disposition: inline; filename=\"$filename\"");
137:
138: return $this->_output(self::OUTPUT_INLINE, self::FORMAT_GIF, $filename);
139: }
140:
141: /**
142: * Echos the chart as a JPEG and instructs the browser to display it inline.
143: * @param string [$filename] The filename for the picture.
144: * @return bool true if successful, false otherwise (implementation-dependent).
145: */
146: public function outputJPEG($filename = 'pie-chart.jpg') {
147: header('Content-Type: image/jpeg');
148: header("Content-Disposition: inline; filename=\"$filename\"");
149:
150: return $this->_output(self::OUTPUT_INLINE, self::FORMAT_JPEG, $filename);
151: }
152:
153: /**
154: * Echos the chart as a PNG and instructs the browser to display it inline.
155: * @param string [$filename] The filename for the picture.
156: * @return bool true if successful, false otherwise (implementation-dependent).
157: */
158: public function outputPNG($filename = 'pie-chart.png') {
159: header('Content-Type: image/png');
160: header("Content-Disposition: inline; filename=\"$filename\"");
161:
162: return $this->_output(self::OUTPUT_INLINE, self::FORMAT_PNG, $filename);
163: }
164:
165: /**
166: * Echos the chart as a GIF and instructs the browser to force the user to
167: * save it.
168: * @param string [$filename] The filename for the picture.
169: * @return bool true if successful, false otherwise (implementation-dependent).
170: */
171: public function forceDownloadGIF($filename = 'pie-chart.gif') {
172: header("Pragma: public");
173: header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
174: header('Content-Type: application/octet-stream');
175: header('Content-Disposition: attachment; filename="'.$filename.'"');
176:
177: return $this->_output(self::OUTPUT_INLINE, self::FORMAT_GIF, $filename);
178: }
179:
180: /**
181: * Echos the chart as a JPEG and instructs the browser to force the user to
182: * save it.
183: * @param string [$filename] The filename for the picture.
184: * @return bool true if successful, false otherwise (implementation-dependent).
185: */
186: public function forceDownloadJPEG($filename = 'pie-chart.jpg') {
187: header("Pragma: public");
188: header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
189: header('Content-Type: application/octet-stream');
190: header('Content-Disposition: attachment; filename="'.$filename.'"');
191:
192: return $this->_output(self::OUTPUT_INLINE, self::FORMAT_JPEG, $filename);
193: }
194:
195: /**
196: * Echos the chart as a PNG and instructs the browser to force the user to
197: * save it.
198: * @param string [$filename] The filename for the picture.
199: * @return bool true if successful, false otherwise (implementation-dependent).
200: */
201: public function forceDownloadPNG($filename = 'pie-chart.png') {
202: header("Pragma: public");
203: header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
204: header('Content-Type: application/octet-stream');
205: header('Content-Disposition: attachment; filename="'.$filename.'"');
206:
207: return $this->_output(self::OUTPUT_DOWNLOAD, self::FORMAT_PNG, $filename);
208: }
209:
210: /**
211: * Saves the chart as a GIF, in the specified location.
212: * @param string $filename
213: * @return int true if successful, false otherwise (implementation-dependent)..
214: */
215: public function saveGIF($filename) {
216: return $this->_output(self::OUTPUT_SAVE, self::FORMAT_GIF, $filename);
217: }
218:
219: /**
220: * Saves the chart as a JPEG, in the specified location.
221: * @param string $filename
222: * @return int true if successful, false otherwise (implementation-dependent).
223: */
224: public function saveJPEG($filename) {
225: return $this->_output(self::OUTPUT_SAVE, self::FORMAT_JPEG, $filename);
226: }
227:
228: /**
229: * Saves the chart as a PNG, in the specified location.
230: * @param string $filename
231: * @return int true if successful, false otherwise (implementation-dependent).
232: */
233: public function savePNG($filename) {
234: return $this->_output(self::OUTPUT_SAVE, self::FORMAT_PNG, $filename);
235: }
236: }
237:
238: /**
239: * Utility class for storing colours in a library-agnostic format.
240: * @package PieChart
241: */
242: class PieChartColor {
243: public $r;
244: public $g;
245: public $b;
246:
247: /**
248: * Sets the colour using {@link setColor()}, if an argument is provided.
249: * @param array|int|string [$color]
250: */
251: public function __construct($color = NULL) {
252: if (!is_null($color)) {
253: $this->setColor($color);
254: }
255: }
256:
257: /**
258: * Sets the colour, using one of the following formats: '#FFFFFF', '#fff',
259: * 'rgb(255, 255, 255)', [$r, $g, $b] or ARGB
260: * ({@link http://en.wikipedia.org/wiki/ARGB#ARGB}).
261: * @param array|int|string $color
262: */
263: public function setColor($color) {
264:
265: switch (getType($color)) {
266: case 'array':
267: $this->r = $color[0];
268: $this->g = $color[1];
269: $this->b = $color[2];
270: break;
271:
272: case 'integer': // ARGB format
273: $this->r = $color >> 16 & 0xFF;
274: $this->g = $color >> 8 & 0xFF;
275: $this->b = $color & 0xFF;
276: break;
277:
278: case 'string':
279: $length = strLen($color);
280:
281: if ($length == 7) {
282: // e.g. '#FFFFFF'.
283: $this->r = hexDec(subStr($color, 1, 2));
284: $this->g = hexDec(subStr($color, 3, 2));
285: $this->b = hexDec(subStr($color, 5, 2));
286:
287: } else if ($length == 4) {
288: // e.g. '#FFF'.
289: $this->r = hexDec(subStr($color, 1, 1)) * 17;
290: $this->g = hexDec(subStr($color, 2, 1)) * 17;
291: $this->b = hexDec(subStr($color, 3, 1)) * 17;
292:
293: } else if (strToLower(subStr($color, 0, 4)) == 'rgb(') {
294: // e.g. 'rgb(255, 255, 255)'.
295: $listOfColors = subStr($color, 4, -1);
296: $arrayOfColors = explode(',', $listOfColors);
297:
298: $this->r = intVal($arrayOfColors[0]);
299: $this->g = intVal($arrayOfColors[1]);
300: $this->b = intVal($arrayOfColors[2]);
301: }
302: break;
303: }
304: }
305:
306: /**
307: * Returns the colour as an integer, in the ARGB format
308: * ({@link http://en.wikipedia.org/wiki/ARGB#ARGB}).
309: * @return int
310: */
311: public function toInt() {
312: $color = 0;
313:
314: $color |= $this->r << 16;
315: $color |= $this->g << 8;
316: $color |= $this->b;
317:
318: return $color;
319: }
320:
321: /**
322: * Returns the colour in the RGB string format, e.g. 'rgb(0,0,0)'.
323: * @return string
324: */
325: public function toRGB() {
326: return 'rgb(' . $this->r . ',' . $this->g . ',' . $this->b . ')';
327: }
328:
329: /**
330: * Returns the color in the CSS hexadecimal format, e.g. '#000000'.
331: * @return string
332: */
333: public function toHex() {
334: return sprintf('#%02s%02s%02s', decHex($this->r), decHex($this->g), decHex($this->b));
335: }
336: }