<?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 (!isset($row['value']))
$row['value'] = 1;
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_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;
}