diff --git a/application/src/main/data/json/system/widget_bundles/charts.json b/application/src/main/data/json/system/widget_bundles/charts.json index 2ecd90be28..9c97a03084 100644 --- a/application/src/main/data/json/system/widget_bundles/charts.json +++ b/application/src/main/data/json/system/widget_bundles/charts.json @@ -35,7 +35,7 @@ "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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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}" @@ -147,7 +147,7 @@ "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('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\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('bar');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(false, 'bar');\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},\"stack\":true,\"tooltipIndividual\":false,\"defaultBarWidth\":600},\"title\":\"Timeseries Bars - Flot\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{}}" @@ -163,7 +163,7 @@ "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, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true);\n}\n\nself.onDestroy = function() {\n self.ctx.flot.destroy();\n}\n", + "controllerScript": "self.onInit = function() {\n self.ctx.flot = new TbFlot(self.ctx, 'state'); \n}\n\nself.onDataUpdated = function() {\n self.ctx.flot.update();\n}\n\nself.onResize = function() {\n self.ctx.flot.resize();\n}\n\nself.typeParameters = function() {\n return {\n stateData: true\n };\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('graph');\n}\n\nself.getDataKeySettingsSchema = function() {\n return TbFlot.datakeySettingsSchema(true, 'graph');\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\":\"Switch 1\",\"color\":\"#2196f3\",\"settings\":{\"showLines\":true,\"fillLines\":true,\"showPoints\":false,\"axisPosition\":\"left\",\"showSeparateAxis\":false},\"_hash\":0.8587686344902596,\"funcBody\":\"return Math.random() > 0.5 ? 1 : 0;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Switch 2\",\"color\":\"#ffc107\",\"settings\":{\"showLines\":true,\"fillLines\":false,\"showPoints\":false,\"axisPosition\":\"left\"},\"_hash\":0.12775350966079668,\"funcBody\":\"return Math.random() <= 0.5 ? 1 : 0;\"}]}],\"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\",\"ticksFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\"},\"grid\":{\"color\":\"#545454\",\"tickColor\":\"#DDDDDD\",\"verticalLines\":true,\"horizontalLines\":true,\"outlineWidth\":1},\"stack\":false,\"tooltipIndividual\":false,\"tooltipValueFormatter\":\"if (value > 0 && value <= 1) {\\n return 'On';\\n} else if (value === 0) {\\n return 'Off';\\n} else {\\n return '';\\n}\",\"smoothLines\":false},\"title\":\"State Chart\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"mobileHeight\":null,\"widgetStyle\":{},\"useDashboardTimewindow\":true,\"showLegend\":true,\"actions\":{},\"legendConfig\":{\"position\":\"bottom\",\"showMin\":false,\"showMax\":false,\"showAvg\":false,\"showTotal\":false}}" diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js index e89650c6a4..728af2d38f 100644 --- a/ui/src/app/api/subscription.js +++ b/ui/src/app/api/subscription.js @@ -132,6 +132,13 @@ export default class Subscription { } this.subscriptionTimewindow = null; + this.comparisonEnabled = options.comparisonEnabled; + if (this.comparisonEnabled) { + this.timeForComparison = options.timeForComparison; + + this.comparisonTimeWindow = {}; + this.timewindowForComparison = null; + } this.units = options.units || ''; this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2; @@ -311,13 +318,28 @@ export default class Subscription { } configureData() { + var additionalDatasources = []; var dataIndex = 0; + var additionalKeysNumber = 0; for (var i = 0; i < this.datasources.length; i++) { var datasource = this.datasources[i]; + var additionalDataKeys = []; + let datasourceAdditionalKeysNumber = 0; + for (var a = 0; a < datasource.dataKeys.length; a++) { var dataKey = datasource.dataKeys[a]; dataKey.hidden = false; dataKey.pattern = angular.copy(dataKey.label); + + if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) { + datasourceAdditionalKeysNumber++; + additionalKeysNumber++; + let additionalDataKey = this.ctx.utils.createAdditionalDataKey(dataKey,datasource, this.timeForComparison,this.datasources,additionalKeysNumber); + dataKey.settings.comparisonSettings.color = additionalDataKey.color; + + additionalDataKeys.push(additionalDataKey); + } + var datasourceData = { datasource: datasource, dataKey: dataKey, @@ -341,8 +363,46 @@ export default class Subscription { this.legendData.data.push(legendKeyData); } } + + if (datasourceAdditionalKeysNumber > 0) { + let additionalDatasource = angular.copy(datasource); + additionalDatasource.dataKeys = additionalDataKeys; + additionalDatasource.isAdditional = true; + additionalDatasources.push(additionalDatasource); + } + } + + for (var j=0; j < additionalDatasources.length; j++) { + let additionalDatasource = additionalDatasources[j]; + for (var k=0; k < additionalDatasource.dataKeys.length; k++) { + let additionalDataKey = additionalDatasource.dataKeys[k]; + var additionalDatasourceData = { + datasource: additionalDatasource, + dataKey: additionalDataKey, + data: [] + }; + this.data.push(additionalDatasourceData); + this.hiddenData.push({data: []}); + if (this.displayLegend) { + var additionalLegendKey = { + dataKey: additionalDataKey, + dataIndex: dataIndex++ + }; + this.legendData.keys.push(additionalLegendKey); + var additionalLegendKeyData = { + min: null, + max: null, + avg: null, + total: null, + hidden: false + }; + this.legendData.data.push(additionalLegendKeyData); + } + } } + this.datasources = this.datasources.concat(additionalDatasources); + var subscription = this; var registration; @@ -638,7 +698,7 @@ export default class Subscription { updateTimewindow() { this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; if (this.subscriptionTimewindow.realtimeWindowMs) { - this.timeWindow.maxTime = (new Date).getTime() + this.timeWindow.stDiff; + this.timeWindow.maxTime = (moment()).valueOf() + this.timeWindow.stDiff;//eslint-disable-line this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; } else if (this.subscriptionTimewindow.fixedWindow) { this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; @@ -659,6 +719,26 @@ export default class Subscription { return this.subscriptionTimewindow; } + updateComparisonTimewindow() { + this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000; + if (this.timewindowForComparison.realtimeWindowMs) { + this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); //eslint-disable-line + this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs; + } else if (this.timewindowForComparison.fixedWindow) { + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs; + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs; + } + } + + updateSubscriptionForComparison() { + if (!this.subscriptionTimewindow) { + this.subscriptionTimewindow = this.updateRealtimeSubscription(); + } + this.timewindowForComparison = this.ctx.timeService.createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison); + this.updateComparisonTimewindow(); + return this.timewindowForComparison; + } + dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { for (var x = 0; x < this.datasourceListeners.length; x++) { this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; @@ -689,6 +769,9 @@ export default class Subscription { if (update) { if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { this.updateTimewindow(); + if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) { + this.updateComparisonTimewindow(); + } } currentData.data = sourceData.data; if (this.caulculateLegendData) { @@ -745,6 +828,9 @@ export default class Subscription { this.notifyDataLoading(); if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { this.updateRealtimeSubscription(); + if (this.comparisonEnabled) { + this.updateSubscriptionForComparison(); + } if (this.subscriptionTimewindow.fixedWindow) { this.onDataUpdated(); } @@ -776,6 +862,17 @@ export default class Subscription { datasourceIndex: index }; + if (this.comparisonEnabled && datasource.isAdditional) { + listener.subscriptionTimewindow = this.timewindowForComparison; + listener.updateRealtimeSubscription = function () { + this.subscriptionTimewindow = subscription.updateSubscriptionForComparison(); + return this.subscriptionTimewindow; + }; + listener.setRealtimeSubscription = function () { + subscription.updateSubscriptionForComparison(); + }; + } + for (var a = 0; a < datasource.dataKeys.length; a++) { this.data[index + a].data = []; } @@ -908,7 +1005,6 @@ export default class Subscription { }); this.registrations = []; } - } function calculateMin(data) { diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js index 878d43eed8..6b2e3f7687 100644 --- a/ui/src/app/api/time.service.js +++ b/ui/src/app/api/time.service.js @@ -47,6 +47,7 @@ function TimeService($translate, $http, $q, types) { defaultTimewindow: defaultTimewindow, toHistoryTimewindow: toHistoryTimewindow, createSubscriptionTimewindow: createSubscriptionTimewindow, + createTimewindowForComparison: createTimewindowForComparison, getMaxDatapointsLimit: function () { return maxDatapointsLimit; }, @@ -383,5 +384,27 @@ function TimeService($translate, $http, $q, types) { } } + function createTimewindowForComparison(subscriptionTimewindow, timeUnit) { + var timewindowForComparison = { + fixedWindow: null, + realtimeWindowMs: null, + aggregation: subscriptionTimewindow.aggregation + }; + + if (subscriptionTimewindow.realtimeWindowMs) { + timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf(); //eslint-disable-line + timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs; + } else if (subscriptionTimewindow.fixedWindow) { + var timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs; + var endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); //eslint-disable-line + timewindowForComparison.startTs = endTimeMs - timeInterval; + timewindowForComparison.fixedWindow = { + startTimeMs: timewindowForComparison.startTs, + endTimeMs: endTimeMs + }; + } + + return timewindowForComparison; + } } diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index dd2a32de3e..23a29308de 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -144,6 +144,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t isLocalUrl: isLocalUrl, validateDatasources: validateDatasources, createKey: createKey, + createAdditionalDataKey: createAdditionalDataKey, createLabelFromDatasource: createLabelFromDatasource, insertVariable: insertVariable, customTranslation: customTranslation, @@ -411,8 +412,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t return copy; } - function genNextColor(datasources) { - var index = 0; + function genNextColor(datasources, initialIndex) { + var index = initialIndex || 0; if (datasources) { for (var i = 0; i < datasources.length; i++) { var datasource = datasources[i]; @@ -491,6 +492,23 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t return dataKey; } + function createAdditionalDataKey(dataKey, datasource, timeUnit, datasources, additionalKeysNumber) { + let additionalDataKey = angular.copy(dataKey); + if (dataKey.settings.comparisonSettings.comparisonValuesLabel) { + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); + } else { + additionalDataKey.label = dataKey.label + ' ' + $translate.instant('legend.comparison-time-ago.'+timeUnit); + } + additionalDataKey.pattern = additionalDataKey.label; + if (dataKey.settings.comparisonSettings.color) { + additionalDataKey.color = dataKey.settings.comparisonSettings.color; + } else { + additionalDataKey.color = genNextColor(datasources, additionalKeysNumber); + } + additionalDataKey._hash = Math.random(); + return additionalDataKey; + } + function createLabelFromDatasource(datasource, pattern) { var label = angular.copy(pattern); var match = varsRegex.exec(pattern); diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js index 593c3de36e..5d98e44d0e 100644 --- a/ui/src/app/components/widget/widget.controller.js +++ b/ui/src/app/components/widget/widget.controller.js @@ -357,8 +357,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { options = { type: widget.type, - stateData: vm.typeParameters.stateData - } + stateData: vm.typeParameters.stateData, + comparisonEnabled: widgetContext.settings.comparisonEnabled, + timeForComparison: widgetContext.settings.timeForComparison + }; if (widget.type == types.widgetType.alarm.value) { options.alarmSource = angular.copy(widget.config.alarmSource); options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ? diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 63050c0fa1..b7f6a0f23f 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -1195,7 +1195,13 @@ "min": "min", "max": "max", "avg": "avg", - "total": "total" + "total": "total", + "comparison-time-ago": { + "days": "(day ago)", + "weeks": "(week ago)", + "months": "(month ago)", + "years": "(year ago)" + } }, "login": { "login": "Login", diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json index 42fd47e2b8..c2a9fd2109 100644 --- a/ui/src/app/locale/locale.constant-ru_RU.json +++ b/ui/src/app/locale/locale.constant-ru_RU.json @@ -1169,7 +1169,13 @@ "min": "Мин", "max": "Макс", "avg": "Среднее", - "total": "Сумма" + "total": "Сумма", + "comparison-time-ago": { + "days": "(день назад)", + "weeks": "(неделю назад)", + "months": "(месяц назад)", + "years": "(год назад)" + } }, "login": { "login": "Войти", diff --git a/ui/src/app/locale/locale.constant-uk_UA.json b/ui/src/app/locale/locale.constant-uk_UA.json index fa458eddb3..41c8db576f 100644 --- a/ui/src/app/locale/locale.constant-uk_UA.json +++ b/ui/src/app/locale/locale.constant-uk_UA.json @@ -1584,7 +1584,13 @@ "min": "мін", "max": "макс", "avg": "середнє", - "total": "Сума" + "total": "Сума", + "comparison-time-ago": { + "days": "(день тому)", + "weeks": "(тиждень тому)", + "months": "(місяць тому)", + "years": "(рік тому)" + } }, "login": { "login": "Вхід", diff --git a/ui/src/app/widget/lib/flot-widget.js b/ui/src/app/widget/lib/flot-widget.js index 6a26e6bec2..08496d574b 100644 --- a/ui/src/app/widget/lib/flot-widget.js +++ b/ui/src/app/widget/lib/flot-widget.js @@ -23,6 +23,7 @@ import 'flot/src/plugins/jquery.flot.selection'; import 'flot/src/plugins/jquery.flot.pie'; import 'flot/src/plugins/jquery.flot.crosshair'; import 'flot/src/plugins/jquery.flot.stack'; +import 'flot/src/plugins/jquery.flot.symbol'; import 'flot.curvedlines/curvedLines'; /* eslint-disable angular/angularelement */ @@ -32,6 +33,7 @@ export default class TbFlot { this.ctx = ctx; this.chartType = chartType || 'line'; var settings = ctx.settings; + var utils = this.ctx.$scope.$injector.get('utils'); ctx.tooltip = $('#flot-series-tooltip'); if (ctx.tooltip.length === 0) { @@ -59,7 +61,7 @@ export default class TbFlot { divElement.css({ display: "flex", alignItems: "center", - justifyContent: "flex-start" + justifyContent: "space-between" }); var lineSpan = $(''); lineSpan.css({ @@ -125,55 +127,99 @@ export default class TbFlot { } else { ctx.tooltipFormatter = function(hoverInfo, seriesIndex) { var content = ''; - var timestamp = parseInt(hoverInfo.time); - var date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); - var dateDiv = $('
' + date + '
'); - dateDiv.css({ - display: "flex", - alignItems: "center", - justifyContent: "center", - padding: "4px", - fontWeight: "700" - }); - content += dateDiv.prop('outerHTML'); + if (tbFlot.ctx.tooltipIndividual) { - var found = hoverInfo.seriesHover.filter((seriesHover) => { + var seriesHoverArray; + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + seriesHoverArray = hoverInfo[0].seriesHover.concat(hoverInfo[1].seriesHover); + } else { + seriesHoverArray = hoverInfo[0].seriesHover; + } + var found = seriesHoverArray.filter((seriesHover) => { return seriesHover.index === seriesIndex; }); if (found && found.length) { + let timestamp; + if (!angular.isNumber(hoverInfo[0].time) || (found[0].time < hoverInfo[0].time)) { + timestamp = parseInt(hoverInfo[1].time); + } else { + timestamp = parseInt(hoverInfo[0].time); + } + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); + let dateDiv = $('
' + date + '
'); + dateDiv.css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "4px", + fontWeight: "700" + }); + content += dateDiv.prop('outerHTML'); content += seriesInfoDivFromInfo(found[0], seriesIndex); } } else { - var seriesDiv = $('
'); - seriesDiv.css({ - display: "flex", - flexDirection: "row" - }); - const maxRows = 15; - var columns = Math.ceil(hoverInfo.seriesHover.length / maxRows); - var columnsContent = ''; - for (var c = 0; c < columns; c++) { - var columnDiv = $('
'); - columnDiv.css({ - display: "flex", - flexDirection: "column" - }); - var columnContent = ''; - for (var i = c*maxRows; i < (c+1)*maxRows; i++) { - if (i == hoverInfo.seriesHover.length) { - break; + var maxRows; + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + maxRows = 5; + } else { + maxRows = 15; + } + var columns = 0; + if (hoverInfo[1] && (hoverInfo[1].seriesHover.length > hoverInfo[0].seriesHover.length)) { + columns = Math.ceil(hoverInfo[1].seriesHover.length / maxRows); + } else { + columns = Math.ceil(hoverInfo[0].seriesHover.length / maxRows); + } + + for (var j = 0; j < hoverInfo.length; j++) { + var hoverData = hoverInfo[j]; + if (angular.isNumber(hoverData.time)) { + var columnsContent = ''; + let timestamp = parseInt(hoverData.time); + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss'); + let dateDiv = $('
' + date + '
'); + dateDiv.css({ + display: "flex", + alignItems: "center", + justifyContent: "center", + padding: "4px", + fontWeight: "700" + }); + content += dateDiv.prop('outerHTML'); + + var seriesDiv = $('
'); + seriesDiv.css({ + display: "flex", + flexDirection: "row" + }); + for (var c = 0; c < columns; c++) { + var columnDiv = $('
'); + columnDiv.css({ + display: "flex", + flexDirection: "column", + flex: "1" + }); + var columnContent = ''; + for (var i = c*maxRows; i < (c+1)*maxRows; i++) { + if (i >= hoverData.seriesHover.length) { + break; + } + var seriesHoverInfo = hoverData.seriesHover[i]; + columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); + } + columnDiv.html(columnContent); + + if (columnContent) { + if (c > 0) { + columnsContent += ''; + } + columnsContent += columnDiv.prop('outerHTML'); + } } - var seriesHoverInfo = hoverInfo.seriesHover[i]; - columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex); - } - columnDiv.html(columnContent); - if (c > 0) { - columnsContent += ''; + seriesDiv.html(columnsContent); + content += seriesDiv.prop('outerHTML'); } - columnsContent += columnDiv.prop('outerHTML'); } - seriesDiv.html(columnsContent); - content += seriesDiv.prop('outerHTML'); } return content; }; @@ -185,6 +231,7 @@ export default class TbFlot { ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false); ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false; + ctx.hideZeros = angular.isDefined(settings.hideZeros) ? settings.hideZeros : false; var font = { color: settings.fontColor || "#545454", @@ -209,7 +256,8 @@ export default class TbFlot { }; if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { - options.xaxis = { + options.xaxes = []; + this.xaxis = { mode: 'time', timezone: 'browser', font: angular.copy(font), @@ -220,16 +268,11 @@ export default class TbFlot { labelFont: angular.copy(font) }; if (settings.xaxis) { - if (settings.xaxis.showLabels === false) { - options.xaxis.tickFormatter = function() { - return ''; - }; - } - options.xaxis.font.color = settings.xaxis.color || options.xaxis.font.color; - options.xaxis.label = settings.xaxis.title || null; - options.xaxis.labelFont.color = options.xaxis.font.color; - options.xaxis.labelFont.size = options.xaxis.font.size+2; - options.xaxis.labelFont.weight = "bold"; + this.xaxis.font.color = settings.xaxis.color || this.xaxis.font.color; + this.xaxis.label = utils.customTranslation(settings.xaxis.title, settings.xaxis.title) || null; + this.xaxis.labelFont.color = this.xaxis.font.color; + this.xaxis.labelFont.size = this.xaxis.font.size+2; + this.xaxis.labelFont.weight = "bold"; } ctx.yAxisTickFormatter = function(value/*, axis*/) { @@ -263,7 +306,7 @@ export default class TbFlot { this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color; this.yaxis.min = angular.isDefined(settings.yaxis.min) ? settings.yaxis.min : null; this.yaxis.max = angular.isDefined(settings.yaxis.max) ? settings.yaxis.max : null; - this.yaxis.label = settings.yaxis.title || null; + this.yaxis.label = utils.customTranslation(settings.yaxis.title, settings.yaxis.title) || null; this.yaxis.labelFont.color = this.yaxis.font.color; this.yaxis.labelFont.size = this.yaxis.font.size+2; this.yaxis.labelFont.weight = "bold"; @@ -296,7 +339,7 @@ export default class TbFlot { options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ? settings.grid.outlineWidth : 1; if (settings.grid.verticalLines === false) { - options.xaxis.tickLength = 0; + this.xaxis.tickLength = 0; } if (settings.grid.horizontalLines === false) { this.yaxis.tickLength = 0; @@ -309,14 +352,42 @@ export default class TbFlot { } } - options.crosshair = { - mode: 'x' + options.xaxes[0] = angular.copy(this.xaxis); + + if (settings.xaxis && settings.xaxis.showLabels === false) { + options.xaxes[0].tickFormatter = function() { + return ''; + }; } - options.series = { - stack: settings.stack === true + if (settings.comparisonEnabled) { + var xaxis = angular.copy(this.xaxis); + xaxis.position = 'top'; + if (settings.xaxisSecond) { + if (settings.xaxisSecond.showLabels === false) { + xaxis.tickFormatter = function() { + return ''; + }; + } + xaxis.label = utils.customTranslation(settings.xaxisSecond.title, settings.xaxisSecond.title) || null; + xaxis.position = settings.xaxisSecond.axisPosition; + } + xaxis.tickLength = 0; + options.xaxes.push(xaxis); + + options.series = { + stack: false + }; + } else { + options.series = { + stack: settings.stack === true + }; } + options.crosshair = { + mode: 'x' + }; + if (this.chartType === 'line' && settings.smoothLines) { options.series.curvedLines = { active: true, @@ -429,6 +500,13 @@ export default class TbFlot { series.lines = { fill: keySettings.fillLines === true }; + + if (this.ctx.settings.stack && !this.ctx.settings.comparisonEnabled) { + series.stack = !keySettings.excludeFromStacking; + } else { + series.stack = false; + } + if (this.chartType === 'line' || this.chartType === 'state') { series.lines.show = keySettings.showLines !== false } else { @@ -445,14 +523,23 @@ export default class TbFlot { }; if (keySettings.showPoints === true) { series.points.show = true; - series.points.lineWidth = 5; + series.points.lineWidth = angular.isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5; series.points.radius = angular.isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3; + series.points.symbol = angular.isDefined(keySettings.showPointShape) ? keySettings.showPointShape : 'circle'; + if (series.points.symbol == 'custom' && keySettings.pointShapeFormatter) { + try { + series.points.symbol = new Function('ctx, x, y, radius, shadow', keySettings.pointShapeFormatter); + } catch (e) { + series.points.symbol = 'circle'; + } + } + } if (this.chartType === 'line' && this.ctx.settings.smoothLines && !series.points.show) { series.curvedLines = { apply: true - } + }; } var lineColor = tinycolor(series.dataKey.color); @@ -460,6 +547,15 @@ export default class TbFlot { series.highlightColor = lineColor.toRgbString(); + if (series.datasource.isAdditional) { + series.xaxisIndex = 1; + series.xaxis = 2; + } else { + series.xaxisIndex = 0; + series.xaxis = 1; + } + + if (this.yaxis) { var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits; var yaxis; @@ -477,7 +573,7 @@ export default class TbFlot { series.yaxisIndex = this.yaxes.indexOf(yaxis); series.yaxis = series.yaxisIndex+1; yaxis.keysInfo[i] = {hidden: false}; - yaxis.hidden = false; + yaxis.show = true; } } @@ -491,8 +587,12 @@ export default class TbFlot { this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; } } - this.options.xaxis.min = this.subscription.timeWindow.minTime; - this.options.xaxis.max = this.subscription.timeWindow.maxTime; + this.options.xaxes[0].min = this.subscription.timeWindow.minTime; + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } } this.checkMouseEvents(); @@ -500,6 +600,7 @@ export default class TbFlot { if (this.ctx.plot) { this.ctx.plot.destroy(); } + if (this.chartType === 'pie' && this.ctx.animatedPie) { this.ctx.pieDataAnimationDuration = 250; this.pieData = angular.copy(this.subscription.data); @@ -608,8 +709,12 @@ export default class TbFlot { } } - this.options.xaxis.min = this.subscription.timeWindow.minTime; - this.options.xaxis.max = this.subscription.timeWindow.maxTime; + this.options.xaxes[0].min = this.subscription.timeWindow.minTime; + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } if (this.chartType === 'bar') { if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { this.options.series.bars.barWidth = this.ctx.defaultBarWidth; @@ -623,6 +728,10 @@ export default class TbFlot { } else { this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; + if (this.ctx.settings.comparisonEnabled) { + this.ctx.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime; + this.ctx.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime; + } if (this.chartType === 'bar') { if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth; @@ -902,6 +1011,11 @@ export default class TbFlot { "type": "string", "default": "" }; + properties["hideZeros"] = { + "title": "Hide zero/false values from tooltip", + "type": "boolean", + "default": false + }; properties["grid"] = { "title": "Grid settings", @@ -1029,6 +1143,7 @@ export default class TbFlot { "key": "tooltipValueFormatter", "type": "javascript" }); + schema["form"].push("hideZeros"); schema["form"].push({ "key": "grid", "items": [ @@ -1079,6 +1194,21 @@ export default class TbFlot { } ] }); + if (chartType === 'graph' || chartType === 'bar') { + schema.groupInfoes = [{ + "formIndex":0, + "GroupTitle":"Common Settings" + }]; + schema.form = [schema.form]; + angular.merge(schema.schema.properties, chartSettingsSchemaForComparison.schema.properties); + schema.schema.required = schema.schema.required.concat(chartSettingsSchemaForComparison.schema.required); + schema.form.push(chartSettingsSchemaForComparison.form); + schema.groupInfoes.push({ + "formIndex":schema.groupInfoes.length, + "GroupTitle":"Comparison Settings" + }); + } + return schema; } @@ -1086,12 +1216,18 @@ export default class TbFlot { return {} } - static datakeySettingsSchema(defaultShowLines) { - return { + static datakeySettingsSchema(defaultShowLines, chartType) { + + var schema = { "schema": { "type": "object", "title": "DataKeySettings", "properties": { + "excludeFromStacking": { + "title": "Exclude from stacking(available in \"Stacking\" mode)", + "type": "boolean", + "default": false + }, "showLines": { "title": "Show lines", "type": "boolean", @@ -1107,6 +1243,25 @@ export default class TbFlot { "type": "boolean", "default": false }, + "showPointShape": { + "title": "Select point shape:", + "type": "string", + "default": "circle" + }, + "pointShapeFormatter": { + "title": "Point shape format function, f(ctx, x, y, radius, shadow)", + "type": "string", + "default": "var size = radius * Math.sqrt(Math.PI) / 2;\n" + + "ctx.moveTo(x - size, y - size);\n" + + "ctx.lineTo(x + size, y + size);\n" + + "ctx.moveTo(x - size, y + size);\n" + + "ctx.lineTo(x + size, y - size);" + }, + "showPointsLineWidth": { + "title": "Line width of points", + "type": "number", + "default": 5 + }, "showPointsRadius": { "title": "Radius of points", "type": "number", @@ -1161,9 +1316,46 @@ export default class TbFlot { "required": ["showLines", "fillLines", "showPoints"] }, "form": [ + "excludeFromStacking", "showLines", "fillLines", "showPoints", + { + "key": "showPointShape", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "circle", + "label": "Circle" + }, + { + "value": "cross", + "label": "Cross" + }, + { + "value": "diamond", + "label": "Diamond" + }, + { + "value": "square", + "label": "Square" + }, + { + "value": "triangle", + "label": "Triangle" + }, + { + "value": "custom", + "label": "Custom function" + } + ] + }, + { + "key": "pointShapeFormatter", + "type": "javascript" + }, + "showPointsLineWidth", "showPointsRadius", { "key": "tooltipValueFormatter", @@ -1195,7 +1387,47 @@ export default class TbFlot { "type": "javascript" } ] + }; + + var properties = schema["schema"]["properties"]; + if (chartType === 'graph' || chartType === 'bar') { + properties["comparisonSettings"] = { + "title": "Comparison Settings", + "type": "object", + "properties": { + "showValuesForComparison": { + "title": "Show historical values for comparison", + "type": "boolean", + "default": true + }, + "comparisonValuesLabel": { + "title": "Historical values label", + "type": "string", + "default": "" + }, + "color": { + "title": "Color", + "type": "string", + "default": "" + } + }, + "required": ["showValuesForComparison"] + }; + schema["form"].push({ + "key": "comparisonSettings", + "items": [ + "comparisonSettings.showValuesForComparison", + "comparisonSettings.comparisonValuesLabel", + { + "key": "comparisonSettings.color", + "type": "color" + } + ] + }); + } + + return schema; } enableMouseEvents() { @@ -1227,10 +1459,15 @@ export default class TbFlot { tooltipHtml = tbFlot.ctx.tooltipFormatter(item); } else { var hoverInfo = tbFlot.getHoverInfo(tbFlot.ctx.plot.getData(), pos); - if (angular.isNumber(hoverInfo.time)) { - hoverInfo.seriesHover.sort(function (a, b) { + if (angular.isNumber(hoverInfo[0].time) || (hoverInfo[1] && angular.isNumber(hoverInfo[1].time))) { + hoverInfo[0].seriesHover.sort(function (a, b) { return b.value - a.value; }); + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) { + hoverInfo[1].seriesHover.sort(function (a, b) { + return b.value - a.value; + }); + } tooltipHtml = tbFlot.ctx.tooltipFormatter(hoverInfo, item ? item.seriesIndex : -1); } } @@ -1258,9 +1495,11 @@ export default class TbFlot { }); if (multipleModeTooltip) { - for (var i = 0; i < hoverInfo.seriesHover.length; i++) { - var seriesHoverInfo = hoverInfo.seriesHover[i]; - tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); + for (var j = 0; j < hoverInfo.length; j++) { + for (var i = 0; i < hoverInfo[j].seriesHover.length; i++) { + var seriesHoverInfo = hoverInfo[j].seriesHover[i]; + tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); + } } } } @@ -1399,19 +1638,38 @@ export default class TbFlot { getHoverInfo (seriesList, pos) { - var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime; + var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime, hoverData; var last_value = 0; - var results = { + var results = [{ seriesHover: [] - }; + }]; + if (this.ctx.settings.comparisonEnabled) { + results.push({ + seriesHover: [] + }); + var minDistanceHistorical, minTimeHistorical; + } for (i = 0; i < seriesList.length; i++) { series = seriesList[i]; - hoverIndex = this.findHoverIndexFromData(pos.x, series); + var posx; + if (series.datasource.isAdditional) { + posx = pos.x2; + } else { + posx = pos.x; + } + hoverIndex = this.findHoverIndexFromData(posx, series); if (series.data[hoverIndex] && series.data[hoverIndex][0]) { - hoverDistance = pos.x - series.data[hoverIndex][0]; + hoverDistance = posx - series.data[hoverIndex][0]; pointTime = series.data[hoverIndex][0]; - if (!minDistance + if (series.datasource.isAdditional) { + if (!minDistanceHistorical + || (hoverDistance >= 0 && (hoverDistance < minDistanceHistorical || minDistanceHistorical < 0)) + || (hoverDistance < 0 && hoverDistance > minDistanceHistorical)) { + minDistanceHistorical = hoverDistance; + minTimeHistorical = pointTime; + } + } else if (!minDistance || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) || (hoverDistance < 0 && hoverDistance > minDistance)) { minDistance = hoverDistance; @@ -1429,23 +1687,33 @@ export default class TbFlot { } if (series.stack || (series.curvedLines && series.curvedLines.apply)) { - hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); + hoverIndex = this.findHoverIndexFromDataPoints(posx, series, hoverIndex); + } + if (!this.ctx.hideZeros || value) { + hoverData = { + value: value, + hoverIndex: hoverIndex, + color: series.dataKey.color, + label: series.dataKey.label, + units: series.dataKey.units, + decimals: series.dataKey.decimals, + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, + time: pointTime, + distance: hoverDistance, + index: i + }; + if (series.datasource.isAdditional) { + results[1].seriesHover.push(hoverData); + } else { + results[0].seriesHover.push(hoverData); + } } - results.seriesHover.push({ - value: value, - hoverIndex: hoverIndex, - color: series.dataKey.color, - label: series.dataKey.label, - units: series.dataKey.units, - decimals: series.dataKey.decimals, - tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction, - time: pointTime, - distance: hoverDistance, - index: i - }); } } - results.time = minTime; + if (results[1] && results[1].seriesHover.length) { + results[1].time = minTimeHistorical; + } + results[0].time = minTime; return results; } @@ -1524,4 +1792,93 @@ export default class TbFlot { } } +const chartSettingsSchemaForComparison = { + "schema": { + "title": "Comparison Settings", + "type": "object", + "properties": { + "comparisonEnabled": { + "title": "Enable comparison", + "type": "boolean", + "default": false + }, + "timeForComparison": { + "title": "Time to show historical data", + "type": "string", + "default": "months" + }, + "xaxisSecond": { + "title": "Second X axis", + "type": "object", + "properties": { + "axisPosition": { + "title": "Axis position", + "type": "string", + "default": "top" + }, + "showLabels": { + "title": "Show labels", + "type": "boolean", + "default": true + }, + "title": { + "title": "Axis title", + "type": "string", + "default": null + } + } + } + }, + "required": [] + }, + "form": [ + "comparisonEnabled", + { + "key": "timeForComparison", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "days", + "label": "Day ago" + }, + { + "value": "weeks", + "label": "Week ago" + }, + { + "value": "months", + "label": "Month ago (default)" + }, + { + "value": "years", + "label": "Year ago" + } + ] + }, + { + "key": "xaxisSecond", + "items": [ + { + "key": "xaxisSecond.axisPosition", + "type": "rc-select", + "multiple": false, + "items": [ + { + "value": "top", + "label": "Top (default)" + }, + { + "value": "bottom", + "label": "Bottom" + } + ] + }, + "xaxisSecond.showLabels", + "xaxisSecond.title", + ] + } + ] +}; + /* eslint-enable angular/angularelement */