thingsboard-aplcache

Changes

Details

diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
index ed48206..e95496b 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
@@ -23,19 +23,21 @@ public class BaseTsKvQuery implements TsKvQuery {
     private final String key;
     private final long startTs;
     private final long endTs;
+    private final long interval;
     private final int limit;
     private final Aggregation aggregation;
 
-    public BaseTsKvQuery(String key, long startTs, long endTs, int limit, Aggregation aggregation) {
+    public BaseTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
         this.key = key;
         this.startTs = startTs;
         this.endTs = endTs;
+        this.interval = interval;
         this.limit = limit;
         this.aggregation = aggregation;
     }
 
     public BaseTsKvQuery(String key, long startTs, long endTs) {
-        this(key, startTs, endTs, 1, Aggregation.AVG);
+        this(key, startTs, endTs, endTs-startTs, 1, Aggregation.AVG);
     }
 
 }
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
index 10a13ce..8d60f52 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
@@ -25,6 +25,8 @@ public interface TsKvQuery {
 
     long getEndTs();
 
+    long getInterval();
+
     int getLimit();
 
     Aggregation getAggregation();
diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java
index 10651cb..c7584fe 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesDao.java
@@ -112,13 +112,13 @@ public class BaseTimeseriesDao extends AbstractAsyncDao implements TimeseriesDao
         if (query.getAggregation() == Aggregation.NONE) {
             return findAllAsyncWithLimit(entityType, entityId, query);
         } else {
-            long step = Math.max((query.getEndTs() - query.getStartTs()) / query.getLimit(), minAggregationStepMs);
+            long step = Math.max(query.getInterval(), minAggregationStepMs);
             long stepTs = query.getStartTs();
             List<ListenableFuture<Optional<TsKvEntry>>> futures = new ArrayList<>();
             while (stepTs < query.getEndTs()) {
                 long startTs = stepTs;
                 long endTs = stepTs + step;
-                TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, 1, query.getAggregation());
+                TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation());
                 futures.add(findAndAggregateAsync(entityType, entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs)));
                 stepTs = endTs;
             }
diff --git a/dao/src/main/resources/system-data.cql b/dao/src/main/resources/system-data.cql
index e7ccc56..cb274a5 100644
--- a/dao/src/main/resources/system-data.cql
+++ b/dao/src/main/resources/system-data.cql
@@ -272,17 +272,17 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'maps', 'route_map',
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
 VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'pie',
-'{"type":"latest","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.pie-label {\n    font-size: 12px;\n    font-family: ''Roboto'';\n    font-weight: bold;\n    text-align: center;\n    padding: 2px;\n    color: white;\n}\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, ''pie'');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.pieSettingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.pieDatakeySettingsSchema;\n}\n\nself.onDestroy = function() {\n}\n","settingsSchema":"{}\n","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"}',
+'{"type":"latest","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.pie-label {\n    font-size: 12px;\n    font-family: ''Roboto'';\n    font-weight: bold;\n    text-align: center;\n    padding: 2px;\n    color: white;\n}\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, ''pie'');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.pieSettingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.pieDatakeySettingsSchema;\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n","settingsSchema":"{}\n","dataKeySettingsSchema":"{}\n","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#4caf50\",\"settings\":{},\"_hash\":0.6114638304362894,\"funcBody\":\"var value = (prevValue-20) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+20;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Third\",\"color\":\"#f44336\",\"settings\":{},\"_hash\":0.9955906536344441,\"funcBody\":\"var value = (prevValue-40) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+40;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Fourth\",\"color\":\"#ffc107\",\"settings\":{},\"_hash\":0.9430835931647599,\"funcBody\":\"var value = (prevValue-50) + Math.random() * 2 - 1;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value+50;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"radius\":1,\"fontColor\":\"#545454\",\"fontSize\":10,\"decimals\":1,\"legend\":{\"show\":true,\"position\":\"nw\",\"labelBoxBorderColor\":\"#CCCCCC\",\"backgroundColor\":\"#F0F0F0\",\"backgroundOpacity\":0.85},\"innerRadius\":0,\"showLabels\":true,\"stroke\":{\"width\":5},\"tilt\":1,\"animatedPie\":false},\"title\":\"Pie - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400}}"}',
 'Pie - Flot' );
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
 VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'timeseries_bars_flot',
-'{"type":"timeseries","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, ''bar'');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n}\n","settingsSchema":"{}","dataKeySettingsSchema":"{}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":true,\"tooltipIndividual\":false},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"}',
+'{"type":"timeseries","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx, ''bar'');    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n","settingsSchema":"{}","dataKeySettingsSchema":"{}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":false,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 0) {\\n\\tvalue = 0;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000},\"aggregation\":{\"limit\":200,\"type\":\"AVG\"}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":true,\"tooltipIndividual\":false},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"}',
 'Timeseries Bars - Flot' );
 
 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
 VALUES ( now ( ), minTimeuuid ( 0 ), 'charts', 'basic_timeseries',
-'{"type":"timeseries","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx);    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n}\n","settingsSchema":"{}","dataKeySettingsSchema":"{}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"}',
+'{"type":"timeseries","sizeX":8,"sizeY":5,"resources":[],"templateHtml":"","templateCss":".legend {\n    font-size: 13px;\n    line-height: 10px;\n}\n\n.legend table { \n    border-spacing: 0px;\n    border-collapse: separate;\n}\n\n.mouse-events .flot-overlay {\n    cursor: crosshair; \n}\n\n","controllerScript":"self.onInit = function() {\n    self.ctx.flot = new TbFlot(self.ctx);    \n}\n\nself.onDataUpdated = function() {\n    self.ctx.flot.update();\n}\n\nself.onResize = function() {\n    self.ctx.flot.resize();\n}\n\nself.onEditModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.onMobileModeChanged = function() {\n    self.ctx.flot.checkMouseEvents();\n}\n\nself.getSettingsSchema = function() {\n    return TbFlot.settingsSchema;\n}\n\nself.getDataKeySettingsSchema = function() {\n    return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n    self.ctx.flot.destroy();\n}\n","settingsSchema":"{}","dataKeySettingsSchema":"{}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"First\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Second\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"shadowSize\":4,\"fontColor\":\"#545454\",\"fontSize\":10,\"xaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"yaxis\":{\"showLabels\":true,\"color\":\"#545454\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"legend\":{\"show\":true,\"position\":\"nw\",\"backgroundColor\":\"#f0f0f0\",\"backgroundOpacity\":0.85,\"labelBoxBorderColor\":\"rgba(1, 1, 1, 0.45)\"},\"decimals\":1,\"stack\":false,\"tooltipIndividual\":false},\"title\":\"Timeseries - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null}"}',
 'Timeseries - Flot' );
 
 /** System plugins and rules **/
diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
index fd16b75..134f471 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/TimeseriesServiceTest.java
@@ -115,7 +115,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         entries.add(save(deviceId, 55000, 600));
 
         List<TsKvEntry> list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.NONE))).get();
+                60000, 20000, 3, Aggregation.NONE))).get();
         assertEquals(3, list.size());
         assertEquals(55000, list.get(0).getTs());
         assertEquals(java.util.Optional.of(600L), list.get(0).getLongValue());
@@ -127,7 +127,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
 
         list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.AVG))).get();
+                60000, 20000, 3, Aggregation.AVG))).get();
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
         assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue());
@@ -139,7 +139,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
 
         list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.SUM))).get();
+                60000, 20000, 3, Aggregation.SUM))).get();
 
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
@@ -152,7 +152,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
 
         list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.MIN))).get();
+                60000, 20000, 3, Aggregation.MIN))).get();
 
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
@@ -165,7 +165,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
 
         list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.MAX))).get();
+                60000, 20000, 3, Aggregation.MAX))).get();
 
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
@@ -178,7 +178,7 @@ public class TimeseriesServiceTest extends AbstractServiceTest {
         assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
 
         list = tsService.findAll(DataConstants.DEVICE, deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
-                60000, 3, Aggregation.COUNT))).get();
+                60000, 20000, 3, Aggregation.COUNT))).get();
 
         assertEquals(3, list.size());
         assertEquals(10000, list.get(0).getTs());
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java
index 9f06895..145f8c4 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/GetHistoryCmd.java
@@ -32,6 +32,7 @@ public class GetHistoryCmd implements TelemetryPluginCmd {
     private String keys;
     private long startTs;
     private long endTs;
+    private long interval;
     private int limit;
     private String agg;
 
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TimeseriesSubscriptionCmd.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TimeseriesSubscriptionCmd.java
index 20bd3e2..9f3f7ec 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TimeseriesSubscriptionCmd.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/cmd/TimeseriesSubscriptionCmd.java
@@ -30,6 +30,7 @@ public class TimeseriesSubscriptionCmd extends SubscriptionCmd {
 
     private long startTs;
     private long timeWindow;
+    private long interval;
     private int limit;
     private String agg;
 
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java
index 78fa4ad..fb50484 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryRestMsgHandler.java
@@ -89,11 +89,12 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
                     String keysStr = request.getParameter("keys");
                     Optional<Long> startTs = request.getLongParamValue("startTs");
                     Optional<Long> endTs = request.getLongParamValue("endTs");
+                    Optional<Long> interval = request.getLongParamValue("interval");
                     Optional<Integer> limit = request.getIntParamValue("limit");
                     Aggregation agg = Aggregation.valueOf(request.getParameter("agg", Aggregation.NONE.name()));
 
                     List<String> keys = Arrays.asList(keysStr.split(","));
-                    List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)).collect(Collectors.toList());
+                    List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs.get(), endTs.get(), interval.get(), limit.orElse(TelemetryWebsocketMsgHandler.DEFAULT_LIMIT), agg)).collect(Collectors.toList());
                     ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
                         @Override
                         public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
diff --git a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java
index 51181fd..f018aaa 100644
--- a/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java
+++ b/extensions-core/src/main/java/org/thingsboard/server/extensions/core/plugin/telemetry/handlers/TelemetryWebsocketMsgHandler.java
@@ -193,7 +193,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
                         log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), cmd.getDeviceId());
                         startTs = cmd.getStartTs();
                         long endTs = cmd.getStartTs() + cmd.getTimeWindow();
-                        List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
+                        List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
                         ctx.loadTimeseries(deviceId, queries, getSubscriptionCallback(sessionRef, cmd, sessionId, deviceId, startTs, keys));
                     } else {
                         List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
@@ -277,7 +277,7 @@ public class TelemetryWebsocketMsgHandler extends DefaultWebsocketMsgHandler {
         }
         DeviceId deviceId = DeviceId.fromString(cmd.getDeviceId());
         List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
-        List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
+        List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
         ctx.loadTimeseries(deviceId, queries, new PluginCallback<List<TsKvEntry>>() {
             @Override
             public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
diff --git a/ui/src/app/api/data-aggregator.js b/ui/src/app/api/data-aggregator.js
index e273a9d..31baff1 100644
--- a/ui/src/app/api/data-aggregator.js
+++ b/ui/src/app/api/data-aggregator.js
@@ -25,11 +25,12 @@ export default class DataAggregator {
         this.$timeout = $timeout;
         this.$filter = $filter;
         this.dataReceived = false;
+        this.resetPending = false;
         this.noAggregation = aggregationType === types.aggregation.none.value;
         this.limit = limit;
         this.timeWindow = timeWindow;
         this.interval = interval;
-        this.aggregationTimeout = this.interval;
+        this.aggregationTimeout = Math.max(this.interval, 1000);
         switch (aggregationType) {
             case types.aggregation.min.value:
                 this.aggFunction = min;
@@ -54,11 +55,37 @@ export default class DataAggregator {
         }
     }
 
+    reset(startTs, timeWindow, interval) {
+        if (this.intervalTimeoutHandle) {
+            this.$timeout.cancel(this.intervalTimeoutHandle);
+            this.intervalTimeoutHandle = null;
+        }
+        this.intervalScheduledTime = currentTime();
+        this.startTs = startTs;
+        this.timeWindow = timeWindow;
+        this.interval = interval;
+        this.endTs = this.startTs + this.timeWindow;
+        this.elapsed = 0;
+        this.aggregationTimeout = Math.max(this.interval, 1000);
+        this.resetPending = true;
+        var self = this;
+        this.intervalTimeoutHandle = this.$timeout(function() {
+            self.onInterval();
+        }, this.aggregationTimeout, false);
+    }
+
     onData(data, update, history) {
-        if (!this.dataReceived) {
-            this.elapsed = 0;
-            this.dataReceived = true;
-            this.endTs = this.startTs + this.timeWindow;
+        if (!this.dataReceived || this.resetPending) {
+            var updateIntervalScheduledTime = true;
+            if (!this.dataReceived) {
+                this.elapsed = 0;
+                this.dataReceived = true;
+                this.endTs = this.startTs + this.timeWindow;
+            }
+            if (this.resetPending) {
+                this.resetPending = false;
+                updateIntervalScheduledTime = false;
+            }
             if (update) {
                 this.aggregationMap = {};
                 updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
@@ -66,19 +93,24 @@ export default class DataAggregator {
             } else {
                 this.aggregationMap = processAggregatedData(data.data, this.aggregationType === this.types.aggregation.count.value, this.noAggregation);
             }
-            this.onInterval(currentTime(), history);
+            if (updateIntervalScheduledTime) {
+                this.intervalScheduledTime = currentTime();
+            }
+            this.onInterval(history);
         } else {
             updateAggregatedData(this.aggregationMap, this.aggregationType === this.types.aggregation.count.value,
                 this.noAggregation, this.aggFunction, data.data, this.interval, this.startTs);
             if (history) {
-                this.onInterval(currentTime(), history);
+                this.intervalScheduledTime = currentTime();
+                this.onInterval(history);
             }
         }
     }
 
-    onInterval(startedTime, history) {
+    onInterval(history) {
         var now = currentTime();
-        this.elapsed += now - startedTime;
+        this.elapsed += now - this.intervalScheduledTime;
+        this.intervalScheduledTime = now;
         if (this.intervalTimeoutHandle) {
             this.$timeout.cancel(this.intervalTimeoutHandle);
             this.intervalTimeoutHandle = null;
@@ -101,16 +133,11 @@ export default class DataAggregator {
         var self = this;
         if (!history) {
             this.intervalTimeoutHandle = this.$timeout(function() {
-                self.onInterval(now);
+                self.onInterval();
             }, this.aggregationTimeout, false);
         }
     }
 
-    reset() {
-        this.destroy();
-        this.dataReceived = false;
-    }
-
     destroy() {
         if (this.intervalTimeoutHandle) {
             this.$timeout.cancel(this.intervalTimeoutHandle);
diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js
index b44f85d..7d2c1be 100644
--- a/ui/src/app/api/datasource.service.js
+++ b/ui/src/app/api/datasource.service.js
@@ -254,6 +254,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                         keys: tsKeys,
                         startTs: subsTw.fixedWindow.startTimeMs,
                         endTs: subsTw.fixedWindow.endTimeMs,
+                        interval: subsTw.aggregation.interval,
                         limit: subsTw.aggregation.limit,
                         agg: subsTw.aggregation.type
                     };
@@ -266,9 +267,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                                 onData(data.data, types.dataKeyType.timeseries);
                             }
                         },
-                        onReconnected: function() {
-                            onReconnected();
-                        }
+                        onReconnected: function() {}
                     };
 
                     telemetryWebsocketService.subscribe(subscriber);
@@ -287,35 +286,26 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                     };
 
                     if (datasourceSubscription.type === types.widgetType.timeseries.value) {
-                        subscriptionCommand.startTs = subsTw.startTs;
-                        subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow;
-                        subscriptionCommand.limit = subsTw.aggregation.limit;
-                        subscriptionCommand.agg = subsTw.aggregation.type;
-                        dataAggregator = new DataAggregator(
-                            function(data, startTs, endTs) {
-                                onData(data, types.dataKeyType.timeseries, startTs, endTs);
-                            },
-                            tsKeyNames,
-                            subsTw.startTs,
-                            subsTw.aggregation.limit,
-                            subsTw.aggregation.type,
-                            subsTw.aggregation.timeWindow,
-                            subsTw.aggregation.interval,
-                            types,
-                            $timeout,
-                            $filter
-                        );
+                        updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw);
+                        dataAggregator = createRealtimeDataAggregator(subsTw, tsKeyNames);
                         subscriber.onData = function(data) {
                             dataAggregator.onData(data);
                         }
                         subscriber.onReconnected = function() {
-                            dataAggregator.reset();
-                            onReconnected();
+                            var newSubsTw = null;
+                            for (var i2 in listeners) {
+                                var listener = listeners[i2];
+                                if (!newSubsTw) {
+                                    newSubsTw = listener.updateRealtimeSubscription();
+                                } else {
+                                    listener.setRealtimeSubscription(newSubsTw);
+                                }
+                            }
+                            updateRealtimeSubscriptionCommand(this.subscriptionCommand, newSubsTw);
+                            dataAggregator.reset(newSubsTw.startTs,  newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval);
                         }
                     } else {
-                        subscriber.onReconnected = function() {
-                            onReconnected();
-                        }
+                        subscriber.onReconnected = function() {}
                         subscriber.onData = function(data) {
                             if (data.data) {
                                 onData(data.data, types.dataKeyType.timeseries);
@@ -344,9 +334,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                             onData(data.data, types.dataKeyType.attribute);
                         }
                     },
-                    onReconnected: function() {
-                        onReconnected();
-                    }
+                    onReconnected: function() {}
                 };
 
                 telemetryWebsocketService.subscribe(subscriber);
@@ -384,7 +372,31 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
                 timer = $timeout(onTick, 0, false);
             }
         }
+    }
+
+    function createRealtimeDataAggregator(subsTw, tsKeyNames) {
+        return new DataAggregator(
+            function(data, startTs, endTs) {
+                onData(data, types.dataKeyType.timeseries, startTs, endTs);
+            },
+            tsKeyNames,
+            subsTw.startTs,
+            subsTw.aggregation.limit,
+            subsTw.aggregation.type,
+            subsTw.aggregation.timeWindow,
+            subsTw.aggregation.interval,
+            types,
+            $timeout,
+            $filter
+        );
+    }
 
+    function updateRealtimeSubscriptionCommand(subscriptionCommand, subsTw) {
+        subscriptionCommand.startTs = subsTw.startTs;
+        subscriptionCommand.timeWindow = subsTw.aggregation.timeWindow;
+        subscriptionCommand.interval = subsTw.aggregation.interval;
+        subscriptionCommand.limit = subsTw.aggregation.limit;
+        subscriptionCommand.agg = subsTw.aggregation.type;
     }
 
     function unsubscribe() {
@@ -495,27 +507,6 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
         }
     }
 
-    function onReconnected() {
-        if (datasourceType === types.datasourceType.device) {
-            for (var key in dataKeys) {
-                var dataKeysList = dataKeys[key];
-                for (var i = 0; i < dataKeysList.length; i++) {
-                    var dataKey = dataKeysList[i];
-                    var datasourceKey = key + '_' + i;
-                    datasourceData[datasourceKey] = {
-                        data: []
-                    };
-                    for (var l in listeners) {
-                        var listener = listeners[l];
-                        listener.dataUpdated(datasourceData[datasourceKey],
-                            listener.datasourceIndex,
-                            dataKey.index);
-                    }
-                }
-            }
-        }
-    }
-
     function isNumeric(val) {
         return (val - parseFloat( val ) + 1) >= 0;
     }
diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js
new file mode 100644
index 0000000..a4bb571
--- /dev/null
+++ b/ui/src/app/api/time.service.js
@@ -0,0 +1,330 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export default angular.module('thingsboard.api.time', [])
+    .factory('timeService', TimeService)
+    .name;
+
+const SECOND = 1000;
+const MINUTE = 60 * SECOND;
+const HOUR = 60 * MINUTE;
+const DAY = 24 * HOUR;
+
+const MIN_INTERVAL = SECOND;
+const MAX_INTERVAL = 365 * 20 * DAY;
+
+const MIN_LIMIT = 10;
+const AVG_LIMIT = 200;
+const MAX_LIMIT = 500;
+
+/*@ngInject*/
+function TimeService($translate, types) {
+
+    var predefIntervals = [
+        {
+            name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
+            value: 1 * SECOND
+        },
+        {
+            name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
+            value: 5 * SECOND
+        },
+        {
+            name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
+            value: 10 * SECOND
+        },
+        {
+            name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
+            value: 15 * SECOND
+        },
+        {
+            name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
+            value: 30 * SECOND
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
+            value: 1 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
+            value: 2 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
+            value: 5 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
+            value: 10 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
+            value: 15 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
+            value: 30 * MINUTE
+        },
+        {
+            name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
+            value: 1 * HOUR
+        },
+        {
+            name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
+            value: 2 * HOUR
+        },
+        {
+            name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
+            value: 5 * HOUR
+        },
+        {
+            name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
+            value: 10 * HOUR
+        },
+        {
+            name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
+            value: 12 * HOUR
+        },
+        {
+            name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
+            value: 1 * DAY
+        },
+        {
+            name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
+            value: 7 * DAY
+        },
+        {
+            name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
+            value: 30 * DAY
+        }
+    ];
+
+    var service = {
+        minIntervalLimit: minIntervalLimit,
+        maxIntervalLimit: maxIntervalLimit,
+        boundMinInterval: boundMinInterval,
+        boundMaxInterval: boundMaxInterval,
+        getIntervals: getIntervals,
+        matchesExistingInterval: matchesExistingInterval,
+        boundToPredefinedInterval: boundToPredefinedInterval,
+        defaultTimewindow: defaultTimewindow,
+        toHistoryTimewindow: toHistoryTimewindow,
+        createSubscriptionTimewindow: createSubscriptionTimewindow,
+        avgAggregationLimit: function () {
+            return AVG_LIMIT;
+        }
+    }
+
+    return service;
+
+    function minIntervalLimit(timewindow) {
+        var min = timewindow / MAX_LIMIT;
+        return boundMinInterval(min);
+    }
+
+    function avgInterval(timewindow) {
+        var avg = timewindow / AVG_LIMIT;
+        return boundMinInterval(avg);
+    }
+
+    function maxIntervalLimit(timewindow) {
+        var max = timewindow / MIN_LIMIT;
+        return boundMaxInterval(max);
+    }
+
+    function boundMinInterval(min) {
+        return toBound(min, MIN_INTERVAL, MAX_INTERVAL, MIN_INTERVAL);
+    }
+
+    function boundMaxInterval(max) {
+        return toBound(max, MIN_INTERVAL, MAX_INTERVAL, MAX_INTERVAL);
+    }
+
+    function toBound(value, min, max, defValue) {
+        if (angular.isDefined(value)) {
+            value = Math.max(value, min);
+            value = Math.min(value, max);
+            return value;
+        } else {
+            return defValue;
+        }
+    }
+
+    function getIntervals(min, max) {
+        min = boundMinInterval(min);
+        max = boundMaxInterval(max);
+        var intervals = [];
+        for (var i in predefIntervals) {
+            var interval = predefIntervals[i];
+            if (interval.value >= min && interval.value <= max) {
+                intervals.push(interval);
+            }
+        }
+        return intervals;
+    }
+
+    function matchesExistingInterval(min, max, intervalMs) {
+        var intervals = getIntervals(min, max);
+        for (var i in intervals) {
+            var interval = intervals[i];
+            if (intervalMs === interval.value) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    function boundToPredefinedInterval(min, max, intervalMs) {
+        var intervals = getIntervals(min, max);
+        var minDelta = MAX_INTERVAL;
+        var boundedInterval = intervalMs || min;
+        var matchedInterval;
+        for (var i in intervals) {
+            var interval = intervals[i];
+            var delta = Math.abs(interval.value - boundedInterval);
+            if (delta < minDelta) {
+                matchedInterval = interval;
+                minDelta = delta;
+            }
+        }
+        boundedInterval = matchedInterval.value;
+        return boundedInterval;
+    }
+
+    function defaultTimewindow() {
+        var currentTime = (new Date).getTime();
+        var timewindow = {
+                displayValue: "",
+                selectedTab: 0,
+                realtime: {
+                interval: SECOND,
+                timewindowMs: MINUTE // 1 min by default
+            },
+            history: {
+                historyType: 0,
+                    interval: SECOND,
+                    timewindowMs: MINUTE, // 1 min by default
+                    fixedTimewindow: {
+                        startTimeMs: currentTime - DAY, // 1 day by default
+                        endTimeMs: currentTime
+                    }
+            },
+            aggregation: {
+                type: types.aggregation.avg.value,
+                limit: AVG_LIMIT
+            }
+        }
+        return timewindow;
+    }
+
+    function toHistoryTimewindow(timewindow, startTimeMs, endTimeMs) {
+
+        var interval = 0;
+        if (timewindow.history) {
+            interval = timewindow.history.interval;
+        } else if (timewindow.realtime) {
+            interval = timewindow.realtime.interval;
+        }
+
+        var historyTimewindow = {
+            history: {
+                fixedTimewindow: {
+                    startTimeMs: startTimeMs,
+                    endTimeMs: endTimeMs
+                },
+                interval: boundIntervalToTimewindow(endTimeMs - startTimeMs, interval)
+            },
+            aggregation: {
+
+            }
+        }
+        if (timewindow.aggregation) {
+            historyTimewindow.aggregation.type = timewindow.aggregation.type || types.aggregation.avg.value;
+        } else {
+            historyTimewindow.aggregation.type = types.aggregation.avg.value;
+        }
+
+        return historyTimewindow;
+    }
+
+    function createSubscriptionTimewindow(timewindow, stDiff) {
+
+        var subscriptionTimewindow = {
+            fixedWindow: null,
+            realtimeWindowMs: null,
+            aggregation: {
+                interval: SECOND,
+                limit: AVG_LIMIT,
+                type: types.aggregation.avg.value
+            }
+        };
+        var aggTimewindow = 0;
+
+        if (angular.isDefined(timewindow.aggregation)) {
+            subscriptionTimewindow.aggregation = {
+                type: timewindow.aggregation.type || types.aggregation.avg.value,
+                limit: timewindow.aggregation.limit || AVG_LIMIT
+            };
+        }
+        if (angular.isDefined(timewindow.realtime)) {
+            subscriptionTimewindow.realtimeWindowMs = timewindow.realtime.timewindowMs;
+            subscriptionTimewindow.aggregation.interval =
+                boundIntervalToTimewindow(subscriptionTimewindow.realtimeWindowMs, timewindow.realtime.interval);
+            subscriptionTimewindow.startTs = (new Date).getTime() + stDiff - subscriptionTimewindow.realtimeWindowMs;
+            var startDiff = subscriptionTimewindow.startTs % subscriptionTimewindow.aggregation.interval;
+            aggTimewindow = subscriptionTimewindow.realtimeWindowMs;
+            if (startDiff) {
+                subscriptionTimewindow.startTs -= startDiff;
+                aggTimewindow += subscriptionTimewindow.aggregation.interval;
+            }
+        } else if (angular.isDefined(timewindow.history)) {
+            if (angular.isDefined(timewindow.history.timewindowMs)) {
+                var currentTime = (new Date).getTime();
+                subscriptionTimewindow.fixedWindow = {
+                    startTimeMs: currentTime - timewindow.history.timewindowMs,
+                    endTimeMs: currentTime
+                }
+                aggTimewindow = timewindow.history.timewindowMs;
+
+            } else {
+                subscriptionTimewindow.fixedWindow = {
+                    startTimeMs: timewindow.history.fixedTimewindow.startTimeMs,
+                    endTimeMs: timewindow.history.fixedTimewindow.endTimeMs
+                }
+                aggTimewindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
+            }
+            subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs;
+            subscriptionTimewindow.aggregation.interval = boundIntervalToTimewindow(aggTimewindow, timewindow.history.interval);
+        }
+        var aggregation = subscriptionTimewindow.aggregation;
+        aggregation.timeWindow = aggTimewindow;
+        if (aggregation.type !== types.aggregation.none.value) {
+            aggregation.limit = Math.ceil(aggTimewindow / subscriptionTimewindow.aggregation.interval);
+        }
+        return subscriptionTimewindow;
+    }
+
+    function boundIntervalToTimewindow(timewindow, intervalMs) {
+        var min = minIntervalLimit(timewindow);
+        var max = maxIntervalLimit(timewindow);
+        if (intervalMs) {
+            return toBound(intervalMs, min, max, intervalMs);
+        } else {
+            return boundToPredefinedInterval(min, max, avgInterval(timewindow));
+        }
+    }
+
+
+}
\ No newline at end of file
diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js
index 3d750e3..8d6359a 100644
--- a/ui/src/app/api/widget.service.js
+++ b/ui/src/app/api/widget.service.js
@@ -129,7 +129,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
             resources: [],
             templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-not-found</div></div>',
             templateCss: '',
-            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
+            controllerScript: 'self.onInit = function() {}',
             settingsSchema: '{}\n',
             dataKeySettingsSchema: '{}\n',
             defaultConfig: '{\n' +
@@ -147,7 +147,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
             resources: [],
             templateHtml: '<div class="tb-widget-error-container"><div translate class="tb-widget-error-msg">widget.widget-type-load-error</div>',
             templateCss: '',
-            controllerScript: 'fns.init = function(containerElement, settings, datasources,\n    data) {}\n\n\nfns.redraw = function(containerElement, width, height, data) {};',
+            controllerScript: 'self.onInit = function() {}',
             settingsSchema: '{}\n',
             dataKeySettingsSchema: '{}\n',
             defaultConfig: '{\n' +
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index 3acb1c0..5e09b38 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -51,6 +51,7 @@ import thingsboardMenu from './services/menu.service';
 import thingsboardRaf from './common/raf.provider';
 import thingsboardUtils from './common/utils.service';
 import thingsboardTypes from './common/types.constant';
+import thingsboardApiTime from './api/time.service';
 import thingsboardKeyboardShortcut from './components/keyboard-shortcut.filter';
 import thingsboardHelp from './help/help.directive';
 import thingsboardToast from './services/toast';
@@ -101,6 +102,7 @@ angular.module('thingsboard', [
     thingsboardRaf,
     thingsboardUtils,
     thingsboardTypes,
+    thingsboardApiTime,
     thingsboardKeyboardShortcut,
     thingsboardHelp,
     thingsboardToast,
diff --git a/ui/src/app/components/timeinterval.directive.js b/ui/src/app/components/timeinterval.directive.js
index 47251b0..eaaa4a1 100644
--- a/ui/src/app/components/timeinterval.directive.js
+++ b/ui/src/app/components/timeinterval.directive.js
@@ -26,7 +26,7 @@ export default angular.module('thingsboard.directives.timeinterval', [])
     .name;
 
 /*@ngInject*/
-function Timeinterval($compile, $templateCache, $translate) {
+function Timeinterval($compile, $templateCache, timeService) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
 
@@ -39,62 +39,33 @@ function Timeinterval($compile, $templateCache, $translate) {
         scope.mins = 1;
         scope.secs = 0;
 
-        scope.predefIntervals = [
-            {
-                name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
-                value: 10 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
-                value: 30 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
-                value: 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
-                value: 2 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
-                value: 5 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
-                value: 10 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
-                value: 30 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
-                value: 60 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
-                value: 2 * 60 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
-                value: 10 * 60 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
-                value: 24 * 60 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
-                value: 7 * 24 * 60 * 60 * 1000
-            },
-            {
-                name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
-                value: 30 * 24 * 60 * 60 * 1000
-            }
-        ];
+        scope.advanced = false;
+
+        scope.boundInterval = function() {
+            var min = timeService.boundMinInterval(scope.min);
+            var max = timeService.boundMaxInterval(scope.max);
+            scope.intervals = timeService.getIntervals(scope.min, scope.max);
+            if (scope.rendered) {
+                var newIntervalMs = ngModelCtrl.$viewValue;
+                if (newIntervalMs < min) {
+                    newIntervalMs = min;
+                } else if (newIntervalMs > max) {
+                    newIntervalMs = max;
+                }
+                if (!scope.advanced) {
+                    newIntervalMs = timeService.boundToPredefinedInterval(min, max, newIntervalMs);
+                }
+                if (newIntervalMs !== ngModelCtrl.$viewValue) {
+                    scope.setIntervalMs(newIntervalMs);
+                    scope.updateView();
+                }
+            }
+        }
 
         scope.setIntervalMs = function (intervalMs) {
+            if (!scope.advanced) {
+                scope.intervalMs = intervalMs;
+            }
             var intervalSeconds = Math.floor(intervalMs / 1000);
             scope.days = Math.floor(intervalSeconds / 86400);
             scope.hours = Math.floor((intervalSeconds % 86400) / 3600);
@@ -105,6 +76,9 @@ function Timeinterval($compile, $templateCache, $translate) {
         ngModelCtrl.$render = function () {
             if (ngModelCtrl.$viewValue) {
                 var intervalMs = ngModelCtrl.$viewValue;
+                if (!scope.rendered) {
+                    scope.advanced = !timeService.matchesExistingInterval(scope.min, scope.max, intervalMs);
+                }
                 scope.setIntervalMs(intervalMs);
             }
             scope.rendered = true;
@@ -115,10 +89,15 @@ function Timeinterval($compile, $templateCache, $translate) {
                 return;
             }
             var value = null;
-            var intervalMs = (scope.days * 86400 +
+            var intervalMs;
+            if (!scope.advanced) {
+                intervalMs = scope.intervalMs;
+            } else {
+                intervalMs = (scope.days * 86400 +
                 scope.hours * 3600 +
                 scope.mins * 60 +
                 scope.secs) * 1000;
+            }
             if (!isNaN(intervalMs) && intervalMs > 0) {
                 value = intervalMs;
                 ngModelCtrl.$setValidity('tb-timeinterval', true);
@@ -126,6 +105,7 @@ function Timeinterval($compile, $templateCache, $translate) {
                 ngModelCtrl.$setValidity('tb-timeinterval', !scope.required);
             }
             ngModelCtrl.$setViewValue(value);
+            scope.boundInterval();
         }
 
         scope.$watch('required', function (newRequired, prevRequired) {
@@ -134,6 +114,38 @@ function Timeinterval($compile, $templateCache, $translate) {
             }
         });
 
+        scope.$watch('min', function (newMin, prevMin) {
+            if (angular.isDefined(newMin) && newMin !== prevMin) {
+                scope.updateView();
+            }
+        });
+
+        scope.$watch('max', function (newMax, prevMax) {
+            if (angular.isDefined(newMax) && newMax !== prevMax) {
+                scope.updateView();
+            }
+        });
+
+        scope.$watch('intervalMs', function (newIntervalMs, prevIntervalMs) {
+            if (angular.isDefined(newIntervalMs) && newIntervalMs !== prevIntervalMs) {
+                scope.updateView();
+            }
+        });
+
+        scope.$watch('advanced', function (newAdvanced, prevAdvanced) {
+            if (angular.isDefined(newAdvanced) && newAdvanced !== prevAdvanced) {
+                if (!scope.advanced) {
+                    scope.intervalMs = (scope.days * 86400 +
+                        scope.hours * 3600 +
+                        scope.mins * 60 +
+                        scope.secs) * 1000;
+                } else {
+                    scope.setIntervalMs(scope.intervalMs);
+                }
+                scope.updateView();
+            }
+        });
+
         scope.$watch('secs', function (newSecs) {
             if (angular.isUndefined(newSecs)) {
                 return;
@@ -198,6 +210,8 @@ function Timeinterval($compile, $templateCache, $translate) {
             scope.updateView();
         });
 
+        scope.boundInterval();
+
         $compile(element.contents())(scope);
 
     }
@@ -206,7 +220,10 @@ function Timeinterval($compile, $templateCache, $translate) {
         restrict: "E",
         require: "^ngModel",
         scope: {
-            required: '=ngRequired'
+            required: '=ngRequired',
+            min: '=?',
+            max: '=?',
+            predefinedName: '=?'
         },
         link: linker
     };
diff --git a/ui/src/app/components/timeinterval.scss b/ui/src/app/components/timeinterval.scss
index 2d7af71..525bfd1 100644
--- a/ui/src/app/components/timeinterval.scss
+++ b/ui/src/app/components/timeinterval.scss
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 tb-timeinterval {
+  min-width: 355px;
   md-input-container {
     margin-bottom: 0px;
     .md-errors-spacer {
@@ -25,10 +26,13 @@ tb-timeinterval {
       width: 150px;
     }
   }
-}
-
-tb-timeinterval {
   .md-input {
     width: 70px !important;
   }
+  .advanced-switch {
+    margin-top: 0;
+  }
+  .advanced-label {
+    margin: 5px 0;
+  }
 }
diff --git a/ui/src/app/components/timeinterval.tpl.html b/ui/src/app/components/timeinterval.tpl.html
index 75ecd30..e6719f2 100644
--- a/ui/src/app/components/timeinterval.tpl.html
+++ b/ui/src/app/components/timeinterval.tpl.html
@@ -15,33 +15,41 @@
     limitations under the License.
 
 -->
-<section layout="row" layout-align="start start">
-	<md-input-container>
-		 <label translate>timeinterval.days</label>
-	     <input type="number" ng-model="days" step="1" aria-label="{{ 'timeinterval.days' | translate }}">
-	</md-input-container>
-	<md-input-container>
-		 <label translate>timeinterval.hours</label>
-	     <input type="number" ng-model="hours" step="1" aria-label="{{ 'timeinterval.hours' | translate }}">
-	</md-input-container>
-	<md-input-container>
-		 <label translate>timeinterval.minutes</label>
-	     <input type="number" ng-model="mins" step="1" aria-label="{{ 'timeinterval.minutes' | translate }}">
-	</md-input-container>
-	<md-input-container>
-		 <label translate>timeinterval.seconds</label>
-	     <input type="number" ng-model="secs" step="1" aria-label="{{ 'timeinterval.seconds' | translate }}">
-	</md-input-container>
-     <md-menu md-position-mode="target-right target">
-      <md-button class="md-icon-button"  aria-label="Open intervals" ng-click="$mdOpenMenu($event)">
-      	<md-icon md-menu-origin aria-label="arrow_drop_down" class="material-icons">arrow_drop_down</md-icon>	
-      </md-button>
-      <md-menu-content width="4">
-        <md-menu-item ng-repeat="interval in predefIntervals" >
-          <md-button ng-click="setIntervalMs(interval.value)">
-			<span>{{interval.name}}</span>
-          </md-button>
-        </md-menu-item>
-      </md-menu-content>
-     </md-menu>		
+<section layout="row">
+	<section layout="column" flex ng-show="advanced">
+		<label class="tb-small" translate>{{ predefinedName }}</label>
+		<section layout="row" layout-align="start start" flex>
+			<md-input-container>
+				 <label translate>timeinterval.days</label>
+				 <input type="number" ng-model="days" step="1" aria-label="{{ 'timeinterval.days' | translate }}">
+			</md-input-container>
+			<md-input-container>
+				 <label translate>timeinterval.hours</label>
+				 <input type="number" ng-model="hours" step="1" aria-label="{{ 'timeinterval.hours' | translate }}">
+			</md-input-container>
+			<md-input-container>
+				 <label translate>timeinterval.minutes</label>
+				 <input type="number" ng-model="mins" step="1" aria-label="{{ 'timeinterval.minutes' | translate }}">
+			</md-input-container>
+			<md-input-container>
+				 <label translate>timeinterval.seconds</label>
+				 <input type="number" ng-model="secs" step="1" aria-label="{{ 'timeinterval.seconds' | translate }}">
+			</md-input-container>
+		</section>
+	</section>
+	<section layout="row" flex ng-show="!advanced">
+		<md-input-container flex>
+			<label translate>{{ predefinedName }}</label>
+			<md-select ng-model="intervalMs" style="min-width: 150px;" aria-label="predefined-interval">
+				<md-option ng-repeat="interval in intervals" ng-value="interval.value">
+					{{interval.name}}
+				</md-option>
+			</md-select>
+		</md-input-container>
+	</section>
+	<section layout="column" layout-align="center center">
+		<label class="tb-small advanced-label" translate>timeinterval.advanced</label>
+		<md-switch class="advanced-switch" ng-model="advanced" aria-label="predefined-switcher">
+		</md-switch>
+	</section>
 </section>
diff --git a/ui/src/app/components/timewindow.directive.js b/ui/src/app/components/timewindow.directive.js
index f06c119..14fff62 100644
--- a/ui/src/app/components/timewindow.directive.js
+++ b/ui/src/app/components/timewindow.directive.js
@@ -37,16 +37,18 @@ export default angular.module('thingsboard.directives.timewindow', [thingsboardT
 
 /* eslint-disable angular/angularelement */
 /*@ngInject*/
-function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdMedia, $translate, types) {
+function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdMedia, $translate, timeService) {
 
     var linker = function (scope, element, attrs, ngModelCtrl) {
 
         /* tbTimewindow (ng-model)
          * {
          * 	  realtime: {
+         * 	        interval: 0,
          * 			timewindowMs: 0
          * 	  },
          * 	  history: {
+         * 	        interval: 0,
          * 			timewindowMs: 0,
          * 			fixedTimewindow: {
          * 				startTimeMs: 0,
@@ -54,8 +56,8 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
          * 			}
          * 	  },
          * 	  aggregation: {
-         * 	        limit: 200,
-         * 	        type: types.aggregation.avg.value
+         * 	        type: types.aggregation.avg.value,
+         * 	        limit: 200
          * 	  }
          * }
          */
@@ -81,16 +83,6 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
         }
         element.html(template);
 
-        scope.isHovered = false;
-
-        scope.onHoverIn = function () {
-            scope.isHovered = true;
-        }
-
-        scope.onHoverOut = function () {
-            scope.isHovered = false;
-        }
-
         scope.openEditMode = function (event) {
             var position;
             var isGtSm = $mdMedia('gt-sm');
@@ -143,15 +135,18 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
             var model = scope.model;
             if (model.selectedTab === 0) {
                 value.realtime = {
+                    interval: model.realtime.interval,
                     timewindowMs: model.realtime.timewindowMs
                 };
             } else {
                 if (model.history.historyType === 0) {
                     value.history = {
+                        interval: model.history.interval,
                         timewindowMs: model.history.timewindowMs
                     };
                 } else {
                     value.history = {
+                        interval: model.history.interval,
                         fixedTimewindow: {
                             startTimeMs: model.history.fixedTimewindow.startTimeMs,
                             endTimeMs: model.history.fixedTimewindow.endTimeMs
@@ -160,8 +155,8 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
                 }
             }
             value.aggregation = {
-                limit: model.aggregation.limit,
-                type: model.aggregation.type
+                type: model.aggregation.type,
+                limit: model.aggregation.limit
             };
             ngModelCtrl.$setViewValue(value);
             scope.updateDisplayValue();
@@ -190,34 +185,17 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
         }
 
         ngModelCtrl.$render = function () {
-            var currentTime = (new Date).getTime();
-            scope.model = {
-                displayValue: "",
-                selectedTab: 0,
-                realtime: {
-                    timewindowMs: 60000 // 1 min by default
-                },
-                history: {
-                    historyType: 0,
-                    timewindowMs: 60000, // 1 min by default
-                    fixedTimewindow: {
-                        startTimeMs: currentTime - 24 * 60 * 60 * 1000, // 1 day by default
-                        endTimeMs: currentTime
-                    }
-                },
-                aggregation: {
-                    limit: 200,
-                    type: types.aggregation.avg.value
-                }
-            };
+            scope.model = timeService.defaultTimewindow();
             if (ngModelCtrl.$viewValue) {
                 var value = ngModelCtrl.$viewValue;
                 var model = scope.model;
                 if (angular.isDefined(value.realtime)) {
                     model.selectedTab = 0;
+                    model.realtime.interval = value.realtime.interval;
                     model.realtime.timewindowMs = value.realtime.timewindowMs;
                 } else {
                     model.selectedTab = 1;
+                    model.history.interval = value.history.interval;
                     if (angular.isDefined(value.history.timewindowMs)) {
                         model.history.historyType = 0;
                         model.history.timewindowMs = value.history.timewindowMs;
@@ -228,10 +206,10 @@ function Timewindow($compile, $templateCache, $filter, $mdPanel, $document, $mdM
                     }
                 }
                 if (angular.isDefined(value.aggregation)) {
-                    model.aggregation.limit = value.aggregation.limit || 200;
                     if (angular.isDefined(value.aggregation.type) && value.aggregation.type.length > 0) {
                         model.aggregation.type = value.aggregation.type;
                     }
+                    model.aggregation.limit = value.aggregation.limit || timeService.avgAggregationLimit();
                 }
             }
             scope.updateDisplayValue();
diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss
index 16c89e8..0c85d52 100644
--- a/ui/src/app/components/timewindow.scss
+++ b/ui/src/app/components/timewindow.scss
@@ -21,14 +21,39 @@
 }
 
 .tb-timewindow-panel {
-  min-height: 375px;
+  max-height: 440px;
+  min-width: 417px;
   background: white;
   border-radius: 4px;
   box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
   0 13px 19px 2px rgba(0, 0, 0, 0.14),
   0 5px 24px 4px rgba(0, 0, 0, 0.12);
   overflow: hidden;
+  form, fieldset {
+    height: 100%;
+  }
   md-content {
     background-color: #fff;
+    overflow: hidden;
+  }
+  .md-padding {
+    padding: 0 16px;
+  }
+  .md-radio-interactive {
+    md-select, md-switch {
+      pointer-events: all;
+    }
+  }
+  md-radio-button {
+    .md-label {
+      width: 100%;
+    }
+    tb-timeinterval {
+      width: 355px;
+      .advanced-switch {
+        min-height: 30px;
+        max-width: 44px;
+      }
+    }
   }
 }
diff --git a/ui/src/app/components/timewindow.tpl.html b/ui/src/app/components/timewindow.tpl.html
index e428365..0de81e2 100644
--- a/ui/src/app/components/timewindow.tpl.html
+++ b/ui/src/app/components/timewindow.tpl.html
@@ -15,9 +15,9 @@
     limitations under the License.
 
 -->
-<section ng-mouseover="onHoverIn()" ng-mouseleave="onHoverOut()" layout='row' layout-align="start center" style="min-height: 32px;">
+<section layout='row' layout-align="start center" style="min-height: 32px;">
 	<span ng-click="openEditMode($event)">{{model.displayValue}}</span>
-    <md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-show="isHovered" ng-click="openEditMode($event)">
+    <md-button class="md-icon-button tb-md-32" aria-label="{{ 'timewindow.edit' | translate }}" ng-click="openEditMode($event)">
      	  <md-icon ng-style="{ color: buttonColor }" aria-label="{{ 'timewindow.date-range' | translate }}" class="material-icons">date_range</md-icon>
     </md-button>	
 </section>
\ No newline at end of file
diff --git a/ui/src/app/components/timewindow-panel.controller.js b/ui/src/app/components/timewindow-panel.controller.js
index ab81e9d..e6af0b0 100644
--- a/ui/src/app/components/timewindow-panel.controller.js
+++ b/ui/src/app/components/timewindow-panel.controller.js
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 /*@ngInject*/
-export default function TimewindowPanelController(mdPanelRef, $scope, types, timewindow, historyOnly, aggregation, onTimewindowUpdate) {
+export default function TimewindowPanelController(mdPanelRef, $scope, timeService, types, timewindow, historyOnly, aggregation, onTimewindowUpdate) {
 
     var vm = this;
 
@@ -24,6 +24,13 @@ export default function TimewindowPanelController(mdPanelRef, $scope, types, tim
     vm.aggregation = aggregation;
     vm.onTimewindowUpdate = onTimewindowUpdate;
     vm.aggregationTypes = types.aggregation;
+    vm.showLimit = showLimit;
+    vm.showRealtimeAggInterval = showRealtimeAggInterval;
+    vm.showHistoryAggInterval = showHistoryAggInterval;
+    vm.minRealtimeAggInterval = minRealtimeAggInterval;
+    vm.maxRealtimeAggInterval = maxRealtimeAggInterval;
+    vm.minHistoryAggInterval = minHistoryAggInterval;
+    vm.maxHistoryAggInterval = maxHistoryAggInterval;
 
     if (vm.historyOnly) {
         vm.timewindow.selectedTab = 1;
@@ -48,4 +55,45 @@ export default function TimewindowPanelController(mdPanelRef, $scope, types, tim
             vm.onTimewindowUpdate && vm.onTimewindowUpdate(vm.timewindow);
         });
     };
+
+    function showLimit() {
+        return vm.timewindow.aggregation.type === vm.aggregationTypes.none.value;
+    }
+
+    function showRealtimeAggInterval() {
+        return vm.timewindow.aggregation.type !== vm.aggregationTypes.none.value &&
+               vm.timewindow.selectedTab === 0;
+    }
+
+    function showHistoryAggInterval() {
+        return vm.timewindow.aggregation.type !== vm.aggregationTypes.none.value &&
+            vm.timewindow.selectedTab === 1;
+    }
+
+    function minRealtimeAggInterval () {
+        return timeService.minIntervalLimit(vm.timewindow.realtime.timewindowMs);
+    }
+
+    function maxRealtimeAggInterval () {
+        return timeService.maxIntervalLimit(vm.timewindow.realtime.timewindowMs);
+    }
+
+    function minHistoryAggInterval () {
+        return timeService.minIntervalLimit(currentHistoryTimewindow());
+    }
+
+    function maxHistoryAggInterval () {
+        return timeService.maxIntervalLimit(currentHistoryTimewindow());
+    }
+
+    function currentHistoryTimewindow() {
+        if (vm.timewindow.history.historyType === 0) {
+            return vm.timewindow.history.timewindowMs;
+        } else {
+            return vm.timewindow.history.fixedTimewindow.endTimeMs -
+                vm.timewindow.history.fixedTimewindow.startTimeMs;
+        }
+    }
+
 }
+
diff --git a/ui/src/app/components/timewindow-panel.tpl.html b/ui/src/app/components/timewindow-panel.tpl.html
index 89825b0..b7550e8 100644
--- a/ui/src/app/components/timewindow-panel.tpl.html
+++ b/ui/src/app/components/timewindow-panel.tpl.html
@@ -17,61 +17,70 @@
 -->
 <form name="theForm" ng-submit="vm.update()">
 	<fieldset ng-disabled="loading">
-		<md-content layout="column">
-			<md-tabs ng-class="{'tb-headless': vm.historyOnly}" flex md-dynamic-height md-selected="vm.timewindow.selectedTab" md-border-bottom>
-				<md-tab label="{{ 'timewindow.realtime' | translate }}">
-					<md-content class="md-padding" layout="column">
-						<span translate>timewindow.last</span>
-						<tb-timeinterval
-							ng-required="vm.timewindow.selectedTab === 0"
-							ng-model="vm.timewindow.realtime.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
-					</md-content>
-				</md-tab>
-				<md-tab label="{{ 'timewindow.history' | translate }}">
-					<md-content class="md-padding" layout="column">
-						<md-radio-group ng-model="vm.timewindow.history.historyType" class="md-primary">
-							<md-radio-button ng-value=0 class="md-primary md-align-top-left md-radio-interactive">
-								<section layout="column">
-									<span translate>timewindow.last</span>
-									<tb-timeinterval
-										ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 0"
-										ng-show="vm.timewindow.history.historyType === 0"
-										ng-model="vm.timewindow.history.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
-								</section>	
-							</md-radio-button>
-							<md-radio-button ng-value=1 class="md-primary md-align-top-left md-radio-interactive">
-								<section layout="column">
-									<span translate>timewindow.time-period</span>
-									<tb-datetime-period
-											ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 1"
-											ng-show="vm.timewindow.history.historyType === 1"
-											ng-model="vm.timewindow.history.fixedTimewindow" style="padding-top: 8px;"></tb-datetime-period>
-								</section>	
-							</md-radio-button>
-						</md-radio-group>
-					</md-content>
-				</md-tab>
-			</md-tabs>
-			<md-content ng-if="vm.aggregation" class="md-padding" layout="column">
-				<md-input-container>
-					<label translate>aggregation.function</label>
-					<md-select ng-model="vm.timewindow.aggregation.type" style="min-width: 150px;">
-						<md-option ng-repeat="type in vm.aggregationTypes" ng-value="type.value">
-							{{type.name | translate}}
-						</md-option>
-					</md-select>
-				</md-input-container>
-				<md-slider-container>
-					<span translate>aggregation.limit</span>
-					<md-slider flex min="10" max="500" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider">
-					</md-slider>
+		<md-content style="height: 100%" flex layout="column">
+			<section layout="column">
+				<md-tabs ng-class="{'tb-headless': vm.historyOnly}" flex md-dynamic-height md-selected="vm.timewindow.selectedTab" md-border-bottom>
+					<md-tab label="{{ 'timewindow.realtime' | translate }}">
+						<md-content class="md-padding" layout="column">
+							<tb-timeinterval predefined-name="'timewindow.last'"
+								ng-required="vm.timewindow.selectedTab === 0"
+								ng-model="vm.timewindow.realtime.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
+						</md-content>
+					</md-tab>
+					<md-tab label="{{ 'timewindow.history' | translate }}">
+						<md-content class="md-padding" layout="column" style="padding-top: 8px;">
+							<md-radio-group ng-model="vm.timewindow.history.historyType" class="md-primary">
+								<md-radio-button ng-value=0 aria-label="{{ 'timewindow.last' | translate }}" class="md-primary md-align-top-left md-radio-interactive">
+									<section layout="column">
+										<tb-timeinterval predefined-name="'timewindow.last'"
+											ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 0"
+											ng-show="vm.timewindow.history.historyType === 0"
+											ng-model="vm.timewindow.history.timewindowMs" style="padding-top: 8px;"></tb-timeinterval>
+									</section>
+								</md-radio-button>
+								<md-radio-button ng-value=1 aria-label="{{ 'timewindow.time-period' | translate }}" class="md-primary md-align-top-left md-radio-interactive">
+									<section layout="column">
+										<span translate>timewindow.time-period</span>
+										<tb-datetime-period
+												ng-required="vm.timewindow.selectedTab === 1 && vm.timewindow.history.historyType === 1"
+												ng-show="vm.timewindow.history.historyType === 1"
+												ng-model="vm.timewindow.history.fixedTimewindow" style="padding-top: 8px;"></tb-datetime-period>
+									</section>
+								</md-radio-button>
+							</md-radio-group>
+						</md-content>
+					</md-tab>
+				</md-tabs>
+				<md-content ng-if="vm.aggregation" class="md-padding" layout="column">
 					<md-input-container>
-						<input flex type="number" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider">
+						<label translate>aggregation.function</label>
+						<md-select ng-model="vm.timewindow.aggregation.type" style="min-width: 150px;">
+							<md-option ng-repeat="type in vm.aggregationTypes" ng-value="type.value">
+								{{type.name | translate}}
+							</md-option>
+						</md-select>
 					</md-input-container>
-				</md-slider-container>
-			</md-content>
+					<md-slider-container ng-show="vm.showLimit()">
+						<span translate>aggregation.limit</span>
+						<md-slider flex min="10" max="500" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" id="limit-slider">
+						</md-slider>
+						<md-input-container>
+							<input flex type="number" ng-model="vm.timewindow.aggregation.limit" aria-label="limit" aria-controls="limit-slider">
+						</md-input-container>
+					</md-slider-container>
+					<tb-timeinterval ng-show="vm.showRealtimeAggInterval()" min="vm.minRealtimeAggInterval()" max="vm.maxRealtimeAggInterval()"
+									 predefined-name="'aggregation.group-interval'"
+									 ng-model="vm.timewindow.realtime.interval">
+					</tb-timeinterval>
+					<tb-timeinterval ng-show="vm.showHistoryAggInterval()" min="vm.minHistoryAggInterval()" max="vm.maxHistoryAggInterval()"
+									 predefined-name="'aggregation.group-interval'"
+									 ng-model="vm.timewindow.history.interval">
+					</tb-timeinterval>
+				</md-content>
+			</section>
+			<span flex></span>
 			<section layout="row" layout-alignment="start center">
-			      <span flex></span>
+				  <span flex></span>
 				  <md-button ng-disabled="loading || theForm.$invalid || !theForm.$dirty" type="submit" class="md-raised md-primary">
 				  		{{ 'action.update' | translate }}
 				  </md-button>
@@ -79,6 +88,6 @@
 					  {{ 'action.cancel' | translate }}
 			      </md-button>
 			</section> 
-		</section>
+		</md-content>
 	</fieldset>
 </form>
\ No newline at end of file
diff --git a/ui/src/app/components/widget.controller.js b/ui/src/app/components/widget.controller.js
index f7e9498..a2056e0 100644
--- a/ui/src/app/components/widget.controller.js
+++ b/ui/src/app/components/widget.controller.js
@@ -19,7 +19,7 @@ import 'javascript-detect-element-resize/detect-element-resize';
 /* eslint-disable angular/angularelement */
 
 /*@ngInject*/
-export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils,
+export default function WidgetController($scope, $timeout, $window, $element, $q, $log, $injector, tbRaf, types, utils, timeService,
                                          datasourceService, deviceService, visibleRect, isEdit, stDiff, widget, deviceAliasList, widgetType) {
 
     var vm = this;
@@ -41,11 +41,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
     var targetDeviceAliasId = null;
     var targetDeviceId = null;
     var originalTimewindow = null;
-    var subscriptionTimewindow = {
-        fixedWindow: null,
-        realtimeWindowMs: null,
-        aggregation: null
-    };
+    var subscriptionTimewindow = null;
     var dataUpdateCaf = null;
 
     /*
@@ -488,15 +484,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         if (!originalTimewindow) {
             originalTimewindow = angular.copy(widget.config.timewindow);
         }
-        widget.config.timewindow = {
-            history: {
-                fixedTimewindow: {
-                    startTimeMs: startTimeMs,
-                    endTimeMs: endTimeMs
-                }
-            },
-            aggregation: angular.copy(widget.config.timewindow.aggregation)
-        };
+        widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs);
     }
 
     function dataUpdated(sourceData, datasourceIndex, dataKeyIndex) {
@@ -511,7 +499,7 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
             }
         }
         if (update) {
-            if (subscriptionTimewindow.realtimeWindowMs) {
+            if (subscriptionTimewindow && subscriptionTimewindow.realtimeWindowMs) {
                 updateTimewindow();
             }
             widgetContext.data[datasourceIndex + dataKeyIndex].data = sourceData.data;
@@ -555,62 +543,26 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
         }
     }
 
+    function updateRealtimeSubscription(_subscriptionTimewindow) {
+        if (_subscriptionTimewindow) {
+            subscriptionTimewindow = _subscriptionTimewindow;
+        } else {
+            subscriptionTimewindow = timeService.createSubscriptionTimewindow(widget.config.timewindow, widgetContext.timeWindow.stDiff);
+        }
+        updateTimewindow();
+        return subscriptionTimewindow;
+    }
+
     function subscribe() {
         if (widget.type !== types.widgetType.rpc.value) {
-            var index = 0;
-            subscriptionTimewindow.fixedWindow = null;
-            subscriptionTimewindow.realtimeWindowMs = null;
-            subscriptionTimewindow.aggregation = {
-                limit: 200,
-                type: types.aggregation.avg.value
-            };
             if (widget.type === types.widgetType.timeseries.value &&
                 angular.isDefined(widget.config.timewindow)) {
-                var timeWindow = 0;
-                if (angular.isDefined(widget.config.timewindow.aggregation)) {
-                    subscriptionTimewindow.aggregation = {
-                        limit: widget.config.timewindow.aggregation.limit || 200,
-                        type: widget.config.timewindow.aggregation.type || types.aggregation.avg.value
-                    };
-                }
-
-                if (angular.isDefined(widget.config.timewindow.realtime)) {
-                    subscriptionTimewindow.realtimeWindowMs = widget.config.timewindow.realtime.timewindowMs;
-                    subscriptionTimewindow.startTs = (new Date).getTime() + widgetContext.timeWindow.stDiff - subscriptionTimewindow.realtimeWindowMs;
-                    timeWindow = subscriptionTimewindow.realtimeWindowMs;
-                } else if (angular.isDefined(widget.config.timewindow.history)) {
-                    if (angular.isDefined(widget.config.timewindow.history.timewindowMs)) {
-                        var currentTime = (new Date).getTime();
-                        subscriptionTimewindow.fixedWindow = {
-                            startTimeMs: currentTime - widget.config.timewindow.history.timewindowMs,
-                            endTimeMs: currentTime
-                        }
-                        timeWindow = widget.config.timewindow.history.timewindowMs;
-                    } else {
-                        subscriptionTimewindow.fixedWindow = {
-                            startTimeMs: widget.config.timewindow.history.fixedTimewindow.startTimeMs,
-                            endTimeMs: widget.config.timewindow.history.fixedTimewindow.endTimeMs
-                        }
-                        timeWindow = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
-                    }
-                    subscriptionTimewindow.startTs = subscriptionTimewindow.fixedWindow.startTimeMs;
-                }
-                var aggregation = subscriptionTimewindow.aggregation;
-                var noAggregation = aggregation.type === types.aggregation.none.value;
-                var interval = Math.floor(timeWindow / aggregation.limit);
-                if (!noAggregation) {
-                    aggregation.interval = Math.max(interval, 1000);
-                    aggregation.limit = Math.ceil(interval/aggregation.interval * aggregation.limit);
-                    aggregation.timeWindow = aggregation.interval * aggregation.limit;
-                } else {
-                    aggregation.timeWindow = interval * aggregation.limit;
-                    aggregation.interval = 1000;
-                }
-                updateTimewindow();
+                updateRealtimeSubscription();
                 if (subscriptionTimewindow.fixedWindow) {
                     onDataUpdated();
                 }
             }
+            var index = 0;
             for (var i in widget.config.datasources) {
                 var datasource = widget.config.datasources[i];
                 var deviceId = null;
@@ -630,6 +582,13 @@ export default function WidgetController($scope, $timeout, $window, $element, $q
                     dataUpdated: function (data, datasourceIndex, dataKeyIndex) {
                         dataUpdated(data, datasourceIndex, dataKeyIndex);
                     },
+                    updateRealtimeSubscription: function() {
+                        this.subscriptionTimewindow = updateRealtimeSubscription();
+                        return this.subscriptionTimewindow;
+                    },
+                    setRealtimeSubscription: function(subscriptionTimewindow) {
+                        updateRealtimeSubscription(angular.copy(subscriptionTimewindow));
+                    },
                     datasourceIndex: index
                 };
 
diff --git a/ui/src/app/device/attribute/attribute-table.directive.js b/ui/src/app/device/attribute/attribute-table.directive.js
index ba3e466..c25c6f2 100644
--- a/ui/src/app/device/attribute/attribute-table.directive.js
+++ b/ui/src/app/device/attribute/attribute-table.directive.js
@@ -29,7 +29,7 @@ import EditAttributeValueController from './edit-attribute-value.controller';
 
 /*@ngInject*/
 export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
-                                                $document, $translate, utils, types, dashboardService, deviceService, widgetService) {
+                                                $document, $translate, $filter, utils, types, dashboardService, deviceService, widgetService) {
 
     var linker = function (scope, element, attrs) {
 
@@ -303,6 +303,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
                         var isSystem = scope.widgetsBundle.tenantId.id === types.id.nullUid;
                         widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then(
                             function success(widgetTypes) {
+
+                                widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']);
+
                                 for (var i = 0; i < widgetTypes.length; i++) {
                                     var widgetType = widgetTypes[i];
                                     var widgetInfo = widgetService.toWidgetInfo(widgetType);
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 525c307..05411d7 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -67,6 +67,7 @@ export default angular.module('thingsboard.locale', [])
                     "aggregation": "Aggregation",
                     "function": "Data aggregation function",
                     "limit": "Max values",
+                    "group-interval": "Grouping interval",
                     "min": "Min",
                     "max": "Max",
                     "avg": "Average",
@@ -558,7 +559,8 @@ export default angular.module('thingsboard.locale', [])
                     "days": "Days",
                     "hours": "Hours",
                     "minutes": "Minutes",
-                    "seconds": "Seconds"
+                    "seconds": "Seconds",
+                    "advanced": "Advanced"
                 },
                 "timewindow": {
                     "days": "{ days, select, 1 { day } other {# days } }",
diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js
index c35b363..2888b0f 100644
--- a/ui/src/app/widget/lib/flot-widget.js
+++ b/ui/src/app/widget/lib/flot-widget.js
@@ -167,6 +167,7 @@ export default class TbFlot {
         var settings = ctx.settings;
         ctx.trackDecimals = angular.isDefined(settings.decimals) ? settings.decimals : 1;
         ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false);
+        ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false;
 
         var font = {
             color: settings.fontColor || "#545454",
@@ -232,6 +233,21 @@ export default class TbFlot {
                     options.yaxis.tickFormatter = function() {
                         return '';
                     };
+                } else if (settings.units && settings.units.length > 0) {
+                    options.yaxis.tickFormatter = function(value, axis) {
+                        var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1,
+                            formatted = "" + Math.round(value * factor) / factor;
+                        if (axis.tickDecimals != null) {
+                            var decimal = formatted.indexOf("."),
+                                precision = decimal === -1 ? 0 : formatted.length - decimal - 1;
+
+                            if (precision < axis.tickDecimals) {
+                                formatted = (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
+                            }
+                        }
+                        formatted += ' ' + tbFlot.ctx.settings.units;
+                        return formatted;
+                    };
                 }
                 options.yaxis.font.color = settings.yaxis.color || options.yaxis.font.color;
                 options.yaxis.label = settings.yaxis.title || null;
@@ -323,6 +339,8 @@ export default class TbFlot {
 
         this.options = options;
 
+        this.checkMouseEvents();
+
         if (this.chartType === 'pie' && this.ctx.animatedPie) {
             this.ctx.pieDataAnimationDuration = 250;
             this.ctx.pieData = angular.copy(this.ctx.data);
@@ -337,7 +355,6 @@ export default class TbFlot {
         } else {
             this.ctx.plot = $.plot(this.ctx.$container, this.ctx.data, this.options);
         }
-        this.checkMouseEvents();
     }
 
     update() {
@@ -577,6 +594,11 @@ export default class TbFlot {
                         "type": "boolean",
                         "default": false
                     },
+                    "tooltipCumulative": {
+                        "title": "Show cumulative values in stacking mode",
+                        "type": "boolean",
+                        "default": false
+                    },
                     "grid": {
                         "title": "Grid settings",
                         "type": "object",
@@ -710,6 +732,7 @@ export default class TbFlot {
                 "decimals",
                 "units",
                 "tooltipIndividual",
+                "tooltipCumulative",
                 {
                     "key": "grid",
                     "items": [
@@ -834,10 +857,28 @@ export default class TbFlot {
     }
 
     checkMouseEvents() {
-        if (this.ctx.isMobile || this.ctx.isEdit) {
-            this.disableMouseEvents();
-        } else if (!this.ctx.isEdit) {
-            this.enableMouseEvents();
+        var enabled = !this.ctx.isMobile &&  !this.ctx.isEdit;
+        if (angular.isUndefined(this.mouseEventsEnabled) || this.mouseEventsEnabled != enabled) {
+            this.mouseEventsEnabled = enabled;
+            if (enabled) {
+                this.enableMouseEvents();
+            } else {
+                this.disableMouseEvents();
+            }
+            if (this.ctx.plot) {
+                this.ctx.plot.destroy();
+                if (this.chartType === 'pie' && this.ctx.animatedPie) {
+                    this.ctx.plot = $.plot(this.ctx.$container, this.ctx.pieData, this.options);
+                } else {
+                    this.ctx.plot = $.plot(this.ctx.$container, this.ctx.data, this.options);
+                }
+            }
+        }
+    }
+
+    destroy() {
+        if (this.ctx.plot) {
+            this.ctx.plot.destroy();
         }
     }
 
@@ -1030,7 +1071,7 @@ export default class TbFlot {
                     minTime = pointTime;
                 }
                 if (series.stack) {
-                    if (this.ctx.tooltipIndividual) {
+                    if (this.ctx.tooltipIndividual || !this.ctx.tooltipCumulative) {
                         value = series.data[hoverIndex][1];
                     } else {
                         last_value += series.data[hoverIndex][1];
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 606b1d9..31c256d 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -201,6 +201,14 @@ md-sidenav {
   color: rgba(0,0,0,0.54);
 }
 
+label {
+  &.tb-small {
+    pointer-events: none;
+    color: rgba(0,0,0,0.54);
+    font-size: 12px;
+  }
+}
+
 /***********************
  * Prompt
  ***********************/