From 70ddf204482424be4a0532c22a5e1de6df6060a7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 8 Sep 2017 19:50:58 +0300 Subject: [PATCH] Stepped chart --- ui/src/app/api/data-aggregator.js | 9 +++- ui/src/app/api/datasource.service.js | 43 +++++++++++++++++++ ui/src/app/api/subscription.js | 4 +- ui/src/app/api/time.service.js | 18 +++++++- ui/src/app/api/widget.service.js | 6 ++- .../components/widget/widget.controller.js | 3 +- ui/src/app/widget/lib/flot-widget.js | 14 ++++-- 7 files changed, 87 insertions(+), 10 deletions(-) diff --git a/ui/src/app/api/data-aggregator.js b/ui/src/app/api/data-aggregator.js index ec415369b9..43b2e939f4 100644 --- a/ui/src/app/api/data-aggregator.js +++ b/ui/src/app/api/data-aggregator.js @@ -16,7 +16,8 @@ export default class DataAggregator { - constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, types, $timeout, $filter) { + constructor(onDataCb, tsKeyNames, startTs, limit, aggregationType, timeWindow, interval, + steppedChart, types, $timeout, $filter) { this.onDataCb = onDataCb; this.tsKeyNames = tsKeyNames; this.dataBuffer = {}; @@ -34,6 +35,8 @@ export default class DataAggregator { this.limit = limit; this.timeWindow = timeWindow; this.interval = interval; + this.steppedChart = steppedChart; + this.firstStepDataReceived = !this.steppedChart; this.aggregationTimeout = Math.max(this.interval, 1000); switch (aggregationType) { case types.aggregation.min.value: @@ -78,6 +81,10 @@ export default class DataAggregator { }, this.aggregationTimeout, false); } + onFirstStepData(data) { + this.firstStepData = data; + } + onData(data, update, history, apply) { if (!this.dataReceived || this.resetPending) { var updateIntervalScheduledTime = true; diff --git a/ui/src/app/api/datasource.service.js b/ui/src/app/api/datasource.service.js index 1f265bb83a..a68e14ce71 100644 --- a/ui/src/app/api/datasource.service.js +++ b/ui/src/app/api/datasource.service.js @@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.datasource', [thingsboardApiDevic .factory('datasourceService', DatasourceService) .name; +const YEAR = 1000 * 60 * 60 * 24 * 365; + /*@ngInject*/ function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, types, utils) { @@ -280,6 +282,10 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic telemetryWebsocketService.subscribe(subscriber); subscribers[subscriber.historyCommand.cmdId] = subscriber; + if (subsTw.aggregation.steppedChart) { + createFirstStepSubscription(subsTw, tsKeys); + } + } else { subscriptionCommand = { @@ -312,6 +318,11 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic updateRealtimeSubscriptionCommand(this.subscriptionCommand, newSubsTw); dataAggregator.reset(newSubsTw.startTs, newSubsTw.aggregation.timeWindow, newSubsTw.aggregation.interval); } + + if (subsTw.aggregation.steppedChart) { + createFirstStepSubscription(subsTw, tsKeys); + } + } else { subscriber.onReconnected = function() {} subscriber.onData = function(data) { @@ -377,6 +388,37 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic } } + function createFirstStepSubscription(subsTw, tsKeys) { + var startStepCommand = { + entityType: datasourceSubscription.entityType, + entityId: datasourceSubscription.entityId, + keys: tsKeys, + startTs: subsTw.fixedWindow.startTimeMs - YEAR, + endTs: subsTw.fixedWindow.startTimeMs, + interval: subsTw.aggregation.interval, + limit: 1, + agg: subsTw.aggregation.type + }; + var subscriber = { + historyCommand: startStepCommand, + type: types.dataKeyType.timeseries, + onData: function (data) { + if (data.data) { + for (var key in data.data) { + var keyData = data.data[key]; + data.data[key] = $filter('orderBy')(keyData, '+this[0]'); + } + //onData(data.data, types.dataKeyType.timeseries, true); + //TODO: onStartStepData + } + }, + onReconnected: function() {} + }; + + telemetryWebsocketService.subscribe(subscriber); + subscribers[subscriber.historyCommand.cmdId] = subscriber; + } + function createRealtimeDataAggregator(subsTw, tsKeyNames, dataKeyType) { return new DataAggregator( function(data, apply) { @@ -388,6 +430,7 @@ function DatasourceSubscription(datasourceSubscription, telemetryWebsocketServic subsTw.aggregation.type, subsTw.aggregation.timeWindow, subsTw.aggregation.interval, + subsTw.aggregation.steppedChart, types, $timeout, $filter diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index fd26629502..a4646177f9 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -128,7 +128,7 @@ export default class Subscription { stDiff: this.ctx.stDiff } this.useDashboardTimewindow = options.useDashboardTimewindow; - + this.steppedChart = options.steppedChart; if (this.useDashboardTimewindow) { this.timeWindowConfig = angular.copy(options.dashboardTimewindow); } else { @@ -612,7 +612,7 @@ export default class Subscription { this.subscriptionTimewindow = this.ctx.timeService.createSubscriptionTimewindow( this.timeWindowConfig, - this.timeWindow.stDiff); + this.timeWindow.stDiff, this.steppedChart); } this.updateTimewindow(); return this.subscriptionTimewindow; diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js index 1f42290c6a..d34c77e334 100644 --- a/ui/src/app/api/time.service.js +++ b/ui/src/app/api/time.service.js @@ -261,7 +261,7 @@ function TimeService($translate, types) { return historyTimewindow; } - function createSubscriptionTimewindow(timewindow, stDiff) { + function createSubscriptionTimewindow(timewindow, stDiff, steppedChart) { var subscriptionTimewindow = { fixedWindow: null, @@ -273,8 +273,22 @@ function TimeService($translate, types) { } }; var aggTimewindow = 0; + if (steppedChart) { + subscriptionTimewindow.aggregation = { + interval: SECOND, + limit: MAX_LIMIT, + type: types.aggregation.none.value, + steppedChart: true + }; + } else { + subscriptionTimewindow.aggregation = { + interval: SECOND, + limit: AVG_LIMIT, + type: types.aggregation.avg.value + }; + } - if (angular.isDefined(timewindow.aggregation)) { + if (angular.isDefined(timewindow.aggregation) && !steppedChart) { subscriptionTimewindow.aggregation = { type: timewindow.aggregation.type || types.aggregation.avg.value, limit: timewindow.aggregation.limit || AVG_LIMIT diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index 842b9768dd..9fbb8198fc 100644 --- a/ui/src/app/api/widget.service.js +++ b/ui/src/app/api/widget.service.js @@ -560,7 +560,8 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr useCustomDatasources: false, maxDatasources: -1, //unlimited maxDataKeys: -1, //unlimited - dataKeysOptional: false + dataKeysOptional: false, + steppedChart: false }; ' }\n\n' + @@ -631,6 +632,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr if (angular.isUndefined(result.typeParameters.dataKeysOptional)) { result.typeParameters.dataKeysOptional = false; } + if (angular.isUndefined(result.typeParameters.steppedChart)) { + result.typeParameters.steppedChart = false; + } if (angular.isFunction(widgetTypeInstance.actionSources)) { result.actionSources = widgetTypeInstance.actionSources(); } else { diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index 8dde1fac4c..34a48f7f4c 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -339,7 +339,8 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele var deferred = $q.defer(); if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { options = { - type: widget.type + type: widget.type, + steppedChart: vm.typeParameters.steppedChart } if (widget.type == types.widgetType.alarm.value) { options.alarmSource = angular.copy(widget.config.alarmSource); diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index ff60bde407..bf2b77e5cd 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -168,7 +168,7 @@ export default class TbFlot { } }; - if (this.chartType === 'line' || this.chartType === 'bar') { + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') { options.xaxis = { mode: 'time', timezone: 'browser', @@ -270,6 +270,14 @@ export default class TbFlot { fill: 0.9 } } + + if (this.chartType === 'stepped') { + options.series.lines = { + steps: true, + show: true + } + } + } else if (this.chartType === 'pie') { options.series = { pie: { @@ -381,7 +389,7 @@ export default class TbFlot { this.options.colors = colors; this.options.yaxes = angular.copy(this.yaxes); - if (this.chartType === 'line' || this.chartType === 'bar') { + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') { if (this.chartType === 'bar') { this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; } @@ -434,7 +442,7 @@ export default class TbFlot { } if (this.subscription) { if (!this.isMouseInteraction && this.ctx.plot) { - if (this.chartType === 'line' || this.chartType === 'bar') { + if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'stepped') { var axisVisibilityChanged = false; if (this.yaxis) {