in tensorboard/webapp/widgets/line_chart_v2/sub_view/line_chart_interactive_view.ts [216:433]
ngAfterViewInit() {
// dblclick event cannot be prevented. Using css to disallow selecting instead.
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'dblclick', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe(() => {
this.onViewExtentReset.emit();
this.state = InteractionState.NONE;
this.changeDetector.markForCheck();
});
fromEvent<MouseEvent>(window, 'keydown', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
const newState = this.shouldPan(event);
if (newState !== this.specialKeyPressed) {
this.specialKeyPressed = newState;
this.changeDetector.markForCheck();
}
});
fromEvent<MouseEvent>(window, 'keyup', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
const newState = this.shouldPan(event);
if (newState !== this.specialKeyPressed) {
this.specialKeyPressed = newState;
this.changeDetector.markForCheck();
}
});
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'mousedown', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
const prevState = this.state;
const nextState = this.shouldPan(event)
? InteractionState.PANNING
: InteractionState.DRAG_ZOOMING;
// Override the dragStartCoord and zoomBox only when started to zoom.
// For instance, if you press left button then right, drag zoom should start at
// the left button down so the second mousedown is ignored.
if (
prevState === InteractionState.NONE &&
nextState === InteractionState.DRAG_ZOOMING
) {
this.dragStartCoord = {x: event.offsetX, y: event.offsetY};
this.zoomBoxInUiCoordinate = {
x: event.offsetX,
width: 0,
y: event.offsetY,
height: 0,
};
}
if (prevState !== nextState) {
this.state = nextState;
this.changeDetector.markForCheck();
}
});
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'mouseup', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
const leftClicked =
(event.buttons & MouseEventButtons.LEFT) === MouseEventButtons.LEFT;
this.dragStartCoord = null;
const zoomBox = this.zoomBoxInUiCoordinate;
if (
!leftClicked &&
this.state === InteractionState.DRAG_ZOOMING &&
zoomBox.width > 0 &&
zoomBox.height > 0
) {
const xMin = this.getDataX(zoomBox.x);
const xMax = this.getDataX(zoomBox.x + zoomBox.width);
const yMin = this.getDataY(zoomBox.y + zoomBox.height);
const yMax = this.getDataY(zoomBox.y);
this.onViewExtentChange.emit({
dataExtent: {
x: [xMin, xMax],
y: [yMin, yMax],
},
});
}
if (this.state !== InteractionState.NONE) {
this.state = InteractionState.NONE;
this.changeDetector.markForCheck();
}
});
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'mouseenter', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
this.isCursorInside = true;
this.updateTooltip(event);
this.changeDetector.markForCheck();
});
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'mouseleave', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
this.dragStartCoord = null;
this.isCursorInside = false;
this.updateTooltip(event);
this.state = InteractionState.NONE;
this.changeDetector.markForCheck();
});
fromEvent<MouseEvent>(this.dotsContainer.nativeElement, 'mousemove', {
passive: true,
})
.pipe(takeUntil(this.ngUnsubscribe))
.subscribe((event) => {
switch (this.state) {
case InteractionState.SCROLL_ZOOMING: {
this.state = InteractionState.NONE;
this.updateTooltip(event);
this.changeDetector.markForCheck();
break;
}
case InteractionState.NONE:
this.updateTooltip(event);
this.changeDetector.markForCheck();
break;
case InteractionState.PANNING: {
const deltaX = -event.movementX;
const deltaY = -event.movementY;
const {width: domWidth, height: domHeight} = this.domDim;
const xMin = this.getDataX(deltaX);
const xMax = this.getDataX(domWidth + deltaX);
const yMin = this.getDataY(domHeight + deltaY);
const yMax = this.getDataY(deltaY);
this.onViewExtentChange.emit({
dataExtent: {
x: [xMin, xMax],
y: [yMin, yMax],
},
});
break;
}
case InteractionState.DRAG_ZOOMING:
{
if (!this.dragStartCoord) {
break;
}
const xs = [this.dragStartCoord.x, event.offsetX];
const ys = [this.dragStartCoord.y, event.offsetY];
this.zoomBoxInUiCoordinate = {
x: Math.min(...xs),
width: Math.max(...xs) - Math.min(...xs),
y: Math.min(...ys),
height: Math.max(...ys) - Math.min(...ys),
};
}
this.changeDetector.markForCheck();
break;
}
});
fromEvent<WheelEvent>(this.dotsContainer.nativeElement, 'wheel', {
passive: false,
})
.pipe(
takeUntil(this.ngUnsubscribe),
switchMap((event: WheelEvent) => {
const shouldZoom = !event.ctrlKey && !event.shiftKey && event.altKey;
this.showZoomInstruction = !shouldZoom;
this.changeDetector.markForCheck();
if (shouldZoom) {
event.preventDefault();
return of(event);
}
return timer(3000).pipe(
tap(() => {
this.showZoomInstruction = false;
this.changeDetector.markForCheck();
}),
map(() => null)
);
}),
filter((eventOrNull) => Boolean(eventOrNull))
)
.subscribe((eventOrNull) => {
const event = eventOrNull!;
this.onViewExtentChange.emit({
dataExtent: getProposedViewExtentOnZoom(
event,
this.viewExtent,
this.domDim,
SCROLL_ZOOM_SPEED_FACTOR,
this.xScale,
this.yScale
),
});
if (this.state !== InteractionState.SCROLL_ZOOMING) {
this.state = InteractionState.SCROLL_ZOOMING;
this.changeDetector.markForCheck();
}
});
}