Commit 70ddf204482424be4a0532c22a5e1de6df6060a7

Authored by Igor Kulikov
1 parent 3ef1a7d4

Stepped chart

@@ -16,7 +16,8 @@ @@ -16,7 +16,8 @@
16 16
17 export default class DataAggregator { 17 export default class DataAggregator {
18 18
19 - constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, types, $timeout, $filter) { 19 + constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval,
  20 + steppedChart, types, $timeout, $filter) {
20 this.onDataCb = onDataCb; 21 this.onDataCb = onDataCb;
21 this.tsKeyNames = tsKeyNames; 22 this.tsKeyNames = tsKeyNames;
22 this.dataBuffer = {}; 23 this.dataBuffer = {};
@@ -34,6 +35,8 @@ export default class DataAggregator { @@ -34,6 +35,8 @@ export default class DataAggregator {
34 this.limit = limit; 35 this.limit = limit;
35 this.timeWindow = timeWindow; 36 this.timeWindow = timeWindow;
36 this.interval = interval; 37 this.interval = interval;
  38 + this.steppedChart = steppedChart;
  39 + this.firstStepDataReceived = !this.steppedChart;
37 this.aggregationTimeout = Math.max(this.interval, 1000); 40 this.aggregationTimeout = Math.max(this.interval, 1000);
38 switch (aggregationType) { 41 switch (aggregationType) {
39 case types.aggregation.min.value: 42 case types.aggregation.min.value:
@@ -78,6 +81,10 @@ export default class DataAggregator { @@ -78,6 +81,10 @@ export default class DataAggregator {
78 }, this.aggregationTimeout, false); 81 }, this.aggregationTimeout, false);
79 } 82 }
80 83
  84 + onFirstStepData(data) {
  85 + this.firstStepData = data;
  86 + }
  87 +
81 onData(data, update, history, apply) { 88 onData(data, update, history, apply) {
82 if (!this.dataReceived || this.resetPending) { 89 if (!this.dataReceived || this.resetPending) {
83 var updateIntervalScheduledTime = true; 90 var updateIntervalScheduledTime = true;
@@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.datasource', [thingsboardApiDevic @@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.datasource', [thingsboardApiDevic
23 .factory('datasourceService', DatasourceService) 23 .factory('datasourceService', DatasourceService)
24 .name; 24 .name;
25 25
  26 +const YEAR = 1000 * 60 * 60 * 24 * 365;
  27 +
26 /*@ngInject*/ 28 /*@ngInject*/
27 function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, types, utils) { 29 function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, types, utils) {
28 30
@@ -280,6 +282,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic @@ -280,6 +282,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
280 telemetryWebsocketService.subscribe(subscriber); 282 telemetryWebsocketService.subscribe(subscriber);
281 subscribers[subscriber.historyCommand.cmdId] = subscriber; 283 subscribers[subscriber.historyCommand.cmdId] = subscriber;
282 284
  285 + if (subsTw.aggregation.steppedChart) {
  286 + createFirstStepSubscription(subsTw, tsKeys);
  287 + }
  288 +
283 } else { 289 } else {
284 290
285 subscriptionCommand = { 291 subscriptionCommand = {
@@ -312,6 +318,11 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic @@ -312,6 +318,11 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
312 updateRealtimeSubscriptionCommand(this.subscriptionCommand, newSubsTw); 318 updateRealtimeSubscriptionCommand(this.subscriptionCommand, newSubsTw);
313 dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); 319 dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval);
314 } 320 }
  321 +
  322 + if (subsTw.aggregation.steppedChart) {
  323 + createFirstStepSubscription(subsTw, tsKeys);
  324 + }
  325 +
315 } else { 326 } else {
316 subscriber.onReconnected = function() {} 327 subscriber.onReconnected = function() {}
317 subscriber.onData = function(data) { 328 subscriber.onData = function(data) {
@@ -377,6 +388,37 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic @@ -377,6 +388,37 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
377 } 388 }
378 } 389 }
379 390
  391 + function createFirstStepSubscription(subsTw, tsKeys) {
  392 + var startStepCommand = {
  393 + entityType: datasourceSubscription.entityType,
  394 + entityId: datasourceSubscription.entityId,
  395 + keys: tsKeys,
  396 + startTs: subsTw.fixedWindow.startTimeMs - YEAR,
  397 + endTs: subsTw.fixedWindow.startTimeMs,
  398 + interval: subsTw.aggregation.interval,
  399 + limit: 1,
  400 + agg: subsTw.aggregation.type
  401 + };
  402 + var subscriber = {
  403 + historyCommand: startStepCommand,
  404 + type: types.dataKeyType.timeseries,
  405 + onData: function (data) {
  406 + if (data.data) {
  407 + for (var key in data.data) {
  408 + var keyData = data.data[key];
  409 + data.data[key] = $filter('orderBy')(keyData, '+this[0]');
  410 + }
  411 + //onData(data.data, types.dataKeyType.timeseries, true);
  412 + //TODO: onStartStepData
  413 + }
  414 + },
  415 + onReconnected: function() {}
  416 + };
  417 +
  418 + telemetryWebsocketService.subscribe(subscriber);
  419 + subscribers[subscriber.historyCommand.cmdId] = subscriber;
  420 + }
  421 +
380 function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) { 422 function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) {
381 return new DataAggregator( 423 return new DataAggregator(
382 function(data, apply) { 424 function(data, apply) {
@@ -388,6 +430,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic @@ -388,6 +430,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic
388 subsTw.aggregation.type, 430 subsTw.aggregation.type,
389 subsTw.aggregation.timeWindow, 431 subsTw.aggregation.timeWindow,
390 subsTw.aggregation.interval, 432 subsTw.aggregation.interval,
  433 + subsTw.aggregation.steppedChart,
391 types, 434 types,
392 $timeout, 435 $timeout,
393 $filter 436 $filter
@@ -128,7 +128,7 @@ export default class Subscription { @@ -128,7 +128,7 @@ export default class Subscription {
128 stDiff: this.ctx.stDiff 128 stDiff: this.ctx.stDiff
129 } 129 }
130 this.useDashboardTimewindow = options.useDashboardTimewindow; 130 this.useDashboardTimewindow = options.useDashboardTimewindow;
131 - 131 + this.steppedChart = options.steppedChart;
132 if (this.useDashboardTimewindow) { 132 if (this.useDashboardTimewindow) {
133 this.timeWindowConfig = angular.copy(options.dashboardTimewindow); 133 this.timeWindowConfig = angular.copy(options.dashboardTimewindow);
134 } else { 134 } else {
@@ -612,7 +612,7 @@ export default class Subscription { @@ -612,7 +612,7 @@ export default class Subscription {
612 this.subscriptionTimewindow = 612 this.subscriptionTimewindow =
613 this.ctx.timeService.createSubscriptionTimewindow( 613 this.ctx.timeService.createSubscriptionTimewindow(
614 this.timeWindowConfig, 614 this.timeWindowConfig,
615 - this.timeWindow.stDiff); 615 + this.timeWindow.stDiff, this.steppedChart);
616 } 616 }
617 this.updateTimewindow(); 617 this.updateTimewindow();
618 return this.subscriptionTimewindow; 618 return this.subscriptionTimewindow;
@@ -261,7 +261,7 @@ function TimeService($translate, types) { @@ -261,7 +261,7 @@ function TimeService($translate, types) {
261 return historyTimewindow; 261 return historyTimewindow;
262 } 262 }
263 263
264 - function createSubscriptionTimewindow(timewindow, stDiff) { 264 + function createSubscriptionTimewindow(timewindow, stDiff, steppedChart) {
265 265
266 var subscriptionTimewindow = { 266 var subscriptionTimewindow = {
267 fixedWindow: null, 267 fixedWindow: null,
@@ -273,8 +273,22 @@ function TimeService($translate, types) { @@ -273,8 +273,22 @@ function TimeService($translate, types) {
273 } 273 }
274 }; 274 };
275 var aggTimewindow = 0; 275 var aggTimewindow = 0;
  276 + if (steppedChart) {
  277 + subscriptionTimewindow.aggregation = {
  278 + interval: SECOND,
  279 + limit: MAX_LIMIT,
  280 + type: types.aggregation.none.value,
  281 + steppedChart: true
  282 + };
  283 + } else {
  284 + subscriptionTimewindow.aggregation = {
  285 + interval: SECOND,
  286 + limit: AVG_LIMIT,
  287 + type: types.aggregation.avg.value
  288 + };
  289 + }
276 290
277 - if (angular.isDefined(timewindow.aggregation)) { 291 + if (angular.isDefined(timewindow.aggregation) && !steppedChart) {
278 subscriptionTimewindow.aggregation = { 292 subscriptionTimewindow.aggregation = {
279 type: timewindow.aggregation.type || types.aggregation.avg.value, 293 type: timewindow.aggregation.type || types.aggregation.avg.value,
280 limit: timewindow.aggregation.limit || AVG_LIMIT 294 limit: timewindow.aggregation.limit || AVG_LIMIT
@@ -560,7 +560,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr @@ -560,7 +560,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
560 useCustomDatasources: false, 560 useCustomDatasources: false,
561 maxDatasources: -1, //unlimited 561 maxDatasources: -1, //unlimited
562 maxDataKeys: -1, //unlimited 562 maxDataKeys: -1, //unlimited
563 - dataKeysOptional: false 563 + dataKeysOptional: false,
  564 + steppedChart: false
564 }; 565 };
565 ' }\n\n' + 566 ' }\n\n' +
566 567
@@ -631,6 +632,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr @@ -631,6 +632,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr
631 if (angular.isUndefined(result.typeParameters.dataKeysOptional)) { 632 if (angular.isUndefined(result.typeParameters.dataKeysOptional)) {
632 result.typeParameters.dataKeysOptional = false; 633 result.typeParameters.dataKeysOptional = false;
633 } 634 }
  635 + if (angular.isUndefined(result.typeParameters.steppedChart)) {
  636 + result.typeParameters.steppedChart = false;
  637 + }
634 if (angular.isFunction(widgetTypeInstance.actionSources)) { 638 if (angular.isFunction(widgetTypeInstance.actionSources)) {
635 result.actionSources = widgetTypeInstance.actionSources(); 639 result.actionSources = widgetTypeInstance.actionSources();
636 } else { 640 } else {
@@ -339,7 +339,8 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele @@ -339,7 +339,8 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
339 var deferred = $q.defer(); 339 var deferred = $q.defer();
340 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { 340 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
341 options = { 341 options = {
342 - type: widget.type 342 + type: widget.type,
  343 + steppedChart: vm.typeParameters.steppedChart
343 } 344 }
344 if (widget.type == types.widgetType.alarm.value) { 345 if (widget.type == types.widgetType.alarm.value) {
345 options.alarmSource = angular.copy(widget.config.alarmSource); 346 options.alarmSource = angular.copy(widget.config.alarmSource);
@@ -168,7 +168,7 @@ export default class TbFlot { @@ -168,7 +168,7 @@ export default class TbFlot {
168 } 168 }
169 }; 169 };
170 170
171 - if (this.chartType === 'line' || this.chartType === 'bar') { 171 + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') {
172 options.xaxis = { 172 options.xaxis = {
173 mode: 'time', 173 mode: 'time',
174 timezone: 'browser', 174 timezone: 'browser',
@@ -270,6 +270,14 @@ export default class TbFlot { @@ -270,6 +270,14 @@ export default class TbFlot {
270 fill: 0.9 270 fill: 0.9
271 } 271 }
272 } 272 }
  273 +
  274 + if (this.chartType === 'stepped') {
  275 + options.series.lines = {
  276 + steps: true,
  277 + show: true
  278 + }
  279 + }
  280 +
273 } else if (this.chartType === 'pie') { 281 } else if (this.chartType === 'pie') {
274 options.series = { 282 options.series = {
275 pie: { 283 pie: {
@@ -381,7 +389,7 @@ export default class TbFlot { @@ -381,7 +389,7 @@ export default class TbFlot {
381 389
382 this.options.colors = colors; 390 this.options.colors = colors;
383 this.options.yaxes = angular.copy(this.yaxes); 391 this.options.yaxes = angular.copy(this.yaxes);
384 - if (this.chartType === 'line' || this.chartType === 'bar') { 392 + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') {
385 if (this.chartType === 'bar') { 393 if (this.chartType === 'bar') {
386 this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; 394 this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
387 } 395 }
@@ -434,7 +442,7 @@ export default class TbFlot { @@ -434,7 +442,7 @@ export default class TbFlot {
434 } 442 }
435 if (this.subscription) { 443 if (this.subscription) {
436 if (!this.isMouseInteraction && this.ctx.plot) { 444 if (!this.isMouseInteraction && this.ctx.plot) {
437 - if (this.chartType === 'line' || this.chartType === 'bar') { 445 + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') {
438 446
439 var axisVisibilityChanged = false; 447 var axisVisibilityChanged = false;
440 if (this.yaxis) { 448 if (this.yaxis) {