Прикрутил jQplot

master
vitalif 2012-03-04 20:12:26 +00:00
parent ab17362c49
commit 62a5c76d06
8 changed files with 456 additions and 13 deletions

View File

@ -37,8 +37,10 @@ class OLAP
static $sources;
static $functions = array(
'year' => array('name' => 'Год', 'time_format' => 'Y', 'sql' => 'SUBSTR($,1,4)'),
'month' => array('name' => 'Месяц', 'time_format' => 'Y-m', 'sql' => 'SUBSTR($,1,7)'),
'day' => array('name' => 'Дата', 'time_format' => 'Y-m-d', 'sql' => 'SUBSTR($,1,10)'),
'ym' => array('name' => 'Год-месяц', 'time_format' => 'Y-m', 'sql' => 'SUBSTR($,1,7)'),
'month' => array('name' => 'Месяц', 'time_format' => 'm', 'sql' => 'MONTH($)'),
'ymd' => array('name' => 'Дата', 'time_format' => 'Y-m-d', 'sql' => 'SUBSTR($,1,10)'),
'day' => array('name' => 'День месяца', 'time_format' => 'd', 'sql' => 'DAY($)'),
'weekday' => array('name' => 'День недели', 'time_format' => 'N', 'sql' => 'WEEKDAY($)'),
'week' => array('name' => '№ недели', 'time_format' => 'W', 'sql' => 'WEEK($)'),
'hour' => array('name' => 'Час', 'time_format' => 'H', 'sql' => 'HOUR($)'),
@ -146,6 +148,11 @@ class OLAP
$this->where = array();
$this->where_nosql = array();
$this->group_fields = array();
if ($request['graph_log'])
{
$this->graph_logarithmic = true;
$this->request['graph_log'] = 1;
}
foreach ($request as $k => $v)
{
// условия выборки: where-ОПЕРАЦИЯ-ПОЛЕ = ЗНАЧЕНИЕ
@ -199,6 +206,7 @@ class OLAP
{
$gf['num'] = count($this->group_fields);
$this->group_fields[] = $gf;
$this->request[$k] = $v;
}
}
}
@ -304,7 +312,10 @@ class OLAP
$vars = array('build' => 1);
if ($this->group_fields[0]['type'] == 'graph')
{
$vars['graphs'] = $this->build_graphs($this->group_fields, $data, $tdkeys);
$vars['groups'] = $this->tpl_jsgraphs($vars['graphs']);
}
else
$vars['tables'] = $this->build_tables($this->group_fields, $data, $tdkeys);
if (!$this->csv)
@ -338,7 +349,7 @@ class OLAP
}
// Загрузка данных с группировкой и подсчётом показателей через SQL
// FIXME корректно вычисляется только первый, остальные по сути просто заполняются
// FIXME корректно вычисляется только первый показатель, остальные по сути просто заполняются
// наборами ключей с некоторыми значениями, не равными правильным агрегатам.
function load_sql($field, $func, $aggr, $output)
{
@ -481,6 +492,7 @@ class OLAP
$graphs = array();
if (count($fields))
{
// Если кроме $my остались ещё поля - уйдём в рекурсию
foreach ($fields as &$v)
if ($v['sort'])
$v['orig_sort'] = &$v['sort'];
@ -492,13 +504,15 @@ class OLAP
if ($v['orig_sort'])
$v['sort'] = &$v['orig_sort'][$k];
$gr = $this->build_graphs($fields, $data[$k], $tdkeys);
// Допишем своё значение поля в начало описаний "дочерних" графиков
$my_desc = array(
'field' => $this->current_src['fielddescs'][$my['field']]['name'],
'func' => self::$functions[$my['func']]['name'],
'value' => $this->field_format($my['field'], '', '', $k)
);
foreach ($gr as &$g)
{
array_unshift($g['desc'], array(
'field' => $this->current_src['fielddescs'][$my['field']]['name'],
'func' => self::$functions[$my['func']]['name'],
'value' => $this->field_format($my['field'], '', '', $k)
));
array_unshift($g['desc'], $my_desc);
$graphs[] = $g;
}
}
@ -506,6 +520,7 @@ class OLAP
}
else
{
// А вот здесь реально строим данные для графика
$gr = array(
'desc' => array(),
'field' => $this->current_src['fielddescs'][$my['field']]['name'],
@ -538,12 +553,14 @@ class OLAP
$gr['min'] = 0;
else
$gr['bi_sign'] = true;
// Масштабируем график до целых чисел с точностью $this->graph_scale
if ($gr['max'] > $gr['min'])
{
if ($gr['bi_sign'])
{
foreach ($gr['data'] as &$d)
{
// $d[2] - положительные значения, $d[3] - отрицательные
if ($d[2] >= 0)
{
$d[2] = intval($d[2]/$gr['max']*$this->graph_scale/2);
@ -976,4 +993,55 @@ class OLAP
{
$aggr = count($aggr);
}
// Упрощение вывода JS-графиков - разбивает их на группы
static function tpl_jsgraphs($graphs)
{
$groups = array();
foreach ($graphs as $i => $graph)
{
$min_ne = count($graph['desc']);
foreach ($graph['desc'] as $j => $d)
{
if ($i == 0 || $d['value'] != $graphs[$i-1]['desc'][$j]['value'])
{
$min_ne = $j;
break;
}
}
$a = $b = array();
for ($j = 0; $j < count($graph['desc']); $j++)
{
$d = $graph['desc'][$j];
$d = $d['field'] . ($d['func'] ? ' ('.strtolower($d['func']).')' : '') .
': ' . $d['value'];
$a[] = $d;
if ($j >= $min_ne)
$b[] = array('level' => $j, 'text' => $d);
}
$graph['last_desc'] = array_pop($a);
array_pop($b);
if ($min_ne >= count($graph['desc'])-1 || !$group)
{
if (!$group)
{
$group = array(
'desc' => array_slice($graph['desc'], 0, -1),
'full_desc' => $a,
'changed_desc' => $b,
'graphs' => array(),
);
}
$group['graphs'][] = $graph;
}
else
{
$groups[] = $group;
$group = false;
}
}
if ($group)
$groups[] = $group;
return $groups;
}
}

View File

@ -34,6 +34,7 @@ p { margin: 4px 0; }
.simpletable th.v0, .simpletable th.v1, .simpletable th.v2, .simpletable th.v3, .simpletable th.v4, .simpletable th.vsort { border-bottom: 1px dashed gray; }
.simpletable td { padding: 3px; vertical-align: top; }
#middle ul.list div.right { float: left; }
td.jqplot-table-legend-label { white-space:nowrap; }
-->
</style>
<script language="JavaScript" type="text/javascript" src="{DOMAIN}/tpldata/lightbox.js"></script>
@ -42,6 +43,7 @@ p { margin: 4px 0; }
<!--#<script type="text/javascript" language="JavaScript" src="{DOMAIN}/admin/afm/ajex.js"></script> -->
<!-- END -->
<link rel="stylesheet" type="text/css" href="{DOMAIN}/tpldata/lightbox.css" />
{headscripts}
</head>
<body {body_args}>
<div id="admcontent">

View File

@ -1,4 +1,11 @@
<!-- SET title -->Статистика<!-- END -->
<!-- SET headscripts -->
<script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jquery.min.js"></script>
<!--[if lte IE 8]><script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/excanvas.min.js"></script><![endif]-->
<script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jqplot.all.min.js"></script>
<script language="javascript" type="text/javascript" src="{DOMAIN}/tpldata/jqplot.plugins.all.min.js"></script>
<link rel="stylesheet" type="text/css" href="{DOMAIN}/tpldata/jquery.jqplot.css" />
<!-- END -->
<!-- INCLUDE admin_header.tpl -->
<!-- IF NOT srcid -->
<p>Добро пожаловать в простую OLAPообразную статистику. Выберите источник данных:</p>
@ -26,7 +33,7 @@
<select style="width:100px" name="cell_field">
<option value="">---</option>
<!-- FOR f = fielddescs -->
<option value="{s f.id}"<!-- IF eq(cell.field, f.id) --> selected="selected"<!-- END -->>{s f.name}</option>
<option value="{s f.id}"<!-- IF eq(cell.field, f.id) --> selected="selected"<!-- SET cell.fname = f.name --><!-- END -->>{s f.name}</option>
<!-- END -->
</select>
<select style="width:100px" name="cell_func">
@ -80,11 +87,11 @@
<!-- END -->
</select>
<!-- ELSE -->
<input style="width: 100px" type="text" name="where-eq-{f.id}" value="{s get(wh,concat('where-eq-',f.id))}" />
<input style="width: 100px" type="text" name="where-eq-{f.id}" value="{s get(request,concat('where-eq-',f.id))}" />
<!-- END -->
<!-- IF f.le_ge -->
или &ge; <input style="width: 100px" type="text" name="where-ge-{f.id}" value="{s get(wh,concat('where-ge-',f.id))}" />
и &le; <input style="width: 100px" type="text" name="where-le-{f.id}" value="{s get(wh,concat('where-le-',f.id))}" />
или &ge; <input style="width: 100px" type="text" name="where-ge-{f.id}" value="{s get(request,concat('where-ge-',f.id))}" />
и &le; <input style="width: 100px" type="text" name="where-le-{f.id}" value="{s get(request,concat('where-le-',f.id))}" />
<!-- END -->
<!-- IF f.is_time --> (YYYY-MM-DD HH:MM:SS)<!-- END -->
{f.comment}
@ -98,17 +105,98 @@
<p><a href="{s rpt_link}">Ссылка на данный отчёт</a> | <a href="{s csv_link}">В формате CSV</a></p>
<!-- IF and(not tables, not graphs) -->
<p>Нет данных для показа.</p>
<!-- ELSIF groups -->
<!-- FOR group = groups -->
<div style="width: 600px; height: 300px; margin-top: 20px" id="group{group#}"></div>
<!-- END -->
<p>
<input type="checkbox" name="graph_log" id="graph_log" value="1"
onclick="doPlot(this.checked)" />
<label for="graph_log">Логарифмическая шкала графиков</label>
</p>
<script language="javascript" type="text/javascript">
var g = [
<!-- FOR group = groups -->{yesno(group#,',','')}
{
id: "group{group#}",
desc: "{q join(', ',group.full_desc)}",
data: [
<!-- FOR graph = group.graphs -->{yesno(graph#,',','')}
[
<!-- FOR g = graph.data -->
[ '{g.0}', {g.1} ],
<!-- END -->
]
<!-- END -->
],
x: "{q group.graphs.0.field}<!-- IF group.graphs.0.func --> ({q group.graphs.0.func})<!-- END -->",
titles: [
<!-- FOR graph = group.graphs -->{yesno(graph#,',','')}
'{q graph.last_desc}'
<!-- END -->
]
}
<!-- END -->
];
var yLabel = '{cell.fname}';
var plots = [];
function doPlot(logarithmic)
{
for (var i = 0; i < g.length; i++)
{
if (plots[i])
plots[i].destroy();
plots[i] = $.jqplot(g[i].id, g[i].data, {
seriesDefaults: {},
title: g[i].desc,
legend: {
show: true,
renderer: $.jqplot.EnhancedLegendRenderer,
location: 'ne',
labels: g[i].titles
},
axesDefaults: {
tickRenderer: $.jqplot.CanvasAxisTickRenderer,
tickOptions: {
fontSize: '10pt',
}
},
axes: {
xaxis: {
label: g[i].x,
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
renderer: $.jqplot.CategoryAxisRenderer,
sortMergedLabels: true,
tickOptions: {
angle: -30
}
},
yaxis: {
label: yLabel,
renderer: logarithmic ? $.jqplot.LogAxisRenderer : $.jqplot.LinearAxisRenderer,
labelRenderer: $.jqplot.CanvasAxisLabelRenderer,
tickOptions: {
labelPosition: 'end'
}
}
}
});
}
}
doPlot(false);
</script>
<!-- ELSIF graphs -->
<!-- FOR graph = graphs -->
<!-- IF graph.desc -->
<!-- SET o = 0 -->
<!-- FOR d = graph.desc -->
<!--# Описание графика (поле: значение, ..., только значения, не равные значениям предыдущего графика) -->
<!-- SET o = or(o, not(graph#), ne(get(get(get(get(graphs,sub(graph#,1)),'desc'),d#),'value'),d.value)) -->
<!-- IF o -->
<p style="margin-left: {mul(d#,20)}px">{d.field}<!-- IF d.func --> ({lc d.func})<!-- END -->: {d.value}</p>
<!-- END -->
<!-- END -->
<div style="margin-left: {mul(count(table.desc),20)}px">
<div style="margin-left: {mul(count(graph.desc),20)}px">
<!-- END -->
<table>
<!-- IF graph.bi_sign -->

1
tpldata/excanvas.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
tpldata/jqplot.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
tpldata/jqplot.plugins.all.min.js vendored Normal file

File diff suppressed because one or more lines are too long

259
tpldata/jquery.jqplot.css Normal file
View File

@ -0,0 +1,259 @@
/*rules for the plot target div. These will be cascaded down to all plot elements according to css rules*/
.jqplot-target {
position: relative;
color: #666666;
font-family: "Trebuchet MS", Arial, Helvetica, sans-serif;
font-size: 1em;
/* height: 300px;
width: 400px;*/
}
/*rules applied to all axes*/
.jqplot-axis {
font-size: 0.75em;
}
.jqplot-xaxis {
margin-top: 10px;
}
.jqplot-x2axis {
margin-bottom: 10px;
}
.jqplot-yaxis {
margin-right: 10px;
}
.jqplot-y2axis, .jqplot-y3axis, .jqplot-y4axis, .jqplot-y5axis, .jqplot-y6axis, .jqplot-y7axis, .jqplot-y8axis, .jqplot-y9axis, .jqplot-yMidAxis {
margin-left: 10px;
margin-right: 10px;
}
/*rules applied to all axis tick divs*/
.jqplot-axis-tick, .jqplot-xaxis-tick, .jqplot-yaxis-tick, .jqplot-x2axis-tick, .jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick, .jqplot-yMidAxis-tick {
position: absolute;
white-space: pre;
}
.jqplot-xaxis-tick {
top: 0px;
/* initial position untill tick is drawn in proper place */
left: 15px;
/* padding-top: 10px;*/
vertical-align: top;
}
.jqplot-x2axis-tick {
bottom: 0px;
/* initial position untill tick is drawn in proper place */
left: 15px;
/* padding-bottom: 10px;*/
vertical-align: bottom;
}
.jqplot-yaxis-tick {
right: 0px;
/* initial position untill tick is drawn in proper place */
top: 15px;
/* padding-right: 10px;*/
text-align: right;
}
.jqplot-yaxis-tick.jqplot-breakTick {
right: -20px;
margin-right: 0px;
padding:1px 5px 1px 5px;
/* background-color: white;*/
z-index: 2;
font-size: 1.5em;
}
.jqplot-y2axis-tick, .jqplot-y3axis-tick, .jqplot-y4axis-tick, .jqplot-y5axis-tick, .jqplot-y6axis-tick, .jqplot-y7axis-tick, .jqplot-y8axis-tick, .jqplot-y9axis-tick {
left: 0px;
/* initial position untill tick is drawn in proper place */
top: 15px;
/* padding-left: 10px;*/
/* padding-right: 15px;*/
text-align: left;
}
.jqplot-yMidAxis-tick {
text-align: center;
white-space: nowrap;
}
.jqplot-xaxis-label {
margin-top: 10px;
font-size: 11pt;
position: absolute;
}
.jqplot-x2axis-label {
margin-bottom: 10px;
font-size: 11pt;
position: absolute;
}
.jqplot-yaxis-label {
margin-right: 10px;
/* text-align: center;*/
font-size: 11pt;
position: absolute;
}
.jqplot-yMidAxis-label {
font-size: 11pt;
position: absolute;
}
.jqplot-y2axis-label, .jqplot-y3axis-label, .jqplot-y4axis-label, .jqplot-y5axis-label, .jqplot-y6axis-label, .jqplot-y7axis-label, .jqplot-y8axis-label, .jqplot-y9axis-label {
/* text-align: center;*/
font-size: 11pt;
margin-left: 10px;
position: absolute;
}
.jqplot-meterGauge-tick {
font-size: 0.75em;
color: #999999;
}
.jqplot-meterGauge-label {
font-size: 1em;
color: #999999;
}
table.jqplot-table-legend {
margin-top: 12px;
margin-bottom: 12px;
margin-left: 12px;
margin-right: 12px;
}
table.jqplot-table-legend, table.jqplot-cursor-legend {
background-color: rgba(255,255,255,0.6);
border: 1px solid #cccccc;
position: absolute;
font-size: 0.75em;
}
td.jqplot-table-legend {
vertical-align:middle;
}
/*
These rules could be used instead of assigning
element styles and relying on js object properties.
*/
/*
td.jqplot-table-legend-swatch {
padding-top: 0.5em;
text-align: center;
}
tr.jqplot-table-legend:first td.jqplot-table-legend-swatch {
padding-top: 0px;
}
*/
td.jqplot-seriesToggle:hover, td.jqplot-seriesToggle:active {
cursor: pointer;
}
.jqplot-table-legend .jqplot-series-hidden {
text-decoration: line-through;
}
div.jqplot-table-legend-swatch-outline {
border: 1px solid #cccccc;
padding:1px;
}
div.jqplot-table-legend-swatch {
width:0px;
height:0px;
border-top-width: 5px;
border-bottom-width: 5px;
border-left-width: 6px;
border-right-width: 6px;
border-top-style: solid;
border-bottom-style: solid;
border-left-style: solid;
border-right-style: solid;
}
.jqplot-title {
top: 0px;
left: 0px;
padding-bottom: 0.5em;
font-size: 1.2em;
}
table.jqplot-cursor-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
}
.jqplot-cursor-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
white-space: nowrap;
background: rgba(208,208,208,0.5);
padding: 1px;
}
.jqplot-highlighter-tooltip, .jqplot-canvasOverlay-tooltip {
border: 1px solid #cccccc;
font-size: 0.75em;
white-space: nowrap;
background: rgba(208,208,208,0.5);
padding: 1px;
}
.jqplot-point-label {
font-size: 0.75em;
z-index: 2;
}
td.jqplot-cursor-legend-swatch {
vertical-align: middle;
text-align: center;
}
div.jqplot-cursor-legend-swatch {
width: 1.2em;
height: 0.7em;
}
.jqplot-error {
/* Styles added to the plot target container when there is an error go here.*/
text-align: center;
}
.jqplot-error-message {
/* Styling of the custom error message div goes here.*/
position: relative;
top: 46%;
display: inline-block;
}
div.jqplot-bubble-label {
font-size: 0.8em;
/* background: rgba(90%, 90%, 90%, 0.15);*/
padding-left: 2px;
padding-right: 2px;
color: rgb(20%, 20%, 20%);
}
div.jqplot-bubble-label.jqplot-bubble-label-highlight {
background: rgba(90%, 90%, 90%, 0.7);
}
div.jqplot-noData-container {
text-align: center;
background-color: rgba(96%, 96%, 96%, 0.3);
}

23
tpldata/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long