From 1f143432a8a4b959320acf3b759dec6e683dcbcb Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Wed, 17 Feb 2021 14:19:34 -0800 Subject: [PATCH 1/3] feat: separate zoom into a shared util file --- projects/observability/src/public-api.ts | 2 + .../components/topology/d3/d3-topology.ts | 2 +- .../d3/interactions/zoom/topology-zoom.ts | 260 +----------------- .../components/utils/d3/zoom/d3-zoom.ts | 258 +++++++++++++++++ 4 files changed, 274 insertions(+), 248 deletions(-) create mode 100644 projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts diff --git a/projects/observability/src/public-api.ts b/projects/observability/src/public-api.ts index 3e13cd1f3..6e952a56d 100644 --- a/projects/observability/src/public-api.ts +++ b/projects/observability/src/public-api.ts @@ -204,6 +204,8 @@ export * from './shared/graphql/model/schema/observability-traces'; export * from './shared/components/utils/d3/d3-visualization-builder.service'; export * from './shared/components/utils/d3/d3-util.service'; +export * from './shared/components/utils/d3/zoom/d3-zoom'; + export * from './shared/components/utils/chart-tooltip/chart-tooltip-builder.service'; export * from './shared/components/utils/chart-tooltip/chart-tooltip.module'; export * from './shared/components/utils/svg/svg-util.service'; diff --git a/projects/observability/src/shared/components/topology/d3/d3-topology.ts b/projects/observability/src/shared/components/topology/d3/d3-topology.ts index 4187aa4d6..0b0695e46 100644 --- a/projects/observability/src/shared/components/topology/d3/d3-topology.ts +++ b/projects/observability/src/shared/components/topology/d3/d3-topology.ts @@ -281,7 +281,7 @@ export class D3Topology implements Topology { topologyData.nodes.forEach(node => nodeRenderer.drawNode(groupElement, node)); topologyData.edges.forEach(edge => edgeRenderer.drawEdge(groupElement, edge)); topologyData.nodes.forEach(node => this.select(nodeRenderer.getElementForNode(node)!).raise()); - this.zoom.updateBrushOverlay(topologyData.nodes); + this.zoom.updateBrushOverlayWithData(topologyData.nodes); } private updateMeasuredDimensions(): void { diff --git a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts index 2f0eb4095..c2312502e 100644 --- a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts +++ b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts @@ -1,81 +1,12 @@ -import { Key, MouseButton, throwIfNil, unionOfClientRects } from '@hypertrace/common'; -import { brush, BrushBehavior, D3BrushEvent } from 'd3-brush'; -// tslint:disable-next-line: no-restricted-globals weird tslint error. Rename event so we can type it and not mistake it for other events -import { event as _d3CurrentEvent, Selection } from 'd3-selection'; -import { D3ZoomEvent, zoom, ZoomBehavior, zoomIdentity, ZoomTransform } from 'd3-zoom'; -import { isEqual } from 'lodash-es'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { D3ZoomConfiguration } from './../../../../utils/d3/zoom/d3-zoom'; +import { throwIfNil, unionOfClientRects } from '@hypertrace/common'; +import { D3Zoom } from '../../../../utils/d3/zoom/d3-zoom'; import { RenderableTopologyNode, RenderableTopologyNodeRenderedData } from '../../../topology'; -export class TopologyZoom { - private static readonly DEFAULT_MIN_ZOOM: number = 0.2; - private static readonly DEFAULT_MAX_ZOOM: number = 5.0; - private static readonly DATA_BRUSH_CONTEXT_CLASS: string = 'brush-context'; - private static readonly DATA_BRUSH_OVERLAY_CLASS: string = 'overlay'; - private static readonly DATA_BRUSH_SELECTION_CLASS: string = 'selection'; - - private static readonly DATA_BRUSH_OVERLAY_WIDTH: number = 200; - private static readonly DATA_BRUSH_OVERLAY_HEIGHT: number = 200; - private config?: TopologyZoomConfiguration; - private readonly zoomBehavior: ZoomBehavior; - private readonly zoomChangeSubject: BehaviorSubject = new BehaviorSubject(1); - private readonly brushBehaviour: BrushBehavior; - public readonly zoomChange$: Observable = this.zoomChangeSubject.asObservable(); - - private get minScale(): number { - return this.config && this.config.minScale !== undefined ? this.config.minScale : TopologyZoom.DEFAULT_MIN_ZOOM; - } - - private get maxScale(): number { - return this.config && this.config.maxScale !== undefined ? this.config.maxScale : TopologyZoom.DEFAULT_MAX_ZOOM; - } - - private getCurrentD3Event(): T { - // Returned event type depends on where this is invoked. Filters get a source event. - return _d3CurrentEvent; - } - - public constructor() { - this.zoomBehavior = zoom() - .filter(() => this.checkValidZoomEvent(this.getCurrentD3Event())) - .on('zoom', () => this.updateZoom(this.getCurrentD3Event().transform)) - .on('start.drag', () => this.updateDraggingClassIfNeeded(this.getCurrentD3Event())) - .on('end.drag', () => this.updateDraggingClassIfNeeded(this.getCurrentD3Event())); - - this.brushBehaviour = brush().on('start end', () => this.onBrushSelection(_d3CurrentEvent)); - } - - public attachZoom(configuration: TopologyZoomConfiguration): this { - this.config = configuration; - this.zoomBehavior.scaleExtent([this.minScale, this.maxScale]); - this.config.container - .call(this.zoomBehavior) - // tslint:disable-next-line: no-null-keyword - .on('dblclick.zoom', null); // Remove default double click handler - - return this; - } - - public getZoomScale(): number { - return this.zoomChangeSubject.getValue(); - } - - public setZoomScale(factor: number): void { - this.zoomBehavior.scaleTo(this.getContainerSelectionOrThrow(), factor); - } - - public resetZoom(): void { - this.zoomBehavior.transform(this.getContainerSelectionOrThrow(), zoomIdentity); - } - - public canIncreaseScale(): boolean { - return this.maxScale > this.getZoomScale(); - } - - public canDecreaseScale(): boolean { - return this.minScale < this.getZoomScale(); - } - +export class TopologyZoom extends D3Zoom< + TContainer, + TTarget +> { public zoomToFit(nodes: RenderableTopologyNode[]): void { const nodeClientRects = nodes .map(node => node.renderedData()) @@ -104,17 +35,7 @@ export class TopologyZoom node.renderedData()) .filter((renderedData): renderedData is RenderableTopologyNodeRenderedData => !!renderedData) @@ -130,7 +51,7 @@ export class TopologyZoom, - overlayZoomScale: number - ): void { - // Map values - const overlayBorderRadius = 4 / overlayZoomScale; - const selectionBorderRadius = 4 / overlayZoomScale; - const strokeWidth = 1 / overlayZoomScale; - brushSelection - .select(`.${TopologyZoom.DATA_BRUSH_OVERLAY_CLASS}`) - .attr('rx', overlayBorderRadius) - .attr('ry', overlayBorderRadius); - - brushSelection - .select(`.${TopologyZoom.DATA_BRUSH_SELECTION_CLASS}`) - .attr('rx', selectionBorderRadius) - .attr('ry', selectionBorderRadius) - .style('stroke-width', strokeWidth) - .style('stroke-dasharray', `${strokeWidth}, ${strokeWidth}`); - } - - private onBrushSelection(event: D3BrushEvent): void { - if (!event.selection) { - return; - } - - const [start, end] = event.selection as [[number, number], [number, number]]; - if (isEqual(start, end)) { - return; - } - const chartZoomScale = this.getZoomScale(); - const viewRect = { - top: start[1] * chartZoomScale, - left: start[0] * chartZoomScale, - bottom: end[1] * chartZoomScale, - right: end[0] * chartZoomScale, - width: end[0] * chartZoomScale - start[0] * chartZoomScale, - height: end[1] * chartZoomScale - start[1] * chartZoomScale - }; - - this.panToRect(viewRect); - } - - private translateToRect(rect: ClientRect): void { - const centerX = rect.left + rect.width / 2; - const centerY = rect.top + rect.height / 2; - this.zoomBehavior.translateTo(this.getContainerSelectionOrThrow(), centerX, centerY); - } - - private updateZoom(transform: ZoomTransform): void { - this.getTargetSelectionOrThrow().attr('transform', transform.toString()); - this.zoomChangeSubject.next(transform.k); - } - - private checkValidZoomEvent(receivedEvent: ZoomSourceEvent): boolean { - if (this.isScrollEvent(receivedEvent)) { - return this.isValidTriggerEvent(receivedEvent, this.config && this.config.scroll); - } - if (this.isPrimaryMouseOrTouchEvent(receivedEvent)) { - return this.isValidTriggerEvent(receivedEvent, this.config && this.config.pan); - } - - return false; - } - - private isValidTriggerEvent( - receivedEvent: TouchEvent | MouseEvent, - triggerConfig?: TopologyEventTriggerConfig - ): boolean { - if (!triggerConfig) { - return false; - } - if (!triggerConfig.requireModifiers) { - return true; - } - - return triggerConfig.requireModifiers.some(key => this.eventHasModifier(receivedEvent, key)); - } - - private eventHasModifier(receivedEvent: TouchEvent | MouseEvent, modifier: ZoomEventKeyModifier): boolean { - switch (modifier) { - case Key.Control: - return receivedEvent.ctrlKey; - case Key.Meta: - return receivedEvent.metaKey; - default: - return false; - } - } - - private isPrimaryMouseOrTouchEvent(receivedEvent: ZoomSourceEvent): receivedEvent is TouchEvent | MouseEvent { - return ( - ('TouchEvent' in window && receivedEvent instanceof TouchEvent) || - (receivedEvent instanceof MouseEvent && - !this.isScrollEvent(receivedEvent) && - receivedEvent.button === MouseButton.Primary) - ); - } - - private isScrollEvent(receivedEvent: ZoomSourceEvent): receivedEvent is WheelEvent { - return receivedEvent instanceof WheelEvent; - } - - private updateDraggingClassIfNeeded(zoomEvent: ZoomHandlerEvent): void { - this.getContainerSelectionOrThrow().classed('dragging', this.isPanStartEvent(zoomEvent)); - } - - private isPanStartEvent(zoomEvent: ZoomHandlerEvent): boolean { - return zoomEvent.type === 'start' && this.isPrimaryMouseOrTouchEvent(zoomEvent.sourceEvent); - } - - private getTargetSelectionOrThrow(): Selection { - return throwIfNil(this.config).target; - } + const overlayZoomScale = this.determineZoomScale(nodes, boundingBox); - private getContainerSelectionOrThrow(): Selection { - return throwIfNil(this.config).container; + this.updateBrushOverlay(overlayZoomScale); } } -type ZoomEventKeyModifier = Key.Control | Key.Meta; -// Type ZoomSourceEventType = 'wheel' | 'mousedown' | 'mouseup' | 'mousemove'; -type ZoomSourceEvent = MouseEvent | TouchEvent | null; -interface ZoomHandlerEvent extends D3ZoomEvent { - sourceEvent: ZoomSourceEvent; - type: 'start' | 'zoom' | 'end'; -} - -export interface TopologyZoomConfiguration { - container: Selection; - target: Selection; - brushOverlay?: Selection; - scroll?: TopologyEventTriggerConfig; - pan?: TopologyEventTriggerConfig; - minScale?: number; - maxScale?: number; -} - -interface TopologyEventTriggerConfig { - requireModifiers?: ZoomEventKeyModifier[]; -} +export interface TopologyZoomConfiguration + extends D3ZoomConfiguration {} diff --git a/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts b/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts new file mode 100644 index 000000000..9003e4191 --- /dev/null +++ b/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts @@ -0,0 +1,258 @@ +import { Key, MouseButton, throwIfNil } from '@hypertrace/common'; +import { brush, BrushBehavior, D3BrushEvent } from 'd3-brush'; +// tslint:disable-next-line: no-restricted-globals weird tslint error. Rename event so we can type it and not mistake it for other events +import { event as _d3CurrentEvent, Selection } from 'd3-selection'; +import { D3ZoomEvent, zoom, ZoomBehavior, zoomIdentity, ZoomTransform } from 'd3-zoom'; +import { isEqual } from 'lodash-es'; +import { BehaviorSubject, Observable } from 'rxjs'; + +export abstract class D3Zoom { + protected static readonly DEFAULT_MIN_ZOOM: number = 0.2; + protected static readonly DEFAULT_MAX_ZOOM: number = 5.0; + protected static readonly DATA_BRUSH_CONTEXT_CLASS: string = 'brush-context'; + protected static readonly DATA_BRUSH_OVERLAY_CLASS: string = 'overlay'; + protected static readonly DATA_BRUSH_SELECTION_CLASS: string = 'selection'; + + protected static readonly DATA_BRUSH_OVERLAY_WIDTH: number = 200; + protected static readonly DATA_BRUSH_OVERLAY_HEIGHT: number = 200; + protected config?: D3ZoomConfiguration; + protected readonly zoomBehavior: ZoomBehavior; + protected readonly zoomChangeSubject: BehaviorSubject = new BehaviorSubject(1); + protected readonly brushBehaviour: BrushBehavior; + public readonly zoomChange$: Observable = this.zoomChangeSubject.asObservable(); + + protected get minScale(): number { + return this.config && this.config.minScale !== undefined ? this.config.minScale : D3Zoom.DEFAULT_MIN_ZOOM; + } + + protected get maxScale(): number { + return this.config && this.config.maxScale !== undefined ? this.config.maxScale : D3Zoom.DEFAULT_MAX_ZOOM; + } + + protected getCurrentD3Event(): T { + // Returned event type depends on where this is invoked. Filters get a source event. + return _d3CurrentEvent; + } + + public constructor() { + this.zoomBehavior = zoom() + .filter(() => this.checkValidZoomEvent(this.getCurrentD3Event())) + .on('zoom', () => this.updateZoom(this.getCurrentD3Event().transform)) + .on('start.drag', () => this.updateDraggingClassIfNeeded(this.getCurrentD3Event())) + .on('end.drag', () => this.updateDraggingClassIfNeeded(this.getCurrentD3Event())); + + this.brushBehaviour = brush().on('start end', () => this.onBrushSelection(_d3CurrentEvent)); + } + + public attachZoom(configuration: D3ZoomConfiguration): this { + this.config = configuration; + this.zoomBehavior.scaleExtent([this.minScale, this.maxScale]); + this.config.container + .call(this.zoomBehavior) + // tslint:disable-next-line: no-null-keyword + .on('dblclick.zoom', null); // Remove default double click handler + + return this; + } + + public getZoomScale(): number { + return this.zoomChangeSubject.getValue(); + } + + public setZoomScale(factor: number): void { + this.zoomBehavior.scaleTo(this.getContainerSelectionOrThrow(), factor); + } + + public resetZoom(): void { + this.zoomBehavior.transform(this.getContainerSelectionOrThrow(), zoomIdentity); + } + + public canIncreaseScale(): boolean { + return this.maxScale > this.getZoomScale(); + } + + public canDecreaseScale(): boolean { + return this.minScale < this.getZoomScale(); + } + + public panToRect(viewRect: ClientRect): void { + const availableRect = throwIfNil(this.config && this.config.container.node()).getBoundingClientRect(); + // AvailableRect is used for width since we are always keeping scale as 1 + this.zoomBehavior.translateTo( + this.getContainerSelectionOrThrow(), + viewRect.left + availableRect.width / 2, + viewRect.top + availableRect.height / 2 + ); + } + + public updateBrushOverlay(overlayZoomScale: number = 1): void { + const containerSelection = this.getContainerSelectionOrThrow(); + containerSelection.select(`.${D3Zoom.DATA_BRUSH_CONTEXT_CLASS}`).remove(); + const containerdBox = throwIfNil(containerSelection.node()).getBoundingClientRect(); + + const boundingBox: ClientRect = { + bottom: containerdBox.bottom, + top: containerdBox.height - D3Zoom.DATA_BRUSH_OVERLAY_HEIGHT, + left: containerdBox.width - D3Zoom.DATA_BRUSH_OVERLAY_WIDTH, + right: containerdBox.right, + width: D3Zoom.DATA_BRUSH_OVERLAY_WIDTH, + height: D3Zoom.DATA_BRUSH_OVERLAY_HEIGHT + }; + + this.brushBehaviour.extent([ + [0, 0], + [D3Zoom.DATA_BRUSH_OVERLAY_WIDTH / overlayZoomScale, D3Zoom.DATA_BRUSH_OVERLAY_HEIGHT / overlayZoomScale] + ]); + + this.config!.brushOverlay = throwIfNil(this.config) + .target.clone(true) + .classed(D3Zoom.DATA_BRUSH_CONTEXT_CLASS, true) + .attr('width', boundingBox.width) + .attr('height', boundingBox.height) + .attr('transform', `translate(${boundingBox.left - 20}, ${boundingBox.top - 40}) scale(${overlayZoomScale})`) + .insert('g', '.entity-edge') + // tslint:disable-next-line: no-any + .call(this.brushBehaviour as any); + + this.styleBrushSelection(this.config!.brushOverlay, overlayZoomScale); + } + + protected styleBrushSelection( + brushSelection: Selection, + overlayZoomScale: number + ): void { + // Map values + const overlayBorderRadius = 4 / overlayZoomScale; + const selectionBorderRadius = 4 / overlayZoomScale; + const strokeWidth = 1 / overlayZoomScale; + + brushSelection + .select(`.${D3Zoom.DATA_BRUSH_OVERLAY_CLASS}`) + .attr('rx', overlayBorderRadius) + .attr('ry', overlayBorderRadius); + + brushSelection + .select(`.${D3Zoom.DATA_BRUSH_SELECTION_CLASS}`) + .attr('rx', selectionBorderRadius) + .attr('ry', selectionBorderRadius) + .style('stroke-width', strokeWidth) + .style('stroke-dasharray', `${strokeWidth}, ${strokeWidth}`); + } + + protected onBrushSelection(event: D3BrushEvent): void { + if (!event.selection) { + return; + } + + const [start, end] = event.selection as [[number, number], [number, number]]; + if (isEqual(start, end)) { + return; + } + const chartZoomScale = this.getZoomScale(); + const viewRect = { + top: start[1] * chartZoomScale, + left: start[0] * chartZoomScale, + bottom: end[1] * chartZoomScale, + right: end[0] * chartZoomScale, + width: end[0] * chartZoomScale - start[0] * chartZoomScale, + height: end[1] * chartZoomScale - start[1] * chartZoomScale + }; + + this.panToRect(viewRect); + } + + protected translateToRect(rect: ClientRect): void { + const centerX = rect.left + rect.width / 2; + const centerY = rect.top + rect.height / 2; + this.zoomBehavior.translateTo(this.getContainerSelectionOrThrow(), centerX, centerY); + } + + protected updateZoom(transform: ZoomTransform): void { + this.getTargetSelectionOrThrow().attr('transform', transform.toString()); + this.zoomChangeSubject.next(transform.k); + } + + protected checkValidZoomEvent(receivedEvent: ZoomSourceEvent): boolean { + if (this.isScrollEvent(receivedEvent)) { + return this.isValidTriggerEvent(receivedEvent, this.config && this.config.scroll); + } + if (this.isPrimaryMouseOrTouchEvent(receivedEvent)) { + return this.isValidTriggerEvent(receivedEvent, this.config && this.config.pan); + } + + return false; + } + + protected isValidTriggerEvent(receivedEvent: TouchEvent | MouseEvent, triggerConfig?: D3EventTriggerConfig): boolean { + if (!triggerConfig) { + return false; + } + if (!triggerConfig.requireModifiers) { + return true; + } + + return triggerConfig.requireModifiers.some(key => this.eventHasModifier(receivedEvent, key)); + } + + protected eventHasModifier(receivedEvent: TouchEvent | MouseEvent, modifier: ZoomEventKeyModifier): boolean { + switch (modifier) { + case Key.Control: + return receivedEvent.ctrlKey; + case Key.Meta: + return receivedEvent.metaKey; + default: + return false; + } + } + + protected isPrimaryMouseOrTouchEvent(receivedEvent: ZoomSourceEvent): receivedEvent is TouchEvent | MouseEvent { + return ( + ('TouchEvent' in window && receivedEvent instanceof TouchEvent) || + (receivedEvent instanceof MouseEvent && + !this.isScrollEvent(receivedEvent) && + receivedEvent.button === MouseButton.Primary) + ); + } + + protected isScrollEvent(receivedEvent: ZoomSourceEvent): receivedEvent is WheelEvent { + return receivedEvent instanceof WheelEvent; + } + + protected updateDraggingClassIfNeeded(zoomEvent: ZoomHandlerEvent): void { + this.getContainerSelectionOrThrow().classed('dragging', this.isPanStartEvent(zoomEvent)); + } + + protected isPanStartEvent(zoomEvent: ZoomHandlerEvent): boolean { + return zoomEvent.type === 'start' && this.isPrimaryMouseOrTouchEvent(zoomEvent.sourceEvent); + } + + protected getTargetSelectionOrThrow(): Selection { + return throwIfNil(this.config).target; + } + + protected getContainerSelectionOrThrow(): Selection { + return throwIfNil(this.config).container; + } +} + +type ZoomEventKeyModifier = Key.Control | Key.Meta; +// Type ZoomSourceEventType = 'wheel' | 'mousedown' | 'mouseup' | 'mousemove'; +type ZoomSourceEvent = MouseEvent | TouchEvent | null; +interface ZoomHandlerEvent extends D3ZoomEvent { + sourceEvent: ZoomSourceEvent; + type: 'start' | 'zoom' | 'end'; +} + +export interface D3ZoomConfiguration { + container: Selection; + target: Selection; + brushOverlay?: Selection; + scroll?: D3EventTriggerConfig; + pan?: D3EventTriggerConfig; + minScale?: number; + maxScale?: number; +} + +interface D3EventTriggerConfig { + requireModifiers?: ZoomEventKeyModifier[]; +} From 62c2c4d04f5e5712cc658bd3471c5a1a3fdc9ea9 Mon Sep 17 00:00:00 2001 From: anandtiwary <52081890+anandtiwary@users.noreply.github.com> Date: Wed, 17 Feb 2021 16:53:17 -0800 Subject: [PATCH 2/3] refactor: more changes --- .../topology/d3/interactions/zoom/topology-zoom.ts | 11 ++--------- .../src/shared/components/utils/d3/zoom/d3-zoom.ts | 14 +++++++++++++- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts index c2312502e..0ddb65c3f 100644 --- a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts +++ b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts @@ -14,15 +14,8 @@ export class TopologyZoom renderedData.getBoudingBox()); const requestedRect = unionOfClientRects(...nodeClientRects); - const availableRect = throwIfNil(this.config && this.config.container.node()).getBoundingClientRect(); - // Add a bit of padding to requested width/height for padding - const requestedWidthScale = availableRect.width / (requestedRect.width + 24); - const requestedHeightScale = availableRect.height / (requestedRect.height + 24); - // Zoomed in more than this is fine, but this is min to fit everything - const minOverallScale = Math.min(requestedWidthScale, requestedHeightScale); - // Never zoom beyond 100% with zoom to fit - this.setZoomScale(Math.min(1, Math.max(this.minScale, minOverallScale))); - this.translateToRect(requestedRect); + + this.zoomToRect(requestedRect); } public panToTopLeft(nodes: RenderableTopologyNode[]): void { diff --git a/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts b/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts index 9003e4191..4e8a47ab4 100644 --- a/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts +++ b/projects/observability/src/shared/components/utils/d3/zoom/d3-zoom.ts @@ -36,6 +36,7 @@ export abstract class D3Zoom() + .duration(750) .filter(() => this.checkValidZoomEvent(this.getCurrentD3Event())) .on('zoom', () => this.updateZoom(this.getCurrentD3Event().transform)) .on('start.drag', () => this.updateDraggingClassIfNeeded(this.getCurrentD3Event())) @@ -75,6 +76,17 @@ export abstract class D3Zoom Date: Thu, 18 Feb 2021 10:57:25 -0800 Subject: [PATCH 3/3] refactor: updating config --- .../components/topology/d3/d3-topology.ts | 3 +- .../d3/interactions/zoom/topology-zoom.ts | 4 +-- .../components/utils/d3/zoom/d3-zoom.ts | 30 +++++++++++++------ 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/projects/observability/src/shared/components/topology/d3/d3-topology.ts b/projects/observability/src/shared/components/topology/d3/d3-topology.ts index 0b0695e46..9f64de3ea 100644 --- a/projects/observability/src/shared/components/topology/d3/d3-topology.ts +++ b/projects/observability/src/shared/components/topology/d3/d3-topology.ts @@ -181,7 +181,8 @@ export class D3Topology implements Topology { container: svg, target: data, scroll: this.config.zoomable ? zoomScrollConfig : undefined, - pan: this.config.zoomable ? zoomPanConfig : undefined + pan: this.config.zoomable ? zoomPanConfig : undefined, + showBrush: true }); this.onDestroy(() => { diff --git a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts index 0ddb65c3f..8ec520da3 100644 --- a/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts +++ b/projects/observability/src/shared/components/topology/d3/interactions/zoom/topology-zoom.ts @@ -1,7 +1,7 @@ -import { D3ZoomConfiguration } from './../../../../utils/d3/zoom/d3-zoom'; import { throwIfNil, unionOfClientRects } from '@hypertrace/common'; import { D3Zoom } from '../../../../utils/d3/zoom/d3-zoom'; import { RenderableTopologyNode, RenderableTopologyNodeRenderedData } from '../../../topology'; +import { D3ZoomConfiguration } from './../../../../utils/d3/zoom/d3-zoom'; export class TopologyZoom extends D3Zoom< TContainer, @@ -60,7 +60,7 @@ export class TopologyZoom; + + protected config?: InternalD3ZoomConfiguration; + protected readonly zoomBehavior: ZoomBehavior; protected readonly zoomChangeSubject: BehaviorSubject = new BehaviorSubject(1); protected readonly brushBehaviour: BrushBehavior; @@ -46,13 +47,17 @@ export abstract class D3Zoom): this { - this.config = configuration; + this.config = { ...configuration }; this.zoomBehavior.scaleExtent([this.minScale, this.maxScale]); this.config.container .call(this.zoomBehavior) // tslint:disable-next-line: no-null-keyword .on('dblclick.zoom', null); // Remove default double click handler + if (this.config.showBrush) { + this.showBrushOverlay(); + } + return this; } @@ -97,7 +102,11 @@ export abstract class D3Zoom { export interface D3ZoomConfiguration { container: Selection; target: Selection; - brushOverlay?: Selection; scroll?: D3EventTriggerConfig; pan?: D3EventTriggerConfig; + showBrush?: boolean; minScale?: number; maxScale?: number; } - +interface InternalD3ZoomConfiguration + extends D3ZoomConfiguration { + brushOverlay?: Selection; +} interface D3EventTriggerConfig { requireModifiers?: ZoomEventKeyModifier[]; }