1<?php
2/**
3 *
4 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
5 * @author     Andreas Gohr <andi@splitbrain.org>
6 */
7
8if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
9if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
10require_once(DOKU_PLUGIN.'syntax.php');
11
12
13/**
14 * All DokuWiki plugins to extend the parser/rendering mechanism
15 * need to inherit from this class
16 */
17class syntax_plugin_gchart extends DokuWiki_Syntax_Plugin {
18
19    private $supported_charts = array(
20        'qr' => 'qr',
21        'pie' => 'p3',
22        'pie3d' => 'p3',
23        'pie2d' => 'p',
24        'line' => 'lc',
25        'spark' => 'ls',
26        'sparkline' => 'ls',
27        'bar' => 'bvs',
28        'hbar' => 'bhs',
29        'vbar' => 'bvs',
30    );
31
32    /**
33     * What kind of syntax are we?
34     */
35    function getType(){
36        return 'substition';
37    }
38
39    function getPType(){
40        return 'block';
41    }
42
43    /**
44     * Where to sort in?
45     */
46    function getSort(){
47        return 160;
48    }
49
50    /**
51     * Connect pattern to lexer
52     */
53    function connectTo($mode) {
54      $this->Lexer->addSpecialPattern('<gchart.*?>\n.*?\n</gchart>',$mode,'plugin_gchart');
55    }
56
57    /**
58     * Handle the match
59     */
60    function handle($match, $state, $pos, Doku_Handler $handler){
61
62        // prepare default data
63        $return = array(
64                     'type'   => 'p3',
65                     'data'   => array(),
66                     'width'  => 320,
67                     'height' => 140,
68                     'align'  => 'right',
69                     'legend' => false,
70                     'value'  => false,
71                     'title'  => '',
72                     'fg'     => ltrim($this->getConf('fg'),'#'),
73                     'bg'     => ltrim($this->getConf('bg'),'#'),
74                    );
75
76        // prepare input
77        $lines = explode("\n",$match);
78        $conf = array_shift($lines);
79        array_pop($lines);
80
81        // parse adhoc configs
82        if(preg_match('/"([^"]+)"/',$conf,$match)){
83            $return['title'] = $match[1];
84            $conf = preg_replace('/"([^"]+)"/','',$conf);
85        }
86        if(preg_match('/\b(left|center|right)\b/i',$conf,$match)){
87            $return['align'] = strtolower($match[1]);
88        }
89        if(preg_match('/\b(legend)\b/i',$conf,$match)){
90            $return['legend'] = true;
91        }
92        if(preg_match('/\b(values?)\b/i',$conf,$match)){
93            $return['value'] = true;
94        }
95        if(preg_match('/\b(\d+)x(\d+)\b/',$conf,$match)){
96            $return['width']  = $match[1];
97            $return['height'] = $match[2];
98        }
99
100        $type_regex = '/\b(' . join('|', array_keys($this->supported_charts)) . ')\b/i';
101        if(preg_match($type_regex,$conf,$match)){
102            $return['type'] = $this->supported_charts[strtolower($match[1])];
103        }
104        if(preg_match_all('/#([0-9a-f]{6}([0-9a-f][0-9a-f])?)\b/i',$conf,$match)){
105            if(isset($match[1][0])){
106                $return['fg'] = $match[1][0];
107            }
108            if(isset($match[1][1])){
109                $return['bg'] = $match[1][1];
110            }
111        }
112
113        // parse chart data
114        $data = array();
115        foreach ( $lines as $line ) {
116            //ignore comments (except escaped ones)
117            $line = preg_replace('/(?<![&\\\\])#.*$/','',$line);
118            $line = str_replace('\\#','#',$line);
119            $line = trim($line);
120            if(empty($line)){
121                continue;
122            }
123            $line = preg_split('/(?<!\\\\)=/',$line,2); //split on unescaped equal sign
124            $line[0] = str_replace('\\=','=',$line[0]);
125            $line[1] = str_replace('\\=','=',$line[1]);
126            $data[trim($line[0])] = trim($line[1]);
127        }
128        $return['data'] = $data;
129
130        return $return;
131    }
132
133    /**
134     * Create output
135     */
136    function render($mode, Doku_Renderer $R, $data) {
137        if ($mode != 'xhtml') {
138            return false;
139        }
140
141        $val = array_map('floatval', array_values($data['data']));
142        $max = max(0, ceil(max($val)));
143        $min = min(0, floor(min($val)));
144        $key = array_keys($data['data']);
145
146        $url  = 'https://chart.apis.google.com/chart?';
147        $parameters = array();
148
149        $parameters['cht'] = $data['type'];
150        if($data['bg']) {
151            $parameters['chf'] = 'bg,s,'.$data['bg'];
152        }
153        if($data['fg']) {
154            $parameters['chco'] = $data['fg'];
155        }
156        $parameters['chs'] = $data['width'].'x'.$data['height']; # size
157        $parameters['chd'] = 't:'.join(',',$val);
158        $parameters['chds'] = $min.','.$max;
159        $parameters['choe'] = 'UTF-8';
160        if($data['title']) {
161            $parameters['chtt'] = $data['title'];
162        }
163
164        switch($data['type']){
165            case 'bhs': # horizontal bar
166                $parameters['chxt'] = 'y';
167                $parameters['chxl'] = '0:|'.join('|',array_reverse($key));
168                $parameters['chbh'] = 'a';
169                if($data['value']) {
170                    $parameters['chm'] = 'N*f*,333333,0,-1,11';
171                }
172                break;
173            case 'bvs': # vertical bar
174                $parameters['chxt'] = 'y,x';
175                $parameters['chxr'] = '0,'.$min.','.$max;
176                $parameters['chxl'] = '1:|'.join('|',$key);
177                $parameters['chbh'] = 'a';
178                if($data['value']) {
179                    $parameters['chm'] = 'N*f*,333333,0,-1,11';
180                }
181                break;
182            case 'lc':  # line graph
183                $parameters['chxt'] = 'y,x';
184                $parameters['chxr'] = '0,'.floor(min($min,0)).','.ceil($max);
185                $parameters['chxl'] = '1:|'.join('|',$key);
186                if($data['value']) {
187                    $parameters['chm'] = 'N*f*,333333,0,-1,11';
188                }
189                break;
190            case 'ls':  # spark line
191                if($data['value']) {
192                    $parameters['chm'] = 'N*f*,333333,0,-1,11';
193                }
194                break;
195            case 'p3':  # pie graphs
196            case 'p':
197                if($data['legend']){
198                    $parameters['chdl'] = join('|',$key);
199                    if($data['value']){
200                        $parameters['chl'] = join('|',$val);
201                    }
202                }else{
203                    if($data['value']){
204                        $cnt = count($key);
205                        for($i = 0; $i < $cnt; $i++){
206                            $key[$i] .= ' ('.$val[$i].')';
207                        }
208                    }
209                    $parameters['chl'] = join('|',$key);
210                }
211                break;
212            case 'qr':
213                $rawval = array_keys($data['data']);
214                if (in_array($rawval[0], ['L', 'M', 'Q', 'H'])) {
215                    $parameters['chld'] = array_shift($rawval);
216                }
217                unset($parameters['chd']);
218                unset($parameters['chds']);
219                $parameters['chl'] = join(';', $rawval);
220                break;
221        }
222
223        $url .= http_build_query($parameters, '', '&') . '&.png';
224
225        $align = '';
226        if($data['align'] == 'left') {
227            $align=' align="left"';
228        } elseif($data['align'] == 'right') {
229            $align=' align="right"';
230        }
231
232        $R->doc .= '<img src="'.ml($url).'" class="media'.$data['align'].'" alt="" width="'.$data['width'].'" height="'.$data['height'].'"'.$align.' />';
233
234        return true;
235    }
236}
237
238//Setup VIM: ex: et ts=4 enc=utf-8 :
239