Commit 934981432f9ea5110eacded1e0f3ea1154c1e859

Authored by Igor Kulikov
1 parent 7bce418c

Improve comparison timewindow

... ... @@ -16,7 +16,7 @@
16 16
17 17 import { SubscriptionData, SubscriptionDataHolder } from '@app/shared/models/telemetry/telemetry.models';
18 18 import {
19   - AggregationType,
  19 + AggregationType, calculateIntervalComparisonEndTime,
20 20 calculateIntervalEndTime,
21 21 calculateIntervalStartTime,
22 22 getCurrentTime,
... ... @@ -26,6 +26,7 @@ import {
26 26 import { UtilsService } from '@core/services/utils.service';
27 27 import { deepClone } from '@core/utils';
28 28 import Timeout = NodeJS.Timeout;
  29 +import * as moment_ from 'moment';
29 30
30 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
31 32
... ... @@ -87,7 +88,7 @@ export class DataAggregator {
87 88 private intervalTimeoutHandle: Timeout;
88 89 private intervalScheduledTime: number;
89 90
90   - private startTs = this.subsTw.startTs + this.subsTw.tsOffset;
  91 + private startTs: number;
91 92 private endTs: number;
92 93 private elapsed: number;
93 94
... ... @@ -139,13 +140,7 @@ export class DataAggregator {
139 140 }
140 141 this.subsTw = subsTw;
141 142 this.intervalScheduledTime = this.utils.currentPerfTime();
142   - this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
143   - if (this.subsTw.quickInterval) {
144   - const currentDate = this.getCurrentTime();
145   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
146   - } else {
147   - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
148   - }
  143 + this.calculateStartEndTs();
149 144 this.elapsed = 0;
150 145 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
151 146 this.resetPending = true;
... ... @@ -168,12 +163,7 @@ export class DataAggregator {
168 163 if (!this.dataReceived) {
169 164 this.elapsed = 0;
170 165 this.dataReceived = true;
171   - if (this.subsTw.quickInterval) {
172   - const currentDate = this.getCurrentTime();
173   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
174   - } else {
175   - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
176   - }
  166 + this.calculateStartEndTs();
177 167 }
178 168 if (this.resetPending) {
179 169 this.resetPending = false;
... ... @@ -198,6 +188,21 @@ export class DataAggregator {
198 188 }
199 189 }
200 190
  191 + private calculateStartEndTs() {
  192 + this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
  193 + if (this.subsTw.quickInterval) {
  194 + if (this.subsTw.timeForComparison === 'previousInterval') {
  195 + const currentDate = getCurrentTime(this.subsTw.timezone);
  196 + this.endTs = calculateIntervalComparisonEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  197 + } else {
  198 + const currentDate = this.getCurrentTime();
  199 + this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
  200 + }
  201 + } else {
  202 + this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
  203 + }
  204 + }
  205 +
201 206 private onInterval(history?: boolean, detectChanges?: boolean) {
202 207 const now = this.utils.currentPerfTime();
203 208 this.elapsed += now - this.intervalScheduledTime;
... ... @@ -362,7 +367,7 @@ export class DataAggregator {
362 367
363 368 private getCurrentTime() {
364 369 if (this.subsTw.timeForComparison) {
365   - return getCurrentTimeForComparison(this.subsTw.timeForComparison, this.subsTw.timezone);
  370 + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone);
366 371 } else {
367 372 return getCurrentTime(this.subsTw.timezone);
368 373 }
... ...
... ... @@ -419,8 +419,8 @@ export class EntityDataSubscription {
419 419 latestTsOffsetChanged = this.subscriber.setTsOffset(this.latestTsOffset);
420 420 }
421 421 if (latestTsOffsetChanged) {
422   - if (this.listener.initialPageDataChanged) {
423   - this.listener.initialPageDataChanged(this.pageData);
  422 + if (this.listener.forceReInit) {
  423 + this.listener.forceReInit();
424 424 }
425 425 } else if (!this.subsCommand.isEmpty()) {
426 426 this.subscriber.subscriptionCommands = [this.subsCommand];
... ... @@ -428,8 +428,8 @@ export class EntityDataSubscription {
428 428 }
429 429 } else if (this.datasourceType === DatasourceType.entityCount) {
430 430 if (this.subscriber.setTsOffset(this.latestTsOffset)) {
431   - if (this.listener.initialPageDataChanged) {
432   - this.listener.initialPageDataChanged(this.pageData);
  431 + if (this.listener.forceReInit) {
  432 + this.listener.forceReInit();
433 433 }
434 434 }
435 435 } else if (this.datasourceType === DatasourceType.function) {
... ...
... ... @@ -40,6 +40,7 @@ export interface EntityDataListener {
40 40 datasourceIndex: number, pageLink: EntityDataPageLink) => void;
41 41 dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void;
42 42 initialPageDataChanged?: (nextPageData: PageData<EntityData>) => void;
  43 + forceReInit?: () => void;
43 44 updateRealtimeSubscription?: () => SubscriptionTimewindow;
44 45 setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void;
45 46 subscriptionOptions?: EntityDataSubscriptionOptions;
... ...
... ... @@ -209,6 +209,7 @@ export interface WidgetSubscriptionCallbacks {
209 209 onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void;
210 210 onSubscriptionMessage?: (subscription: IWidgetSubscription, message: SubscriptionMessage) => void;
211 211 onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData<EntityData>) => void;
  212 + forceReInit?: () => void;
212 213 dataLoading?: (subscription: IWidgetSubscription) => void;
213 214 legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void;
214 215 timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void;
... ... @@ -269,6 +270,7 @@ export interface IWidgetSubscription {
269 270 hiddenData?: Array<{data: DataSet}>;
270 271 timeWindowConfig?: Timewindow;
271 272 timeWindow?: WidgetTimewindow;
  273 + comparisonEnabled?: boolean;
272 274 comparisonTimeWindow?: WidgetTimewindow;
273 275
274 276 alarms?: PageData<AlarmData>;
... ...
... ... @@ -36,6 +36,18 @@ import {
36 36 widgetType
37 37 } from '@app/shared/models/widget.models';
38 38 import { HttpErrorResponse } from '@angular/common/http';
  39 +import {
  40 + calculateIntervalEndTime,
  41 + calculateIntervalStartTime,
  42 + calculateTsOffset, ComparisonDuration,
  43 + createSubscriptionTimewindow,
  44 + createTimewindowForComparison,
  45 + getCurrentTime, isHistoryTypeTimewindow,
  46 + SubscriptionTimewindow,
  47 + Timewindow, timewindowTypeChanged,
  48 + toHistoryTimewindow,
  49 + WidgetTimewindow
  50 +} from '@app/shared/models/time/time.models';
39 51 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
40 52 import { CancelAnimationFrame } from '@core/services/raf.service';
41 53 import { EntityType } from '@shared/models/entity-type.models';
... ... @@ -56,19 +68,6 @@ import {
56 68 } from '@shared/models/query/query.models';
57 69 import { map } from 'rxjs/operators';
58 70 import { AlarmDataListener } from '@core/api/alarm-data.service';
59   -import {
60   - calculateIntervalEndTime,
61   - calculateIntervalStartTime,
62   - calculateTsOffset,
63   - createSubscriptionTimewindow,
64   - createTimewindowForComparison,
65   - getCurrentTime,
66   - isHistoryTypeTimewindow,
67   - SubscriptionTimewindow,
68   - Timewindow,
69   - toHistoryTimewindow,
70   - WidgetTimewindow
71   -} from '@app/shared/models/time/time.models';
72 71
73 72 const moment = moment_;
74 73
... ... @@ -108,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription {
108 107 decimals: number;
109 108 units: string;
110 109 comparisonEnabled: boolean;
111   - timeForComparison: moment_.unitOfTime.DurationConstructor;
  110 + timeForComparison: ComparisonDuration;
112 111 comparisonTimeWindow: WidgetTimewindow;
113 112 timewindowForComparison: SubscriptionTimewindow;
114 113
... ... @@ -199,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription {
199 198 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {});
200 199 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {});
201 200 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {});
  201 + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {});
202 202 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
203 203 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {});
204 204 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
... ... @@ -229,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription {
229 229 }
230 230
231 231 this.subscriptionTimewindow = null;
232   - this.comparisonEnabled = options.comparisonEnabled;
  232 + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig);
233 233 if (this.comparisonEnabled) {
234 234 this.timeForComparison = options.timeForComparison;
235 235
... ... @@ -388,7 +388,7 @@ export class WidgetSubscription implements IWidgetSubscription {
388 388 this.notifyDataLoaded();
389 389 return of(null);
390 390 }
391   - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) {
  391 + if (this.comparisonEnabled) {
392 392 const additionalDatasources: Datasource[] = [];
393 393 this.configuredDatasources.forEach((datasource, datasourceIndex) => {
394 394 const additionalDataKeys: DataKey[] = [];
... ... @@ -419,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription {
419 419 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true);
420 420 },
421 421 initialPageDataChanged: this.initialPageDataChanged.bind(this),
  422 + forceReInit: this.forceReInit.bind(this),
422 423 dataUpdated: this.dataUpdated.bind(this),
423 424 updateRealtimeSubscription: () => {
424   - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) {
425   - return this.updateSubscriptionForComparison();
426   - } else {
427   - return this.updateRealtimeSubscription();
428   - }
  425 + return this.updateRealtimeSubscription();
429 426 },
430 427 setRealtimeSubscription: (subscriptionTimewindow) => {
431   - if (this.comparisonEnabled && datasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) {
432   - this.updateSubscriptionForComparison(subscriptionTimewindow);
433   - } else {
434   - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
435   - }
  428 + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
436 429 }
437 430 };
438 431 this.entityDataListeners.push(listener);
... ... @@ -585,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription {
585 578 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) {
586 579 if (this.useDashboardTimewindow) {
587 580 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
  581 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow);
588 582 this.timeWindowConfig = deepClone(newDashboardTimewindow);
589   - this.update();
  583 + this.update(isTimewindowTypeChanged);
590 584 }
591 585 }
592 586 } else if (this.type === widgetType.latest) {
... ... @@ -615,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription {
615 609
616 610 updateTimewindowConfig(newTimewindow: Timewindow): void {
617 611 if (!this.useDashboardTimewindow) {
  612 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow);
618 613 this.timeWindowConfig = newTimewindow;
619   - this.update();
  614 + this.update(isTimewindowTypeChanged);
620 615 }
621 616 }
622 617
... ... @@ -625,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription {
625 620 this.ctx.dashboardTimewindowApi.onResetTimewindow();
626 621 } else {
627 622 if (this.originalTimewindow) {
  623 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
628 624 this.timeWindowConfig = deepClone(this.originalTimewindow);
629 625 this.originalTimewindow = null;
630 626 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
631   - this.update();
  627 + this.update(isTimewindowTypeChanged);
632 628 }
633 629 }
634 630 }
... ... @@ -642,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription {
642 638 }
643 639 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService);
644 640 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
645   - this.update();
  641 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
  642 + this.update(isTimewindowTypeChanged);
646 643 }
647 644 }
648 645
... ... @@ -771,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription {
771 768 }
772 769 }
773 770
774   - update() {
  771 + update(isTimewindowTypeChanged = false) {
775 772 if (this.type !== widgetType.rpc) {
776 773 if (this.type === widgetType.alarm) {
777 774 this.updateAlarmDataSubscription();
778 775 } else {
779   - if (this.hasDataPageLink) {
780   - this.updateDataSubscriptions();
  776 + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) {
  777 + this.forceReInit();
781 778 } else {
782   - this.notifyDataLoading();
783   - this.dataSubscribe();
  779 + if (this.hasDataPageLink) {
  780 + this.updateDataSubscriptions();
  781 + } else {
  782 + this.notifyDataLoading();
  783 + this.dataSubscribe();
  784 + }
784 785 }
785 786 }
786 787 }
... ... @@ -889,7 +890,7 @@ export class WidgetSubscription implements IWidgetSubscription {
889 890 if (!this.hasDataPageLink) {
890 891 if (this.type === widgetType.timeseries && this.timeWindowConfig) {
891 892 this.updateRealtimeSubscription();
892   - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) {
  893 + if (this.comparisonEnabled) {
893 894 this.updateSubscriptionForComparison();
894 895 }
895 896 }
... ... @@ -905,7 +906,7 @@ export class WidgetSubscription implements IWidgetSubscription {
905 906 const forceUpdate = !this.datasources.length;
906 907 const notifyDataLoaded = !this.entityDataListeners.filter((listener) => listener.subscription ? true : false).length;
907 908 this.entityDataListeners.forEach((listener) => {
908   - if (this.comparisonEnabled && listener.configDatasource.isAdditional && isHistoryTypeTimewindow(this.timeWindowConfig)) {
  909 + if (this.comparisonEnabled && listener.configDatasource.isAdditional) {
909 910 listener.subscriptionTimewindow = this.timewindowForComparison;
910 911 } else {
911 912 listener.subscriptionTimewindow = this.subscriptionTimewindow;
... ... @@ -1144,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription {
1144 1145 private updateComparisonTimewindow() {
1145 1146 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
1146 1147 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
1147   - if (this.timewindowForComparison.realtimeWindowMs) {
1148   - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();
1149   - this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();
1150   - } else if (this.timewindowForComparison.fixedWindow) {
1151   - this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs;
1152   - this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs;
  1148 + if (this.timewindowForComparison.fixedWindow) {
  1149 + this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
  1150 + this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
1153 1151 }
1154 1152 }
1155 1153
1156   - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow {
1157   - if (subscriptionTimewindow) {
1158   - this.timewindowForComparison = subscriptionTimewindow;
1159   - } else {
1160   - if (!this.subscriptionTimewindow) {
1161   - this.subscriptionTimewindow = this.updateRealtimeSubscription();
1162   - }
1163   - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1164   - }
  1154 + private updateSubscriptionForComparison(): SubscriptionTimewindow {
  1155 + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1165 1156 this.updateComparisonTimewindow();
1166 1157 return this.timewindowForComparison;
1167 1158 }
... ... @@ -1170,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription {
1170 1161 this.callbacks.onInitialPageDataChanged(this, nextPageData);
1171 1162 }
1172 1163
  1164 + private forceReInit() {
  1165 + this.callbacks.forceReInit();
  1166 + }
  1167 +
1173 1168 private dataLoaded(pageData: PageData<EntityData>,
1174 1169 data: Array<Array<DataSetHolder>>,
1175 1170 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) {
... ... @@ -1262,7 +1257,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1262 1257 index++;
1263 1258 });
1264 1259 });
1265   - if (this.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig)) {
  1260 + if (this.comparisonEnabled) {
1266 1261 this.datasourcePages.forEach(datasourcePage => {
1267 1262 datasourcePage.data.forEach((datasource, dIndex) => {
1268 1263 if (datasource.isAdditional) {
... ...
... ... @@ -20,6 +20,7 @@
20 20 import { DataKey, Datasource, DatasourceData, JsonSettingsSchema } from '@shared/models/widget.models';
21 21 import * as moment_ from 'moment';
22 22 import { DataKeyType } from "@shared/models/telemetry/telemetry.models";
  23 +import { ComparisonDuration } from '@shared/models/time/time.models';
23 24
24 25 export declare type ChartType = 'line' | 'pie' | 'bar' | 'state' | 'graph';
25 26
... ... @@ -142,7 +143,7 @@ export interface TbFlotBaseSettings {
142 143
143 144 export interface TbFlotComparisonSettings {
144 145 comparisonEnabled: boolean;
145   - timeForComparison: moment_.unitOfTime.DurationConstructor;
  146 + timeForComparison: ComparisonDuration;
146 147 xaxisSecond: TbFlotSecondXAxisSettings;
147 148 }
148 149
... ... @@ -543,7 +544,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
543 544 timeForComparison: {
544 545 title: 'Time to show historical data',
545 546 type: 'string',
546   - default: 'months'
  547 + default: 'previousInterval'
547 548 },
548 549 xaxisSecond: {
549 550 title: 'Second X axis',
... ... @@ -577,6 +578,10 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
577 578 multiple: false,
578 579 items: [
579 580 {
  581 + value: 'previousInterval',
  582 + label: 'Previous interval (default)'
  583 + },
  584 + {
580 585 value: 'days',
581 586 label: 'Day ago'
582 587 },
... ... @@ -586,7 +591,7 @@ const chartSettingsSchemaForComparison: JsonSettingsSchema = {
586 591 },
587 592 {
588 593 value: 'months',
589   - label: 'Month ago (default)'
  594 + label: 'Month ago'
590 595 },
591 596 {
592 597 value: 'years',
... ...
... ... @@ -74,6 +74,7 @@ export class TbFlot {
74 74 private readonly utils: UtilsService;
75 75
76 76 private settings: TbFlotSettings;
  77 + private comparisonEnabled: boolean;
77 78
78 79 private readonly tooltip: JQuery<any>;
79 80
... ... @@ -263,29 +264,7 @@ export class TbFlot {
263 264 };
264 265 }
265 266
266   - if (this.settings.comparisonEnabled) {
267   - const xaxis = deepClone(this.xaxis);
268   - xaxis.position = 'top';
269   - if (this.settings.xaxisSecond) {
270   - if (this.settings.xaxisSecond.showLabels === false) {
271   - xaxis.tickFormatter = () => {
272   - return '';
273   - };
274   - }
275   - xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
276   - xaxis.position = this.settings.xaxisSecond.axisPosition;
277   - }
278   - xaxis.tickLength = 0;
279   - this.options.xaxes.push(xaxis);
280   -
281   - this.options.series = {
282   - stack: false
283   - };
284   - } else {
285   - this.options.series = {
286   - stack: this.settings.stack === true
287   - };
288   - }
  267 + this.options.series = {};
289 268
290 269 this.options.crosshair = {
291 270 mode: 'x'
... ... @@ -364,7 +343,6 @@ export class TbFlot {
364 343
365 344 // Experimental
366 345 this.animatedPie = this.settings.animatedPie === true;
367   -
368 346 }
369 347
370 348 if (this.ctx.defaultSubscription) {
... ... @@ -372,10 +350,29 @@ export class TbFlot {
372 350 }
373 351 }
374 352
375   -
376 353 private init($element: JQuery<any>, subscription: IWidgetSubscription) {
377   - this.subscription = subscription;
378 354 this.$element = $element;
  355 + this.subscription = subscription;
  356 + this.comparisonEnabled = this.subscription ? this.subscription.comparisonEnabled : this.settings.comparisonEnabled;
  357 + if (this.comparisonEnabled) {
  358 + const xaxis = deepClone(this.xaxis);
  359 + xaxis.position = 'top';
  360 + if (this.settings.xaxisSecond) {
  361 + if (this.settings.xaxisSecond.showLabels === false) {
  362 + xaxis.tickFormatter = () => {
  363 + return '';
  364 + };
  365 + }
  366 + xaxis.label = this.utils.customTranslation(this.settings.xaxisSecond.title, this.settings.xaxisSecond.title) || null;
  367 + xaxis.position = this.settings.xaxisSecond.axisPosition;
  368 + }
  369 + xaxis.tickLength = 0;
  370 + this.options.xaxes.push(xaxis);
  371 +
  372 + this.options.series.stack = false;
  373 + } else {
  374 + this.options.series.stack = this.settings.stack === true;
  375 + }
379 376 const colors: string[] = [];
380 377 this.yaxes = [];
381 378 const yaxesMap: {[units: string]: TbFlotAxisOptions} = {};
... ... @@ -387,7 +384,7 @@ export class TbFlot {
387 384 this.settings.dataKeysListForLabels.forEach((item) => {
388 385 item.settings = {};
389 386 });
390   - subscription.datasources.forEach((item) => {
  387 + this.subscription.datasources.forEach((item) => {
391 388 const datasource: Datasource = {
392 389 type: item.type,
393 390 entityType: item.entityType,
... ... @@ -425,7 +422,7 @@ export class TbFlot {
425 422 fill: keySettings.fillLines === true
426 423 };
427 424
428   - if (this.settings.stack && !this.settings.comparisonEnabled) {
  425 + if (this.settings.stack && !this.comparisonEnabled) {
429 426 series.stack = !keySettings.excludeFromStacking;
430 427 } else {
431 428 series.stack = false;
... ... @@ -557,7 +554,7 @@ export class TbFlot {
557 554 }
558 555 this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
559 556 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
560   - if (this.settings.comparisonEnabled) {
  557 + if (this.comparisonEnabled) {
561 558 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
562 559 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
563 560 }
... ... @@ -636,7 +633,7 @@ export class TbFlot {
636 633
637 634 this.options.xaxes[0].min = this.subscription.timeWindow.minTime;
638 635 this.options.xaxes[0].max = this.subscription.timeWindow.maxTime;
639   - if (this.settings.comparisonEnabled) {
  636 + if (this.comparisonEnabled) {
640 637 this.options.xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
641 638 this.options.xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
642 639 }
... ... @@ -654,7 +651,7 @@ export class TbFlot {
654 651 } else {
655 652 this.plot.getOptions().xaxes[0].min = this.subscription.timeWindow.minTime;
656 653 this.plot.getOptions().xaxes[0].max = this.subscription.timeWindow.maxTime;
657   - if (this.settings.comparisonEnabled) {
  654 + if (this.comparisonEnabled) {
658 655 this.plot.getOptions().xaxes[1].min = this.subscription.comparisonTimeWindow.minTime;
659 656 this.plot.getOptions().xaxes[1].max = this.subscription.comparisonTimeWindow.maxTime;
660 657 }
... ... @@ -1293,7 +1290,7 @@ export class TbFlot {
1293 1290 const results: TbFlotHoverInfo[] = [{
1294 1291 seriesHover: []
1295 1292 }];
1296   - if (this.settings.comparisonEnabled) {
  1293 + if (this.comparisonEnabled) {
1297 1294 results.push({
1298 1295 seriesHover: []
1299 1296 });
... ...
... ... @@ -857,6 +857,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
857 857 onInitialPageDataChanged: (subscription, nextPageData) => {
858 858 this.reInit();
859 859 },
  860 + forceReInit: () => {
  861 + this.reInit();
  862 + },
860 863 dataLoading: (subscription) => {
861 864 if (this.loadingData !== subscription.loadingData) {
862 865 this.loadingData = subscription.loadingData;
... ...
... ... @@ -28,6 +28,8 @@ export const DAY = 24 * HOUR;
28 28 export const WEEK = 7 * DAY;
29 29 export const YEAR = DAY * 365;
30 30
  31 +export type ComparisonDuration = moment_.unitOfTime.DurationConstructor | 'previousInterval';
  32 +
31 33 export enum TimewindowType {
32 34 REALTIME,
33 35 HISTORY
... ... @@ -118,7 +120,7 @@ export interface SubscriptionTimewindow {
118 120 realtimeWindowMs?: number;
119 121 fixedWindow?: FixedWindow;
120 122 aggregation?: SubscriptionAggregation;
121   - timeForComparison?: moment_.unitOfTime.DurationConstructor;
  123 + timeForComparison?: ComparisonDuration;
122 124 }
123 125
124 126 export interface WidgetTimewindow {
... ... @@ -319,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
319 321 return historyTimewindow;
320 322 }
321 323
  324 +export function timewindowTypeChanged(newTimewindow: Timewindow, oldTimewindow: Timewindow): boolean {
  325 + if (!newTimewindow || !oldTimewindow) {
  326 + return false;
  327 + }
  328 + const newType = getTimewindowType(newTimewindow);
  329 + const oldType = getTimewindowType(oldTimewindow);
  330 + return newType !== oldType;
  331 +}
  332 +
322 333 export function calculateTsOffset(timezone?: string): number {
323 334 if (timezone) {
324 335 const tz = getTimezone(timezone);
... ... @@ -555,8 +566,78 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
555 566 }
556 567 }
557 568
  569 +export function calculateIntervalComparisonStartTime(interval: QuickTimeInterval,
  570 + currentDate: moment_.Moment): number {
  571 + switch (interval) {
  572 + case QuickTimeInterval.YESTERDAY:
  573 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  574 + case QuickTimeInterval.CURRENT_DAY:
  575 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  576 + currentDate.subtract(1, 'days');
  577 + return currentDate.startOf('day').valueOf();
  578 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  579 + currentDate.subtract(1, 'weeks');
  580 + return currentDate.startOf('day').valueOf();
  581 + case QuickTimeInterval.PREVIOUS_WEEK:
  582 + case QuickTimeInterval.CURRENT_WEEK:
  583 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  584 + currentDate.subtract(1, 'weeks');
  585 + return currentDate.startOf('week').valueOf();
  586 + case QuickTimeInterval.PREVIOUS_MONTH:
  587 + case QuickTimeInterval.CURRENT_MONTH:
  588 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  589 + currentDate.subtract(1, 'months');
  590 + return currentDate.startOf('month').valueOf();
  591 + case QuickTimeInterval.PREVIOUS_YEAR:
  592 + case QuickTimeInterval.CURRENT_YEAR:
  593 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  594 + currentDate.subtract(1, 'years');
  595 + return currentDate.startOf('year').valueOf();
  596 + case QuickTimeInterval.CURRENT_HOUR:
  597 + currentDate.subtract(1, 'hour');
  598 + return currentDate.startOf('hour').valueOf();
  599 + }
  600 +}
  601 +
  602 +export function calculateIntervalComparisonEndTime(interval: QuickTimeInterval,
  603 + currentDate: moment_.Moment): number {
  604 + switch (interval) {
  605 + case QuickTimeInterval.YESTERDAY:
  606 + case QuickTimeInterval.DAY_BEFORE_YESTERDAY:
  607 + case QuickTimeInterval.CURRENT_DAY:
  608 + currentDate.subtract(1, 'days');
  609 + return currentDate.endOf('day').valueOf();
  610 + case QuickTimeInterval.CURRENT_DAY_SO_FAR:
  611 + return currentDate.subtract(1, 'days').valueOf();
  612 + case QuickTimeInterval.THIS_DAY_LAST_WEEK:
  613 + currentDate.subtract(1, 'weeks');
  614 + return currentDate.endOf('day').valueOf();
  615 + case QuickTimeInterval.PREVIOUS_WEEK:
  616 + case QuickTimeInterval.CURRENT_WEEK:
  617 + currentDate.subtract(1, 'weeks');
  618 + return currentDate.endOf('week').valueOf();
  619 + case QuickTimeInterval.CURRENT_WEEK_SO_FAR:
  620 + return currentDate.subtract(1, 'week').valueOf();
  621 + case QuickTimeInterval.PREVIOUS_MONTH:
  622 + case QuickTimeInterval.CURRENT_MONTH:
  623 + currentDate.subtract(1, 'months');
  624 + return currentDate.endOf('month').valueOf();
  625 + case QuickTimeInterval.CURRENT_MONTH_SO_FAR:
  626 + return currentDate.subtract(1, 'month').valueOf();
  627 + case QuickTimeInterval.PREVIOUS_YEAR:
  628 + case QuickTimeInterval.CURRENT_YEAR:
  629 + currentDate.subtract(1, 'years');
  630 + return currentDate.endOf('year').valueOf();
  631 + case QuickTimeInterval.CURRENT_YEAR_SO_FAR:
  632 + return currentDate.subtract(1, 'year').valueOf();
  633 + case QuickTimeInterval.CURRENT_HOUR:
  634 + currentDate.subtract(1, 'hour');
  635 + return currentDate.endOf('hour').valueOf();
  636 + }
  637 +}
  638 +
558 639 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
559   - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow {
  640 + timeUnit: ComparisonDuration): SubscriptionTimewindow {
560 641 const timewindowForComparison: SubscriptionTimewindow = {
561 642 fixedWindow: null,
562 643 realtimeWindowMs: null,
... ... @@ -564,18 +645,30 @@ export function createTimewindowForComparison(subscriptionTimewindow: Subscripti
564 645 tsOffset: subscriptionTimewindow.tsOffset
565 646 };
566 647
567   - if (subscriptionTimewindow.realtimeWindowMs) {
568   - if (subscriptionTimewindow.quickInterval) {
569   - timewindowForComparison.quickInterval = subscriptionTimewindow.quickInterval;
570   - timewindowForComparison.timeForComparison = timeUnit;
  648 + if (subscriptionTimewindow.fixedWindow) {
  649 + let startTimeMs;
  650 + let endTimeMs;
  651 + if (timeUnit === 'previousInterval') {
  652 + if (subscriptionTimewindow.quickInterval) {
  653 + const startDate = moment(subscriptionTimewindow.fixedWindow.startTimeMs);
  654 + const endDate = moment(subscriptionTimewindow.fixedWindow.endTimeMs);
  655 + if (subscriptionTimewindow.timezone) {
  656 + startDate.tz(subscriptionTimewindow.timezone);
  657 + endDate.tz(subscriptionTimewindow.timezone);
  658 + }
  659 + startTimeMs = calculateIntervalComparisonStartTime(subscriptionTimewindow.quickInterval, startDate);
  660 + endTimeMs = calculateIntervalComparisonEndTime(subscriptionTimewindow.quickInterval, endDate);
  661 + } else {
  662 + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
  663 + endTimeMs = subscriptionTimewindow.fixedWindow.startTimeMs;
  664 + startTimeMs = endTimeMs - timeInterval;
  665 + }
  666 + } else {
  667 + const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
  668 + endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();
  669 + startTimeMs = endTimeMs - timeInterval;
571 670 }
572   - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf();
573   - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;
574   - } else if (subscriptionTimewindow.fixedWindow) {
575   - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
576   - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();
577   -
578   - timewindowForComparison.startTs = endTimeMs - timeInterval;
  671 + timewindowForComparison.startTs = startTimeMs;
579 672 timewindowForComparison.fixedWindow = {
580 673 startTimeMs: timewindowForComparison.startTs,
581 674 endTimeMs
... ...
... ... @@ -1798,6 +1798,7 @@
1798 1798 "avg": "avg",
1799 1799 "total": "total",
1800 1800 "comparison-time-ago": {
  1801 + "previousInterval": "(previous interval)",
1801 1802 "days": "(day ago)",
1802 1803 "weeks": "(week ago)",
1803 1804 "months": "(month ago)",
... ...