UNPKG

@antv/g6

Version:

A Graph Visualization Framework in JavaScript

289 lines (257 loc) 10.2 kB
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}`; } }