Showing
10 changed files
with
223 additions
and
121 deletions
... | ... | @@ -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 | ... | ... |