Showing
7 changed files
with
87 additions
and
10 deletions
@@ -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) { |