Commit b5444e6c2792ac2e483d4bc2d151679c61c65d5b
Merge branch 'vvlladd28-improvement/comparison-flot'
Showing
10 changed files
with
241 additions
and
115 deletions
... | ... | @@ -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 | +} | ... | ... |