Commit 467d7b5c3c5d9dce13b82e3c2da4dc15fcc0999f

Authored by Chantsova Ekaterina
Committed by Igor Kulikov
1 parent 14c2d2e5

Flots comparison option (#2079)

* Add new hidden widget cards for latest and timeseries value

* Revert "Add new hidden widget cards for latest and timeseries value"

This reverts commit 09b73d5afcc66baf942a776f83a0295700f83d22.

* Allow hiding of zero/false dataKey values from tbFlot widgets tooltips

* comparison option draft

* Added dataKey setting for excluding from Stacking mode in chart widgets.

* Flot comparison option (draft)

* Flot comparison option (draft)

* Fix color generation for additional keys

* Add new time options for comparison

* Add ability to define points symbol and line width in 'line' flot charts

* Change history timeinterval calculation, translations definition
@@ -35,7 +35,7 @@ @@ -35,7 +35,7 @@
35 "resources": [], 35 "resources": [],
36 "templateHtml": "", 36 "templateHtml": "",
37 "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", 37 "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",
38 - "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", 38 + "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",
39 "settingsSchema": "{}", 39 "settingsSchema": "{}",
40 "dataKeySettingsSchema": "{}", 40 "dataKeySettingsSchema": "{}",
41 "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}" 41 "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 @@ @@ -147,7 +147,7 @@
147 "resources": [], 147 "resources": [],
148 "templateHtml": "", 148 "templateHtml": "",
149 "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", 149 "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",
150 - "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", 150 + "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",
151 "settingsSchema": "{}", 151 "settingsSchema": "{}",
152 "dataKeySettingsSchema": "{}", 152 "dataKeySettingsSchema": "{}",
153 "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\":{}}" 153 "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 @@ @@ -163,7 +163,7 @@
163 "resources": [], 163 "resources": [],
164 "templateHtml": "", 164 "templateHtml": "",
165 "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", 165 "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",
166 - "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", 166 + "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",
167 "settingsSchema": "{}", 167 "settingsSchema": "{}",
168 "dataKeySettingsSchema": "{}", 168 "dataKeySettingsSchema": "{}",
169 "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}}" 169 "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}}"
@@ -132,6 +132,13 @@ export default class Subscription { @@ -132,6 +132,13 @@ export default class Subscription {
132 } 132 }
133 133
134 this.subscriptionTimewindow = null; 134 this.subscriptionTimewindow = null;
  135 + this.comparisonEnabled = options.comparisonEnabled;
  136 + if (this.comparisonEnabled) {
  137 + this.timeForComparison = options.timeForComparison;
  138 +
  139 + this.comparisonTimeWindow = {};
  140 + this.timewindowForComparison = null;
  141 + }
135 142
136 this.units = options.units || ''; 143 this.units = options.units || '';
137 this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2; 144 this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2;
@@ -311,13 +318,28 @@ export default class Subscription { @@ -311,13 +318,28 @@ export default class Subscription {
311 } 318 }
312 319
313 configureData() { 320 configureData() {
  321 + var additionalDatasources = [];
314 var dataIndex = 0; 322 var dataIndex = 0;
  323 + var additionalKeysNumber = 0;
315 for (var i = 0; i < this.datasources.length; i++) { 324 for (var i = 0; i < this.datasources.length; i++) {
316 var datasource = this.datasources[i]; 325 var datasource = this.datasources[i];
  326 + var additionalDataKeys = [];
  327 + let datasourceAdditionalKeysNumber = 0;
  328 +
317 for (var a = 0; a < datasource.dataKeys.length; a++) { 329 for (var a = 0; a < datasource.dataKeys.length; a++) {
318 var dataKey = datasource.dataKeys[a]; 330 var dataKey = datasource.dataKeys[a];
319 dataKey.hidden = false; 331 dataKey.hidden = false;
320 dataKey.pattern = angular.copy(dataKey.label); 332 dataKey.pattern = angular.copy(dataKey.label);
  333 +
  334 + if (this.comparisonEnabled && dataKey.settings.comparisonSettings && dataKey.settings.comparisonSettings.showValuesForComparison) {
  335 + datasourceAdditionalKeysNumber++;
  336 + additionalKeysNumber++;
  337 + let additionalDataKey = this.ctx.utils.createAdditionalDataKey(dataKey,datasource, this.timeForComparison,this.datasources,additionalKeysNumber);
  338 + dataKey.settings.comparisonSettings.color = additionalDataKey.color;
  339 +
  340 + additionalDataKeys.push(additionalDataKey);
  341 + }
  342 +
321 var datasourceData = { 343 var datasourceData = {
322 datasource: datasource, 344 datasource: datasource,
323 dataKey: dataKey, 345 dataKey: dataKey,
@@ -341,8 +363,46 @@ export default class Subscription { @@ -341,8 +363,46 @@ export default class Subscription {
341 this.legendData.data.push(legendKeyData); 363 this.legendData.data.push(legendKeyData);
342 } 364 }
343 } 365 }
  366 +
  367 + if (datasourceAdditionalKeysNumber > 0) {
  368 + let additionalDatasource = angular.copy(datasource);
  369 + additionalDatasource.dataKeys = additionalDataKeys;
  370 + additionalDatasource.isAdditional = true;
  371 + additionalDatasources.push(additionalDatasource);
  372 + }
  373 + }
  374 +
  375 + for (var j=0; j < additionalDatasources.length; j++) {
  376 + let additionalDatasource = additionalDatasources[j];
  377 + for (var k=0; k < additionalDatasource.dataKeys.length; k++) {
  378 + let additionalDataKey = additionalDatasource.dataKeys[k];
  379 + var additionalDatasourceData = {
  380 + datasource: additionalDatasource,
  381 + dataKey: additionalDataKey,
  382 + data: []
  383 + };
  384 + this.data.push(additionalDatasourceData);
  385 + this.hiddenData.push({data: []});
  386 + if (this.displayLegend) {
  387 + var additionalLegendKey = {
  388 + dataKey: additionalDataKey,
  389 + dataIndex: dataIndex++
  390 + };
  391 + this.legendData.keys.push(additionalLegendKey);
  392 + var additionalLegendKeyData = {
  393 + min: null,
  394 + max: null,
  395 + avg: null,
  396 + total: null,
  397 + hidden: false
  398 + };
  399 + this.legendData.data.push(additionalLegendKeyData);
  400 + }
  401 + }
344 } 402 }
345 403
  404 + this.datasources = this.datasources.concat(additionalDatasources);
  405 +
346 var subscription = this; 406 var subscription = this;
347 var registration; 407 var registration;
348 408
@@ -638,7 +698,7 @@ export default class Subscription { @@ -638,7 +698,7 @@ export default class Subscription {
638 updateTimewindow() { 698 updateTimewindow() {
639 this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; 699 this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
640 if (this.subscriptionTimewindow.realtimeWindowMs) { 700 if (this.subscriptionTimewindow.realtimeWindowMs) {
641 - this.timeWindow.maxTime = (new Date).getTime() + this.timeWindow.stDiff; 701 + this.timeWindow.maxTime = (moment()).valueOf() + this.timeWindow.stDiff;//eslint-disable-line
642 this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; 702 this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
643 } else if (this.subscriptionTimewindow.fixedWindow) { 703 } else if (this.subscriptionTimewindow.fixedWindow) {
644 this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; 704 this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs;
@@ -659,6 +719,26 @@ export default class Subscription { @@ -659,6 +719,26 @@ export default class Subscription {
659 return this.subscriptionTimewindow; 719 return this.subscriptionTimewindow;
660 } 720 }
661 721
  722 + updateComparisonTimewindow() {
  723 + this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
  724 + if (this.timewindowForComparison.realtimeWindowMs) {
  725 + this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf(); //eslint-disable-line
  726 + this.comparisonTimeWindow.minTime = this.comparisonTimeWindow.maxTime - this.timewindowForComparison.realtimeWindowMs;
  727 + } else if (this.timewindowForComparison.fixedWindow) {
  728 + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs;
  729 + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs;
  730 + }
  731 + }
  732 +
  733 + updateSubscriptionForComparison() {
  734 + if (!this.subscriptionTimewindow) {
  735 + this.subscriptionTimewindow = this.updateRealtimeSubscription();
  736 + }
  737 + this.timewindowForComparison = this.ctx.timeService.createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
  738 + this.updateComparisonTimewindow();
  739 + return this.timewindowForComparison;
  740 + }
  741 +
662 dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { 742 dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
663 for (var x = 0; x < this.datasourceListeners.length; x++) { 743 for (var x = 0; x < this.datasourceListeners.length; x++) {
664 this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; 744 this.datasources[x].dataReceived = this.datasources[x].dataReceived === true;
@@ -689,6 +769,9 @@ export default class Subscription { @@ -689,6 +769,9 @@ export default class Subscription {
689 if (update) { 769 if (update) {
690 if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { 770 if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
691 this.updateTimewindow(); 771 this.updateTimewindow();
  772 + if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) {
  773 + this.updateComparisonTimewindow();
  774 + }
692 } 775 }
693 currentData.data = sourceData.data; 776 currentData.data = sourceData.data;
694 if (this.caulculateLegendData) { 777 if (this.caulculateLegendData) {
@@ -745,6 +828,9 @@ export default class Subscription { @@ -745,6 +828,9 @@ export default class Subscription {
745 this.notifyDataLoading(); 828 this.notifyDataLoading();
746 if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { 829 if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) {
747 this.updateRealtimeSubscription(); 830 this.updateRealtimeSubscription();
  831 + if (this.comparisonEnabled) {
  832 + this.updateSubscriptionForComparison();
  833 + }
748 if (this.subscriptionTimewindow.fixedWindow) { 834 if (this.subscriptionTimewindow.fixedWindow) {
749 this.onDataUpdated(); 835 this.onDataUpdated();
750 } 836 }
@@ -776,6 +862,17 @@ export default class Subscription { @@ -776,6 +862,17 @@ export default class Subscription {
776 datasourceIndex: index 862 datasourceIndex: index
777 }; 863 };
778 864
  865 + if (this.comparisonEnabled && datasource.isAdditional) {
  866 + listener.subscriptionTimewindow = this.timewindowForComparison;
  867 + listener.updateRealtimeSubscription = function () {
  868 + this.subscriptionTimewindow = subscription.updateSubscriptionForComparison();
  869 + return this.subscriptionTimewindow;
  870 + };
  871 + listener.setRealtimeSubscription = function () {
  872 + subscription.updateSubscriptionForComparison();
  873 + };
  874 + }
  875 +
779 for (var a = 0; a < datasource.dataKeys.length; a++) { 876 for (var a = 0; a < datasource.dataKeys.length; a++) {
780 this.data[index + a].data = []; 877 this.data[index + a].data = [];
781 } 878 }
@@ -908,7 +1005,6 @@ export default class Subscription { @@ -908,7 +1005,6 @@ export default class Subscription {
908 }); 1005 });
909 this.registrations = []; 1006 this.registrations = [];
910 } 1007 }
911 -  
912 } 1008 }
913 1009
914 function calculateMin(data) { 1010 function calculateMin(data) {
@@ -47,6 +47,7 @@ function TimeService($translate, $http, $q, types) { @@ -47,6 +47,7 @@ function TimeService($translate, $http, $q, types) {
47 defaultTimewindow: defaultTimewindow, 47 defaultTimewindow: defaultTimewindow,
48 toHistoryTimewindow: toHistoryTimewindow, 48 toHistoryTimewindow: toHistoryTimewindow,
49 createSubscriptionTimewindow: createSubscriptionTimewindow, 49 createSubscriptionTimewindow: createSubscriptionTimewindow,
  50 + createTimewindowForComparison: createTimewindowForComparison,
50 getMaxDatapointsLimit: function () { 51 getMaxDatapointsLimit: function () {
51 return maxDatapointsLimit; 52 return maxDatapointsLimit;
52 }, 53 },
@@ -383,5 +384,27 @@ function TimeService($translate, $http, $q, types) { @@ -383,5 +384,27 @@ function TimeService($translate, $http, $q, types) {
383 } 384 }
384 } 385 }
385 386
  387 + function createTimewindowForComparison(subscriptionTimewindow, timeUnit) {
  388 + var timewindowForComparison = {
  389 + fixedWindow: null,
  390 + realtimeWindowMs: null,
  391 + aggregation: subscriptionTimewindow.aggregation
  392 + };
  393 +
  394 + if (subscriptionTimewindow.realtimeWindowMs) {
  395 + timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf(); //eslint-disable-line
  396 + timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;
  397 + } else if (subscriptionTimewindow.fixedWindow) {
  398 + var timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
  399 + var endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf(); //eslint-disable-line
386 400
  401 + timewindowForComparison.startTs = endTimeMs - timeInterval;
  402 + timewindowForComparison.fixedWindow = {
  403 + startTimeMs: timewindowForComparison.startTs,
  404 + endTimeMs: endTimeMs
  405 + };
  406 + }
  407 +
  408 + return timewindowForComparison;
  409 + }
387 } 410 }
@@ -144,6 +144,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t @@ -144,6 +144,7 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
144 isLocalUrl: isLocalUrl, 144 isLocalUrl: isLocalUrl,
145 validateDatasources: validateDatasources, 145 validateDatasources: validateDatasources,
146 createKey: createKey, 146 createKey: createKey,
  147 + createAdditionalDataKey: createAdditionalDataKey,
147 createLabelFromDatasource: createLabelFromDatasource, 148 createLabelFromDatasource: createLabelFromDatasource,
148 insertVariable: insertVariable, 149 insertVariable: insertVariable,
149 customTranslation: customTranslation, 150 customTranslation: customTranslation,
@@ -411,8 +412,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t @@ -411,8 +412,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
411 return copy; 412 return copy;
412 } 413 }
413 414
414 - function genNextColor(datasources) {  
415 - var index = 0; 415 + function genNextColor(datasources, initialIndex) {
  416 + var index = initialIndex || 0;
416 if (datasources) { 417 if (datasources) {
417 for (var i = 0; i < datasources.length; i++) { 418 for (var i = 0; i < datasources.length; i++) {
418 var datasource = datasources[i]; 419 var datasource = datasources[i];
@@ -491,6 +492,23 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t @@ -491,6 +492,23 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t
491 return dataKey; 492 return dataKey;
492 } 493 }
493 494
  495 + function createAdditionalDataKey(dataKey, datasource, timeUnit, datasources, additionalKeysNumber) {
  496 + let additionalDataKey = angular.copy(dataKey);
  497 + if (dataKey.settings.comparisonSettings.comparisonValuesLabel) {
  498 + additionalDataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
  499 + } else {
  500 + additionalDataKey.label = dataKey.label + ' ' + $translate.instant('legend.comparison-time-ago.'+timeUnit);
  501 + }
  502 + additionalDataKey.pattern = additionalDataKey.label;
  503 + if (dataKey.settings.comparisonSettings.color) {
  504 + additionalDataKey.color = dataKey.settings.comparisonSettings.color;
  505 + } else {
  506 + additionalDataKey.color = genNextColor(datasources, additionalKeysNumber);
  507 + }
  508 + additionalDataKey._hash = Math.random();
  509 + return additionalDataKey;
  510 + }
  511 +
494 function createLabelFromDatasource(datasource, pattern) { 512 function createLabelFromDatasource(datasource, pattern) {
495 var label = angular.copy(pattern); 513 var label = angular.copy(pattern);
496 var match = varsRegex.exec(pattern); 514 var match = varsRegex.exec(pattern);
@@ -357,8 +357,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL @@ -357,8 +357,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL
357 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { 357 if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) {
358 options = { 358 options = {
359 type: widget.type, 359 type: widget.type,
360 - stateData: vm.typeParameters.stateData  
361 - } 360 + stateData: vm.typeParameters.stateData,
  361 + comparisonEnabled: widgetContext.settings.comparisonEnabled,
  362 + timeForComparison: widgetContext.settings.timeForComparison
  363 + };
362 if (widget.type == types.widgetType.alarm.value) { 364 if (widget.type == types.widgetType.alarm.value) {
363 options.alarmSource = angular.copy(widget.config.alarmSource); 365 options.alarmSource = angular.copy(widget.config.alarmSource);
364 options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ? 366 options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ?
@@ -1195,7 +1195,13 @@ @@ -1195,7 +1195,13 @@
1195 "min": "min", 1195 "min": "min",
1196 "max": "max", 1196 "max": "max",
1197 "avg": "avg", 1197 "avg": "avg",
1198 - "total": "total" 1198 + "total": "total",
  1199 + "comparison-time-ago": {
  1200 + "days": "(day ago)",
  1201 + "weeks": "(week ago)",
  1202 + "months": "(month ago)",
  1203 + "years": "(year ago)"
  1204 + }
1199 }, 1205 },
1200 "login": { 1206 "login": {
1201 "login": "Login", 1207 "login": "Login",
@@ -1169,7 +1169,13 @@ @@ -1169,7 +1169,13 @@
1169 "min": "Мин", 1169 "min": "Мин",
1170 "max": "Макс", 1170 "max": "Макс",
1171 "avg": "Среднее", 1171 "avg": "Среднее",
1172 - "total": "Сумма" 1172 + "total": "Сумма",
  1173 + "comparison-time-ago": {
  1174 + "days": "(день назад)",
  1175 + "weeks": "(неделю назад)",
  1176 + "months": "(месяц назад)",
  1177 + "years": "(год назад)"
  1178 + }
1173 }, 1179 },
1174 "login": { 1180 "login": {
1175 "login": "Войти", 1181 "login": "Войти",
@@ -1584,7 +1584,13 @@ @@ -1584,7 +1584,13 @@
1584 "min": "мін", 1584 "min": "мін",
1585 "max": "макс", 1585 "max": "макс",
1586 "avg": "середнє", 1586 "avg": "середнє",
1587 - "total": "Сума" 1587 + "total": "Сума",
  1588 + "comparison-time-ago": {
  1589 + "days": "(день тому)",
  1590 + "weeks": "(тиждень тому)",
  1591 + "months": "(місяць тому)",
  1592 + "years": "(рік тому)"
  1593 + }
1588 }, 1594 },
1589 "login": { 1595 "login": {
1590 "login": "Вхід", 1596 "login": "Вхід",
@@ -23,6 +23,7 @@ import 'flot/src/plugins/jquery.flot.selection'; @@ -23,6 +23,7 @@ import 'flot/src/plugins/jquery.flot.selection';
23 import 'flot/src/plugins/jquery.flot.pie'; 23 import 'flot/src/plugins/jquery.flot.pie';
24 import 'flot/src/plugins/jquery.flot.crosshair'; 24 import 'flot/src/plugins/jquery.flot.crosshair';
25 import 'flot/src/plugins/jquery.flot.stack'; 25 import 'flot/src/plugins/jquery.flot.stack';
  26 +import 'flot/src/plugins/jquery.flot.symbol';
26 import 'flot.curvedlines/curvedLines'; 27 import 'flot.curvedlines/curvedLines';
27 28
28 /* eslint-disable angular/angularelement */ 29 /* eslint-disable angular/angularelement */
@@ -32,6 +33,7 @@ export default class TbFlot { @@ -32,6 +33,7 @@ export default class TbFlot {
32 this.ctx = ctx; 33 this.ctx = ctx;
33 this.chartType = chartType || 'line'; 34 this.chartType = chartType || 'line';
34 var settings = ctx.settings; 35 var settings = ctx.settings;
  36 + var utils = this.ctx.$scope.$injector.get('utils');
35 37
36 ctx.tooltip = $('#flot-series-tooltip'); 38 ctx.tooltip = $('#flot-series-tooltip');
37 if (ctx.tooltip.length === 0) { 39 if (ctx.tooltip.length === 0) {
@@ -59,7 +61,7 @@ export default class TbFlot { @@ -59,7 +61,7 @@ export default class TbFlot {
59 divElement.css({ 61 divElement.css({
60 display: "flex", 62 display: "flex",
61 alignItems: "center", 63 alignItems: "center",
62 - justifyContent: "flex-start" 64 + justifyContent: "space-between"
63 }); 65 });
64 var lineSpan = $('<span></span>'); 66 var lineSpan = $('<span></span>');
65 lineSpan.css({ 67 lineSpan.css({
@@ -125,55 +127,99 @@ export default class TbFlot { @@ -125,55 +127,99 @@ export default class TbFlot {
125 } else { 127 } else {
126 ctx.tooltipFormatter = function(hoverInfo, seriesIndex) { 128 ctx.tooltipFormatter = function(hoverInfo, seriesIndex) {
127 var content = ''; 129 var content = '';
128 - var timestamp = parseInt(hoverInfo.time);  
129 - var date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');  
130 - var dateDiv = $('<div>' + date + '</div>');  
131 - dateDiv.css({  
132 - display: "flex",  
133 - alignItems: "center",  
134 - justifyContent: "center",  
135 - padding: "4px",  
136 - fontWeight: "700"  
137 - });  
138 - content += dateDiv.prop('outerHTML'); 130 +
139 if (tbFlot.ctx.tooltipIndividual) { 131 if (tbFlot.ctx.tooltipIndividual) {
140 - var found = hoverInfo.seriesHover.filter((seriesHover) => { 132 + var seriesHoverArray;
  133 + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
  134 + seriesHoverArray = hoverInfo[0].seriesHover.concat(hoverInfo[1].seriesHover);
  135 + } else {
  136 + seriesHoverArray = hoverInfo[0].seriesHover;
  137 + }
  138 + var found = seriesHoverArray.filter((seriesHover) => {
141 return seriesHover.index === seriesIndex; 139 return seriesHover.index === seriesIndex;
142 }); 140 });
143 if (found && found.length) { 141 if (found && found.length) {
  142 + let timestamp;
  143 + if (!angular.isNumber(hoverInfo[0].time) || (found[0].time < hoverInfo[0].time)) {
  144 + timestamp = parseInt(hoverInfo[1].time);
  145 + } else {
  146 + timestamp = parseInt(hoverInfo[0].time);
  147 + }
  148 + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
  149 + let dateDiv = $('<div>' + date + '</div>');
  150 + dateDiv.css({
  151 + display: "flex",
  152 + alignItems: "center",
  153 + justifyContent: "center",
  154 + padding: "4px",
  155 + fontWeight: "700"
  156 + });
  157 + content += dateDiv.prop('outerHTML');
144 content += seriesInfoDivFromInfo(found[0], seriesIndex); 158 content += seriesInfoDivFromInfo(found[0], seriesIndex);
145 } 159 }
146 } else { 160 } else {
147 - var seriesDiv = $('<div></div>');  
148 - seriesDiv.css({  
149 - display: "flex",  
150 - flexDirection: "row"  
151 - });  
152 - const maxRows = 15;  
153 - var columns = Math.ceil(hoverInfo.seriesHover.length / maxRows);  
154 - var columnsContent = '';  
155 - for (var c = 0; c < columns; c++) {  
156 - var columnDiv = $('<div></div>');  
157 - columnDiv.css({  
158 - display: "flex",  
159 - flexDirection: "column"  
160 - });  
161 - var columnContent = '';  
162 - for (var i = c*maxRows; i < (c+1)*maxRows; i++) {  
163 - if (i == hoverInfo.seriesHover.length) {  
164 - break; 161 + var maxRows;
  162 + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
  163 + maxRows = 5;
  164 + } else {
  165 + maxRows = 15;
  166 + }
  167 + var columns = 0;
  168 + if (hoverInfo[1] && (hoverInfo[1].seriesHover.length > hoverInfo[0].seriesHover.length)) {
  169 + columns = Math.ceil(hoverInfo[1].seriesHover.length / maxRows);
  170 + } else {
  171 + columns = Math.ceil(hoverInfo[0].seriesHover.length / maxRows);
  172 + }
  173 +
  174 + for (var j = 0; j < hoverInfo.length; j++) {
  175 + var hoverData = hoverInfo[j];
  176 + if (angular.isNumber(hoverData.time)) {
  177 + var columnsContent = '';
  178 + let timestamp = parseInt(hoverData.time);
  179 + let date = moment(timestamp).format('YYYY-MM-DD HH:mm:ss');
  180 + let dateDiv = $('<div>' + date + '</div>');
  181 + dateDiv.css({
  182 + display: "flex",
  183 + alignItems: "center",
  184 + justifyContent: "center",
  185 + padding: "4px",
  186 + fontWeight: "700"
  187 + });
  188 + content += dateDiv.prop('outerHTML');
  189 +
  190 + var seriesDiv = $('<div></div>');
  191 + seriesDiv.css({
  192 + display: "flex",
  193 + flexDirection: "row"
  194 + });
  195 + for (var c = 0; c < columns; c++) {
  196 + var columnDiv = $('<div></div>');
  197 + columnDiv.css({
  198 + display: "flex",
  199 + flexDirection: "column",
  200 + flex: "1"
  201 + });
  202 + var columnContent = '';
  203 + for (var i = c*maxRows; i < (c+1)*maxRows; i++) {
  204 + if (i >= hoverData.seriesHover.length) {
  205 + break;
  206 + }
  207 + var seriesHoverInfo = hoverData.seriesHover[i];
  208 + columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);
  209 + }
  210 + columnDiv.html(columnContent);
  211 +
  212 + if (columnContent) {
  213 + if (c > 0) {
  214 + columnsContent += '<span style="min-width: 20px;"></span>';
  215 + }
  216 + columnsContent += columnDiv.prop('outerHTML');
  217 + }
165 } 218 }
166 - var seriesHoverInfo = hoverInfo.seriesHover[i];  
167 - columnContent += seriesInfoDivFromInfo(seriesHoverInfo, seriesIndex);  
168 - }  
169 - columnDiv.html(columnContent);  
170 - if (c > 0) {  
171 - columnsContent += '<span style="width: 20px;"></span>'; 219 + seriesDiv.html(columnsContent);
  220 + content += seriesDiv.prop('outerHTML');
172 } 221 }
173 - columnsContent += columnDiv.prop('outerHTML');  
174 } 222 }
175 - seriesDiv.html(columnsContent);  
176 - content += seriesDiv.prop('outerHTML');  
177 } 223 }
178 return content; 224 return content;
179 }; 225 };
@@ -185,6 +231,7 @@ export default class TbFlot { @@ -185,6 +231,7 @@ export default class TbFlot {
185 231
186 ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false); 232 ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false);
187 ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false; 233 ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false;
  234 + ctx.hideZeros = angular.isDefined(settings.hideZeros) ? settings.hideZeros : false;
188 235
189 var font = { 236 var font = {
190 color: settings.fontColor || "#545454", 237 color: settings.fontColor || "#545454",
@@ -209,7 +256,8 @@ export default class TbFlot { @@ -209,7 +256,8 @@ export default class TbFlot {
209 }; 256 };
210 257
211 if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { 258 if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') {
212 - options.xaxis = { 259 + options.xaxes = [];
  260 + this.xaxis = {
213 mode: 'time', 261 mode: 'time',
214 timezone: 'browser', 262 timezone: 'browser',
215 font: angular.copy(font), 263 font: angular.copy(font),
@@ -220,16 +268,11 @@ export default class TbFlot { @@ -220,16 +268,11 @@ export default class TbFlot {
220 labelFont: angular.copy(font) 268 labelFont: angular.copy(font)
221 }; 269 };
222 if (settings.xaxis) { 270 if (settings.xaxis) {
223 - if (settings.xaxis.showLabels === false) {  
224 - options.xaxis.tickFormatter = function() {  
225 - return '';  
226 - };  
227 - }  
228 - options.xaxis.font.color = settings.xaxis.color || options.xaxis.font.color;  
229 - options.xaxis.label = settings.xaxis.title || null;  
230 - options.xaxis.labelFont.color = options.xaxis.font.color;  
231 - options.xaxis.labelFont.size = options.xaxis.font.size+2;  
232 - options.xaxis.labelFont.weight = "bold"; 271 + this.xaxis.font.color = settings.xaxis.color || this.xaxis.font.color;
  272 + this.xaxis.label = utils.customTranslation(settings.xaxis.title, settings.xaxis.title) || null;
  273 + this.xaxis.labelFont.color = this.xaxis.font.color;
  274 + this.xaxis.labelFont.size = this.xaxis.font.size+2;
  275 + this.xaxis.labelFont.weight = "bold";
233 } 276 }
234 277
235 ctx.yAxisTickFormatter = function(value/*, axis*/) { 278 ctx.yAxisTickFormatter = function(value/*, axis*/) {
@@ -263,7 +306,7 @@ export default class TbFlot { @@ -263,7 +306,7 @@ export default class TbFlot {
263 this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color; 306 this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color;
264 this.yaxis.min = angular.isDefined(settings.yaxis.min) ? settings.yaxis.min : null; 307 this.yaxis.min = angular.isDefined(settings.yaxis.min) ? settings.yaxis.min : null;
265 this.yaxis.max = angular.isDefined(settings.yaxis.max) ? settings.yaxis.max : null; 308 this.yaxis.max = angular.isDefined(settings.yaxis.max) ? settings.yaxis.max : null;
266 - this.yaxis.label = settings.yaxis.title || null; 309 + this.yaxis.label = utils.customTranslation(settings.yaxis.title, settings.yaxis.title) || null;
267 this.yaxis.labelFont.color = this.yaxis.font.color; 310 this.yaxis.labelFont.color = this.yaxis.font.color;
268 this.yaxis.labelFont.size = this.yaxis.font.size+2; 311 this.yaxis.labelFont.size = this.yaxis.font.size+2;
269 this.yaxis.labelFont.weight = "bold"; 312 this.yaxis.labelFont.weight = "bold";
@@ -296,7 +339,7 @@ export default class TbFlot { @@ -296,7 +339,7 @@ export default class TbFlot {
296 options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ? 339 options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ?
297 settings.grid.outlineWidth : 1; 340 settings.grid.outlineWidth : 1;
298 if (settings.grid.verticalLines === false) { 341 if (settings.grid.verticalLines === false) {
299 - options.xaxis.tickLength = 0; 342 + this.xaxis.tickLength = 0;
300 } 343 }
301 if (settings.grid.horizontalLines === false) { 344 if (settings.grid.horizontalLines === false) {
302 this.yaxis.tickLength = 0; 345 this.yaxis.tickLength = 0;
@@ -309,14 +352,42 @@ export default class TbFlot { @@ -309,14 +352,42 @@ export default class TbFlot {
309 } 352 }
310 } 353 }
311 354
312 - options.crosshair = {  
313 - mode: 'x' 355 + options.xaxes[0] = angular.copy(this.xaxis);
  356 +
  357 + if (settings.xaxis && settings.xaxis.showLabels === false) {
  358 + options.xaxes[0].tickFormatter = function() {
  359 + return '';
  360 + };
314 } 361 }
315 362
316 - options.series = {  
317 - stack: settings.stack === true 363 + if (settings.comparisonEnabled) {
  364 + var xaxis = angular.copy(this.xaxis);
  365 + xaxis.position = 'top';
  366 + if (settings.xaxisSecond) {
  367 + if (settings.xaxisSecond.showLabels === false) {
  368 + xaxis.tickFormatter = function() {
  369 + return '';
  370 + };
  371 + }
  372 + xaxis.label = utils.customTranslation(settings.xaxisSecond.title, settings.xaxisSecond.title) || null;
  373 + xaxis.position = settings.xaxisSecond.axisPosition;
  374 + }
  375 + xaxis.tickLength = 0;
  376 + options.xaxes.push(xaxis);
  377 +
  378 + options.series = {
  379 + stack: false
  380 + };
  381 + } else {
  382 + options.series = {
  383 + stack: settings.stack === true
  384 + };
318 } 385 }
319 386
  387 + options.crosshair = {
  388 + mode: 'x'
  389 + };
  390 +
320 if (this.chartType === 'line' && settings.smoothLines) { 391 if (this.chartType === 'line' && settings.smoothLines) {
321 options.series.curvedLines = { 392 options.series.curvedLines = {
322 active: true, 393 active: true,
@@ -429,6 +500,13 @@ export default class TbFlot { @@ -429,6 +500,13 @@ export default class TbFlot {
429 series.lines = { 500 series.lines = {
430 fill: keySettings.fillLines === true 501 fill: keySettings.fillLines === true
431 }; 502 };
  503 +
  504 + if (this.ctx.settings.stack && !this.ctx.settings.comparisonEnabled) {
  505 + series.stack = !keySettings.excludeFromStacking;
  506 + } else {
  507 + series.stack = false;
  508 + }
  509 +
432 if (this.chartType === 'line' || this.chartType === 'state') { 510 if (this.chartType === 'line' || this.chartType === 'state') {
433 series.lines.show = keySettings.showLines !== false 511 series.lines.show = keySettings.showLines !== false
434 } else { 512 } else {
@@ -445,14 +523,23 @@ export default class TbFlot { @@ -445,14 +523,23 @@ export default class TbFlot {
445 }; 523 };
446 if (keySettings.showPoints === true) { 524 if (keySettings.showPoints === true) {
447 series.points.show = true; 525 series.points.show = true;
448 - series.points.lineWidth = 5; 526 + series.points.lineWidth = angular.isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5;
449 series.points.radius = angular.isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3; 527 series.points.radius = angular.isDefined(keySettings.showPointsRadius) ? keySettings.showPointsRadius : 3;
  528 + series.points.symbol = angular.isDefined(keySettings.showPointShape) ? keySettings.showPointShape : 'circle';
  529 + if (series.points.symbol == 'custom' && keySettings.pointShapeFormatter) {
  530 + try {
  531 + series.points.symbol = new Function('ctx, x, y, radius, shadow', keySettings.pointShapeFormatter);
  532 + } catch (e) {
  533 + series.points.symbol = 'circle';
  534 + }
  535 + }
  536 +
450 } 537 }
451 538
452 if (this.chartType === 'line' && this.ctx.settings.smoothLines && !series.points.show) { 539 if (this.chartType === 'line' && this.ctx.settings.smoothLines && !series.points.show) {
453 series.curvedLines = { 540 series.curvedLines = {
454 apply: true 541 apply: true
455 - } 542 + };
456 } 543 }
457 544
458 var lineColor = tinycolor(series.dataKey.color); 545 var lineColor = tinycolor(series.dataKey.color);
@@ -460,6 +547,15 @@ export default class TbFlot { @@ -460,6 +547,15 @@ export default class TbFlot {
460 547
461 series.highlightColor = lineColor.toRgbString(); 548 series.highlightColor = lineColor.toRgbString();
462 549
  550 + if (series.datasource.isAdditional) {
  551 + series.xaxisIndex = 1;
  552 + series.xaxis = 2;
  553 + } else {
  554 + series.xaxisIndex = 0;
  555 + series.xaxis = 1;
  556 + }
  557 +
  558 +
463 if (this.yaxis) { 559 if (this.yaxis) {
464 var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits; 560 var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits;
465 var yaxis; 561 var yaxis;
@@ -477,7 +573,7 @@ export default class TbFlot { @@ -477,7 +573,7 @@ export default class TbFlot {
477 series.yaxisIndex = this.yaxes.indexOf(yaxis); 573 series.yaxisIndex = this.yaxes.indexOf(yaxis);
478 series.yaxis = series.yaxisIndex+1; 574 series.yaxis = series.yaxisIndex+1;
479 yaxis.keysInfo[i] = {hidden: false}; 575 yaxis.keysInfo[i] = {hidden: false};
480 - yaxis.hidden = false; 576 + yaxis.show = true;
481 } 577 }
482 } 578 }
483 579
@@ -491,8 +587,12 @@ export default class TbFlot { @@ -491,8 +587,12 @@ export default class TbFlot {
491 this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6; 587 this.options.series.bars.barWidth = this.subscription.timeWindow.interval * 0.6;
492 } 588 }
493 } 589 }
494 - this.options.xaxis.min = this.subscription.timeWindow.minTime;  
495 - this.options.xaxis.max = this.subscription.timeWindow.maxTime; 590 + this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
  591 + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
  592 + if (this.ctx.settings.comparisonEnabled) {
  593 + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
  594 + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
  595 + }
496 } 596 }
497 597
498 this.checkMouseEvents(); 598 this.checkMouseEvents();
@@ -500,6 +600,7 @@ export default class TbFlot { @@ -500,6 +600,7 @@ export default class TbFlot {
500 if (this.ctx.plot) { 600 if (this.ctx.plot) {
501 this.ctx.plot.destroy(); 601 this.ctx.plot.destroy();
502 } 602 }
  603 +
503 if (this.chartType === 'pie' && this.ctx.animatedPie) { 604 if (this.chartType === 'pie' && this.ctx.animatedPie) {
504 this.ctx.pieDataAnimationDuration = 250; 605 this.ctx.pieDataAnimationDuration = 250;
505 this.pieData = angular.copy(this.subscription.data); 606 this.pieData = angular.copy(this.subscription.data);
@@ -608,8 +709,12 @@ export default class TbFlot { @@ -608,8 +709,12 @@ export default class TbFlot {
608 } 709 }
609 } 710 }
610 711
611 - this.options.xaxis.min = this.subscription.timeWindow.minTime;  
612 - this.options.xaxis.max = this.subscription.timeWindow.maxTime; 712 + this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
  713 + this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
  714 + if (this.ctx.settings.comparisonEnabled) {
  715 + this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
  716 + this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
  717 + }
613 if (this.chartType === 'bar') { 718 if (this.chartType === 'bar') {
614 if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { 719 if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') {
615 this.options.series.bars.barWidth = this.ctx.defaultBarWidth; 720 this.options.series.bars.barWidth = this.ctx.defaultBarWidth;
@@ -623,6 +728,10 @@ export default class TbFlot { @@ -623,6 +728,10 @@ export default class TbFlot {
623 } else { 728 } else {
624 this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; 729 this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
625 this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime; 730 this.ctx.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
  731 + if (this.ctx.settings.comparisonEnabled) {
  732 + this.ctx.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
  733 + this.ctx.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
  734 + }
626 if (this.chartType === 'bar') { 735 if (this.chartType === 'bar') {
627 if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { 736 if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') {
628 this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth; 737 this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth;
@@ -902,6 +1011,11 @@ export default class TbFlot { @@ -902,6 +1011,11 @@ export default class TbFlot {
902 "type": "string", 1011 "type": "string",
903 "default": "" 1012 "default": ""
904 }; 1013 };
  1014 + properties["hideZeros"] = {
  1015 + "title": "Hide zero/false values from tooltip",
  1016 + "type": "boolean",
  1017 + "default": false
  1018 + };
905 1019
906 properties["grid"] = { 1020 properties["grid"] = {
907 "title": "Grid settings", 1021 "title": "Grid settings",
@@ -1029,6 +1143,7 @@ export default class TbFlot { @@ -1029,6 +1143,7 @@ export default class TbFlot {
1029 "key": "tooltipValueFormatter", 1143 "key": "tooltipValueFormatter",
1030 "type": "javascript" 1144 "type": "javascript"
1031 }); 1145 });
  1146 + schema["form"].push("hideZeros");
1032 schema["form"].push({ 1147 schema["form"].push({
1033 "key": "grid", 1148 "key": "grid",
1034 "items": [ 1149 "items": [
@@ -1079,6 +1194,21 @@ export default class TbFlot { @@ -1079,6 +1194,21 @@ export default class TbFlot {
1079 } 1194 }
1080 ] 1195 ]
1081 }); 1196 });
  1197 + if (chartType === 'graph' || chartType === 'bar') {
  1198 + schema.groupInfoes = [{
  1199 + "formIndex":0,
  1200 + "GroupTitle":"Common Settings"
  1201 + }];
  1202 + schema.form = [schema.form];
  1203 + angular.merge(schema.schema.properties, chartSettingsSchemaForComparison.schema.properties);
  1204 + schema.schema.required = schema.schema.required.concat(chartSettingsSchemaForComparison.schema.required);
  1205 + schema.form.push(chartSettingsSchemaForComparison.form);
  1206 + schema.groupInfoes.push({
  1207 + "formIndex":schema.groupInfoes.length,
  1208 + "GroupTitle":"Comparison Settings"
  1209 + });
  1210 + }
  1211 +
1082 return schema; 1212 return schema;
1083 } 1213 }
1084 1214
@@ -1086,12 +1216,18 @@ export default class TbFlot { @@ -1086,12 +1216,18 @@ export default class TbFlot {
1086 return {} 1216 return {}
1087 } 1217 }
1088 1218
1089 - static datakeySettingsSchema(defaultShowLines) {  
1090 - return { 1219 + static datakeySettingsSchema(defaultShowLines, chartType) {
  1220 +
  1221 + var schema = {
1091 "schema": { 1222 "schema": {
1092 "type": "object", 1223 "type": "object",
1093 "title": "DataKeySettings", 1224 "title": "DataKeySettings",
1094 "properties": { 1225 "properties": {
  1226 + "excludeFromStacking": {
  1227 + "title": "Exclude from stacking(available in \"Stacking\" mode)",
  1228 + "type": "boolean",
  1229 + "default": false
  1230 + },
1095 "showLines": { 1231 "showLines": {
1096 "title": "Show lines", 1232 "title": "Show lines",
1097 "type": "boolean", 1233 "type": "boolean",
@@ -1107,6 +1243,25 @@ export default class TbFlot { @@ -1107,6 +1243,25 @@ export default class TbFlot {
1107 "type": "boolean", 1243 "type": "boolean",
1108 "default": false 1244 "default": false
1109 }, 1245 },
  1246 + "showPointShape": {
  1247 + "title": "Select point shape:",
  1248 + "type": "string",
  1249 + "default": "circle"
  1250 + },
  1251 + "pointShapeFormatter": {
  1252 + "title": "Point shape format function, f(ctx, x, y, radius, shadow)",
  1253 + "type": "string",
  1254 + "default": "var size = radius * Math.sqrt(Math.PI) / 2;\n" +
  1255 + "ctx.moveTo(x - size, y - size);\n" +
  1256 + "ctx.lineTo(x + size, y + size);\n" +
  1257 + "ctx.moveTo(x - size, y + size);\n" +
  1258 + "ctx.lineTo(x + size, y - size);"
  1259 + },
  1260 + "showPointsLineWidth": {
  1261 + "title": "Line width of points",
  1262 + "type": "number",
  1263 + "default": 5
  1264 + },
1110 "showPointsRadius": { 1265 "showPointsRadius": {
1111 "title": "Radius of points", 1266 "title": "Radius of points",
1112 "type": "number", 1267 "type": "number",
@@ -1161,9 +1316,46 @@ export default class TbFlot { @@ -1161,9 +1316,46 @@ export default class TbFlot {
1161 "required": ["showLines", "fillLines", "showPoints"] 1316 "required": ["showLines", "fillLines", "showPoints"]
1162 }, 1317 },
1163 "form": [ 1318 "form": [
  1319 + "excludeFromStacking",
1164 "showLines", 1320 "showLines",
1165 "fillLines", 1321 "fillLines",
1166 "showPoints", 1322 "showPoints",
  1323 + {
  1324 + "key": "showPointShape",
  1325 + "type": "rc-select",
  1326 + "multiple": false,
  1327 + "items": [
  1328 + {
  1329 + "value": "circle",
  1330 + "label": "Circle"
  1331 + },
  1332 + {
  1333 + "value": "cross",
  1334 + "label": "Cross"
  1335 + },
  1336 + {
  1337 + "value": "diamond",
  1338 + "label": "Diamond"
  1339 + },
  1340 + {
  1341 + "value": "square",
  1342 + "label": "Square"
  1343 + },
  1344 + {
  1345 + "value": "triangle",
  1346 + "label": "Triangle"
  1347 + },
  1348 + {
  1349 + "value": "custom",
  1350 + "label": "Custom function"
  1351 + }
  1352 + ]
  1353 + },
  1354 + {
  1355 + "key": "pointShapeFormatter",
  1356 + "type": "javascript"
  1357 + },
  1358 + "showPointsLineWidth",
1167 "showPointsRadius", 1359 "showPointsRadius",
1168 { 1360 {
1169 "key": "tooltipValueFormatter", 1361 "key": "tooltipValueFormatter",
@@ -1195,7 +1387,47 @@ export default class TbFlot { @@ -1195,7 +1387,47 @@ export default class TbFlot {
1195 "type": "javascript" 1387 "type": "javascript"
1196 } 1388 }
1197 ] 1389 ]
  1390 + };
  1391 +
  1392 + var properties = schema["schema"]["properties"];
  1393 + if (chartType === 'graph' || chartType === 'bar') {
  1394 + properties["comparisonSettings"] = {
  1395 + "title": "Comparison Settings",
  1396 + "type": "object",
  1397 + "properties": {
  1398 + "showValuesForComparison": {
  1399 + "title": "Show historical values for comparison",
  1400 + "type": "boolean",
  1401 + "default": true
  1402 + },
  1403 + "comparisonValuesLabel": {
  1404 + "title": "Historical values label",
  1405 + "type": "string",
  1406 + "default": ""
  1407 + },
  1408 + "color": {
  1409 + "title": "Color",
  1410 + "type": "string",
  1411 + "default": ""
  1412 + }
  1413 + },
  1414 + "required": ["showValuesForComparison"]
  1415 + };
  1416 + schema["form"].push({
  1417 + "key": "comparisonSettings",
  1418 + "items": [
  1419 + "comparisonSettings.showValuesForComparison",
  1420 + "comparisonSettings.comparisonValuesLabel",
  1421 + {
  1422 + "key": "comparisonSettings.color",
  1423 + "type": "color"
  1424 + }
  1425 + ]
  1426 + });
  1427 +
1198 } 1428 }
  1429 +
  1430 + return schema;
1199 } 1431 }
1200 1432
1201 enableMouseEvents() { 1433 enableMouseEvents() {
@@ -1227,10 +1459,15 @@ export default class TbFlot { @@ -1227,10 +1459,15 @@ export default class TbFlot {
1227 tooltipHtml = tbFlot.ctx.tooltipFormatter(item); 1459 tooltipHtml = tbFlot.ctx.tooltipFormatter(item);
1228 } else { 1460 } else {
1229 var hoverInfo = tbFlot.getHoverInfo(tbFlot.ctx.plot.getData(), pos); 1461 var hoverInfo = tbFlot.getHoverInfo(tbFlot.ctx.plot.getData(), pos);
1230 - if (angular.isNumber(hoverInfo.time)) {  
1231 - hoverInfo.seriesHover.sort(function (a, b) { 1462 + if (angular.isNumber(hoverInfo[0].time) || (hoverInfo[1] && angular.isNumber(hoverInfo[1].time))) {
  1463 + hoverInfo[0].seriesHover.sort(function (a, b) {
1232 return b.value - a.value; 1464 return b.value - a.value;
1233 }); 1465 });
  1466 + if (hoverInfo[1] && hoverInfo[1].seriesHover.length) {
  1467 + hoverInfo[1].seriesHover.sort(function (a, b) {
  1468 + return b.value - a.value;
  1469 + });
  1470 + }
1234 tooltipHtml = tbFlot.ctx.tooltipFormatter(hoverInfo, item ? item.seriesIndex : -1); 1471 tooltipHtml = tbFlot.ctx.tooltipFormatter(hoverInfo, item ? item.seriesIndex : -1);
1235 } 1472 }
1236 } 1473 }
@@ -1258,9 +1495,11 @@ export default class TbFlot { @@ -1258,9 +1495,11 @@ export default class TbFlot {
1258 }); 1495 });
1259 1496
1260 if (multipleModeTooltip) { 1497 if (multipleModeTooltip) {
1261 - for (var i = 0; i < hoverInfo.seriesHover.length; i++) {  
1262 - var seriesHoverInfo = hoverInfo.seriesHover[i];  
1263 - tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex); 1498 + for (var j = 0; j < hoverInfo.length; j++) {
  1499 + for (var i = 0; i < hoverInfo[j].seriesHover.length; i++) {
  1500 + var seriesHoverInfo = hoverInfo[j].seriesHover[i];
  1501 + tbFlot.ctx.plot.highlight(seriesHoverInfo.index, seriesHoverInfo.hoverIndex);
  1502 + }
1264 } 1503 }
1265 } 1504 }
1266 } 1505 }
@@ -1399,19 +1638,38 @@ export default class TbFlot { @@ -1399,19 +1638,38 @@ export default class TbFlot {
1399 1638
1400 1639
1401 getHoverInfo (seriesList, pos) { 1640 getHoverInfo (seriesList, pos) {
1402 - var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime; 1641 + var i, series, value, hoverIndex, hoverDistance, pointTime, minDistance, minTime, hoverData;
1403 var last_value = 0; 1642 var last_value = 0;
1404 - var results = { 1643 + var results = [{
1405 seriesHover: [] 1644 seriesHover: []
1406 - }; 1645 + }];
  1646 + if (this.ctx.settings.comparisonEnabled) {
  1647 + results.push({
  1648 + seriesHover: []
  1649 + });
  1650 + var minDistanceHistorical, minTimeHistorical;
  1651 + }
1407 for (i = 0; i < seriesList.length; i++) { 1652 for (i = 0; i < seriesList.length; i++) {
1408 series = seriesList[i]; 1653 series = seriesList[i];
1409 - hoverIndex = this.findHoverIndexFromData(pos.x, series); 1654 + var posx;
  1655 + if (series.datasource.isAdditional) {
  1656 + posx = pos.x2;
  1657 + } else {
  1658 + posx = pos.x;
  1659 + }
  1660 + hoverIndex = this.findHoverIndexFromData(posx, series);
1410 if (series.data[hoverIndex] && series.data[hoverIndex][0]) { 1661 if (series.data[hoverIndex] && series.data[hoverIndex][0]) {
1411 - hoverDistance = pos.x - series.data[hoverIndex][0]; 1662 + hoverDistance = posx - series.data[hoverIndex][0];
1412 pointTime = series.data[hoverIndex][0]; 1663 pointTime = series.data[hoverIndex][0];
1413 1664
1414 - if (!minDistance 1665 + if (series.datasource.isAdditional) {
  1666 + if (!minDistanceHistorical
  1667 + || (hoverDistance >= 0 && (hoverDistance < minDistanceHistorical || minDistanceHistorical < 0))
  1668 + || (hoverDistance < 0 && hoverDistance > minDistanceHistorical)) {
  1669 + minDistanceHistorical = hoverDistance;
  1670 + minTimeHistorical = pointTime;
  1671 + }
  1672 + } else if (!minDistance
1415 || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) 1673 || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0))
1416 || (hoverDistance < 0 && hoverDistance > minDistance)) { 1674 || (hoverDistance < 0 && hoverDistance > minDistance)) {
1417 minDistance = hoverDistance; 1675 minDistance = hoverDistance;
@@ -1429,23 +1687,33 @@ export default class TbFlot { @@ -1429,23 +1687,33 @@ export default class TbFlot {
1429 } 1687 }
1430 1688
1431 if (series.stack || (series.curvedLines && series.curvedLines.apply)) { 1689 if (series.stack || (series.curvedLines && series.curvedLines.apply)) {
1432 - hoverIndex = this.findHoverIndexFromDataPoints(pos.x, series, hoverIndex); 1690 + hoverIndex = this.findHoverIndexFromDataPoints(posx, series, hoverIndex);
  1691 + }
  1692 + if (!this.ctx.hideZeros || value) {
  1693 + hoverData = {
  1694 + value: value,
  1695 + hoverIndex: hoverIndex,
  1696 + color: series.dataKey.color,
  1697 + label: series.dataKey.label,
  1698 + units: series.dataKey.units,
  1699 + decimals: series.dataKey.decimals,
  1700 + tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction,
  1701 + time: pointTime,
  1702 + distance: hoverDistance,
  1703 + index: i
  1704 + };
  1705 + if (series.datasource.isAdditional) {
  1706 + results[1].seriesHover.push(hoverData);
  1707 + } else {
  1708 + results[0].seriesHover.push(hoverData);
  1709 + }
1433 } 1710 }
1434 - results.seriesHover.push({  
1435 - value: value,  
1436 - hoverIndex: hoverIndex,  
1437 - color: series.dataKey.color,  
1438 - label: series.dataKey.label,  
1439 - units: series.dataKey.units,  
1440 - decimals: series.dataKey.decimals,  
1441 - tooltipValueFormatFunction: series.dataKey.tooltipValueFormatFunction,  
1442 - time: pointTime,  
1443 - distance: hoverDistance,  
1444 - index: i  
1445 - });  
1446 } 1711 }
1447 } 1712 }
1448 - results.time = minTime; 1713 + if (results[1] && results[1].seriesHover.length) {
  1714 + results[1].time = minTimeHistorical;
  1715 + }
  1716 + results[0].time = minTime;
1449 return results; 1717 return results;
1450 } 1718 }
1451 1719
@@ -1524,4 +1792,93 @@ export default class TbFlot { @@ -1524,4 +1792,93 @@ export default class TbFlot {
1524 } 1792 }
1525 } 1793 }
1526 1794
  1795 +const chartSettingsSchemaForComparison = {
  1796 + "schema": {
  1797 + "title": "Comparison Settings",
  1798 + "type": "object",
  1799 + "properties": {
  1800 + "comparisonEnabled": {
  1801 + "title": "Enable comparison",
  1802 + "type": "boolean",
  1803 + "default": false
  1804 + },
  1805 + "timeForComparison": {
  1806 + "title": "Time to show historical data",
  1807 + "type": "string",
  1808 + "default": "months"
  1809 + },
  1810 + "xaxisSecond": {
  1811 + "title": "Second X axis",
  1812 + "type": "object",
  1813 + "properties": {
  1814 + "axisPosition": {
  1815 + "title": "Axis position",
  1816 + "type": "string",
  1817 + "default": "top"
  1818 + },
  1819 + "showLabels": {
  1820 + "title": "Show labels",
  1821 + "type": "boolean",
  1822 + "default": true
  1823 + },
  1824 + "title": {
  1825 + "title": "Axis title",
  1826 + "type": "string",
  1827 + "default": null
  1828 + }
  1829 + }
  1830 + }
  1831 + },
  1832 + "required": []
  1833 + },
  1834 + "form": [
  1835 + "comparisonEnabled",
  1836 + {
  1837 + "key": "timeForComparison",
  1838 + "type": "rc-select",
  1839 + "multiple": false,
  1840 + "items": [
  1841 + {
  1842 + "value": "days",
  1843 + "label": "Day ago"
  1844 + },
  1845 + {
  1846 + "value": "weeks",
  1847 + "label": "Week ago"
  1848 + },
  1849 + {
  1850 + "value": "months",
  1851 + "label": "Month ago (default)"
  1852 + },
  1853 + {
  1854 + "value": "years",
  1855 + "label": "Year ago"
  1856 + }
  1857 + ]
  1858 + },
  1859 + {
  1860 + "key": "xaxisSecond",
  1861 + "items": [
  1862 + {
  1863 + "key": "xaxisSecond.axisPosition",
  1864 + "type": "rc-select",
  1865 + "multiple": false,
  1866 + "items": [
  1867 + {
  1868 + "value": "top",
  1869 + "label": "Top (default)"
  1870 + },
  1871 + {
  1872 + "value": "bottom",
  1873 + "label": "Bottom"
  1874 + }
  1875 + ]
  1876 + },
  1877 + "xaxisSecond.showLabels",
  1878 + "xaxisSecond.title",
  1879 + ]
  1880 + }
  1881 + ]
  1882 +};
  1883 +
1527 /* eslint-enable angular/angularelement */ 1884 /* eslint-enable angular/angularelement */