From 20bbc93a2d45b23aabc30e1b849503148fcd24a5 Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 11:33:01 -0800 Subject: [PATCH 1/8] feat: adding a new gauge widget - Using angular svg binding - Using d3 shape to build arc path string - Using resize observer to react on dom dimension changes --- package-lock.json | 10 ++ package.json | 5 +- projects/common/src/public-api.ts | 1 + .../utilities/dom/resize/from-dom-event.ts | 19 ++ projects/components/package.json | 3 +- .../components/src/gauge/gauge.component.scss | 32 ++++ .../src/gauge/gauge.component.test.ts | 61 +++++++ .../components/src/gauge/gauge.component.ts | 169 ++++++++++++++++++ projects/components/src/gauge/gauge.module.ts | 12 ++ projects/components/src/public-api.ts | 4 + tslint.json | 3 +- 11 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 projects/common/src/utilities/dom/resize/from-dom-event.ts create mode 100644 projects/components/src/gauge/gauge.component.scss create mode 100644 projects/components/src/gauge/gauge.component.test.ts create mode 100644 projects/components/src/gauge/gauge.component.ts create mode 100644 projects/components/src/gauge/gauge.module.ts diff --git a/package-lock.json b/package-lock.json index b615700e0..76c2d8b30 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8758,6 +8758,11 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, + "@types/resize-observer-browser": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.4.tgz", + "integrity": "sha512-rPvqs+1hL/5hbES/0HTdUu4lvNmneiwKwccbWe7HGLWbnsLdqKnQHyWLg4Pj0AMO7PLHCwBM1Cs8orChdkDONg==" + }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", @@ -24013,6 +24018,11 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, + "resize-observer-polyfill": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", + "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" + }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", diff --git a/package.json b/package.json index 61b501e2f..e494ab1a3 100644 --- a/package.json +++ b/package.json @@ -35,11 +35,12 @@ "@angular/platform-browser": "^10.0.4", "@angular/platform-browser-dynamic": "^10.0.4", "@angular/router": "^10.0.4", + "@apollo/client": "^3.2.5", "@hypertrace/hyperdash": "^1.1.2", "@hypertrace/hyperdash-angular": "^2.1.0", "@types/d3-hierarchy": "^2.0.0", "@types/d3-transition": "1.1.5", - "@apollo/client": "^3.2.5", + "@types/resize-observer-browser": "^0.1.4", "apollo-angular": "^2.0.4", "core-js": "^3.5.0", "d3-array": "^2.8.0", @@ -60,6 +61,7 @@ "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "lodash-es": "^4.17.15", + "resize-observer-polyfill": "^1.5.1", "rxjs": "~6.6.2", "tslib": "^2.0.1", "uuid": "^8.2.0", @@ -92,6 +94,7 @@ "@types/node": "^14.11.2", "@types/uuid": "^8.3.0", "@types/webpack-env": "^1.14.0", + "resize-observer-polyfill": "^1.5.1", "codelyzer": "^6.0.0", "commitizen": "^4.2.1", "cz-conventional-changelog": "^3.0.2", diff --git a/projects/common/src/public-api.ts b/projects/common/src/public-api.ts index 78c645283..4edd47729 100644 --- a/projects/common/src/public-api.ts +++ b/projects/common/src/public-api.ts @@ -30,6 +30,7 @@ export * from './custom-error/custom-error'; // DOM export { DomElementMeasurerService } from './utilities/dom/dom-element-measurer.service'; export * from './utilities/dom/dom-utilities'; +export { fromDomResize } from './utilities/dom/resize/from-dom-event'; // External export * from './external/external-url-navigator'; diff --git a/projects/common/src/utilities/dom/resize/from-dom-event.ts b/projects/common/src/utilities/dom/resize/from-dom-event.ts new file mode 100644 index 000000000..79898070f --- /dev/null +++ b/projects/common/src/utilities/dom/resize/from-dom-event.ts @@ -0,0 +1,19 @@ +import ResizeObserver from 'resize-observer-polyfill'; +import { Observable } from 'rxjs'; + +export const fromDomResize: (element: Element) => Observable = (element: Element) => + new Observable(subscriber => { + const resizeObserver = new ResizeObserver(entries => { + entries.forEach(entry => { + if (entry.target === element) { + subscriber.next(entry.contentRect); + } + }); + }); + + resizeObserver.observe(element); + + subscriber.next(element.getBoundingClientRect()); + + return () => resizeObserver.disconnect(); + }); diff --git a/projects/components/package.json b/projects/components/package.json index 0d00ed00f..31e559599 100644 --- a/projects/components/package.json +++ b/projects/components/package.json @@ -26,7 +26,8 @@ "d3-array": "^2.2.0", "d3-axis": "^1.0.12", "d3-scale": "^3.0.0", - "d3-selection": "^1.4.0" + "d3-selection": "^1.4.0", + "d3-shape": "^1.3.5" }, "devDependencies": { "@hypertrace/test-utils": "^0.0.0" diff --git a/projects/components/src/gauge/gauge.component.scss b/projects/components/src/gauge/gauge.component.scss new file mode 100644 index 000000000..2f6981232 --- /dev/null +++ b/projects/components/src/gauge/gauge.component.scss @@ -0,0 +1,32 @@ +@import 'font'; +@import 'color-palette'; + +.gauge { + width: 100%; + height: 100%; + + .gauge-ring { + fill: $gray-2; + } + + .input-data { + cursor: default; + } + + .value-ring { + transition: transform 0.2s ease-out; + } + + .value-display { + font-style: normal; + font-weight: bold; + font-size: 56px; + text-anchor: middle; + } + + .label-display { + @include body-1-semibold($gray-7); + font-family: $font-family; + text-anchor: middle; + } +} diff --git a/projects/components/src/gauge/gauge.component.test.ts b/projects/components/src/gauge/gauge.component.test.ts new file mode 100644 index 000000000..ead309a63 --- /dev/null +++ b/projects/components/src/gauge/gauge.component.test.ts @@ -0,0 +1,61 @@ +import { Color } from '@hypertrace/common'; +import { runFakeRxjs } from '@hypertrace/test-utils'; +import { createHostFactory, Spectator } from '@ngneat/spectator/jest'; +import { GaugeComponent } from './gauge.component'; +import { GaugeModule } from './gauge.module'; + +describe('Gauge component', () => { + let spectator: Spectator; + + const createHost = createHostFactory({ + component: GaugeComponent, + declareComponent: false, + imports: [GaugeModule] + }); + + test('render all data', () => { + spectator = createHost(``, { + hostProps: { + value: 80, + maxValue: 100, + thresholds: [ + { + label: 'Medium', + start: 60, + end: 90, + color: Color.Brown1 + }, + { + label: 'High', + start: 90, + end: 100, + color: Color.Red5 + } + ] + } + }); + + runFakeRxjs(({ expectObservable }) => { + expectObservable(spectator.component.gaugeRendererData$).toBe('200ms (x)', { + x: { + backgroundArc: 'M0,0Z', + origin: { + x: 0, + y: 0 + }, + data: { + value: 80, + maxValue: 100, + valueArc: 'M0,0Z', + threshold: { + color: '#9e4c41', + end: 90, + label: 'Medium', + start: 60 + } + } + } + }); + }); + }); +}); diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts new file mode 100644 index 000000000..eb80a60f4 --- /dev/null +++ b/projects/components/src/gauge/gauge.component.ts @@ -0,0 +1,169 @@ +import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges } from '@angular/core'; +import { Color, fromDomResize, Point, SubscriptionLifecycle } from '@hypertrace/common'; +import { Arc, arc, DefaultArcObject } from 'd3-shape'; +import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; +import { debounceTime, map } from 'rxjs/operators'; + +@Component({ + selector: 'ht-gauge', + template: ` + + + + + + + {{ rendererData.data.value }} + + {{ rendererData.data.threshold.label }} + + + + `, + styleUrls: ['./gauge.component.scss'], + providers: [SubscriptionLifecycle], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class GaugeComponent implements OnChanges { + private static readonly GAUGE_RING_WIDTH: number = 20; + private static readonly GAUGE_ARC_CORNER_RADIUS: number = 10; + + @Input() + public value?: number; + + @Input() + public maxValue?: number; + + @Input() + public thresholds: GaugeThreshold[] = []; + + public readonly gaugeRendererData$: Observable; + private readonly inputDataSubject: Subject = new BehaviorSubject< + GaugeInputData | undefined + >(undefined); + private readonly inputData$: Observable = this.inputDataSubject.asObservable(); + + public constructor(public readonly elementRef: ElementRef) { + this.gaugeRendererData$ = this.buildGaugeRendererDataObservable(); + } + + public ngOnChanges(): void { + this.emitInputData(); + } + + private buildGaugeRendererDataObservable(): Observable { + return combineLatest([this.buildDomResizeObservable(), this.inputData$]).pipe( + map(([boundingBox, inputData]) => { + const radius = this.buildRadius(boundingBox); + + return { + origin: this.buildOrigin(boundingBox, radius), + backgroundArc: this.buildBackgroundArc(radius), + data: this.buildGaugeData(radius, inputData) + }; + }) + ); + } + + private buildDomResizeObservable(): Observable { + const element = this.elementRef.nativeElement as HTMLElement; + + return fromDomResize(element).pipe(debounceTime(200)); + } + + private buildBackgroundArc(radius: number): string { + return this.buildArcGenerator()({ + innerRadius: radius - GaugeComponent.GAUGE_RING_WIDTH, + outerRadius: radius, + startAngle: -Math.PI / 2, + endAngle: Math.PI / 2 + })!; + } + + private buildGaugeData(radius: number, inputData?: GaugeInputData): GaugeData | undefined { + if (inputData === undefined) { + return undefined; + } + + return { + valueArc: this.buildValueArc(radius, inputData), + ...inputData + }; + } + + private buildValueArc(radius: number, inputData: GaugeInputData): string { + return this.buildArcGenerator()({ + innerRadius: radius - GaugeComponent.GAUGE_RING_WIDTH, + outerRadius: radius, + startAngle: -Math.PI / 2, + endAngle: -Math.PI / 2 + (inputData.value / inputData.maxValue) * Math.PI + })!; + } + + private buildArcGenerator(): Arc { + return arc().cornerRadius(GaugeComponent.GAUGE_ARC_CORNER_RADIUS); + } + + private buildRadius(boundingBox: ClientRect): number { + return Math.min(boundingBox.height, boundingBox.width) / 2; + } + + private buildOrigin(boundingBox: ClientRect, radius: number): Point { + return { + x: boundingBox.width / 2, + y: boundingBox.height / 2 + radius / 2 + }; + } + + private emitInputData(): void { + let inputData; + if (this.value !== undefined && this.maxValue !== undefined && this.maxValue > 0 && this.thresholds.length > 0) { + const currentThreshold = this.thresholds.find( + threshold => this.value! >= threshold.start && this.value! < threshold.end + ); + + if (currentThreshold) { + inputData = { + value: this.value, + maxValue: this.maxValue, + threshold: currentThreshold + }; + } + } + this.inputDataSubject.next(inputData); + } +} + +export interface GaugeThreshold { + label: string; + start: number; + end: number; + color: Color; +} + +interface GaugeSvgRendererData { + origin: Point; + backgroundArc: string; + data?: GaugeData; +} + +interface GaugeData { + valueArc: string; + value: number; + maxValue: number; + threshold: GaugeThreshold; +} + +interface GaugeInputData { + value: number; + maxValue: number; + threshold: GaugeThreshold; +} diff --git a/projects/components/src/gauge/gauge.module.ts b/projects/components/src/gauge/gauge.module.ts new file mode 100644 index 000000000..d67a89c50 --- /dev/null +++ b/projects/components/src/gauge/gauge.module.ts @@ -0,0 +1,12 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormattingModule } from '@hypertrace/common'; +import { TooltipModule } from './../tooltip/tooltip.module'; +import { GaugeComponent } from './gauge.component'; + +@NgModule({ + declarations: [GaugeComponent], + exports: [GaugeComponent], + imports: [CommonModule, FormattingModule, TooltipModule] +}) +export class GaugeModule {} diff --git a/projects/components/src/public-api.ts b/projects/components/src/public-api.ts index 9e1c174bd..06f22f95b 100644 --- a/projects/components/src/public-api.ts +++ b/projects/components/src/public-api.ts @@ -81,6 +81,10 @@ export * from './filtering/filter-modal/in-filter-modal.component'; // Filter Parser export * from './filtering/filter/parser/filter-parser-lookup.service'; +// Gauge +export * from './gauge/gauge.component'; +export * from './gauge/gauge.module'; + // Header export * from './header/application/application-header.component'; export * from './header/application/application-header.module'; diff --git a/tslint.json b/tslint.json index 1f2cab30d..b45dacbee 100644 --- a/tslint.json +++ b/tslint.json @@ -24,7 +24,8 @@ "@hypertrace/distributed-tracing", "@hypertrace/test-utils", "@hypertrace/observability", - "ng-mocks" + "ng-mocks", + "resize-observer-polyfill" ] ], "no-lifecycle-call": true, From 1da0e3887bcc5feea6aa865b8e6eba5a563f631e Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 11:43:37 -0800 Subject: [PATCH 2/8] refactor: updating package json --- package-lock.json | 5 ----- package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 76c2d8b30..d193979a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8758,11 +8758,6 @@ "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==", "dev": true }, - "@types/resize-observer-browser": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@types/resize-observer-browser/-/resize-observer-browser-0.1.4.tgz", - "integrity": "sha512-rPvqs+1hL/5hbES/0HTdUu4lvNmneiwKwccbWe7HGLWbnsLdqKnQHyWLg4Pj0AMO7PLHCwBM1Cs8orChdkDONg==" - }, "@types/resolve": { "version": "1.17.1", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz", diff --git a/package.json b/package.json index e494ab1a3..43d0e0930 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,6 @@ "@hypertrace/hyperdash-angular": "^2.1.0", "@types/d3-hierarchy": "^2.0.0", "@types/d3-transition": "1.1.5", - "@types/resize-observer-browser": "^0.1.4", "apollo-angular": "^2.0.4", "core-js": "^3.5.0", "d3-array": "^2.8.0", From 244869decad71db973822301c3c16142f3bf23eb Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 12:51:25 -0800 Subject: [PATCH 3/8] refactor: remove subs life cycle service --- projects/components/src/gauge/gauge.component.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts index eb80a60f4..f74c66feb 100644 --- a/projects/components/src/gauge/gauge.component.ts +++ b/projects/components/src/gauge/gauge.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges } from '@angular/core'; -import { Color, fromDomResize, Point, SubscriptionLifecycle } from '@hypertrace/common'; +import { Color, fromDomResize, Point } from '@hypertrace/common'; import { Arc, arc, DefaultArcObject } from 'd3-shape'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; import { debounceTime, map } from 'rxjs/operators'; @@ -29,7 +29,6 @@ import { debounceTime, map } from 'rxjs/operators'; `, styleUrls: ['./gauge.component.scss'], - providers: [SubscriptionLifecycle], changeDetection: ChangeDetectionStrategy.OnPush }) export class GaugeComponent implements OnChanges { From 0691f9c98fc0ca5833b16371fcabc401bf39a8cc Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 14:58:09 -0800 Subject: [PATCH 4/8] refactor: fixing radius and origin logic --- projects/components/src/gauge/gauge.component.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts index f74c66feb..c7c80462e 100644 --- a/projects/components/src/gauge/gauge.component.ts +++ b/projects/components/src/gauge/gauge.component.ts @@ -34,6 +34,7 @@ import { debounceTime, map } from 'rxjs/operators'; export class GaugeComponent implements OnChanges { private static readonly GAUGE_RING_WIDTH: number = 20; private static readonly GAUGE_ARC_CORNER_RADIUS: number = 10; + private static readonly GAUGE_AXIS_PADDING: number = 30; @Input() public value?: number; @@ -112,13 +113,13 @@ export class GaugeComponent implements OnChanges { } private buildRadius(boundingBox: ClientRect): number { - return Math.min(boundingBox.height, boundingBox.width) / 2; + return Math.min(boundingBox.height, boundingBox.width) - GaugeComponent.GAUGE_AXIS_PADDING; } - private buildOrigin(boundingBox: ClientRect, radius: number): Point { + private buildOrigin(boundingBox: ClientRect, _: number): Point { return { x: boundingBox.width / 2, - y: boundingBox.height / 2 + radius / 2 + y: boundingBox.height - GaugeComponent.GAUGE_AXIS_PADDING }; } From a512fdc4e3c44e29a161d182a745d7a187716edb Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 18:19:36 -0800 Subject: [PATCH 5/8] refactor: updating type --- projects/common/src/utilities/dom/resize/from-dom-event.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/projects/common/src/utilities/dom/resize/from-dom-event.ts b/projects/common/src/utilities/dom/resize/from-dom-event.ts index 79898070f..7c392faef 100644 --- a/projects/common/src/utilities/dom/resize/from-dom-event.ts +++ b/projects/common/src/utilities/dom/resize/from-dom-event.ts @@ -1,10 +1,11 @@ -import ResizeObserver from 'resize-observer-polyfill'; import { Observable } from 'rxjs'; + export const fromDomResize: (element: Element) => Observable = (element: Element) => new Observable(subscriber => { + // @ts-ignore const resizeObserver = new ResizeObserver(entries => { - entries.forEach(entry => { + entries.forEach((entry: { target: Element; contentRect: ClientRect }) => { if (entry.target === element) { subscriber.next(entry.contentRect); } From f9b35e61993042d2d3d260dbc6b9bd7555193d0e Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Fri, 6 Nov 2020 18:27:20 -0800 Subject: [PATCH 6/8] refactor: removing resize polyfill --- package-lock.json | 5 ----- package.json | 2 -- .../common/src/utilities/dom/resize/from-dom-event.ts | 2 +- projects/components/src/gauge/gauge.component.ts | 11 +++++++---- tslint.json | 3 +-- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index d193979a8..b615700e0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24013,11 +24013,6 @@ "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, - "resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "resolve": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", diff --git a/package.json b/package.json index 43d0e0930..e248e42a9 100644 --- a/package.json +++ b/package.json @@ -60,7 +60,6 @@ "graphql": "^15.3.0", "graphql-tag": "^2.11.0", "lodash-es": "^4.17.15", - "resize-observer-polyfill": "^1.5.1", "rxjs": "~6.6.2", "tslib": "^2.0.1", "uuid": "^8.2.0", @@ -93,7 +92,6 @@ "@types/node": "^14.11.2", "@types/uuid": "^8.3.0", "@types/webpack-env": "^1.14.0", - "resize-observer-polyfill": "^1.5.1", "codelyzer": "^6.0.0", "commitizen": "^4.2.1", "cz-conventional-changelog": "^3.0.2", diff --git a/projects/common/src/utilities/dom/resize/from-dom-event.ts b/projects/common/src/utilities/dom/resize/from-dom-event.ts index 7c392faef..b938b74e4 100644 --- a/projects/common/src/utilities/dom/resize/from-dom-event.ts +++ b/projects/common/src/utilities/dom/resize/from-dom-event.ts @@ -1,8 +1,8 @@ import { Observable } from 'rxjs'; - export const fromDomResize: (element: Element) => Observable = (element: Element) => new Observable(subscriber => { + // tslint:disable-next-line: ban-ts-ignore // @ts-ignore const resizeObserver = new ResizeObserver(entries => { entries.forEach((entry: { target: Element; contentRect: ClientRect }) => { diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts index c7c80462e..6ec684743 100644 --- a/projects/components/src/gauge/gauge.component.ts +++ b/projects/components/src/gauge/gauge.component.ts @@ -76,7 +76,7 @@ export class GaugeComponent implements OnChanges { private buildDomResizeObservable(): Observable { const element = this.elementRef.nativeElement as HTMLElement; - return fromDomResize(element).pipe(debounceTime(200)); + return fromDomResize(element).pipe(debounceTime(100)); } private buildBackgroundArc(radius: number): string { @@ -113,13 +113,16 @@ export class GaugeComponent implements OnChanges { } private buildRadius(boundingBox: ClientRect): number { - return Math.min(boundingBox.height, boundingBox.width) - GaugeComponent.GAUGE_AXIS_PADDING; + return Math.min( + boundingBox.height - GaugeComponent.GAUGE_AXIS_PADDING, + boundingBox.height / 2 + Math.min(boundingBox.height, boundingBox.width) / 2 + ); } - private buildOrigin(boundingBox: ClientRect, _: number): Point { + private buildOrigin(boundingBox: ClientRect, radius: number): Point { return { x: boundingBox.width / 2, - y: boundingBox.height - GaugeComponent.GAUGE_AXIS_PADDING + y: radius }; } diff --git a/tslint.json b/tslint.json index b45dacbee..1f2cab30d 100644 --- a/tslint.json +++ b/tslint.json @@ -24,8 +24,7 @@ "@hypertrace/distributed-tracing", "@hypertrace/test-utils", "@hypertrace/observability", - "ng-mocks", - "resize-observer-polyfill" + "ng-mocks" ] ], "no-lifecycle-call": true, From 8769e6a5f673f6f53afd73b45f73c776d1e7cefe Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Sun, 8 Nov 2020 19:31:31 -0800 Subject: [PATCH 7/8] refactor: adding layout service --- projects/common/src/public-api.ts | 1 - .../utilities/dom/resize/from-dom-event.ts | 20 --------------- .../src/gauge/gauge.component.test.ts | 4 +++ .../components/src/gauge/gauge.component.ts | 25 +++++++++++-------- projects/components/src/gauge/gauge.module.ts | 3 ++- 5 files changed, 20 insertions(+), 33 deletions(-) delete mode 100644 projects/common/src/utilities/dom/resize/from-dom-event.ts diff --git a/projects/common/src/public-api.ts b/projects/common/src/public-api.ts index 4edd47729..78c645283 100644 --- a/projects/common/src/public-api.ts +++ b/projects/common/src/public-api.ts @@ -30,7 +30,6 @@ export * from './custom-error/custom-error'; // DOM export { DomElementMeasurerService } from './utilities/dom/dom-element-measurer.service'; export * from './utilities/dom/dom-utilities'; -export { fromDomResize } from './utilities/dom/resize/from-dom-event'; // External export * from './external/external-url-navigator'; diff --git a/projects/common/src/utilities/dom/resize/from-dom-event.ts b/projects/common/src/utilities/dom/resize/from-dom-event.ts deleted file mode 100644 index b938b74e4..000000000 --- a/projects/common/src/utilities/dom/resize/from-dom-event.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Observable } from 'rxjs'; - -export const fromDomResize: (element: Element) => Observable = (element: Element) => - new Observable(subscriber => { - // tslint:disable-next-line: ban-ts-ignore - // @ts-ignore - const resizeObserver = new ResizeObserver(entries => { - entries.forEach((entry: { target: Element; contentRect: ClientRect }) => { - if (entry.target === element) { - subscriber.next(entry.contentRect); - } - }); - }); - - resizeObserver.observe(element); - - subscriber.next(element.getBoundingClientRect()); - - return () => resizeObserver.disconnect(); - }); diff --git a/projects/components/src/gauge/gauge.component.test.ts b/projects/components/src/gauge/gauge.component.test.ts index ead309a63..b1fb1db7f 100644 --- a/projects/components/src/gauge/gauge.component.test.ts +++ b/projects/components/src/gauge/gauge.component.test.ts @@ -1,6 +1,8 @@ import { Color } from '@hypertrace/common'; import { runFakeRxjs } from '@hypertrace/test-utils'; import { createHostFactory, Spectator } from '@ngneat/spectator/jest'; +import { MockDirective } from 'ng-mocks'; +import { LayoutChangeDirective } from '../layout/layout-change.directive'; import { GaugeComponent } from './gauge.component'; import { GaugeModule } from './gauge.module'; @@ -10,6 +12,7 @@ describe('Gauge component', () => { const createHost = createHostFactory({ component: GaugeComponent, declareComponent: false, + declarations:[MockDirective(LayoutChangeDirective)], imports: [GaugeModule] }); @@ -34,6 +37,7 @@ describe('Gauge component', () => { ] } }); + spectator.component.onLayoutChange(); runFakeRxjs(({ expectObservable }) => { expectObservable(spectator.component.gaugeRendererData$).toBe('200ms (x)', { diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts index 6ec684743..1c0c58f6b 100644 --- a/projects/components/src/gauge/gauge.component.ts +++ b/projects/components/src/gauge/gauge.component.ts @@ -1,13 +1,13 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges } from '@angular/core'; -import { Color, fromDomResize, Point } from '@hypertrace/common'; +import { Color, LayoutChangeService, Point } from '@hypertrace/common'; import { Arc, arc, DefaultArcObject } from 'd3-shape'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; -import { debounceTime, map } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; @Component({ selector: 'ht-gauge', template: ` - + `, styleUrls: ['./gauge.component.scss'], + providers: [LayoutChangeService], changeDetection: ChangeDetectionStrategy.OnPush }) export class GaugeComponent implements OnChanges { @@ -46,11 +47,14 @@ export class GaugeComponent implements OnChanges { public thresholds: GaugeThreshold[] = []; public readonly gaugeRendererData$: Observable; + private readonly inputDataSubject: Subject = new BehaviorSubject< GaugeInputData | undefined >(undefined); private readonly inputData$: Observable = this.inputDataSubject.asObservable(); + private readonly redrawSubject: Subject = new BehaviorSubject(true); + public constructor(public readonly elementRef: ElementRef) { this.gaugeRendererData$ = this.buildGaugeRendererDataObservable(); } @@ -59,9 +63,14 @@ export class GaugeComponent implements OnChanges { this.emitInputData(); } + public onLayoutChange(): void { + this.redrawSubject.next(true); + } + private buildGaugeRendererDataObservable(): Observable { - return combineLatest([this.buildDomResizeObservable(), this.inputData$]).pipe( - map(([boundingBox, inputData]) => { + return combineLatest([this.inputData$, this.redrawSubject ]).pipe( + map(([inputData]) => { + const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const radius = this.buildRadius(boundingBox); return { @@ -73,12 +82,6 @@ export class GaugeComponent implements OnChanges { ); } - private buildDomResizeObservable(): Observable { - const element = this.elementRef.nativeElement as HTMLElement; - - return fromDomResize(element).pipe(debounceTime(100)); - } - private buildBackgroundArc(radius: number): string { return this.buildArcGenerator()({ innerRadius: radius - GaugeComponent.GAUGE_RING_WIDTH, diff --git a/projects/components/src/gauge/gauge.module.ts b/projects/components/src/gauge/gauge.module.ts index d67a89c50..e06f8079d 100644 --- a/projects/components/src/gauge/gauge.module.ts +++ b/projects/components/src/gauge/gauge.module.ts @@ -1,12 +1,13 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormattingModule } from '@hypertrace/common'; +import { LayoutChangeModule } from '../layout/layout-change.module'; import { TooltipModule } from './../tooltip/tooltip.module'; import { GaugeComponent } from './gauge.component'; @NgModule({ declarations: [GaugeComponent], exports: [GaugeComponent], - imports: [CommonModule, FormattingModule, TooltipModule] + imports: [CommonModule, FormattingModule, TooltipModule, LayoutChangeModule] }) export class GaugeModule {} From 2c9aea002aceb98d0a424df5a240ce0e5f1efeaa Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Sun, 8 Nov 2020 20:09:10 -0800 Subject: [PATCH 8/8] refactor: adding delay --- projects/components/src/gauge/gauge.component.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/projects/components/src/gauge/gauge.component.ts b/projects/components/src/gauge/gauge.component.ts index 1c0c58f6b..a7992d24b 100644 --- a/projects/components/src/gauge/gauge.component.ts +++ b/projects/components/src/gauge/gauge.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, ElementRef, Input, OnChanges } from import { Color, LayoutChangeService, Point } from '@hypertrace/common'; import { Arc, arc, DefaultArcObject } from 'd3-shape'; import { BehaviorSubject, combineLatest, Observable, Subject } from 'rxjs'; -import { map } from 'rxjs/operators'; +import { map, debounceTime } from 'rxjs/operators'; @Component({ selector: 'ht-gauge', @@ -54,6 +54,7 @@ export class GaugeComponent implements OnChanges { private readonly inputData$: Observable = this.inputDataSubject.asObservable(); private readonly redrawSubject: Subject = new BehaviorSubject(true); + private readonly redraw$: Observable = this.redrawSubject.pipe(debounceTime(100)); public constructor(public readonly elementRef: ElementRef) { this.gaugeRendererData$ = this.buildGaugeRendererDataObservable(); @@ -68,7 +69,7 @@ export class GaugeComponent implements OnChanges { } private buildGaugeRendererDataObservable(): Observable { - return combineLatest([this.inputData$, this.redrawSubject ]).pipe( + return combineLatest([this.inputData$, this.redraw$ ]).pipe( map(([inputData]) => { const boundingBox = this.elementRef.nativeElement.getBoundingClientRect(); const radius = this.buildRadius(boundingBox);