From 18a7078d330eca6137fc74258348e2f23a0432aa Mon Sep 17 00:00:00 2001 From: Steffen Vogel Date: Wed, 6 Apr 2011 23:49:54 +0200 Subject: [PATCH] added more options to configuration added snapshot button --- etc/volkszaehler.conf.template.php | 10 +- htdocs/frontend/index.html | 6 +- htdocs/frontend/javascripts/init.js | 4 + htdocs/frontend/javascripts/wui.js | 187 ++++++++++++++-------- htdocs/frontend/stylesheets/style.css | 2 +- htdocs/middleware.php | 6 +- lib/Controller/CapabilitiesController.php | 8 + lib/Router.php | 17 +- lib/View/JpGraph.php | 61 ++++--- 9 files changed, 186 insertions(+), 115 deletions(-) diff --git a/etc/volkszaehler.conf.template.php b/etc/volkszaehler.conf.template.php index abd015e..0411d6d 100644 --- a/etc/volkszaehler.conf.template.php +++ b/etc/volkszaehler.conf.template.php @@ -71,11 +71,19 @@ $config['db']['dbname'] = 'volkszaehler'; //$config['db']['path'] = 'volkszaehler'; /** + * Vendor libs * Set to NULL to use PHP's include path - * @var string path of Doctrine library + * @var string path to vendor libs * @link http://www.php.net/manual/en/ini.core.php#ini.include-path */ $config['lib']['doctrine'] = VZ_DIR . '/lib/vendor/Doctrine'; +$config['lib']['jpgraph'] = VZ_DIR . '/lib/vendor/JpGraph'; + +/** + * Plot colors + * @var array of colors for plot series + */ +$config['colors'] = array('#83CAFF', '#7E0021', '#579D1C', '#FFD320', '#FF420E', '#004586', '#0084D1', '#C5000B', '#FF950E', '#4B1F6F', '#AECF00', '#314004'); /** @var boolean disables some optimizations. Only use it when you exactly know what you are doing. */ $config['devmode'] = FALSE; diff --git a/htdocs/frontend/index.html b/htdocs/frontend/index.html index 93d48eb..9a24983 100644 --- a/htdocs/frontend/index.html +++ b/htdocs/frontend/index.html @@ -16,7 +16,6 @@ - @@ -42,7 +41,10 @@

- +
diff --git a/htdocs/frontend/javascripts/init.js b/htdocs/frontend/javascripts/init.js index 7c231a9..89e69e6 100644 --- a/htdocs/frontend/javascripts/init.js +++ b/htdocs/frontend/javascripts/init.js @@ -86,6 +86,10 @@ $(document).ready(function() { // chaining ajax request with jquery deferred object vz.capabilities.load().done(function() { + if (vz.capabilities.formats.contains('png')) { + $('#snapshot').show(); + } + vz.entities.loadDetails().done(function(a, b, c, d) { vz.entities.showTable(); vz.entities.loadData().done(vz.wui.drawPlot); diff --git a/htdocs/frontend/javascripts/wui.js b/htdocs/frontend/javascripts/wui.js index 8ab8612..a545373 100644 --- a/htdocs/frontend/javascripts/wui.js +++ b/htdocs/frontend/javascripts/wui.js @@ -38,27 +38,9 @@ vz.wui.init = function() { // buttons $('button, input[type=button],[type=image],[type=submit]').button(); $('button[name=options-save]').click(vz.options.save); - $('#permalink').click(function() { - var uuids = []; - var url = window.location.protocol + '//' + - window.location.host + - window.location.pathname + - '?from=' + vz.options.plot.xaxis.min + - '&to=' + vz.options.plot.xaxis.max; - - vz.entities.each(function(entity, parent) { - if (entity.active && entity.definition.model == 'Volkszaehler\\Model\\Channel') { - uuids.push(entity.uuid); - } - }); - - uuids.unique().each(function(key, value) { - url += '&uuid=' + value; - }); - - window.location = url; - }); $('button[name=entity-add]').click(this.dialogs.init); + $('#permalink').click(function() { window.location = vz.wui.getPermalink(); }); + $('#snapshot').click(function() { window.location = vz.wui.getSnaplink(); }).hide(); // bind plot actions $('#controls button').click(this.handleControls); @@ -81,10 +63,12 @@ vz.wui.init = function() { // auto refresh if (vz.options.refresh) { $('#refresh').attr('checked', true); + vz.wui.refresh(); // refresh once vz.wui.setTimeout(); } $('#refresh').change(function() { if (vz.options.refresh = $(this).attr('checked')) { + vz.wui.refresh(); // refresh once vz.wui.setTimeout(); } else { vz.wui.clearTimeout(); @@ -206,17 +190,71 @@ vz.wui.dialogs.init = function() { }); }; +/** + * Build link to current viewport + * + * @return string url + */ +vz.wui.getPermalink = function() { + var uuids = new Array; + vz.entities.each(function(entity, parent) { + if (entity.active && entity.definition.model == 'Volkszaehler\\Model\\Channel') { + uuids.push(entity.uuid); + } + }); + + var params = $.param({ + from: Math.floor(vz.options.plot.xaxis.min), + to: Math.ceil(vz.options.plot.xaxis.max), + uuid: uuids.unique() + }); + + return window.location.protocol + '//' + window.location.host + window.location.pathname + '?' + params; +} + +/** + * Build link to rendered image of current viewport + * + * @return string url + */ +vz.wui.getSnaplink = function() { + var uuids = new Array; + vz.entities.each(function(entity, parent) { + if (entity.active) { + uuids.push(entity.uuid); + } + }); + + return vz.options.middlewareUrl + '/data/' + uuids[0] + '.png?' + $.param({ + from: Math.floor(vz.options.plot.xaxis.min), + to: Math.ceil(vz.options.plot.xaxis.max) + }); +} + +vz.wui.zoom = function(from, to) { + vz.options.plot.xaxis.min = from; + vz.options.plot.xaxis.max = to; + + // we dont want to zoom/pan into the future + if (vz.options.plot.xaxis.max > new Date().getTime()) { + delta = vz.options.plot.xaxis.max - vz.options.plot.xaxis.min; + vz.options.plot.xaxis.max = new Date().getTime(); + vz.options.plot.xaxis.min = new Date().getTime() - delta; + } + + vz.options.plot.yaxis.max = null; // autoscaling + vz.options.plot.yaxis.min = 0; // fixed to 0 + + vz.entities.loadData().done(vz.wui.drawPlot); +} + /** * Bind events to handle plot zooming & panning */ vz.wui.initEvents = function() { $('#plot') .bind("plotselected", function (event, ranges) { - vz.options.plot.xaxis.min = ranges.xaxis.from; - vz.options.plot.xaxis.max = ranges.xaxis.to; - vz.options.plot.yaxis.max = null; // autoscaling - vz.options.plot.yaxis.min = 0; // fixed to 0 - vz.entities.loadData().done(vz.wui.drawPlot); + vz.wui.zoom(ranges.xaxis.from, ranges.xaxis.to); }) /*.bind('plotpan', function (event, plot) { var axes = plot.getAxes(); @@ -247,68 +285,77 @@ vz.wui.handleControls = function () { switch($(this).val()) { case 'move-last': - vz.options.plot.xaxis.max = new Date().getTime(); - vz.options.plot.xaxis.min = new Date().getTime() - delta; + vz.wui.zoom( + new Date().getTime() - delta, + new Date().getTime() + ); break; case 'move-back': - vz.options.plot.xaxis.min -= delta; - vz.options.plot.xaxis.max -= delta; + vz.wui.zoom( + vz.options.plot.xaxis.min - delta, + vz.options.plot.xaxis.max - delta + ); break; case 'move-forward': - vz.options.plot.xaxis.min += delta; - vz.options.plot.xaxis.max += delta; + vz.wui.zoom( + vz.options.plot.xaxis.min + delta, + vz.options.plot.xaxis.max + delta + ); break; case 'zoom-reset': - vz.options.plot.xaxis.min = middle - vz.options.defaultInterval/2; - vz.options.plot.xaxis.max = middle + vz.options.defaultInterval/2; + vz.wui.zoom( + middle - vz.options.defaultInterval/2, + middle + vz.options.defaultInterval/2 + ); break; case 'zoom-in': - vz.options.plot.xaxis.min += delta/4; - vz.options.plot.xaxis.max -= delta/4; + vz.wui.zoom( + middle - delta/4, + middle + delta/4 + ); break; case 'zoom-out': - vz.options.plot.xaxis.min -= delta; - vz.options.plot.xaxis.max += delta; + vz.wui.zoom( + middle - delta, + middle + delta + ); break; case 'zoom-hour': hour = 60*60*1000; - vz.options.plot.xaxis.min = middle - hour/2; - vz.options.plot.xaxis.max = middle + hour/2; + vz.wui.zoom( + middle - hour/2, + middle + hour/2 + ); break; case 'zoom-day': var day = 24*60*60*1000; - vz.options.plot.xaxis.min = middle - day/2; - vz.options.plot.xaxis.max = middle + day/2; + vz.wui.zoom( + middle - day/2, + middle + day/2 + ); break; case 'zoom-week': var week = 7*24*60*60*1000; - vz.options.plot.xaxis.min = middle - week/2; - vz.options.plot.xaxis.max = middle + week/2; + vz.wui.zoom( + middle - week/2, + middle + week/2 + ); break; case 'zoom-month': var month = 30*24*60*60*1000; - vz.options.plot.xaxis.min = middle - month/2; - vz.options.plot.xaxis.max = middle + month/2; + vz.wui.zoom( + middle - month/2, + middle + month/2 + ); break; case 'zoom-year': var year = 365*24*60*60*1000; - vz.options.plot.xaxis.min = middle - year/2; - vz.options.plot.xaxis.max = middle + year/2; + vz.wui.zoom( + middle - year/2, + middle + year/2 + ); break; } - - // reenable autoscaling for yaxis - vz.options.plot.yaxis.max = null; // autoscaling - vz.options.plot.yaxis.min = 0; // fixed to 0 - - // we dont want to zoom/pan into the future - if (vz.options.plot.xaxis.max > new Date().getTime()) { - delta = vz.options.plot.xaxis.max - vz.options.plot.xaxis.min; - vz.options.plot.xaxis.max = new Date().getTime(); - vz.options.plot.xaxis.min = new Date().getTime() - delta; - } - - vz.entities.loadData().done(vz.wui.drawPlot); }; /** @@ -316,10 +363,10 @@ vz.wui.handleControls = function () { */ vz.wui.refresh = function() { var delta = vz.options.plot.xaxis.max - vz.options.plot.xaxis.min; - - vz.options.plot.xaxis.max = new Date().getTime(); // move plot - vz.options.plot.xaxis.min = vz.options.plot.xaxis.max - delta; // move plot - vz.entities.loadData().done(vz.wui.drawPlot); + vz.wui.zoom( // move plot + new Date().getTime() - delta, + new Date().getTime() + ); }; /** @@ -329,8 +376,9 @@ vz.wui.setTimeout = function() { // clear an already set timeout if (vz.wui.timeout != null) { window.clearTimeout(vz.wui.timeout); + vz.wui.timeout = null; } - + var t = Math.max((vz.options.plot.xaxis.max - vz.options.plot.xaxis.min) / vz.options.tuples, vz.options.minTimeout); vz.wui.timeout = window.setTimeout(vz.wui.refresh, t); @@ -340,8 +388,8 @@ vz.wui.setTimeout = function() { /** * Stop auto-refresh of graphs */ -vz.wui.clearTimeout = function() { - $('#refresh-time').html(''); +vz.wui.clearTimeout = function(text) { + $('#refresh-time').html(text || ''); var rc = window.clearTimeout(vz.wui.timeout); vz.wui.timeout = null; @@ -545,7 +593,7 @@ vz.wui.drawPlot = function () { if (series.length == 0) { $('#overlay').html('no data...

nothing to plot...

'); - series.push({}); // add empty dataset to show axes + series.push({}); // add empty dataset to show axes } else { $('#overlay').empty(); @@ -555,8 +603,7 @@ vz.wui.drawPlot = function () { // disable automatic refresh if we are in past if (vz.options.refresh && vz.options.plot.xaxis.max < new Date().getTime() - 1000) { - $('#refresh').attr('checked', vz.options.refresh = false); - vz.wui.clearTimeout(); + vz.wui.clearTimeout('(suspended)'); } else if (vz.options.refresh) { vz.wui.setTimeout(); diff --git a/htdocs/frontend/stylesheets/style.css b/htdocs/frontend/stylesheets/style.css index 320bfaf..7f22f17 100644 --- a/htdocs/frontend/stylesheets/style.css +++ b/htdocs/frontend/stylesheets/style.css @@ -100,7 +100,7 @@ tbody tr td { float: left; } -#permalink { +#links { float: right; } diff --git a/htdocs/middleware.php b/htdocs/middleware.php index 9d599d8..23107f6 100644 --- a/htdocs/middleware.php +++ b/htdocs/middleware.php @@ -42,8 +42,12 @@ require_once VZ_DIR . '/lib/Util/Configuration.php'; // load configuration Util\Configuration::load(VZ_DIR . '/etc/volkszaehler.conf'); +// define include dirs for vendor libs +define('DOCTRINE_DIR', Util\Configuration::read('lib.doctrine') ? Util\Configuration::read('lib.doctrine') : 'Doctrine'); +define('JPGRAPH_DIR', Util\Configuration::read('lib.jpgraph') ? Util\Configuration::read('lib.jpgraph') : 'JpGraph'); + $classLoaders = array( - new Util\ClassLoader('Doctrine', (is_null(Util\Configuration::read('lib.doctrine'))) ? 'Doctrine' : Util\Configuration::read('lib.doctrine')), + new Util\ClassLoader('Doctrine', DOCTRINE_DIR), new Util\ClassLoader('Volkszaehler', VZ_DIR . '/lib') ); diff --git a/lib/Controller/CapabilitiesController.php b/lib/Controller/CapabilitiesController.php index 80183a8..780c9de 100644 --- a/lib/Controller/CapabilitiesController.php +++ b/lib/Controller/CapabilitiesController.php @@ -64,6 +64,14 @@ class CapabilitiesController extends Controller { $capabilities['statistics'] = $statistics; } + if (is_null($section) || $section == 'formats') { + $capabilities['formats'] = array_keys(\Volkszaehler\Router::$viewMapping); + } + + if (is_null($section) || $section == 'contexts') { + $capabilities['contexts'] = array_keys(\Volkszaehler\Router::$controllerMapping); + } + if (is_null($section) || $section == 'definitions') { if (!is_null($section)) { // only caching when we doesn't request dynamic informations $this->view->setCaching('expires', time()+2*7*24*60*60); // cache for 2 weeks diff --git a/lib/Router.php b/lib/Router.php index 492cd67..0de5b63 100644 --- a/lib/Router.php +++ b/lib/Router.php @@ -61,7 +61,7 @@ class Router { /** * @var array HTTP-method => operation mapping */ - protected static $operationMapping = array( + public static $operationMapping = array( 'post' => 'add', 'delete' => 'delete', 'get' => 'get', @@ -71,7 +71,7 @@ class Router { /** * @var array context => controller mapping */ - protected static $controllerMapping = array( + public static $controllerMapping = array( 'channel' => 'Volkszaehler\Controller\ChannelController', 'group' => 'Volkszaehler\Controller\AggregatorController', 'group' => 'Volkszaehler\Controller\AggregatorController', @@ -83,11 +83,7 @@ class Router { /** * @var array format => view mapping */ - protected static $viewMapping = array( - 'png' => 'Volkszaehler\View\JpGraph', - 'jpeg' => 'Volkszaehler\View\JpGraph', - 'jpg' => 'Volkszaehler\View\JpGraph', - 'gif' => 'Volkszaehler\View\JpGraph', + public static $viewMapping = array( 'xml' => 'Volkszaehler\View\XML', 'csv' => 'Volkszaehler\View\CSV', 'json' => 'Volkszaehler\View\JSON', @@ -110,6 +106,13 @@ class Router { $this->debug = new Util\Debug($debugLevel, $this->em); } } + + // check for JpGraph + if (file_exists(JPGRAPH_DIR . '/jpgraph.php')) { + foreach (array('png', 'jpeg', 'jpg', 'gif') as $format) { + self::$viewMapping[$format] = 'Volkszaehler\View\JpGraph'; + } + } // initialize view $this->pathInfo = self::getPathInfo(); diff --git a/lib/View/JpGraph.php b/lib/View/JpGraph.php index cbde4f9..8fc43c4 100644 --- a/lib/View/JpGraph.php +++ b/lib/View/JpGraph.php @@ -27,9 +27,9 @@ use Volkszaehler\Interpreter; use Volkszaehler\Model; use Volkszaehler\Util; -require_once VZ_DIR . '/lib/vendor/JpGraph/jpgraph.php'; -require_once VZ_DIR . '/lib/vendor/JpGraph/jpgraph_scatter.php'; -require_once VZ_DIR . '/lib/vendor/JpGraph/jpgraph_date.php'; +require_once JPGRAPH_DIR . '/jpgraph.php'; +require_once JPGRAPH_DIR . '/jpgraph_line.php'; +require_once JPGRAPH_DIR . '/jpgraph_date.php'; /** * Plotting and graphing of data on the server side @@ -48,7 +48,7 @@ class JpGraph extends View { */ protected $axes = array(); - protected $channels = array(); + protected $count = 0; /** * @var default width @@ -64,10 +64,10 @@ class JpGraph extends View { * @var color palette for the scatter plots * This are the same colors as in the webfronted */ - protected static $colors = array('#83CAFF', '#7E0021', '#579D1C', '#FFD320', '#FF420E', '#004586', '#0084D1', '#C5000B', '#FF950E', '#4B1F6F', '#AECF00', '#314004'); + protected $colors; /** - * @var JPGrpah handle + * @var JPGraph handle */ protected $graph; @@ -92,6 +92,7 @@ class JpGraph extends View { $this->height = $this->request->getParameter('height'); } + $this->colors = Util\Configuration::read('colors'); $this->graph = new \Graph($this->width, $this->height); $this->graph->img->SetImgFormat($format); @@ -99,8 +100,9 @@ class JpGraph extends View { // Specify what scale we want to use, $this->graph->SetScale('datlin'); - $this->graph->legend->SetPos(0.1,0.02, 'left', 'top'); + $this->graph->legend->SetPos(0.03, 0.06); $this->graph->legend->SetShadow(FALSE); + $this->graph->legend->SetFrameWeight(1); $this->graph->SetMarginColor('white'); $this->graph->SetYDeltaDist(65); @@ -121,17 +123,17 @@ class JpGraph extends View { * @param mixed $data */ public function add($data) { - if ($data instanceof Interpreter\Interpreter || $data instanceof Interpreter\AggregatorInterpreter) { + if ($data instanceof Interpreter\Interpreter) { $this->addData($data); } elseif($data instanceof Interpreter\AggregatorInterpreter) { - foreach ($data->getEntity()->getChildren() as $child) { - $this->add($child); + foreach ($data->getChildrenInterpreter() as $childInterpreter) { + $this->add($childInterpreter); } } else { // suppress other classes - //throw new \Exception('Can\'t show ' . get_class($data)); + //throw new \JpGraphException('Can\'t show ' . get_class($data)); } } @@ -141,28 +143,31 @@ class JpGraph extends View { * @param $obj * @param $data */ - public function addData(Interpreter\InterpreterInterface $interpreter){ - $data = $interpreter->processData($this->width/4); + public function addData($interpreter) { + if (is_null($interpreter->getTupleCount())) { + $interpreter->setTupleCount($this->width); + } + + $data = $interpreter->processData(function($tuple) { + $tuple[0] /= 1000; + return $tuple; + }); if (count($data) > 0) { - $count = count($this->channels); $xData = $yData = array(); + // TODO adjust x-Axis foreach ($data as $reading) { - $xData[] = $reading[0] / 1000; + $xData[] = $reading[0]; $yData[] = $reading[1]; } // Create the scatter plot - $plot = new \ScatterPlot($yData, $xData); + $plot = new \LinePlot($yData, $xData); $plot->setLegend($interpreter->getEntity()->getProperty('title') . ': [' . $interpreter->getEntity()->getDefinition()->getUnit() . ']'); - $plot->SetLinkPoints(TRUE, self::$colors[$count]); - - $plot->mark->SetColor(self::$colors[$count]); - $plot->mark->SetFillColor(self::$colors[$count]); - $plot->mark->SetType(MARK_DIAMOND); - $plot->mark->SetWidth(1); + $plot->SetColor($this->colors[$this->count]); + $plot->SetStepStyle($interpreter instanceof Interpreter\MeterInterpreter); $axis = $this->getAxisIndex($interpreter->getEntity()); if ($axis >= 0) { @@ -172,20 +177,10 @@ class JpGraph extends View { $this->graph->Add($plot); } - $this->channels[] = $interpreter->getEntity(); + $this->count++; } } - /** - * Shows exception - * - * @todo avoid graph plotting and set content-type to text/plain - * @param \Exception $exception - */ - protected function addException(\Exception $exception) { - echo $exception; - } - /** * check weather a axis for the indicator of $channel exists *