1<?php
2/**
3 * Helper class for the charter plugin, provides methods for rendering charts
4 * using the pChart library.
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author     Gina Häußge <osd@foosel.net>
8 */
9
10// must be run within Dokuwiki
11if (!defined('DOKU_INC')) die();
12
13if (!defined('DOKU_LF')) define('DOKU_LF', "\n");
14if (!defined('DOKU_TAB')) define('DOKU_TAB', "\t");
15if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/');
16
17require_once(DOKU_PLUGIN.'charter/lib/pchart/pData.class.php');
18require_once(DOKU_PLUGIN.'charter/lib/pchart/pChart.class.php');
19
20class helper_plugin_charter extends DokuWiki_Plugin { // DokuWiki_Helper_Plugin
21
22    function getInfo() {
23        return array (
24            'author' => 'Gina Haeussge',
25            'email' => 'osd@foosel.net',
26            'date' => @file_get_contents(DOKU_PLUGIN.'charter/VERSION'),
27            'name' => 'Charter Plugin (helper component)',
28            'desc' => 'Renders customized charts using the pChart library',
29            'url' => 'http://foosel.org/snippets/dokuwiki/charter',
30        );
31    }
32
33    function getMethods() {
34        $result = array ();
35        $result[] = array (
36            'name' => 'setFlags',
37            'desc' => 'sets the flags to use',
38            'params' => array(
39            	'flags' => 'array',
40            ),
41        );
42        $result[] = array (
43            'name' => 'setData',
44            'desc' => 'sets the data to use',
45            'params' => array (
46                'data' => 'array',
47            ),
48        );
49        $result[] = array (
50            'name' => 'render',
51            'desc' => 'renders the chart into the given file',
52            'params' => array (
53                'filename' => 'string',
54            ),
55            'return' => array (
56                'success' => 'boolean'
57            ),
58        );
59        return $result;
60    }
61
62    /** Flags */
63	var $flags;
64
65	/** Chart data */
66	var $data;
67
68	/** Valid flags */
69	var $validFlags = array(
70		'size', // size of the image, format 'width'x'height'
71		'align', // alignment of image, valid values are 'left', 'right' and 'center'
72		'type', // type of graph to generate, for valid values see below
73		'title', // title of the graph
74
75		'bgcolor', // background color
76		'legendColor', // background color of the legend
77		'graphColor', // background color of the graph area
78		'titleColor', // color of the title
79		'scaleColor', // color of the scale
80		'shadowColor', // color of the shadow
81
82		'bggradient', // background gradient, format '#RRGGBB@shades'
83		'graphGradient', // graph area gradient, format '#RRGGBB@shades'
84
85		'XAxisName', // name of X-axis
86		'YAxisName', // name of Y-axis
87		'XAxisFormat', // format of X-axis
88		'YAxisFormat', // format of Y-axis
89		'XAxisUnit', // unit of X-axis
90		'YAxisUnit', // unit of Y-axis
91
92		'fontTitle', // font for title, format 'fontfile@size'
93		'fontDefault', // font for everything else, format 'formatfile@size'
94		'fontLegend', // font for legend entries
95
96		'labelSerie', // serie to use for labels
97		'legendEntries', // entries for the legend
98		'graphLabels', // special labels to be shown in the graph, comma-separated list of
99		               // Serie-No.|X-Value|Description values
100
101		'dots', // size of circles which plot the given data points, defaults to false (= not plotted)
102		'legend', // whether to show the legend, defaults to true
103		'shadow', // whether to use shadows, defaults to false
104		'grid', // whether to show the grid, defaults to true
105		'alpha', // alpha value to use for bargraphs, filled linegraphs or filled cubic curves, defaults to 50
106		'ticks', // whether to show ticks on scale
107		'decimals', // amount of decimals to display on scale
108
109		'thresholds', // values at which to draw thresholds
110		'palette', // color palette to use
111
112		'pieLabels', // whether to show labels in the pie chart, defaults to true
113		'piePercentages', // whether to show calculated percentages in pie chart, default to false
114		'pieExploded', // whether to draw pie graph in exploded state
115	);
116
117	/** Valid chart types */
118	var $validTypes = array(
119		'line',
120		'lineFilled',
121		'cubic',
122		'cubicFilled',
123		'bar',
124		'barStacked',
125		'barOverlayed',
126		'pie',
127		'pie3d',
128	);
129
130	/** Default values for flags */
131	var $flagDefaults = array();
132
133	/**
134	 * Plugin constructor, initializes default settings and prepares
135	 * default flags.
136	 *
137	 * @author Gina Haeussge <osd@foosel.net>
138	 */
139	function helper_plugin_charter() {
140		$this->flagDefaults = array(
141			'size' => array(
142				'width' => 600,
143				'height' => 300,
144			),
145			'align' => 'left',
146			'type' => 'line',
147			'fontDefault' => array(
148				'name' => DOKU_PLUGIN.'charter/lib/fonts/Vera.ttf',
149				'size' => 8,
150			),
151			'fontLegend' => array(
152				'name' => DOKU_PLUGIN.'charter/lib/fonts/Vera.ttf',
153				'size' => 8,
154			),
155			'fontTitle' => array(
156				'name' => DOKU_PLUGIN.'charter/lib/fonts/VeraBd.ttf',
157				'size' => 10,
158			),
159			'legend' => true,
160			'grid' => true,
161			'alpha' => 50,
162			'dots' => false,
163			'shadow' => false,
164			'ticks' => true,
165			'decimals' => 0,
166
167			'bgcolor' => array(250, 250, 250),
168			'graphColor' => array(255, 255, 255),
169			'legendColor' => array(250, 250, 250),
170			'titleColor' => array(0, 0, 0),
171			'scaleColor' => array(150, 150, 150),
172			'shadowColor' => array(200, 200, 200),
173
174			'pieLabels' => false,
175			'piePercentages' => true,
176			'pieExploded' => false,
177		);
178	}
179
180	/**
181	 * Sets the flags from the given array. While doing so also validates and
182	 * postprocesses them, making sure to fall back to usable default values
183	 * where necessary.
184	 *
185	 * @param flags flags to set
186	 *
187	 * @author Gina Häußge <osd@foosel.net>
188	 */
189	function setFlags($flags = array()) {
190		foreach ($flags as $key => $val) {
191			if (!in_array($key, $this->validFlags))
192				unset($flags[$key]);
193
194			if (($key == 'fontTitle') || ($key == 'fontDefault') || ($key == 'fontLegend')) {
195				// validate fontdefinitions
196				list($fontname, $fontsize) = explode('@', $val, 2);
197				$flags[$key] = array(
198					'name' => DOKU_PLUGIN.'charter/lib/fonts/' . $fontname,
199					'size' => $fontsize,
200				);
201				if (!file_exists($flags[$key]['name']))
202					unset($flags[$key]);
203			} else if ($key == 'size') {
204				// validate and process size
205				list($w, $h) = $this->_trimArray(explode('x', $val, 2));
206				$flags[$key] = array(
207					'width' => $w,
208					'height' => $h,
209				);
210
211				if ($w < 0 || $h < 0)
212					unset($flags[$key]);
213			} else if ($key == 'align') {
214				// validate and process alignment
215				if (!in_array($val, array('left', 'right', 'center')))
216					unset($flags[$key]);
217			} else if ($key == 'grid' || $key == 'legend' || $key == 'shadow' || $key == 'ticks' || $key == 'pieLabels' || $key == 'piePercentages' || $key == 'pieExploded') {
218				// validate and process boolean settings
219				if ($val == 'true' || $val == '1' || $val == 'on')
220					$flags[$key] = true;
221				else if ($val == 'false' || $val == '0' || $val == 'off')
222					$flags[$key] = false;
223				else
224					unset($flags[$key]);
225			} else if ($key == 'legendEntries' || $key == 'thresholds') {
226				// process legend entries and thresholds
227				$flags[$key] = $this->_trimArray(explode(',', $flags[$key]));
228			} else if ($key == 'type') {
229				// validate graph type
230				if (!in_array($val, $this->validTypes))
231					unset($flags[$key]);
232			} else if ($key == 'alpha') {
233				// validate alpha setting
234				if (!is_numeric($val) || $val < 0 || $val > 100)
235					unset($flags[$key]);
236			} else if ($key == 'dots' || $key == 'decimals') {
237				// validate dot and decimals setting
238				if (!is_numeric($val) || $val < 0)
239					unset($flags[$key]);
240			} else if ($key == 'bgcolor' || $key == 'legendColor' || $key == 'graphColor' || $key == 'titleColor' || $key == 'scaleColor' || $key == 'shadowColor') {
241				// validate and process color definitions
242				$flags[$key] = $this->_parseRGB($val);
243				if (!$flags[$key])
244					unset($flags[$key]);
245			} else if ($key == 'palette') {
246				// validate and process palette settings
247				$flags[$key] = DOKU_PLUGIN.'charter/lib/palettes/' . $val . '.txt';
248				if (!file_exists($flags[$key]))
249					unset($flags[$key]);
250			} else if ($key == 'graphLabels') {
251				// validate and process graph labels
252				$flags[$key] = $this->_trimArray(explode(',', $flags[$key]));
253				for ($i = 0; $i < count($flags[$key]); $i++) {
254					$flags[$key][$i] = $this->_trimArray(explode('|', $flags[$key][$i]));
255					if (count($flags[$key][$i]) != 3) {
256						unset($flags[$key]);
257						break;
258					}
259					if (!is_numeric($flags[$key][$i][0]) || $flags[$key][$i][0] < 0) {
260						unset($flags[$key]);
261						break;
262					}
263				}
264			} else if ($key == 'XAxisFormat' || $key == 'YAxisFormat') {
265				// validate axis format settings
266				if (!in_array($val, array('number', 'time', 'date', 'metric', 'currency')))
267					unset($flags[$key]);
268			} else if ($key == 'bggradient' || $key == 'graphGradient') {
269				// validate and process background and graph gradients
270				list($color, $shades) = $this->_trimArray(explode('@', $val, 2));
271				$rgb = $this->_parseRGB($color);
272				if (!$rgb || !is_numeric($shades) || $shades < 0)
273					unset($flags[$key]);
274
275				$flags[$key] = array(
276					'color' => $rgb,
277					'shades' => $shades,
278				);
279			}
280		}
281
282		foreach ($this->flagDefaults as $key => $val) {
283			if (!isset($flags[$key]))
284				$flags[$key] = $val;
285		}
286
287		$this->flags = $flags;
288	}
289
290	/**
291	 * Sets the data to use.
292	 *
293	 * @param data the data
294	 *
295	 * @author Gina Häußge <osd@foosel.net>
296	 */
297	function setData($data = array()) {
298		$this->data = $data;
299	}
300
301	/**
302	 * Renders the graph into given file.
303	 *
304	 * @param filename the file to render to
305	 * @return true on success, false on failure
306	 *
307	 * @author Gina Häußge <osd@foosel.net>
308	 */
309	function render($filename) {
310		if (!$filename)
311			return false;
312
313		// parse input data
314		$csv = $this->_parseCsv($this->data);
315		if (!$csv)
316			return false;
317
318		// create pData instance
319		$pdata = $this->_createGraphData($csv);
320		if (!$pdata)
321			return false;
322
323		// prepare pChart instance based on graph type
324		switch ($this->flags['type']) {
325			case 'line':
326			case 'lineFilled':
327			case 'cubic':
328			case 'cubicFilled':
329			case 'bar':
330			case 'barStacked':
331			case 'barOverlayed':
332			default:
333				$chart = $this->_createLineGraph($pdata);
334				break;
335			case 'pie':
336			case 'pie3d':
337			case 'pieExploded':
338				$chart = $this->_createPieGraph($pdata);
339				break;
340		}
341		if (!$chart)
342			return false;
343
344		// render graph into file
345		if (!$chart->Render($filename))
346			return false;
347
348		return true;
349	}
350
351	/**
352	 * Creates a pData instance from flags and data.
353	 *
354	 * @param csv 2d array containing the data series
355	 * @return object pData object containing both data and data descriptions to use
356	 *
357	 * @author Gina Häußge <osd@foosel.net>
358	 */
359	function _createGraphData($csv) {
360		$pdata = new pData();
361
362		// set axis names
363		if (isset($this->flags['XAxisName']))
364			$pdata->SetXAxisName($this->flags['XAxisName']);
365		if (isset($this->flags['YAxisName']))
366			$pdata->SetYAxisName($this->flags['YAxisName']);
367
368		// set axis units
369		if (isset($this->flags['XAxisUnit']))
370			$pdata->SetXAxisUnit($this->flags['XAxisUnit']);
371		if (isset($this->flags['YAxisUnit']))
372			$pdata->SetYAxisUnit($this->flags['YAxisUnit']);
373
374		// set axis formats
375		if (isset($this->flags['XAxisFormat']))
376			$pdata->SetXAxisFormat($this->flags['XAxisFormat']);
377		if (isset($this->flags['YAxisFormat']))
378			$pdata->SetYAxisFormat($this->flags['YAxisFormat']);
379
380		// add series to graph data
381		$serie = 1;
382		foreach ($csv as $row) {
383			$pdata->AddPoint($row, 'Serie' . $serie);
384			if (isset($this->flags['legendEntries'][$serie-1]))
385				$pdata->SetSerieName($this->flags['legendEntries'][$serie-1], 'Serie' . $serie);
386			$serie++;
387		}
388		$pdata->AddAllSeries();
389
390		// if label serie is defined, mark it as such
391		if (isset($this->flags['labelSerie'])) {
392			$labelSerie = 'Serie' . $this->flags['labelSerie'];
393			$pdata->RemoveSerie($labelSerie);
394			$pdata->SetAbsciseLabelSerie($labelSerie);
395		}
396
397		return $pdata;
398	}
399
400	/**
401	 * Creates the pChart instance used to render the line/curve/bar graph.
402	 *
403	 * @param pdata the pData instance containing the data to plot
404	 * @return object a renderable pChart object
405	 *
406	 * @author Gina Häußge <osd@foosel.net>
407	 */
408	function _createLineGraph($pdata) {
409		$pchart = new pChart($this->flags['size']['width'], $this->flags['size']['height']);
410		$pchart->drawBackground($this->flags['bgcolor'][0], $this->flags['bgcolor'][1], $this->flags['bgcolor'][2]);
411
412		// draw background gradient
413		if (isset($this->flags['bggradient']))
414			$pchart->drawGraphAreaGradient($this->flags['bggradient']['color'][0], $this->flags['bggradient']['color'][1], $this->flags['bggradient']['color'][2], $this->flags['bggradient']['shades'], TARGET_BACKGROUND);
415
416		// set palette
417		if (isset($this->flags['palette']))
418			$pchart->loadColorPalette($this->flags['palette']);
419
420		// get legend size
421		$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
422		$legendSize = array(0, 0);
423		if ($this->flags['legend'])
424			$legendSize = $pchart->getLegendBoxSize($pdata->GetDataDescription());
425
426		// draw graph area
427		$pchart->setFontProperties($this->flags['fontDefault']['name'], $this->flags['fontDefault']['size']);
428		$pchart->setGraphArea(50, 30, $this->flags['size']['width'] - $legendSize[0] - 40, $this->flags['size']['height'] - 50);
429		$pchart->drawGraphArea($this->flags['graphColor'][0], $this->flags['graphColor'][1], $this->flags['graphColor'][2], true);
430
431		// draw graph area gradient
432		if (isset($this->flags['graphGradient']))
433			$pchart->drawGraphAreaGradient($this->flags['graphGradient']['color'][0], $this->flags['graphGradient']['color'][1], $this->flags['graphGradient']['color'][2], $this->flags['graphGradient']['shades']);
434
435		// draw legend
436		if ($this->flags['legend']) {
437			$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
438			$pchart->drawLegend($this->flags['size']['width'] - $legendSize[0] - 15, 30, $pdata->GetDataDescription(), $this->flags['legendColor'][0], $this->flags['legendColor'][1], $this->flags['legendColor'][2]);
439			$pchart->setFontProperties($this->flags['fontDefault']['name'], $this->flags['fontDefault']['size']);
440		}
441
442		// draw scale
443		switch ($this->flags['type']) {
444			case 'bar':
445			case 'barOverlayed':
446				$pchart->drawScale($pdata->GetData(), $pdata->GetDataDescription(), SCALE_START0, $this->flags['scaleColor'][0], $this->flags['scaleColor'][1], $this->flags['scaleColor'][2], $this->flags['ticks'], 0, $this->flags['decimals'], true);
447				break;
448			case 'barStacked':
449				$pchart->drawScale($pdata->GetData(), $pdata->GetDataDescription(), SCALE_ADDALLSTART0, $this->flags['scaleColor'][0], $this->flags['scaleColor'][1], $this->flags['scaleColor'][2], $this->flags['ticks'], 0, $this->flags['decimals'], true);
450				break;
451			default:
452				$pchart->drawScale($pdata->GetData(), $pdata->GetDataDescription(), SCALE_START0, $this->flags['scaleColor'][0], $this->flags['scaleColor'][1], $this->flags['scaleColor'][2], $this->flags['ticks'], 0, $this->flags['decimals'], false);
453				break;
454		}
455
456		// draw grid
457		if ($this->flags['grid'])
458			$pchart->drawGrid(4, true, 230, 230, 230, $this->flags['alpha']);
459
460		// draw thresholds
461		if (isset($this->flags['thresholds'])) {
462			foreach ($this->flags['thresholds'] as $threshold) {
463				$pchart->drawTreshold($threshold, 143, 55, 72, true, true);
464			}
465		}
466
467		// draw graph
468		if ($this->flags['shadow'] && in_array($this->flags['type'], array('line', 'lineFilled', 'cubic', 'cubicFilled')))
469			$pchart->setShadowProperties(3,3,$this->flags['shadowColor'][0],$this->flags['shadowColor'][1],$this->flags['shadowColor'][2],30,4);
470		if ($this->flags['dots'])
471			$pchart->drawPlotGraph($pdata->GetData(), $pdata->GetDataDescription(), $this->flags['dots']);
472		switch ($this->flags['type']) {
473			case 'line':
474				$pchart->drawLineGraph($pdata->GetData(), $pdata->GetDataDescription());
475				break;
476			case 'lineFilled':
477				$pchart->drawFilledLineGraph($pdata->GetData(), $pdata->GetDataDescription(), $this->flags['alpha']);
478				break;
479			case 'cubic':
480				$pchart->drawCubicCurve($pdata->GetData(), $pdata->GetDataDescription());
481				break;
482			case 'cubicFilled':
483				$pchart->drawFilledCubicCurve($pdata->GetData(), $pdata->GetDataDescription(), 0.1, $this->flags['alpha']);
484				break;
485			case 'bar':
486				$pchart->drawBarGraph($pdata->GetData(), $pdata->GetDataDescription(), $this->flags['shadow'], $this->flags['alpha']);
487				break;
488			case 'barStacked':
489				$pchart->drawStackedBarGraph($pdata->GetData(), $pdata->GetDataDescription(), $this->flags['alpha']);
490				break;
491			case 'barOverlayed':
492				$pchart->drawOverlayBarGraph($pdata->GetData(), $pdata->GetDataDescription(), $this->flags['alpha']);
493				break;
494		}
495		$pchart->clearShadow();
496
497		// draw graph labels
498		if (isset($this->flags['graphLabels'])) {
499			$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
500			foreach($this->flags['graphLabels'] as $label) {
501				$pchart->setLabel($pdata->GetData(), $pdata->GetDataDescription(), 'Serie' . $label[0], $label[1], $label[2]);
502			}
503		}
504
505		// draw title
506		if (isset($this->flags['title'])) {
507			$pchart->setFontProperties($this->flags['fontTitle']['name'], $this->flags['fontTitle']['size']);
508			$pchart->drawTitle(50, 20, $this->flags['title'], $this->flags['titleColor'][0], $this->flags['titleColor'][1], $this->flags['titleColor'][2], $this->flags['size']['width'] - $legendSize[0] - 40);
509		}
510
511		return $pchart;
512	}
513
514	/**
515	 * Creates the pChart instance used to render the pie graph.
516	 *
517	 * @param pdata the pData instance containing the data to plot
518	 * @return object a renderable pChart object
519	 *
520	 * @author Gina Häußge <osd@foosel.net>
521	 */
522	function _createPieGraph($pdata) {
523		$pchart = new pChart($this->flags['size']['width'], $this->flags['size']['height']);
524		$pchart->drawBackground($this->flags['bgcolor'][0], $this->flags['bgcolor'][1], $this->flags['bgcolor'][2]);
525
526		// set palette
527		if (isset($this->flags['palette']))
528			$pchart->loadColorPalette($this->flags['palette']);
529
530		// get legend size
531		$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
532		$legendSize = array(0, 0);
533		if ($this->flags['legend'])
534			$legendSize = $pchart->getPieLegendBoxSize($pdata->GetData(), $pdata->GetDataDescription());
535
536		// calculate center positiong and radius of pie chart
537		$center = array(
538			(int)(($this->flags['size']['width'] - $legendSize[0] - 20) / 2),
539			(int)($this->flags['size']['height'] / 2),
540		);
541		$radius = min($center[0], $center[1]) - 40;
542		if ($this->flags['pieExploded'])
543			$radius -= 10;
544
545		// draw legend
546		$pchart->setFontProperties($this->flags['fontDefault']['name'], $this->flags['fontDefault']['size']);
547		if ($this->flags['legend']) {
548			$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
549			$pchart->drawPieLegend($this->flags['size']['width'] - $legendSize[0] - 15, 30, $pdata->GetData(), $pdata->GetDataDescription(), $this->flags['legendColor'][0], $this->flags['legendColor'][1], $this->flags['legendColor'][2]);
550			$pchart->setFontProperties($this->flags['fontDefault']['name'], $this->flags['fontDefault']['size']);
551		}
552
553		// draw graph
554		$labeltype = PIE_NO_LABEL;
555		if ($this->flags['pieLabels'] && $this->flags['piePercentages'])
556			$labeltype = PIE_PERCENTAGE_LABEL;
557		else if ($this->flags['pieLabels'])
558			$labeltype = PIE_LABELS;
559		else if ($this->flags['piePercentages'])
560			$labeltype = PIE_PERCENTAGE;
561		if ($this->flags['shadow'])
562			$pchart->setShadowProperties(3,3,$this->flags['shadowColor'][0],$this->flags['shadowColor'][1],$this->flags['shadowColor'][2], 30, 4);
563		switch ($this->flags['type']) {
564			case 'pie':
565				if ($this->flags['pieExploded']) {
566					$pchart->drawFlatPieGraphWithShadow($pdata->GetData(), $pdata->GetDataDescription(), $center[0], $center[1], $radius, $labeltype, 10, $this->flags['decimals']);
567				} else {
568					$pchart->drawBasicPieGraph($pdata->GetData(), $pdata->GetDataDescription(), $center[0], $center[1], $radius, $labeltype, 255, 255, 255, $this->flags['decimals']);
569				}
570				break;
571			case 'pie3d':
572				$pchart->drawPieGraph($pdata->GetData(), $pdata->GetDataDescription(), $center[0], $center[1], $radius, $labeltype, true, 60, 20, (($this->flags['pieExploded']) ? 10 : 0), $this->flags['decimals']);
573				break;
574		}
575		$pchart->clearShadow();
576
577		// draw title
578		if (isset($this->flags['title'])) {
579			$pchart->setFontProperties($this->flags['fontTitle']['name'], $this->flags['fontTitle']['size']);
580			$pchart->drawTitle(50, 20, $this->flags['title'], $this->flags['titleColor'][0], $this->flags['titleColor'][1], $this->flags['titleColor'][2], $this->flags['size']['width'] - $legendSize[0] - 40);
581			$pchart->setFontProperties($this->flags['fontLegend']['name'], $this->flags['fontLegend']['size']);
582		}
583
584		return $pchart;
585	}
586
587	/**
588	 * Parses the given CSV string or line array into a
589	 * two-dimensional array. Very simplistic approach which
590	 * does not support quoted strings and such, but should
591	 * be sufficient for basic graph data.
592	 *
593	 * @param data the data to parse
594	 * @return array two-dimensional array containing the parsed data
595	 *
596	 * @author Gina Häußge <osd@foosel.net>
597	 */
598	function _parseCsv($data) {
599		if (!is_array($data))
600			$data = explode("\n", $data);
601
602		$output = array();
603		foreach($data as $row) {
604			$values = $this->_trimArray(explode(',', $row));
605			array_push($output, $values);
606		}
607
608		return $output;
609	}
610
611	/**
612	 * Trims all items in a given array.
613	 *
614	 * @param a the array to trim
615	 * @return array trimmed array
616	 *
617	 * @author Gina Häußge <osd@foosel.net>
618	 */
619	function _trimArray($a) {
620		if (!is_array($a))
621			return $a;
622
623		for ($i = 0; $i < count($a); $i++) {
624			$a[$i] = trim($a[$i]);
625		}
626
627		return $a;
628	}
629
630	/**
631	 * Parses a given HTML RGB color into three 8 bit sized
632	 * integers representing the color.
633	 *
634	 * Leading # is optional. Both six and three character wide
635	 * color definitions are supported.
636	 *
637	 * @param input a string containing a RGB color
638	 * @param array 3d array containing integer representations of red, green and blue
639	 *
640	 * @author Gina Häußge <osd@foosel.net>
641	 */
642	function _parseRGB($input) {
643		if ($input[0] == '#')
644			$input = substr($input, 1);
645
646		if (strlen($input) == 6) {
647			// #RRGGBB
648			$r = hexdec(substr($input, 0, 2));
649			$g = hexdec(substr($input, 2, 2));
650			$b = hexdec(substr($input, 4, 2));
651		} else if (strlen($input) == 3) {
652			// #RGB
653			$r = hexdec($input[0]);
654			$g = hexdec($input[1]);
655			$b = hexdec($input[2]);
656
657			$r = $r * 16 + $r;
658			$g = $g * 16 + $g;
659			$b = $b * 16 + $b;
660		} else {
661			return false;
662		}
663
664		return array($r, $g, $b);
665	}
666
667}