<?php $rg_graph_error = ''; function rg_graph_set_error($str) { global $rg_graph_error; $rg_graph_error = $str; rg_log('graph_set_error: ' . $str); } function rg_graph_error() { global $rg_graph_error; return $rg_graph_error; } /* * Helper TODO */ function rg_graph_unit2info($unit) { $ret = array(); switch ($unit) { case 'minute': $ret['time_format'] = 'H'; break; case 'hour': $ret['time_format'] = 'Y-m-d'; break; case 'day': $ret['time_format'] = 'Y-m'; break; case 'month': $ret['time_format'] = 'Y'; break; case 'year': $ret['time_format'] = 'Y'; break; default: $ret['time_format'] = 'Y-m-d'; break; } return $ret; } /* * Helper for rg_graph_data_query to position a value in the correct interval */ function rg_graph_pos($itime, $unit) { $y = gmdate('Y', $itime); $m = gmdate('m', $itime); $d = gmdate('d', $itime); $h = gmdate('H', $itime); $i = gmdate('i', $itime); switch ($unit) { case 'minute': $pos = gmmktime($h, $i, 0, $m, $d, $y); $next = gmmktime($h, $i + 1, 0, $m, $d, $y); $mark = gmmktime($h, 0, 0, $m, $d, $y); break; case 'hour': $pos = gmmktime($h, 0, 0, $m, $d, $y); $next = gmmktime($h + 1, 0, 0, $m, $d, $y); $mark = gmmktime(0, 0, 0, $m, $d, $y); break; case 'day': $pos = gmmktime(0, 0, 0, $m, $d, $y); $next = gmmktime(0, 0, 0, $m, $d + 1, $y); $mark = gmmktime(0, 0, 0, $m, 1, $y); break; case 'month': $pos = gmmktime(0, 0, 0, $m, 0, $y); $next = gmmktime(0, 0, 0, $m + 1, 0, $y); $mark = gmmktime(0, 0, 0, 1, 1, $y); break; case 'year': $pos = gmmktime(0, 0, 0, 0, 0, $y); $next = gmmktime(0, 0, 0, 0, 0, $y + 1); $mark = 0; break; default: rg_graph_set_error('invalid unit ' . $unit); $pos = FALSE; } if ($pos === FALSE) return FALSE; $ret = array('pos' => $pos, 'next' => $next); if ($mark == $pos) $ret['mark'] = $pos; return $ret; } /* * Loads data for graphs (helper) * TODO: what about DST? * @mode - 'avg' if we need averages per bar, 'sum' for sum * @decode_func - optional, a function to decode the data loaded from the database */ function rg_graph_query($db, $start, $end, $unit, $mode, $query, $params, $decode_func) { rg_log_enter('graph_query start=' . $start . ' end=' . $end . ' unit=' . $unit . ' mode=' . $mode . ' query=' . $query); $ret = FALSE; $error = FALSE; while (1) { $res = rg_sql_query_params($db, $query, $params); if ($res === FALSE) { rg_user_set_error('query error: ' . rg_sql_error()); break; } $ret = array( 'list' => array(), 'markers' => array(), 'min' => 0, 'max' => 0, 'unit' => $unit ); $x = rg_graph_pos(time(), $unit); if ($x !== FALSE) $ret['now_pos'] = $x['pos']; $counts = array(); while (($row = rg_sql_fetch_array($res))) { $p = rg_graph_pos($row['itime'], $unit); if ($p === FALSE) break; if (!empty($decode_func)) { $_x = $decode_func($row['value']); if ($_x === FALSE) { $error = TRUE; break; } $row['value'] = $_x; } $pos = $p['pos']; if (!isset($ret['list'][$pos])) $ret['list'][$pos] = $row['value']; else $ret['list'][$pos] += $row['value']; if (!isset($counts[$pos])) $counts[$pos] = 1; else $counts[$pos]++; } rg_sql_free_result($res); if ($error) break; $pos = $start; while ($pos < $end) { $p = rg_graph_pos($pos, $unit); if ($p === FALSE) { $ret = FALSE; break; } $pos = $p['pos']; $next = $p['next']; if (isset($p['mark'])) { $mark = $p['mark']; $ret['markers'][$mark] = 1; } if (!isset($ret['list'][$pos])) { $ret['list'][$pos] = 0; } else if (strcmp($mode, 'avg') == 0) { $ret['list'][$pos] = intval($ret['list'][$pos] / $counts[$pos]); } if ($ret['min'] > $ret['list'][$pos]) $ret['min'] = $ret['list'][$pos]; if ($ret['max'] < $ret['list'][$pos]) $ret['max'] = $ret['list'][$pos]; $ret['last_data_pos'] = $pos; $pos = $next; } // Insert a fake entry to be able to show last time marker $ret['list'][$end + 1] = 0; ksort($ret['list']); break; } // final marker $p = rg_graph_pos($end + 1, $unit); if (($p !== FALSE) && isset($p['mark'])) { $mark = $p['mark']; $ret['markers'][$mark] = 1; } rg_log_exit(); return $ret; } /* * Creates a SVG graph * TODO: user should be able to overwrite styles! Now we overwrite it. */ function rg_graph($a) { rg_log_ml('DEBUG: a[data]: ' . print_r($a['data'], TRUE)); if (!isset($a['scale_style'])) $a['scale_style'] = 'font-size: 8pt'; if (!isset($a['now_style'])) $a['now_style'] = 'fill: #999'; if (!isset($a['time_marker_line_style'])) $a['time_marker_line_style'] = 'fill: #bbb'; if (!isset($a['time_marker_text_style'])) $a['time_marker_text_style'] = 'font-color: #000'; if (!isset($a['footer_style'])) $a['footer_style'] = ''; $now = time(); $data_start_x = 10; $data_start_y = 40; // TODO: devide by 1000, 1000^2 etc. $len = max(4, strlen(sprintf("%s", $a['data']['max']))); $data_start_x += $len * 8 + 3; $c = count($a['data']['list']); $a['w'] = ($c - 1) * $a['gap'] + $c * $a['data_line_width']; rg_log('DEBUG: count=' . count($a['data']['list']) . ' len=' . $len); rg_log('DEBUG: a[w]=' . $a['w']); $time_date_height = 25; $w = $data_start_x + 2 + $a['w'] + 2 + 10; $h = $data_start_y + 2 + $a['h'] + 1 + 2 + $time_date_height + 20; rg_log('DEBUG: w=' . $w); $ret = '<svg width="' . $w . '" height="' . $h . '"' . ' viewbox="0 0 ' . $w . ' ' . $h . '"' . ' class="stats_svg">' . "\n"; // Background $ret .= '<rect x="0" y="0" width="100%" height="100%"' . ' style="' . $a['bg_style'] . '" />' . "\n"; // Title $ret .= '<text x="10" y="20" style="' . $a['title_style'] . '">' . $a['title'] . '</text>' . "\n"; // Subtitle $a['subtitle_style'] .= '; text-anchor: end'; $ret .= '<text x="' . ($data_start_x + 2 + $a['w'] + 2) . '"' . ' y="' . ($data_start_y - 5) . '"' . ' style="' . $a['subtitle_style'] . '">' . $a['subtitle'] . '</text>' . "\n"; // Footer left $a['footer_left_style'] = $a['footer_style'] . '; text-anchor: begin'; $t = '1 unit = 1 ' . $a['data']['unit']; $ret .= '<text x="' . ($data_start_x + 2) . '"' . ' y="' . ($data_start_y + 2 + $a['h'] + 3 + 12 + $time_date_height) . '"' . ' style="' . $a['footer_left_style'] . '">' . $t . '</text>' . "\n"; // Footer right $a['footer_right_style'] = $a['footer_style'] . '; text-anchor: end'; $t = 'Generated at ' . gmdate('Y-m-d H:i') . ' UTC'; $ret .= '<text x="' . ($data_start_x + 2 + $a['w'] + 2) . '"' . ' y="' . ($data_start_y + 2 + $a['h'] + 3 + 12 + $time_date_height) . '"' . ' style="' . $a['footer_right_style'] . '">' . $t . '</text>' . "\n"; // Left scale $a['scale_style'] .= '; text-anchor: end'; $ret .= '<text x="' . ($data_start_x - 3) . '"' . ' y="' . ($data_start_y + 8) . '"' . ' style="' . $a['scale_style'] . '">' . $a['data']['max'] . '</text>' . "\n"; $ret .= '<text x="' . ($data_start_x - 3) . '"' . ' y="' . ($data_start_y + $a['h'] + 3) . '"' . ' style="' . $a['scale_style'] . '">' . $a['data']['min'] . '</text>' . "\n"; // Data rectangle $ret .= '<g>' . "\n"; $ret .= '<rect x="' . $data_start_x . '"' . ' y="' . $data_start_y . '"' . ' width="' . (2 + $a['w'] + 2) . '"' . ' height="' . (2 + $a['h'] + 1 + 2) . '"' . ' style="' . $a['data_style'] . '" />' . "\n"; // Base black line $ret .= '<rect' . ' x="' . ($data_start_x + 2) . '"' . ' y="' . ($data_start_y + 2 + $a['h']) . '"' . ' width="' . $a['w'] . '"' . ' height="1"' . ' style="fill: #333"' . '/>' . "\n"; if ($a['data']['max'] == 0) $factor = 1; else $factor = $a['h'] / $a['data']['max']; $unit_info = rg_graph_unit2info($a['data']['unit']); $time_format = $unit_info['time_format']; $x = $data_start_x + 2; $y_base = $data_start_y + 2; foreach ($a['data']['list'] as $pos => $v0) { // Time markers if (isset($a['data']['markers'][$pos])) { $ret .= '<rect' . ' x="' . $x . '"' . ' y="' . $y_base . '"' . ' width="' . $a['data_line_width'] . '"' . ' height="' . $a['h'] . '"' . ' style="' . $a['time_marker_line_style'] . '"' . '/>' . "\n"; $_x = $x + 3; $_y = $y_base + $a['h'] + 7; $ret .= '<text x="' . $_x . '"' . ' y="' . $_y . '"' . ' style="' . $a['time_marker_text_style'] . '; text-anchor: end; font-size: 4pt" transform="rotate(-45,' . $_x . ',' . $_y . ')">' . gmdate($time_format, $pos) . '</text>' . "\n"; } // 'now' marker if ($pos == $a['data']['now_pos']) { $ret .= '<rect' . ' x="' . $x . '"' . ' y="' . $y_base . '"' . ' width="' . $a['data_line_width'] . '"' . ' height="' . $a['h'] . '"' . ' style="' . $a['now_style'] . '"' . '/>' . "\n"; $ret .= '<text x="' . $x . '"' . ' y="' . ($y_base + 6) . '"' . ' style="' . $a['now_style'] . '; text-anchor: end; font-size: 5pt" transform="rotate(270,' . $x . ',' . $y_base . ')">' . 'now</text>' . "\n"; } if ($pos > $a['data']['last_data_pos']) break; // value $v = sprintf('%u', $v0 * $factor); $y = $y_base + $a['h'] - $v; // TODO: data_line_style should be moved on the <g> tag! $ret .= '<rect' . ' x="' . $x . '"' . ' y="' . $y . '"' . ' width="' . $a['data_line_width'] . '"' . ' height="' . $v . '"' . ' style="' . $a['data_line_style'] . '"' . '/>' . "\n"; $x += $a['data_line_width'] + $a['gap']; } $ret .= '</g>' . "\n"; $ret .= '</svg>' . "\n"; return $ret; }