1<?php 2/** 3 * dataplot-Plugin: Parses Gnuplot data blocks 4 * 5 * @license GPL 3 (http://www.gnu.org/licenses/gpl-3.0.html) 6 * @author Yann Pouillon <yann.pouillon@materialsevolution.es> 7 */ 8 9 10if ( !defined('DOKU_INC') ) { 11 define('DOKU_INC', realpath(dirname(__FILE__).'/../../').'/'); 12} 13if ( !defined('DOKU_PLUGIN') ) { 14 define('DOKU_PLUGIN', DOKU_INC.'lib/plugins/'); 15} 16require_once(DOKU_PLUGIN.'syntax.php'); 17 18class syntax_plugin_dataplot extends DokuWiki_Syntax_Plugin { 19 20 /** 21 * What about paragraphs? 22 */ 23 function getPType() { 24 return 'normal'; 25 } 26 27 /** 28 * What kind of syntax are we? 29 */ 30 function getType() { 31 return 'substition'; 32 } 33 34 /** 35 * Where to sort in? 36 */ 37 function getSort() { 38 return 200; 39 } 40 41 /** 42 * Connect pattern to lexer 43 */ 44 function connectTo($mode) { 45 $this->Lexer->addSpecialPattern('<dataplot.*?>\n.*?\n</dataplot>', $mode, 'plugin_dataplot'); 46 } 47 48 /** 49 * Handle the match 50 */ 51 function handle($match, $state, $pos, Doku_Handler $handler) { 52 $info = $this->getInfo(); 53 54 // Set-up default data 55 $return = array( 56 'width' => 0, 57 'height' => 0, 58 'align' => '', 59 'layout' => '2D', 60 'columns' => 2, 61 'plottype' => 'linespoints', 62 'smooth' => false, 63 'xlabel' => '', 64 'ylabel' => '', 65 'xrange' => '', 66 'yrange' => '', 67 'gnuplot' => '', 68 'debug' => false, 69 'version' => '' 70 ); 71 $gnu_colors = array( 72 'white', 73 'red', 74 'medium-blue', 75 'orange-red', 76 'dark-violet', 77 'dark-turquoise', 78 'dark-chartreuse', 79 'grey40', 80 'black' 81 ); 82 83 // Prepare input 84 $lines = explode("\n", $match); 85 $conf = array_shift($lines); 86 array_pop($lines); 87 $lines = trim(join("\n", $lines))."\n"; 88 $lines = explode("\n", $lines); 89 90 // Get number of data columns 91 $cols = explode(" ", preg_replace("!\s+!", " ", trim($lines[0]))); 92 $return['columns'] = count($cols); 93 94 // Match config options 95 // Note: treating xlabel and ylabel first then removing them from the 96 // config string, in order to avoid misinterpretations of 97 // further options. 98 if ( preg_match('/xlabel="([^"]*)"/i', $conf, $match) ) { 99 $return['xlabel'] = $match[1]; 100 $conf = preg_replace('/xlabel="([^"]*)"/i', '', $conf); 101 } 102 if ( preg_match('/ylabel="([^"]*)"/i', $conf, $match) ) { 103 $return['ylabel'] = $match[1]; 104 $conf = preg_replace('/ylabel="([^"]*)"/i', '', $conf); 105 } 106 if ( preg_match('/xrange=(-?\d*\.\d+(e-?\d+)?:-?\d*\.\d+(e-?\d+)?)/i', $conf, $match) ) { 107 $return['xrange'] = $match[1]; 108 } 109 if ( preg_match('/yrange=(-?\d*\.\d+(e-?\d+)?:-?\d*\.\d+(e-?\d+)?)/i', $conf, $match) ) { 110 $return['yrange'] = $match[1]; 111 } 112 if ( preg_match('/\b(2D|3D)\b/i', $conf, $match) ) { 113 $return['layout'] = strtolower($match[1]); 114 } 115 if ( preg_match('/\b(boxes|lines|linespoints|points)\b/i', $conf, $match) ) { 116 $return['plottype'] = $match[1]; 117 } 118 if ( preg_match('/\b(smooth)\b/i', $conf, $match) ) { 119 $return['smooth'] = true; 120 } 121 if ( preg_match('/\b(left|center|right)\b/i', $conf, $match) ) { 122 $return['align'] = $match[1]; 123 } 124 if ( preg_match('/\b(\d+)x(\d+)\b/', $conf, $match) ) { 125 $return['width'] = $match[1]; 126 $return['height'] = $match[2]; 127 } 128 if ( preg_match('/\bwidth=([0-9]+)\b/i', $conf, $match) ) { 129 $return['width'] = $match[1]; 130 } 131 if ( preg_match('/\bheight=([0-9]+)\b/i', $conf, $match) ) { 132 $return['height'] = $match[1]; 133 } 134 if ( preg_match('/\b(debug)\b/i', $conf, $match) ) { 135 $return['debug'] = true; 136 } 137 138 // Force rebuild of images on update 139 $return['version'] = date('Y-m-d H:i:s'); 140 $return['hash'] = (string) uniqid("dataplot_", true); 141 142 // Generate Gnuplot code (must be last) 143 $input = trim(join("\n", $lines))."\n"; 144 if ( $return['width'] != 0 && $return['height'] != 0 ) { 145 $gnu_size = ' size '.$return['width'].','.$return['height']; 146 } else { 147 $gnu_size = ''; 148 } 149 $gnu_labels = ''; 150 if ( strlen($return['xlabel']) > 0 ) { 151 $gnu_labels .= "set xlabel \"".$return['xlabel']."\"\n"; 152 } 153 if ( strlen($return['ylabel']) > 0 ) { 154 $gnu_labels .= "set ylabel \"".$return['ylabel']."\"\n"; 155 } 156 if ( strlen($return['xrange']) > 0 ) { 157 $gnu_ranges .= "set xrange [".$return['xrange']."]\n"; 158 } 159 if ( strlen($return['yrange']) > 0 ) { 160 $gnu_ranges .= "set yrange [".$return['yrange']."]\n"; 161 } 162 163 $gnu_code = "# Input parameters:\n#\n"; 164 foreach ($return as $param => $value) { 165 if ( $param != 'gnuplot' ) { 166 $gnu_code .= "# - $param = $value\n"; 167 } 168 } 169 $gnu_code .= "#\n\n"; 170 $gnu_code .= 'set terminal pngcairo enhanced dashed font "arial,14" linewidth 2'.$gnu_size."\n"; 171 $gnu_code .= $gnu_labels; 172 $gnu_code .= $gnu_ranges; 173 $gnu_code .= "set output \"@gnu_output@\"\n"; 174 for ($i=1; $i<sizeof($gnu_colors); $i++) { 175 $gnu_code .= "set style line $i linetype rgb \"".$gnu_colors[$i]."\" linewidth 1.2 pointtype $i\n"; 176 } 177 $gnu_code .= 'plot'; 178 $sep = ' '; 179 for ($i=2; $i<=$return['columns']; $i++) { 180 $gnu_style = $i-1; 181 if ( $return['smooth'] && ($return['plottype'] == 'linespoints') ) { 182 $gnu_code .= $sep.'"@gnu_input@" using 1:'.$i.' notitle smooth csplines with lines linestyle '.$gnu_style; 183 $sep = ", \\\n "; 184 $gnu_code .= $sep.'"@gnu_input@" using 1:'.$i.' notitle with points linestyle '.$gnu_style; 185 } else { 186 $gnu_code .= $sep.'"@gnu_input@" using 1:'.$i.' notitle with '.$return[plottype].' linestyle '.$gnu_style; 187 } 188 $sep = ", \\\n "; 189 } 190 $gnu_code .= "\n"; 191 $return['gnuplot'] = $gnu_code; 192 193 // Store input for later use 194 io_saveFile($this->_cachename($return, 'txt'), $input); 195 196 return $return; 197 } 198 199 /** 200 * Cache file is based on parameters that influence the resulting image 201 */ 202 function _cachename($data, $ext) { 203 return getcachename( 204 $data['hash'].'x'.$data['layout'].'x'.$data['plottype'], '.'.$ext); 205 } 206 207 /** 208 * Create output 209 */ 210 function render($format, Doku_Renderer $renderer, $data) { 211 if ( $format == 'xhtml' ) { 212 $img = DOKU_BASE.'lib/plugins/dataplot/img.php?'.buildURLparams($data); 213 $renderer->doc .= '<img src="'.$img.'" class="media'.$data['align'].'" alt=""'; 214 if ( $data['width'] ) $renderer->doc .= ' width="'.$data['width'].'"'; 215 if ( $data['height'] ) $renderer->doc .= ' height="'.$data['height'].'"'; 216 if ( $data['align'] == 'right' ) $renderer->doc .= ' align="right"'; 217 if ( $data['align'] == 'left' ) $renderer->doc .= ' align="left"'; 218 $renderer->doc .= '/>'; 219 220 // Debugging 221 if ( $data['debug'] ) { 222 $renderer->doc .= '<pre>'.$data['gnuplot'].'</pre>'; 223 } 224 225 return true; 226 } elseif ( $format == 'odt' ) { 227 $src = $this->_imgfile($data); 228 $renderer->_odtAddImage($src, $data['width'], $data['height'], $data['align']); 229 230 return true; 231 } 232 233 return false; 234 } 235 236 /** 237 * Return path to the rendered image on our local system 238 */ 239 function _imgfile($data) { 240 $cache = $this->_cachename($data, 'png'); 241 242 // Create the file if needed 243 if ( !file_exists($cache) ) { 244 $in = $this->_cachename($data, 'txt'); 245 if ( $this->getConf('path') ) { 246 $ok = $this->_run($data, $in, $cache); 247 } else { 248 $ok = false; 249 } 250 if ( !$ok ) return false; 251 clearstatcache(); 252 } 253 254 // Resized version 255 if ( $data['width'] ) { 256 $cache = media_resize_image($cache, 'png', $data['width'], $data['height']); 257 } 258 259 // Something went wrong, we're missing the file 260 if ( !file_exists($cache) ) return false; 261 262 return $cache; 263 } 264 265 /** 266 * Run Gnuplot 267 */ 268 function _run($data, $in, $out) { 269 global $conf; 270 271 // Check input data 272 if ( !file_exists($in) ) { 273 if ( $conf['debug'] ) { 274 dbglog($in,'no such dataplot input file'); 275 } 276 277 return false; 278 } 279 280 // Create Gnuplot script 281 $gnu_code = $data['gnuplot']; 282 $gnu_code = preg_replace('!@gnu_input@!', $in, $gnu_code); 283 $gnu_code = preg_replace('!@gnu_output@!', $out, $gnu_code); 284 $gnu_script = tempnam('/tmp', 'dataplot'); 285 $gnu_handle = fopen($gnu_script, 'w'); 286 fwrite($gnu_handle, $gnu_code); 287 fclose($gnu_handle); 288 289 // Run command 290 $cmd = $this->getConf('path'); 291 $cmd .= ' '.$gnu_script; 292 exec($cmd, $output, $error); 293 294 // Remove Gnuplot script 295 //unlink($gnu_script); 296 297 if ( $error != 0 ) { 298 if ( $conf['debug'] ) { 299 dbglog(join("\n", $output), 'dataplot command failed: '.$cmd); 300 } 301 302 return false; 303 } 304 305 return true; 306 } 307 308} 309