Commit b5444e6c2792ac2e483d4bc2d151679c61c65d5b

Authored by Igor Kulikov
2 parents 0a27101e 93498143

Merge branch 'vvlladd28-improvement/comparison-flot'

... ... @@ -16,14 +16,17 @@
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   - calculateIntervalStartTime, getCurrentTime,
22   - QuickTimeInterval, SubscriptionTimewindow
  21 + calculateIntervalStartTime,
  22 + getCurrentTime,
  23 + getCurrentTimeForComparison,
  24 + SubscriptionTimewindow
23 25 } from '@shared/models/time/time.models';
24 26 import { UtilsService } from '@core/services/utils.service';
25 27 import { deepClone } from '@core/utils';
26 28 import Timeout = NodeJS.Timeout;
  29 +import * as moment_ from 'moment';
27 30
28 31 export declare type onAggregatedData = (data: SubscriptionData, detectChanges: boolean) => void;
29 32
... ... @@ -85,7 +88,7 @@ export class DataAggregator {
85 88 private intervalTimeoutHandle: Timeout;
86 89 private intervalScheduledTime: number;
87 90
88   - private startTs = this.subsTw.startTs + this.subsTw.tsOffset;
  91 + private startTs: number;
89 92 private endTs: number;
90 93 private elapsed: number;
91 94
... ... @@ -137,12 +140,7 @@ export class DataAggregator {
137 140 }
138 141 this.subsTw = subsTw;
139 142 this.intervalScheduledTime = this.utils.currentPerfTime();
140   - this.startTs = this.subsTw.startTs + this.subsTw.tsOffset;
141   - if (this.subsTw.quickInterval) {
142   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
143   - } else {
144   - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
145   - }
  143 + this.calculateStartEndTs();
146 144 this.elapsed = 0;
147 145 this.aggregationTimeout = Math.max(this.subsTw.aggregation.interval, 1000);
148 146 this.resetPending = true;
... ... @@ -165,11 +163,7 @@ export class DataAggregator {
165 163 if (!this.dataReceived) {
166 164 this.elapsed = 0;
167 165 this.dataReceived = true;
168   - if (this.subsTw.quickInterval) {
169   - this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, null, this.subsTw.timezone) + this.subsTw.tsOffset;
170   - } else {
171   - this.endTs = this.startTs + this.subsTw.aggregation.timeWindow;
172   - }
  166 + this.calculateStartEndTs();
173 167 }
174 168 if (this.resetPending) {
175 169 this.resetPending = false;
... ... @@ -194,6 +188,21 @@ export class DataAggregator {
194 188 }
195 189 }
196 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 +
197 206 private onInterval(history?: boolean, detectChanges?: boolean) {
198 207 const now = this.utils.currentPerfTime();
199 208 this.elapsed += now - this.intervalScheduledTime;
... ... @@ -207,7 +216,7 @@ export class DataAggregator {
207 216 if (delta || !this.data) {
208 217 const tickTs = delta * this.subsTw.aggregation.interval;
209 218 if (this.subsTw.quickInterval) {
210   - const currentDate = getCurrentTime(this.subsTw.timezone);
  219 + const currentDate = this.getCurrentTime();
211 220 this.startTs = calculateIntervalStartTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
212 221 this.endTs = calculateIntervalEndTime(this.subsTw.quickInterval, currentDate) + this.subsTw.tsOffset;
213 222 } else {
... ... @@ -356,5 +365,13 @@ export class DataAggregator {
356 365 }
357 366 }
358 367
  368 + private getCurrentTime() {
  369 + if (this.subsTw.timeForComparison) {
  370 + return getCurrentTimeForComparison(this.subsTw.timeForComparison as moment_.unitOfTime.DurationConstructor, this.subsTw.timezone);
  371 + } else {
  372 + return getCurrentTime(this.subsTw.timezone);
  373 + }
  374 + }
  375 +
359 376 }
360 377
... ...
... ... @@ -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>;
... ...
... ... @@ -39,12 +39,12 @@ import { HttpErrorResponse } from '@angular/common/http';
39 39 import {
40 40 calculateIntervalEndTime,
41 41 calculateIntervalStartTime,
42   - calculateTsOffset,
  42 + calculateTsOffset, ComparisonDuration,
43 43 createSubscriptionTimewindow,
44 44 createTimewindowForComparison,
45   - getCurrentTime,
  45 + getCurrentTime, isHistoryTypeTimewindow,
46 46 SubscriptionTimewindow,
47   - Timewindow,
  47 + Timewindow, timewindowTypeChanged,
48 48 toHistoryTimewindow,
49 49 WidgetTimewindow
50 50 } from '@app/shared/models/time/time.models';
... ... @@ -107,7 +107,7 @@ export class WidgetSubscription implements IWidgetSubscription {
107 107 decimals: number;
108 108 units: string;
109 109 comparisonEnabled: boolean;
110   - timeForComparison: moment_.unitOfTime.DurationConstructor;
  110 + timeForComparison: ComparisonDuration;
111 111 comparisonTimeWindow: WidgetTimewindow;
112 112 timewindowForComparison: SubscriptionTimewindow;
113 113
... ... @@ -198,6 +198,7 @@ export class WidgetSubscription implements IWidgetSubscription {
198 198 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {});
199 199 this.callbacks.onSubscriptionMessage = this.callbacks.onSubscriptionMessage || (() => {});
200 200 this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {});
  201 + this.callbacks.forceReInit = this.callbacks.forceReInit || (() => {});
201 202 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
202 203 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {});
203 204 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
... ... @@ -228,7 +229,7 @@ export class WidgetSubscription implements IWidgetSubscription {
228 229 }
229 230
230 231 this.subscriptionTimewindow = null;
231   - this.comparisonEnabled = options.comparisonEnabled;
  232 + this.comparisonEnabled = options.comparisonEnabled && isHistoryTypeTimewindow(this.timeWindowConfig);
232 233 if (this.comparisonEnabled) {
233 234 this.timeForComparison = options.timeForComparison;
234 235
... ... @@ -418,20 +419,13 @@ export class WidgetSubscription implements IWidgetSubscription {
418 419 this.dataLoaded(pageData, data1, datasourceIndex, pageLink, true);
419 420 },
420 421 initialPageDataChanged: this.initialPageDataChanged.bind(this),
  422 + forceReInit: this.forceReInit.bind(this),
421 423 dataUpdated: this.dataUpdated.bind(this),
422 424 updateRealtimeSubscription: () => {
423   - if (this.comparisonEnabled && datasource.isAdditional) {
424   - return this.updateSubscriptionForComparison();
425   - } else {
426   - return this.updateRealtimeSubscription();
427   - }
  425 + return this.updateRealtimeSubscription();
428 426 },
429 427 setRealtimeSubscription: (subscriptionTimewindow) => {
430   - if (this.comparisonEnabled && datasource.isAdditional) {
431   - this.updateSubscriptionForComparison(subscriptionTimewindow);
432   - } else {
433   - this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
434   - }
  428 + this.updateRealtimeSubscription(deepClone(subscriptionTimewindow));
435 429 }
436 430 };
437 431 this.entityDataListeners.push(listener);
... ... @@ -584,8 +578,9 @@ export class WidgetSubscription implements IWidgetSubscription {
584 578 if (this.type === widgetType.timeseries || this.type === widgetType.alarm) {
585 579 if (this.useDashboardTimewindow) {
586 580 if (!isEqual(this.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
  581 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newDashboardTimewindow);
587 582 this.timeWindowConfig = deepClone(newDashboardTimewindow);
588   - this.update();
  583 + this.update(isTimewindowTypeChanged);
589 584 }
590 585 }
591 586 } else if (this.type === widgetType.latest) {
... ... @@ -614,8 +609,9 @@ export class WidgetSubscription implements IWidgetSubscription {
614 609
615 610 updateTimewindowConfig(newTimewindow: Timewindow): void {
616 611 if (!this.useDashboardTimewindow) {
  612 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, newTimewindow);
617 613 this.timeWindowConfig = newTimewindow;
618   - this.update();
  614 + this.update(isTimewindowTypeChanged);
619 615 }
620 616 }
621 617
... ... @@ -624,10 +620,11 @@ export class WidgetSubscription implements IWidgetSubscription {
624 620 this.ctx.dashboardTimewindowApi.onResetTimewindow();
625 621 } else {
626 622 if (this.originalTimewindow) {
  623 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
627 624 this.timeWindowConfig = deepClone(this.originalTimewindow);
628 625 this.originalTimewindow = null;
629 626 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
630   - this.update();
  627 + this.update(isTimewindowTypeChanged);
631 628 }
632 629 }
633 630 }
... ... @@ -641,7 +638,8 @@ export class WidgetSubscription implements IWidgetSubscription {
641 638 }
642 639 this.timeWindowConfig = toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs, interval, this.ctx.timeService);
643 640 this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
644   - this.update();
  641 + const isTimewindowTypeChanged = timewindowTypeChanged(this.timeWindowConfig, this.originalTimewindow);
  642 + this.update(isTimewindowTypeChanged);
645 643 }
646 644 }
647 645
... ... @@ -770,16 +768,20 @@ export class WidgetSubscription implements IWidgetSubscription {
770 768 }
771 769 }
772 770
773   - update() {
  771 + update(isTimewindowTypeChanged = false) {
774 772 if (this.type !== widgetType.rpc) {
775 773 if (this.type === widgetType.alarm) {
776 774 this.updateAlarmDataSubscription();
777 775 } else {
778   - if (this.hasDataPageLink) {
779   - this.updateDataSubscriptions();
  776 + if (this.type === widgetType.timeseries && this.options.comparisonEnabled && isTimewindowTypeChanged) {
  777 + this.forceReInit();
780 778 } else {
781   - this.notifyDataLoading();
782   - this.dataSubscribe();
  779 + if (this.hasDataPageLink) {
  780 + this.updateDataSubscriptions();
  781 + } else {
  782 + this.notifyDataLoading();
  783 + this.dataSubscribe();
  784 + }
783 785 }
784 786 }
785 787 }
... ... @@ -1143,24 +1145,14 @@ export class WidgetSubscription implements IWidgetSubscription {
1143 1145 private updateComparisonTimewindow() {
1144 1146 this.comparisonTimeWindow.interval = this.timewindowForComparison.aggregation.interval || 1000;
1145 1147 this.comparisonTimeWindow.timezone = this.timewindowForComparison.timezone;
1146   - if (this.timewindowForComparison.realtimeWindowMs) {
1147   - this.comparisonTimeWindow.maxTime = moment(this.timeWindow.maxTime).subtract(1, this.timeForComparison).valueOf();
1148   - this.comparisonTimeWindow.minTime = moment(this.timeWindow.minTime).subtract(1, this.timeForComparison).valueOf();
1149   - } else if (this.timewindowForComparison.fixedWindow) {
  1148 + if (this.timewindowForComparison.fixedWindow) {
1150 1149 this.comparisonTimeWindow.maxTime = this.timewindowForComparison.fixedWindow.endTimeMs + this.timewindowForComparison.tsOffset;
1151 1150 this.comparisonTimeWindow.minTime = this.timewindowForComparison.fixedWindow.startTimeMs + this.timewindowForComparison.tsOffset;
1152 1151 }
1153 1152 }
1154 1153
1155   - private updateSubscriptionForComparison(subscriptionTimewindow?: SubscriptionTimewindow): SubscriptionTimewindow {
1156   - if (subscriptionTimewindow) {
1157   - this.timewindowForComparison = subscriptionTimewindow;
1158   - } else {
1159   - if (!this.subscriptionTimewindow) {
1160   - this.subscriptionTimewindow = this.updateRealtimeSubscription();
1161   - }
1162   - this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1163   - }
  1154 + private updateSubscriptionForComparison(): SubscriptionTimewindow {
  1155 + this.timewindowForComparison = createTimewindowForComparison(this.subscriptionTimewindow, this.timeForComparison);
1164 1156 this.updateComparisonTimewindow();
1165 1157 return this.timewindowForComparison;
1166 1158 }
... ... @@ -1169,6 +1161,10 @@ export class WidgetSubscription implements IWidgetSubscription {
1169 1161 this.callbacks.onInitialPageDataChanged(this, nextPageData);
1170 1162 }
1171 1163
  1164 + private forceReInit() {
  1165 + this.callbacks.forceReInit();
  1166 + }
  1167 +
1172 1168 private dataLoaded(pageData: PageData<EntityData>,
1173 1169 data: Array<Array<DataSetHolder>>,
1174 1170 datasourceIndex: number, pageLink: EntityDataPageLink, isUpdate: boolean) {
... ...
... ... @@ -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,6 +120,7 @@ export interface SubscriptionTimewindow {
118 120 realtimeWindowMs?: number;
119 121 fixedWindow?: FixedWindow;
120 122 aggregation?: SubscriptionAggregation;
  123 + timeForComparison?: ComparisonDuration;
121 124 }
122 125
123 126 export interface WidgetTimewindow {
... ... @@ -208,6 +211,14 @@ export function defaultTimewindow(timeService: TimeService): Timewindow {
208 211 return timewindow;
209 212 }
210 213
  214 +function getTimewindowType(timewindow: Timewindow): TimewindowType {
  215 + if (isUndefined(timewindow.selectedTab)) {
  216 + return isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;
  217 + } else {
  218 + return timewindow.selectedTab;
  219 + }
  220 +}
  221 +
211 222 export function initModelFromDefaultTimewindow(value: Timewindow, timeService: TimeService): Timewindow {
212 223 const model = defaultTimewindow(timeService);
213 224 if (value) {
... ... @@ -215,15 +226,7 @@ export function initModelFromDefaultTimewindow(value: Timewindow, timeService: T
215 226 model.hideAggregation = value.hideAggregation;
216 227 model.hideAggInterval = value.hideAggInterval;
217 228 model.hideTimezone = value.hideTimezone;
218   - if (isUndefined(value.selectedTab)) {
219   - if (value.realtime) {
220   - model.selectedTab = TimewindowType.REALTIME;
221   - } else {
222   - model.selectedTab = TimewindowType.HISTORY;
223   - }
224   - } else {
225   - model.selectedTab = value.selectedTab;
226   - }
  229 + model.selectedTab = getTimewindowType(value);
227 230 if (model.selectedTab === TimewindowType.REALTIME) {
228 231 if (isDefined(value.realtime.interval)) {
229 232 model.realtime.interval = value.realtime.interval;
... ... @@ -318,6 +321,15 @@ export function toHistoryTimewindow(timewindow: Timewindow, startTimeMs: number,
318 321 return historyTimewindow;
319 322 }
320 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 +
321 333 export function calculateTsOffset(timezone?: string): number {
322 334 if (timezone) {
323 335 const tz = getTimezone(timezone);
... ... @@ -328,6 +340,10 @@ export function calculateTsOffset(timezone?: string): number {
328 340 }
329 341 }
330 342
  343 +export function isHistoryTypeTimewindow(timewindow: Timewindow): boolean {
  344 + return getTimewindowType(timewindow) === TimewindowType.HISTORY;
  345 +}
  346 +
331 347 export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: number, stateData: boolean,
332 348 timeService: TimeService): SubscriptionTimewindow {
333 349 const subscriptionTimewindow: SubscriptionTimewindow = {
... ... @@ -352,10 +368,7 @@ export function createSubscriptionTimewindow(timewindow: Timewindow, stDiff: num
352 368 limit: timewindow.aggregation.limit || timeService.getMaxDatapointsLimit()
353 369 };
354 370 }
355   - let selectedTab = timewindow.selectedTab;
356   - if (isUndefined(selectedTab)) {
357   - selectedTab = isDefined(timewindow.realtime) ? TimewindowType.REALTIME : TimewindowType.HISTORY;
358   - }
  371 + const selectedTab = getTimewindowType(timewindow);
359 372 if (selectedTab === TimewindowType.REALTIME) {
360 373 let realtimeType = timewindow.realtime.realtimeType;
361 374 if (isUndefined(realtimeType)) {
... ... @@ -553,22 +566,109 @@ export function quickTimeIntervalPeriod(interval: QuickTimeInterval): number {
553 566 }
554 567 }
555 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 +
556 639 export function createTimewindowForComparison(subscriptionTimewindow: SubscriptionTimewindow,
557   - timeUnit: moment_.unitOfTime.DurationConstructor): SubscriptionTimewindow {
  640 + timeUnit: ComparisonDuration): SubscriptionTimewindow {
558 641 const timewindowForComparison: SubscriptionTimewindow = {
559 642 fixedWindow: null,
560 643 realtimeWindowMs: null,
561   - aggregation: subscriptionTimewindow.aggregation
  644 + aggregation: subscriptionTimewindow.aggregation,
  645 + tsOffset: subscriptionTimewindow.tsOffset
562 646 };
563 647
564   - if (subscriptionTimewindow.realtimeWindowMs) {
565   - timewindowForComparison.startTs = moment(subscriptionTimewindow.startTs).subtract(1, timeUnit).valueOf();
566   - timewindowForComparison.realtimeWindowMs = subscriptionTimewindow.realtimeWindowMs;
567   - } else if (subscriptionTimewindow.fixedWindow) {
568   - const timeInterval = subscriptionTimewindow.fixedWindow.endTimeMs - subscriptionTimewindow.fixedWindow.startTimeMs;
569   - const endTimeMs = moment(subscriptionTimewindow.fixedWindow.endTimeMs).subtract(1, timeUnit).valueOf();
570   -
571   - timewindowForComparison.startTs = endTimeMs - timeInterval;
  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;
  670 + }
  671 + timewindowForComparison.startTs = startTimeMs;
572 672 timewindowForComparison.fixedWindow = {
573 673 startTimeMs: timewindowForComparison.startTs,
574 674 endTimeMs
... ... @@ -797,3 +897,7 @@ export function getCurrentTime(tz?: string): moment_.Moment {
797 897 export function getTimezone(tz: string): moment_.Moment {
798 898 return moment.tz(tz);
799 899 }
  900 +
  901 +export function getCurrentTimeForComparison(timeForComparison: moment_.unitOfTime.DurationConstructor, tz?: string): moment_.Moment {
  902 + return getCurrentTime(tz).subtract(1, timeForComparison);
  903 +}
... ...
... ... @@ -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)",
... ...