1<?php
2/* Copyright (c) 1998-2010 ILIAS open source, Extended GPL, see docs/LICENSE */
3
4include_once "Services/Chart/classes/class.ilChartLegend.php";
5
6/**
7 * Abstract Chart generator base class
8 *
9 * @author Jörg Lützenkirchen <luetzenkirchen@leifos.com>
10 * @version $Id$
11 * @ingroup ServicesChart
12 */
13abstract class ilChart
14{
15    /**
16     * @var ilTemplate
17     */
18    protected $tpl;
19
20    protected $id; // [string]
21    protected $width; // [string]
22    protected $height; // [string]
23    protected $data; // [array]
24    protected $legend; // [ilChartLegend]
25    protected $shadow; // [int]
26    protected $colors; // [array]
27    protected $auto_resize; // [bool]
28    protected $stacked; // [bool]
29
30    const TYPE_GRID = 1;
31    const TYPE_PIE = 2;
32    const TYPE_SPIDER = 3;
33
34    /**
35     * Constructor
36     *
37     * @param string $a_id
38     */
39    protected function __construct($a_id)
40    {
41        global $DIC;
42
43        $this->tpl = $DIC["tpl"];
44        $this->id = $a_id;
45        $this->data = array();
46
47        $this->setShadow(2);
48    }
49
50    /**
51     * Get type instance
52     *
53     * @param int $a_type
54     * @param string $a_id
55     * @return ilChart
56     */
57    public static function getInstanceByType($a_type, $a_id)
58    {
59        switch ($a_type) {
60            case self::TYPE_GRID:
61                include_once "Services/Chart/classes/class.ilChartGrid.php";
62                return new ilChartGrid($a_id);
63
64            case self::TYPE_PIE:
65                include_once "Services/Chart/classes/class.ilChartPie.php";
66                return new ilChartPie($a_id);
67
68            case self::TYPE_SPIDER:
69                include_once "Services/Chart/classes/class.ilChartSpider.php";
70                return new ilChartSpider($a_id);
71        }
72    }
73
74    /**
75     * Get data series instance
76     *
77     * @return ilChartData
78     */
79    abstract public function getDataInstance($a_type = null);
80
81    /**
82     * Validate data series
83     *
84     * @return bool
85     */
86    abstract protected function isValidDataType(ilChartData $a_series);
87
88    /**
89     * Basic validation
90     *
91     * @return bool
92     */
93    protected function isValid()
94    {
95        if (sizeof($this->data)) {
96            return true;
97        }
98        return false;
99    }
100
101    /**
102     * Set chart size
103     *
104     * @param int $a_x
105     * @param int $a_y
106     */
107    public function setSize($a_x, $a_y)
108    {
109        $this->width = $a_x;
110        $this->height = $a_y;
111    }
112
113    /**
114     * Add data series
115     *
116     * @param ilChartData $a_series
117     * @param mixed $a_id
118     * @return mixed index
119     */
120    public function addData(ilChartData $a_series, $a_idx = null)
121    {
122        if ($this->isValidDataType($a_series)) {
123            if ($a_idx === null) {
124                $a_idx = sizeof($this->data);
125            }
126            $this->data[$a_idx] = $a_series;
127            return $a_idx;
128        }
129    }
130
131    /**
132     * Set chart legend
133     *
134     * @param ilChartLegend $a_legend
135     */
136    public function setLegend(ilChartLegend $a_legend)
137    {
138        $this->legend = $a_legend;
139    }
140
141    /**
142     * Set colors
143     *
144     * @param array $a_values
145     */
146    public function setColors($a_values)
147    {
148        foreach ($a_values as $color) {
149            if (self::isValidColor($color)) {
150                $this->colors[] = $color;
151            }
152        }
153    }
154
155    /**
156     * Get colors
157     *
158     * @return array
159     */
160    public function getColors()
161    {
162        return $this->colors;
163    }
164
165    /**
166     * Validate html color code
167     *
168     * @param string $a_value
169     * @return bool
170     */
171    public static function isValidColor($a_value)
172    {
173        if (preg_match("/^#[0-9a-f]{3}$/i", $a_value, $match)) {
174            return true;
175        } elseif (preg_match("/^#[0-9a-f]{6}$/i", $a_value, $match)) {
176            return true;
177        }
178    }
179
180    /**
181     * Render html color code
182     *
183     * @param string $a_value
184     * @param float $a_opacity
185     * @return string
186     */
187    public static function renderColor($a_value, $a_opacity = 1)
188    {
189        if (self::isValidColor($a_value)) {
190            if (strlen($a_value) == 4) {
191                return "rgba(" . hexdec($a_value[1] . $a_value[1]) . ", " .
192                    hexdec($a_value[2] . $a_value[2]) . ", " .
193                    hexdec($a_value[3] . $a_value[3]) . ", " . $a_opacity . ")";
194            } else {
195                return "rgba(" . hexdec($a_value[1] . $a_value[2]) . ", " .
196                    hexdec($a_value[3] . $a_value[4]) . ", " .
197                    hexdec($a_value[5] . $a_value[6]) . ", " . $a_opacity . ")";
198            }
199        }
200    }
201
202    /**
203     * Set shadow
204     *
205     * @param int $a_value
206     */
207    public function setShadow($a_value)
208    {
209        $this->shadow = (int) $a_value;
210    }
211
212    /**
213     * Get shadow
214     *
215     * @return int
216     */
217    public function getShadow()
218    {
219        return $this->shadow;
220    }
221
222    /**
223     * Toggle auto-resizing on window resize/redraw
224     *
225     * @param bool $a_value
226     */
227    public function setAutoResize($a_value)
228    {
229        $this->auto_resize = (bool) $a_value;
230    }
231
232    /**
233     * Toggle stacking
234     *
235     * @param bool $a_value
236     */
237    public function setStacked($a_value)
238    {
239        $this->stacked = (bool) $a_value;
240    }
241
242    /**
243     * Init JS script files
244     */
245    protected function initJS()
246    {
247        $tpl = $this->tpl;
248
249        include_once "Services/jQuery/classes/class.iljQueryUtil.php";
250        iljQueryUtil::initjQuery();
251
252        $tpl->addJavascript("Services/Chart/js/flot/excanvas.min.js");
253        $tpl->addJavascript("Services/Chart/js/flot/jquery.flot.min.js");
254
255        if ((bool) $this->auto_resize) {
256            // #13108
257            $tpl->addJavascript("Services/Chart/js/flot/jquery.flot.resize.min.js");
258        }
259
260        if ((bool) $this->stacked) {
261            $tpl->addJavascript("Services/Chart/js/flot/jquery.flot.stack.min.js");
262        }
263
264        $this->addCustomJS();
265    }
266
267    /**
268     * Add type-specific JS script
269     */
270    protected function addCustomJS()
271    {
272    }
273
274    /**
275     * Convert (global) properties to flot config
276     *
277     * @param object $a_options
278     */
279    public function parseGlobalOptions(stdClass $a_options)
280    {
281    }
282
283    /**
284     * Render
285     */
286    public function getHTML()
287    {
288        if (!$this->isValid()) {
289            return;
290        }
291
292        $this->initJS();
293
294        $chart = new ilTemplate("tpl.grid.html", true, true, "Services/Chart");
295        $chart->setVariable("ID", $this->id);
296
297        if ($this->width) {
298            if (is_numeric($this->width)) {
299                $chart->setVariable("WIDTH", "width:" . $this->width . "px;");
300            } else {
301                $chart->setVariable("WIDTH", "width:" . $this->width . ";");
302            }
303        }
304        if ($this->height) {
305            if (is_numeric($this->height)) {
306                $chart->setVariable("HEIGHT", "height:" . $this->height . "px;");
307            } else {
308                $chart->setVariable("HEIGHT", "height:" . $this->height . ";");
309            }
310        }
311
312
313        // (series) data
314
315        $json_series = array();
316        foreach ($this->data as $series) {
317            $series->parseData($json_series);
318        }
319        $chart->setVariable("SERIES", json_encode($json_series));
320
321
322        // global options
323
324        $json_options = new stdClass();
325        $json_options->series = new stdClass();
326        $json_options->series->shadowSize = (int) $this->getShadow();
327        $json_options->series->lines = new stdClass();
328        $json_options->series->lines->show = false;
329        $json_options->series->stack = (bool) $this->stacked;
330
331        foreach ($this->data as $series) {
332            $series->parseGlobalOptions($json_options, $this);
333        }
334
335        $this->parseGlobalOptions($json_options);
336
337        $colors = $this->getColors();
338        if ($colors) {
339            $json_options->colors = array();
340            foreach ($colors as $color) {
341                $json_options->colors[] = self::renderColor($color);
342            }
343        }
344
345        // legend
346        $json_options->legend = new stdClass();
347        if (!$this->legend) {
348            $json_options->legend->show = false;
349        } else {
350            $this->legend->parseOptions($json_options->legend);
351        }
352
353        $chart->setVariable("OPTIONS", json_encode($json_options));
354
355        $ret = $chart->get();
356        return $ret;
357    }
358
359    /*
360    ilChart
361    ->setColors
362    ->setHover() [tooltip?]
363    [->setClick]
364    ->setZooming
365    ->setPanning
366    ->setTooltip
367    ->addData[Series]
368        - labels
369        - type
370            - pie: nur 1x
371            - bar, lines, points, steps, stacked?
372        - highlight?!
373        => min/max, transmission type (google api)
374
375
376    grid-based
377    ->setGrid
378    ->setTicks(color, size, decimals, length)
379    ->addAxisX (multiple, Zero) [id, mode, position, color, label]
380    ->addAxisY (multiple?) [id, mode, position, color, label]
381    ->setBackgroundFill(opacity, color/gradient)
382    ->setThreshold(int/float)
383    ->setToggles(bool)
384
385    pie
386    ->setCollapse(int/float, color, label)
387    ->setRotation(int angle?)
388    ->setRadius(inner, outer)
389    */
390}
391