Commit 467d7b5c3c5d9dce13b82e3c2da4dc15fcc0999f
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
Showing
9 changed files
with
617 additions
and
103 deletions
... | ... | @@ -35,7 +35,7 @@ |
35 | 35 | "resources": [], |
36 | 36 | "templateHtml": "", |
37 | 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 | 39 | "settingsSchema": "{}", |
40 | 40 | "dataKeySettingsSchema": "{}", |
41 | 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 | 147 | "resources": [], |
148 | 148 | "templateHtml": "", |
149 | 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 | 151 | "settingsSchema": "{}", |
152 | 152 | "dataKeySettingsSchema": "{}", |
153 | 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 | 163 | "resources": [], |
164 | 164 | "templateHtml": "", |
165 | 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 | 167 | "settingsSchema": "{}", |
168 | 168 | "dataKeySettingsSchema": "{}", |
169 | 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 | 132 | } |
133 | 133 | |
134 | 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 | 143 | this.units = options.units || ''; |
137 | 144 | this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2; |
... | ... | @@ -311,13 +318,28 @@ export default class Subscription { |
311 | 318 | } |
312 | 319 | |
313 | 320 | configureData() { |
321 | + var additionalDatasources = []; | |
314 | 322 | var dataIndex = 0; |
323 | + var additionalKeysNumber = 0; | |
315 | 324 | for (var i = 0; i < this.datasources.length; i++) { |
316 | 325 | var datasource = this.datasources[i]; |
326 | + var additionalDataKeys = []; | |
327 | + let datasourceAdditionalKeysNumber = 0; | |
328 | + | |
317 | 329 | for (var a = 0; a < datasource.dataKeys.length; a++) { |
318 | 330 | var dataKey = datasource.dataKeys[a]; |
319 | 331 | dataKey.hidden = false; |
320 | 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 | 343 | var datasourceData = { |
322 | 344 | datasource: datasource, |
323 | 345 | dataKey: dataKey, |
... | ... | @@ -341,8 +363,46 @@ export default class Subscription { |
341 | 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 | 406 | var subscription = this; |
347 | 407 | var registration; |
348 | 408 | |
... | ... | @@ -638,7 +698,7 @@ export default class Subscription { |
638 | 698 | updateTimewindow() { |
639 | 699 | this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; |
640 | 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 | 702 | this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; |
643 | 703 | } else if (this.subscriptionTimewindow.fixedWindow) { |
644 | 704 | this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; |
... | ... | @@ -659,6 +719,26 @@ export default class Subscription { |
659 | 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 | 742 | dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { |
663 | 743 | for (var x = 0; x < this.datasourceListeners.length; x++) { |
664 | 744 | this.datasources[x].dataReceived = this.datasources[x].dataReceived === true; |
... | ... | @@ -689,6 +769,9 @@ export default class Subscription { |
689 | 769 | if (update) { |
690 | 770 | if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { |
691 | 771 | this.updateTimewindow(); |
772 | + if (this.timewindowForComparison && this.timewindowForComparison.realtimeWindowMs) { | |
773 | + this.updateComparisonTimewindow(); | |
774 | + } | |
692 | 775 | } |
693 | 776 | currentData.data = sourceData.data; |
694 | 777 | if (this.caulculateLegendData) { |
... | ... | @@ -745,6 +828,9 @@ export default class Subscription { |
745 | 828 | this.notifyDataLoading(); |
746 | 829 | if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { |
747 | 830 | this.updateRealtimeSubscription(); |
831 | + if (this.comparisonEnabled) { | |
832 | + this.updateSubscriptionForComparison(); | |
833 | + } | |
748 | 834 | if (this.subscriptionTimewindow.fixedWindow) { |
749 | 835 | this.onDataUpdated(); |
750 | 836 | } |
... | ... | @@ -776,6 +862,17 @@ export default class Subscription { |
776 | 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 | 876 | for (var a = 0; a < datasource.dataKeys.length; a++) { |
780 | 877 | this.data[index + a].data = []; |
781 | 878 | } |
... | ... | @@ -908,7 +1005,6 @@ export default class Subscription { |
908 | 1005 | }); |
909 | 1006 | this.registrations = []; |
910 | 1007 | } |
911 | - | |
912 | 1008 | } |
913 | 1009 | |
914 | 1010 | function calculateMin(data) { | ... | ... |
... | ... | @@ -47,6 +47,7 @@ function TimeService($translate, $http, $q, types) { |
47 | 47 | defaultTimewindow: defaultTimewindow, |
48 | 48 | toHistoryTimewindow: toHistoryTimewindow, |
49 | 49 | createSubscriptionTimewindow: createSubscriptionTimewindow, |
50 | + createTimewindowForComparison: createTimewindowForComparison, | |
50 | 51 | getMaxDatapointsLimit: function () { |
51 | 52 | return maxDatapointsLimit; |
52 | 53 | }, |
... | ... | @@ -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 | 144 | isLocalUrl: isLocalUrl, |
145 | 145 | validateDatasources: validateDatasources, |
146 | 146 | createKey: createKey, |
147 | + createAdditionalDataKey: createAdditionalDataKey, | |
147 | 148 | createLabelFromDatasource: createLabelFromDatasource, |
148 | 149 | insertVariable: insertVariable, |
149 | 150 | customTranslation: customTranslation, |
... | ... | @@ -411,8 +412,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t |
411 | 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 | 417 | if (datasources) { |
417 | 418 | for (var i = 0; i < datasources.length; i++) { |
418 | 419 | var datasource = datasources[i]; |
... | ... | @@ -491,6 +492,23 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t |
491 | 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 | 512 | function createLabelFromDatasource(datasource, pattern) { |
495 | 513 | var label = angular.copy(pattern); |
496 | 514 | var match = varsRegex.exec(pattern); | ... | ... |
... | ... | @@ -357,8 +357,10 @@ export default function WidgetController($scope, $state, $timeout, $window, $ocL |
357 | 357 | if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { |
358 | 358 | options = { |
359 | 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 | 364 | if (widget.type == types.widgetType.alarm.value) { |
363 | 365 | options.alarmSource = angular.copy(widget.config.alarmSource); |
364 | 366 | options.alarmSearchStatus = angular.isDefined(widget.config.alarmSearchStatus) ? | ... | ... |
... | ... | @@ -1195,7 +1195,13 @@ |
1195 | 1195 | "min": "min", |
1196 | 1196 | "max": "max", |
1197 | 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 | 1206 | "login": { |
1201 | 1207 | "login": "Login", | ... | ... |
... | ... | @@ -1169,7 +1169,13 @@ |
1169 | 1169 | "min": "Мин", |
1170 | 1170 | "max": "Макс", |
1171 | 1171 | "avg": "Среднее", |
1172 | - "total": "Сумма" | |
1172 | + "total": "Сумма", | |
1173 | + "comparison-time-ago": { | |
1174 | + "days": "(день назад)", | |
1175 | + "weeks": "(неделю назад)", | |
1176 | + "months": "(месяц назад)", | |
1177 | + "years": "(год назад)" | |
1178 | + } | |
1173 | 1179 | }, |
1174 | 1180 | "login": { |
1175 | 1181 | "login": "Войти", | ... | ... |
... | ... | @@ -1584,7 +1584,13 @@ |
1584 | 1584 | "min": "мін", |
1585 | 1585 | "max": "макс", |
1586 | 1586 | "avg": "середнє", |
1587 | - "total": "Сума" | |
1587 | + "total": "Сума", | |
1588 | + "comparison-time-ago": { | |
1589 | + "days": "(день тому)", | |
1590 | + "weeks": "(тиждень тому)", | |
1591 | + "months": "(місяць тому)", | |
1592 | + "years": "(рік тому)" | |
1593 | + } | |
1588 | 1594 | }, |
1589 | 1595 | "login": { |
1590 | 1596 | "login": "Вхід", | ... | ... |
... | ... | @@ -23,6 +23,7 @@ import 'flot/src/plugins/jquery.flot.selection'; |
23 | 23 | import 'flot/src/plugins/jquery.flot.pie'; |
24 | 24 | import 'flot/src/plugins/jquery.flot.crosshair'; |
25 | 25 | import 'flot/src/plugins/jquery.flot.stack'; |
26 | +import 'flot/src/plugins/jquery.flot.symbol'; | |
26 | 27 | import 'flot.curvedlines/curvedLines'; |
27 | 28 | |
28 | 29 | /* eslint-disable angular/angularelement */ |
... | ... | @@ -32,6 +33,7 @@ export default class TbFlot { |
32 | 33 | this.ctx = ctx; |
33 | 34 | this.chartType = chartType || 'line'; |
34 | 35 | var settings = ctx.settings; |
36 | + var utils = this.ctx.$scope.$injector.get('utils'); | |
35 | 37 | |
36 | 38 | ctx.tooltip = $('#flot-series-tooltip'); |
37 | 39 | if (ctx.tooltip.length === 0) { |
... | ... | @@ -59,7 +61,7 @@ export default class TbFlot { |
59 | 61 | divElement.css({ |
60 | 62 | display: "flex", |
61 | 63 | alignItems: "center", |
62 | - justifyContent: "flex-start" | |
64 | + justifyContent: "space-between" | |
63 | 65 | }); |
64 | 66 | var lineSpan = $('<span></span>'); |
65 | 67 | lineSpan.css({ |
... | ... | @@ -125,55 +127,99 @@ export default class TbFlot { |
125 | 127 | } else { |
126 | 128 | ctx.tooltipFormatter = function(hoverInfo, seriesIndex) { |
127 | 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 | 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 | 139 | return seriesHover.index === seriesIndex; |
142 | 140 | }); |
143 | 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 | 158 | content += seriesInfoDivFromInfo(found[0], seriesIndex); |
145 | 159 | } |
146 | 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 | 224 | return content; |
179 | 225 | }; |
... | ... | @@ -185,6 +231,7 @@ export default class TbFlot { |
185 | 231 | |
186 | 232 | ctx.tooltipIndividual = this.chartType === 'pie' || (angular.isDefined(settings.tooltipIndividual) ? settings.tooltipIndividual : false); |
187 | 233 | ctx.tooltipCumulative = angular.isDefined(settings.tooltipCumulative) ? settings.tooltipCumulative : false; |
234 | + ctx.hideZeros = angular.isDefined(settings.hideZeros) ? settings.hideZeros : false; | |
188 | 235 | |
189 | 236 | var font = { |
190 | 237 | color: settings.fontColor || "#545454", |
... | ... | @@ -209,7 +256,8 @@ export default class TbFlot { |
209 | 256 | }; |
210 | 257 | |
211 | 258 | if (this.chartType === 'line' || this.chartType === 'bar' || this.chartType === 'state') { |
212 | - options.xaxis = { | |
259 | + options.xaxes = []; | |
260 | + this.xaxis = { | |
213 | 261 | mode: 'time', |
214 | 262 | timezone: 'browser', |
215 | 263 | font: angular.copy(font), |
... | ... | @@ -220,16 +268,11 @@ export default class TbFlot { |
220 | 268 | labelFont: angular.copy(font) |
221 | 269 | }; |
222 | 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 | 278 | ctx.yAxisTickFormatter = function(value/*, axis*/) { |
... | ... | @@ -263,7 +306,7 @@ export default class TbFlot { |
263 | 306 | this.yaxis.font.color = settings.yaxis.color || this.yaxis.font.color; |
264 | 307 | this.yaxis.min = angular.isDefined(settings.yaxis.min) ? settings.yaxis.min : null; |
265 | 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 | 310 | this.yaxis.labelFont.color = this.yaxis.font.color; |
268 | 311 | this.yaxis.labelFont.size = this.yaxis.font.size+2; |
269 | 312 | this.yaxis.labelFont.weight = "bold"; |
... | ... | @@ -296,7 +339,7 @@ export default class TbFlot { |
296 | 339 | options.grid.borderWidth = angular.isDefined(settings.grid.outlineWidth) ? |
297 | 340 | settings.grid.outlineWidth : 1; |
298 | 341 | if (settings.grid.verticalLines === false) { |
299 | - options.xaxis.tickLength = 0; | |
342 | + this.xaxis.tickLength = 0; | |
300 | 343 | } |
301 | 344 | if (settings.grid.horizontalLines === false) { |
302 | 345 | this.yaxis.tickLength = 0; |
... | ... | @@ -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 | 391 | if (this.chartType === 'line' && settings.smoothLines) { |
321 | 392 | options.series.curvedLines = { |
322 | 393 | active: true, |
... | ... | @@ -429,6 +500,13 @@ export default class TbFlot { |
429 | 500 | series.lines = { |
430 | 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 | 510 | if (this.chartType === 'line' || this.chartType === 'state') { |
433 | 511 | series.lines.show = keySettings.showLines !== false |
434 | 512 | } else { |
... | ... | @@ -445,14 +523,23 @@ export default class TbFlot { |
445 | 523 | }; |
446 | 524 | if (keySettings.showPoints === true) { |
447 | 525 | series.points.show = true; |
448 | - series.points.lineWidth = 5; | |
526 | + series.points.lineWidth = angular.isDefined(keySettings.showPointsLineWidth) ? keySettings.showPointsLineWidth : 5; | |
449 | 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 | 539 | if (this.chartType === 'line' && this.ctx.settings.smoothLines && !series.points.show) { |
453 | 540 | series.curvedLines = { |
454 | 541 | apply: true |
455 | - } | |
542 | + }; | |
456 | 543 | } |
457 | 544 | |
458 | 545 | var lineColor = tinycolor(series.dataKey.color); |
... | ... | @@ -460,6 +547,15 @@ export default class TbFlot { |
460 | 547 | |
461 | 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 | 559 | if (this.yaxis) { |
464 | 560 | var units = series.dataKey.units && series.dataKey.units.length ? series.dataKey.units : this.ctx.trackUnits; |
465 | 561 | var yaxis; |
... | ... | @@ -477,7 +573,7 @@ export default class TbFlot { |
477 | 573 | series.yaxisIndex = this.yaxes.indexOf(yaxis); |
478 | 574 | series.yaxis = series.yaxisIndex+1; |
479 | 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 | 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 | 598 | this.checkMouseEvents(); |
... | ... | @@ -500,6 +600,7 @@ export default class TbFlot { |
500 | 600 | if (this.ctx.plot) { |
501 | 601 | this.ctx.plot.destroy(); |
502 | 602 | } |
603 | + | |
503 | 604 | if (this.chartType === 'pie' && this.ctx.animatedPie) { |
504 | 605 | this.ctx.pieDataAnimationDuration = 250; |
505 | 606 | this.pieData = angular.copy(this.subscription.data); |
... | ... | @@ -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 | 718 | if (this.chartType === 'bar') { |
614 | 719 | if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { |
615 | 720 | this.options.series.bars.barWidth = this.ctx.defaultBarWidth; |
... | ... | @@ -623,6 +728,10 @@ export default class TbFlot { |
623 | 728 | } else { |
624 | 729 | this.ctx.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime; |
625 | 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 | 735 | if (this.chartType === 'bar') { |
627 | 736 | if (this.subscription.timeWindowConfig.aggregation && this.subscription.timeWindowConfig.aggregation.type === 'NONE') { |
628 | 737 | this.ctx.plot.getOptions().series.bars.barWidth = this.ctx.defaultBarWidth; |
... | ... | @@ -902,6 +1011,11 @@ export default class TbFlot { |
902 | 1011 | "type": "string", |
903 | 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 | 1020 | properties["grid"] = { |
907 | 1021 | "title": "Grid settings", |
... | ... | @@ -1029,6 +1143,7 @@ export default class TbFlot { |
1029 | 1143 | "key": "tooltipValueFormatter", |
1030 | 1144 | "type": "javascript" |
1031 | 1145 | }); |
1146 | + schema["form"].push("hideZeros"); | |
1032 | 1147 | schema["form"].push({ |
1033 | 1148 | "key": "grid", |
1034 | 1149 | "items": [ |
... | ... | @@ -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 | 1212 | return schema; |
1083 | 1213 | } |
1084 | 1214 | |
... | ... | @@ -1086,12 +1216,18 @@ export default class TbFlot { |
1086 | 1216 | return {} |
1087 | 1217 | } |
1088 | 1218 | |
1089 | - static datakeySettingsSchema(defaultShowLines) { | |
1090 | - return { | |
1219 | + static datakeySettingsSchema(defaultShowLines, chartType) { | |
1220 | + | |
1221 | + var schema = { | |
1091 | 1222 | "schema": { |
1092 | 1223 | "type": "object", |
1093 | 1224 | "title": "DataKeySettings", |
1094 | 1225 | "properties": { |
1226 | + "excludeFromStacking": { | |
1227 | + "title": "Exclude from stacking(available in \"Stacking\" mode)", | |
1228 | + "type": "boolean", | |
1229 | + "default": false | |
1230 | + }, | |
1095 | 1231 | "showLines": { |
1096 | 1232 | "title": "Show lines", |
1097 | 1233 | "type": "boolean", |
... | ... | @@ -1107,6 +1243,25 @@ export default class TbFlot { |
1107 | 1243 | "type": "boolean", |
1108 | 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 | 1265 | "showPointsRadius": { |
1111 | 1266 | "title": "Radius of points", |
1112 | 1267 | "type": "number", |
... | ... | @@ -1161,9 +1316,46 @@ export default class TbFlot { |
1161 | 1316 | "required": ["showLines", "fillLines", "showPoints"] |
1162 | 1317 | }, |
1163 | 1318 | "form": [ |
1319 | + "excludeFromStacking", | |
1164 | 1320 | "showLines", |
1165 | 1321 | "fillLines", |
1166 | 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 | 1359 | "showPointsRadius", |
1168 | 1360 | { |
1169 | 1361 | "key": "tooltipValueFormatter", |
... | ... | @@ -1195,7 +1387,47 @@ export default class TbFlot { |
1195 | 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 | 1433 | enableMouseEvents() { |
... | ... | @@ -1227,10 +1459,15 @@ export default class TbFlot { |
1227 | 1459 | tooltipHtml = tbFlot.ctx.tooltipFormatter(item); |
1228 | 1460 | } else { |
1229 | 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 | 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 | 1471 | tooltipHtml = tbFlot.ctx.tooltipFormatter(hoverInfo, item ? item.seriesIndex : -1); |
1235 | 1472 | } |
1236 | 1473 | } |
... | ... | @@ -1258,9 +1495,11 @@ export default class TbFlot { |
1258 | 1495 | }); |
1259 | 1496 | |
1260 | 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 | 1638 | |
1400 | 1639 | |
1401 | 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 | 1642 | var last_value = 0; |
1404 | - var results = { | |
1643 | + var results = [{ | |
1405 | 1644 | seriesHover: [] |
1406 | - }; | |
1645 | + }]; | |
1646 | + if (this.ctx.settings.comparisonEnabled) { | |
1647 | + results.push({ | |
1648 | + seriesHover: [] | |
1649 | + }); | |
1650 | + var minDistanceHistorical, minTimeHistorical; | |
1651 | + } | |
1407 | 1652 | for (i = 0; i < seriesList.length; i++) { |
1408 | 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 | 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 | 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 | 1673 | || (hoverDistance >= 0 && (hoverDistance < minDistance || minDistance < 0)) |
1416 | 1674 | || (hoverDistance < 0 && hoverDistance > minDistance)) { |
1417 | 1675 | minDistance = hoverDistance; |
... | ... | @@ -1429,23 +1687,33 @@ export default class TbFlot { |
1429 | 1687 | } |
1430 | 1688 | |
1431 | 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 | 1717 | return results; |
1450 | 1718 | } |
1451 | 1719 | |
... | ... | @@ -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 | 1884 | /* eslint-enable angular/angularelement */ | ... | ... |