@antv/g6
Version:
A Graph Visualization Framework in JavaScript
289 lines (257 loc) • 10.2 kB
text/typescript
import {
DisplayObjectConfig,
FederatedMouseEvent,
FederatedPointerEvent,
HTML as GHTML,
HTMLStyleProps as GHTMLStyleProps,
Group,
ICanvas,
IEventTarget,
Rect,
} from '@antv/g';
import { Renderer } from '@antv/g-canvas';
import { isNil, isUndefined, pick, set } from '@antv/util';
import { CommonEvent } from '../../constants';
import type { BaseNodeStyleProps } from './base-node';
import { BaseNode } from './base-node';
/**
* <zh/> HTML 节点样式配置项
*
* <en/> HTML node style props
*/
export interface HTMLStyleProps extends BaseNodeStyleProps {
/**
* <zh/> HTML 内容,可以为字符串或者 `HTMLElement`
*
* <en/> HTML content, can be a string or `HTMLElement`
*/
innerHTML: string | HTMLElement;
/**
* <zh/> 横行偏移量。HTML 容器默认以左上角为原点,通过 dx 来进行横向偏移
*
* <en/> Horizontal offset. The HTML container defaults to the upper left corner as the origin, and the horizontal offset is performed through dx
* @defaultValue 0
*/
dx?: number;
/**
* <zh/> 纵向偏移量。HTML 容器默认以左上角为原点,通过 dy 来进行纵向偏移
*
* <en/> Vertical offset. The HTML container defaults to the upper left corner as the origin, and the vertical offset is performed through dy
* @defaultValue 0
*/
dy?: number;
}
/**
* <zh/> HTML 节点
*
* <en/> HTML node
* @see https://github.com/antvis/G/blob/next/packages/g/src/plugins/EventPlugin.ts
*/
export class HTML extends BaseNode<HTMLStyleProps> {
static defaultStyleProps: Partial<HTMLStyleProps> = {
size: [160, 80],
halo: false,
icon: false,
label: false,
pointerEvents: 'auto',
};
constructor(options: DisplayObjectConfig<HTMLStyleProps>) {
super({ ...options, style: Object.assign({}, HTML.defaultStyleProps, options.style) });
}
private rootPointerEvent = new FederatedPointerEvent(null);
private get eventService() {
return this.context.canvas.context.eventService;
}
private get events() {
return [
CommonEvent.CLICK,
CommonEvent.POINTER_DOWN,
CommonEvent.POINTER_MOVE,
CommonEvent.POINTER_UP,
CommonEvent.POINTER_OVER,
CommonEvent.POINTER_LEAVE,
];
}
protected getDomElement() {
return this.getShape<GHTML>('key').getDomElement();
}
/**
* @override
*/
public render(attributes: Required<HTMLStyleProps> = this.parsedAttributes, container: Group = this): void {
this.drawKeyShape(attributes, container);
this.drawPortShapes(attributes, container);
}
protected getKeyStyle(attributes: Required<HTMLStyleProps>): GHTMLStyleProps {
const {
dx = 0,
dy = 0,
...style
} = pick(attributes, ['dx', 'dy', 'innerHTML', 'pointerEvents', 'cursor']) as unknown as HTMLStyleProps;
const [width, height] = this.getSize(attributes);
return { x: dx, y: dy, ...style, width, height };
}
protected drawKeyShape(attributes: Required<HTMLStyleProps>, container: Group) {
const style = this.getKeyStyle(attributes);
const { x, y, width = 0, height = 0 } = style;
const bounds = this.upsert('key-container', Rect, { x, y, width, height, opacity: 0 }, container)!;
return this.upsert('key', GHTML, style, bounds);
}
public connectedCallback() {
// only enable in canvas renderer
const renderer = this.context.canvas.getRenderer('main');
const isCanvasRenderer = renderer instanceof Renderer;
if (!isCanvasRenderer) return;
const element = this.getDomElement();
this.events.forEach((eventName) => {
// @ts-expect-error assert event is PointerEvent
element.addEventListener(eventName, this.forwardEvents);
});
}
public attributeChangedCallback(name: any, oldValue: any, newValue: any) {
if (name === 'zIndex' && oldValue !== newValue) {
this.getDomElement().style.zIndex = newValue;
}
}
public destroy() {
const element = this.getDomElement();
this.events.forEach((eventName) => {
// @ts-expect-error assert event is PointerEvent
element.removeEventListener(eventName, this.forwardEvents);
});
super.destroy();
}
private forwardEvents = (nativeEvent: PointerEvent) => {
const canvas = this.context.canvas;
const iCanvas = canvas.context.renderingContext.root!.ownerDocument!.defaultView!;
const normalizedEvents = this.normalizeToPointerEvent(nativeEvent, iCanvas);
normalizedEvents.forEach((normalizedEvent) => {
const event = this.bootstrapEvent(this.rootPointerEvent, normalizedEvent, iCanvas, nativeEvent);
// 当点击到 html 元素时,避免触发 pointerupoutside 事件
// When clicking on the html element, avoid triggering the pointerupoutside event
set(canvas.context.eventService, 'mappingTable.pointerupoutside', []);
canvas.context.eventService.mapEvent(event);
});
};
private normalizeToPointerEvent(event: PointerEvent, canvas: ICanvas): PointerEvent[] {
const normalizedEvents = [];
if (canvas.isTouchEvent(event)) {
for (let i = 0; i < event.changedTouches.length; i++) {
const touch = event.changedTouches[i] as any;
// use changedTouches instead of touches since touchend has no touches
// @see https://stackoverflow.com/a/10079076
if (isUndefined(touch.button)) touch.button = 0;
if (isUndefined(touch.buttons)) touch.buttons = 1;
if (isUndefined(touch.isPrimary)) {
touch.isPrimary = event.touches.length === 1 && event.type === 'touchstart';
}
if (isUndefined(touch.width)) touch.width = touch.radiusX || 1;
if (isUndefined(touch.height)) touch.height = touch.radiusY || 1;
if (isUndefined(touch.tiltX)) touch.tiltX = 0;
if (isUndefined(touch.tiltY)) touch.tiltY = 0;
if (isUndefined(touch.pointerType)) touch.pointerType = 'touch';
// @see https://developer.mozilla.org/zh-CN/docs/Web/API/Touch/identifier
if (isUndefined(touch.pointerId)) touch.pointerId = touch.identifier || 0;
if (isUndefined(touch.pressure)) touch.pressure = touch.force || 0.5;
if (isUndefined(touch.twist)) touch.twist = 0;
if (isUndefined(touch.tangentialPressure)) touch.tangentialPressure = 0;
touch.isNormalized = true;
touch.type = event.type;
normalizedEvents.push(touch);
}
} else if (canvas.isMouseEvent(event)) {
const tempEvent = event as any;
if (isUndefined(tempEvent.isPrimary)) tempEvent.isPrimary = true;
if (isUndefined(tempEvent.width)) tempEvent.width = 1;
if (isUndefined(tempEvent.height)) tempEvent.height = 1;
if (isUndefined(tempEvent.tiltX)) tempEvent.tiltX = 0;
if (isUndefined(tempEvent.tiltY)) tempEvent.tiltY = 0;
if (isUndefined(tempEvent.pointerType)) tempEvent.pointerType = 'mouse';
if (isUndefined(tempEvent.pointerId)) tempEvent.pointerId = 1;
if (isUndefined(tempEvent.pressure)) tempEvent.pressure = 0.5;
if (isUndefined(tempEvent.twist)) tempEvent.twist = 0;
if (isUndefined(tempEvent.tangentialPressure)) tempEvent.tangentialPressure = 0;
tempEvent.isNormalized = true;
normalizedEvents.push(tempEvent);
} else {
normalizedEvents.push(event);
}
return normalizedEvents as PointerEvent[];
}
private transferMouseData(event: FederatedMouseEvent, nativeEvent: MouseEvent): void {
event.isTrusted = nativeEvent.isTrusted;
event.srcElement = nativeEvent.srcElement as IEventTarget;
event.timeStamp = performance.now();
event.type = nativeEvent.type;
event.altKey = nativeEvent.altKey;
event.metaKey = nativeEvent.metaKey;
event.shiftKey = nativeEvent.shiftKey;
event.ctrlKey = nativeEvent.ctrlKey;
event.button = nativeEvent.button;
event.buttons = nativeEvent.buttons;
event.client.x = nativeEvent.clientX;
event.client.y = nativeEvent.clientY;
event.movement.x = nativeEvent.movementX;
event.movement.y = nativeEvent.movementY;
event.page.x = nativeEvent.pageX;
event.page.y = nativeEvent.pageY;
event.screen.x = nativeEvent.screenX;
event.screen.y = nativeEvent.screenY;
event.relatedTarget = null;
}
private bootstrapEvent(
event: FederatedPointerEvent,
normalizedEvent: PointerEvent,
view: ICanvas,
nativeEvent: PointerEvent | MouseEvent | TouchEvent,
): FederatedPointerEvent {
event.view = view;
// @ts-ignore
event.originalEvent = null;
event.nativeEvent = nativeEvent;
event.pointerId = normalizedEvent.pointerId;
event.width = normalizedEvent.width;
event.height = normalizedEvent.height;
event.isPrimary = normalizedEvent.isPrimary;
event.pointerType = normalizedEvent.pointerType;
event.pressure = normalizedEvent.pressure;
event.tangentialPressure = normalizedEvent.tangentialPressure;
event.tiltX = normalizedEvent.tiltX;
event.tiltY = normalizedEvent.tiltY;
event.twist = normalizedEvent.twist;
this.transferMouseData(event, normalizedEvent);
const { x, y } = this.getViewportXY(normalizedEvent);
event.viewport.x = x;
event.viewport.y = y;
const [canvasX, canvasY] = this.context.canvas.getCanvasByViewport([x, y]);
event.canvas.x = canvasX;
event.canvas.y = canvasY;
event.global.copyFrom(event.canvas);
event.offset.copyFrom(event.canvas);
event.isTrusted = nativeEvent.isTrusted;
if (event.type === 'pointerleave') {
event.type = 'pointerout';
}
return event;
}
private getViewportXY(nativeEvent: PointerEvent | WheelEvent) {
let x: number;
let y: number;
const { offsetX, offsetY, clientX, clientY } = nativeEvent;
if (!isNil(offsetX) && !isNil(offsetY)) {
x = offsetX;
y = offsetY;
} else {
const point = this.eventService.client2Viewport({ x: clientX, y: clientY });
x = point.x;
y = point.y;
}
return { x, y };
}
protected onframe(): void {
super.onframe();
// sync opacity
const { opacity } = this.attributes;
this.getDomElement().style.opacity = `${opacity}`;
}
}