diff --git a/examples/x6-example-features/package.json b/examples/x6-example-features/package.json index da862fa857b..2bdac9c80b5 100644 --- a/examples/x6-example-features/package.json +++ b/examples/x6-example-features/package.json @@ -3,7 +3,7 @@ "name": "@antv/x6-example-features", "version": "2.1.1", "scripts": { - "start": "export NODE_OPTIONS=--openssl-legacy-provider && umi dev", + "start": "set NODE_OPTIONS=--openssl-legacy-provider && umi dev", "build": "umi build", "lint": "eslint 'src/**/*.{js,ts}?(x)' --fix" }, diff --git a/examples/x6-example-features/src/pages/index.tsx b/examples/x6-example-features/src/pages/index.tsx index d218d81cedc..c676fb0d2a4 100755 --- a/examples/x6-example-features/src/pages/index.tsx +++ b/examples/x6-example-features/src/pages/index.tsx @@ -183,6 +183,10 @@ const dataSource = [ example: 'history', description: '时光回溯', }, + { + example: 'minimap', + description: '小地图', + }, ].map((item, index) => ({ key: index, ...item })) const columns = [ diff --git a/examples/x6-example-features/src/pages/minimap/index.less b/examples/x6-example-features/src/pages/minimap/index.less index d79bb953466..166801ed6e2 100644 --- a/examples/x6-example-features/src/pages/minimap/index.less +++ b/examples/x6-example-features/src/pages/minimap/index.less @@ -9,10 +9,10 @@ .app-minimap { position: absolute; - bottom: 0; - left: 620px; - width: 200px; - height: 150px; + bottom: 16px; + right: 167px; + width: 400px; + height: 400px; } .x6-widget-minimap-viewport { diff --git a/examples/x6-example-features/src/pages/minimap/index.tsx b/examples/x6-example-features/src/pages/minimap/index.tsx index 4b6add54367..c3f528141e9 100644 --- a/examples/x6-example-features/src/pages/minimap/index.tsx +++ b/examples/x6-example-features/src/pages/minimap/index.tsx @@ -1,6 +1,6 @@ import * as React from 'react' -import { Graph } from '@antv/x6' -import { MiniMap } from '@antv/x6-plugin-minimap' +import { Graph } from '../../../../../packages/x6/src/index' +import { MiniMap } from '../../../../../packages/x6-plugin-minimap/src/index' import { Scroller } from '@antv/x6-plugin-scroller' import { Radio } from 'antd' import { SimpleNodeView } from './simple-view' @@ -18,29 +18,27 @@ export default class Example extends React.Component { componentDidMount() { this.graph = new Graph({ + panning: { + enabled: true, + modifiers: [], + eventTypes: ['leftMouseDown'], + }, + mousewheel: { + enabled: true, + }, container: this.container, - width: 600, - height: 320, + width: 1600, + height: 800, background: { color: '#F2F7FA', }, }) - this.graph.use( - new Scroller({ - pageVisible: true, - pageBreak: false, - pannable: true, - }), - ) - this.graph.use( - new MiniMap({ - container: this.minimapContainer, - width: 200, - height: 160, - padding: 10, - }), - ) + // this.graph.use( + // new Scroller({ + // pannable: false, + // }), + // ) this.graph.addNode({ x: 200, @@ -78,8 +76,8 @@ export default class Example extends React.Component { const target = this.graph.addNode({ shape: 'circle', - x: 160, - y: 180, + x: 1160, + y: 2180, width: 60, height: 60, label: 'World', @@ -102,6 +100,30 @@ export default class Example extends React.Component { }, }, }) + + // this.graph.use( + // new MiniMap({ + // container: this.minimapContainer, + // width: 400, + // height: 400, + // padding: 1, + // preserveAspectRatio: false, + // graphOptions: { + // width: 400, + // height: 400, + // }, + // }), + // ) + + + this.graph.on('cell:click', ({ e, x, y, cell, view }) => { + console.log('graph cell:click') + }) + + this.graph.on('node:click',()=>{ + console.log('graph node:click') + }) + } onMinimapViewChange = (val: string) => { @@ -148,6 +170,9 @@ export default class Example extends React.Component { refMiniMapContainer = (container: HTMLDivElement) => { this.minimapContainer = container } + changeSize = () => { + this.graph.resize(1800, 800) + } render() { return ( @@ -159,6 +184,7 @@ export default class Example extends React.Component { defaultValue={'detailed'} optionType="button" /> +
diff --git a/packages/x6-common/src/dom/event/core.ts b/packages/x6-common/src/dom/event/core.ts index e37a9171957..41bb2ceda86 100644 --- a/packages/x6-common/src/dom/event/core.ts +++ b/packages/x6-common/src/dom/event/core.ts @@ -8,6 +8,8 @@ import './special' export namespace Core { let triggered: string | undefined + + // Dom的事件注册,与Graph本身的事件注册机制区分开;使用Dom.Event.on进行Dom事件注册 export function on( elem: Store.EventTarget, types: string, @@ -37,13 +39,14 @@ export namespace Core { // if (!Util.isValidSelector(elem, selector)) { // throw new Error('Delegate event with invalid selector.') // } - + // 确保elem进入store,后续dispatch 中Util.getHandlerQueue(elem, event)会用到 const store = Store.ensure(elem) // Ensure the main handle let mainHandler = store.handler if (mainHandler == null) { mainHandler = store.handler = function (e, ...args: any[]) { + // 事件处理器 return triggered !== e.type ? dispatch(elem, e, ...args) : undefined } } @@ -61,6 +64,8 @@ export namespace Core { } let type = originType + // 事件名称hook,通过事件名转换成其它事件名称;EventHook.register方法注册转换逻辑 + // 例如mouseenter 会被转换为mouseover 事件 let hook = EventHook.get(type) // If selector defined, determine special event type, otherwise given type @@ -106,8 +111,10 @@ export namespace Core { Util.setHandlerId(handleObj.handler, guid) } + // Add to the element's handler list, delegates in front if (selector) { + // 有选择器的情况下(理解为具体的cell选择器),将delegateCount+1,代表有多少个具体cell选择器 bag.handlers.splice(bag.delegateCount, 0, handleObj) bag.delegateCount += 1 } else { @@ -209,6 +216,7 @@ export namespace Core { evt: Event | EventObject | string, ...args: any[] ) { + // 构建自定义的event数据 const event = EventObject.create(evt) event.delegateTarget = elem as Element @@ -240,10 +248,12 @@ export namespace Core { event.rnamespace == null || (handleObj.namespace && event.rnamespace.test(handleObj.namespace)) ) { + // 恢复之前的操作数据 event.handleObj = handleObj event.data = handleObj.data const hookHandle = EventHook.get(handleObj.originType).handle + // console.log('dispatch 事件触发', handleObj.originType) const result = hookHandle ? hookHandle(matched.elem as Store.EventTarget, event, ...args) diff --git a/packages/x6-common/src/dom/event/store.ts b/packages/x6-common/src/dom/event/store.ts index 515ead168a8..202f0759374 100644 --- a/packages/x6-common/src/dom/event/store.ts +++ b/packages/x6-common/src/dom/event/store.ts @@ -27,6 +27,7 @@ export namespace Store { export function ensure(target: EventTarget) { if (!cache.has(target)) { + // 构建以element为key 的events对象 cache.set(target, { events: Object.create(null) }) } return cache.get(target)! diff --git a/packages/x6-common/src/dom/event/util.ts b/packages/x6-common/src/dom/event/util.ts index 9f1e788fc41..3ef3eb513ef 100644 --- a/packages/x6-common/src/dom/event/util.ts +++ b/packages/x6-common/src/dom/event/util.ts @@ -100,6 +100,7 @@ export namespace Util { export namespace Util { export function getHandlerQueue(elem: Store.EventTarget, event: EventObject) { const queue = [] + // Store中使用WeakMap进行cache缓存,缓存相关数据 const store = Store.get(elem) const bag = store && store.events && store.events[event.type] const handlers = (bag && bag.handlers) || [] @@ -161,6 +162,7 @@ export namespace Util { // Add the remaining (directly-bound) handlers if (delegateCount < handlers.length) { + // 截取delegateCount之后的handler,也就是用于具体cell选择器的handler queue.push({ elem, handlers: handlers.slice(delegateCount) }) } diff --git a/packages/x6-common/src/dom/index.ts b/packages/x6-common/src/dom/index.ts index 0a5c9523254..4432a36d393 100644 --- a/packages/x6-common/src/dom/index.ts +++ b/packages/x6-common/src/dom/index.ts @@ -1,3 +1,3 @@ import * as Dom from './main' - +// 封装Dom相关的操作方法,可以借鉴 export { Dom } diff --git a/packages/x6-common/src/dom/position.ts b/packages/x6-common/src/dom/position.ts index 0bd58043067..10dfebe11cc 100644 --- a/packages/x6-common/src/dom/position.ts +++ b/packages/x6-common/src/dom/position.ts @@ -20,7 +20,11 @@ export function height(elem: Element) { const rect = elem.getBoundingClientRect() return rect.height } - +/** + * 计算元素相对于父元素的绝对定位 + * @param elem + * @returns + */ export function position(elem: Element) { const isFixed = computeStyle(elem, 'position') === 'fixed' let offsetValue: ReturnType @@ -38,6 +42,7 @@ export function position(elem: Element) { (offsetParent === doc.body || offsetParent === doc.documentElement) && computeStyle(offsetParent, 'position') === 'static' ) { + // 如果 offsetParent 是 body 或 html 并且其定位属性为 static,则继续寻找更高层的定位父元素 offsetParent = offsetParent.parentNode } if (offsetParent !== elem && isElement(offsetParent)) { diff --git a/packages/x6-common/src/object/mixins.ts b/packages/x6-common/src/object/mixins.ts index ff848ad1259..661493fe54c 100644 --- a/packages/x6-common/src/object/mixins.ts +++ b/packages/x6-common/src/object/mixins.ts @@ -1,4 +1,5 @@ /** + * 对象属性混入mixins * @see https://www.typescriptlang.org/docs/handbook/mixins.html */ export function applyMixins(derivedCtor: any, ...baseCtors: any[]) { diff --git a/packages/x6-geometry/src/rectangle.ts b/packages/x6-geometry/src/rectangle.ts index f728e2dbba5..7d1d785f060 100644 --- a/packages/x6-geometry/src/rectangle.ts +++ b/packages/x6-geometry/src/rectangle.ts @@ -624,6 +624,7 @@ export class Rectangle extends Geometry implements Rectangle.RectangleLike { /** * Returns a rectangle that is a union of this rectangle and rectangle `rect`. + * 返回一个矩形区域,该矩形是两个矩形区域的并集(最大区域)。 */ union(rect: Rectangle.RectangleLike | Rectangle.RectangleData) { const ref = Rectangle.clone(rect) diff --git a/packages/x6-plugin-history/src/index.ts b/packages/x6-plugin-history/src/index.ts index dd685eb411f..004c17cb4d8 100644 --- a/packages/x6-plugin-history/src/index.ts +++ b/packages/x6-plugin-history/src/index.ts @@ -176,6 +176,7 @@ export class History this.model.on('batch:start', this.initBatchCommand, this) this.model.on('batch:stop', this.storeBatchCommand, this) if (this.options.eventNames) { + // 监听哪些事件操作需要进入撤销回退队列,统一进入addCommand中处理 this.options.eventNames.forEach((name, index) => { this.handlers[index] = this.addCommand.bind(this, name) this.model.on(name, this.handlers[index]) @@ -525,11 +526,13 @@ export class History } protected push(cmd: History.Command, options: KeyValue) { + // 有新的操作进入撤销队列undoStack时,重做队列redoStack清空 this.redoStack = [] if (cmd.batch) { this.lastBatchIndex = Math.max(this.lastBatchIndex, 0) this.emit('batch', { cmd, options }) } else { + // 操作进入撤销队列 this.undoStackPush(cmd) this.consolidateCommands() this.notify('add', cmd, options) diff --git a/packages/x6-plugin-minimap/src/index.ts b/packages/x6-plugin-minimap/src/index.ts index 42a4167a80c..e7cb12c79f8 100644 --- a/packages/x6-plugin-minimap/src/index.ts +++ b/packages/x6-plugin-minimap/src/index.ts @@ -1,4 +1,4 @@ -import { FunctionExt, CssLoader, Dom, View, Graph, EventArgs } from '@antv/x6' +import { FunctionExt, CssLoader, Dom, View, Graph, EventArgs } from '../../x6/src/index' import { content } from './style/raw' export class MiniMap extends View implements Graph.Plugin { @@ -108,6 +108,7 @@ export class MiniMap extends View implements Graph.Plugin { this.sourceGraph.on('model:updated', this.onModelUpdated, this) } this.sourceGraph.on('resize', this.updatePaper, this) + // 当前container委托mousedown、touchstart事件到startAction事件;只有小地图容器被点击,才开始在startAction中监听mousemove等拖拽事件 this.delegateEvents({ mousedown: 'startAction', touchstart: 'startAction', @@ -140,12 +141,17 @@ export class MiniMap extends View implements Graph.Plugin { } protected onModelUpdated() { + // 模型每次更新,都调用小地图的targetGraph 缩放到合适位置 this.targetGraph.zoomToFit() } protected updatePaper(width: number, height: number): this protected updatePaper({ width, height }: EventArgs['resize']): this protected updatePaper(w: number | EventArgs['resize'], h?: number) { + // Graph 中指定的是viewPort 视口宽高,contentArea会根据内容撑开 + // MiniMap option中的宽高也是指定的 viewPort 视口宽高 + // 置于为什么contentArea 比viewPortArea小,并且全部显示了;还能拖拽移动,是svg的transform: matrix的作用 + let width: number let height: number if (typeof w === 'object') { @@ -156,14 +162,18 @@ export class MiniMap extends View implements Graph.Plugin { height = h as number } + // ratio:宽高比、scale:缩放比 + const origin = this.sourceGraph.options const scale = this.sourceGraph.transform.getScale() + // 小地图的最大宽高 = 宽高 - 2 * padding const maxWidth = this.options.width - 2 * this.options.padding const maxHeight = this.options.height - 2 * this.options.padding - + // 主视图的实际宽高 = 宽高 / 缩放比 width /= scale.sx // eslint-disable-line height /= scale.sy // eslint-disable-line + // 小地图宽|高比 = min(小地图viewArea宽/主视图viewArea宽, 小地图viewArea高/主地图viewArea高) this.ratio = Math.min(maxWidth / width, maxHeight / height) const ratio = this.ratio @@ -172,20 +182,26 @@ export class MiniMap extends View implements Graph.Plugin { width *= ratio // eslint-disable-line height *= ratio // eslint-disable-line + // 小地图图形实际宽高 = 主视图宽高 * 小地图宽|高比 this.targetGraph.resize(width, height) this.targetGraph.translate(x, y) if (this.scroller) { + // 有Scroller插件,直接使用宽高比作为缩放比 this.targetGraph.scale(ratio, ratio) } else { + // 没有使用Scroller插件,自适应缩放;并且计算小地图的缩放比 this.targetGraph.zoomToFit() } this.updateViewport() return this } - + // 更新小地图视口的框选区域 protected updateViewport() { + + + // 缩放比例 const sourceGraphScale = this.sourceGraph.transform.getScale() const targetGraphScale = this.targetGraph.transform.getScale() @@ -197,12 +213,21 @@ export class MiniMap extends View implements Graph.Plugin { } const position = Dom.position(this.targetGraph.container) + const translation = this.targetGraph.translate() translation.ty = translation.ty || 0 + + // 小地图框选div(整体区域)位置 this.geometry = { + // viewPort元素本身的top/left(不变) + 大窗口x.y位置 * 小地图缩放比例 + 小地图matrix矩阵偏移 x.y(不变) top: position.top + origin.y * targetGraphScale.sy + translation.ty, left: position.left + origin.x * targetGraphScale.sx + translation.tx, + // 小地图区域宽度 = 大窗口宽度 * 小地图缩放比例 / 大窗口缩放比例 + // 依据公式:小地图区域宽度 / 小地图缩放比例 = 大窗口宽度 / 大窗口缩放比例 + // 小地图缩放比例越小,小地图区域宽度也就越小 + + // 缩放比 = 视口宽高 / 画布内容区域宽高 width: (this.graphContainer.clientWidth! * targetGraphScale.sx) / sourceGraphScale.sx, @@ -215,8 +240,10 @@ export class MiniMap extends View implements Graph.Plugin { protected startAction(evt: Dom.MouseDownEvent) { const e = this.normalizeEvent(evt) + // 缩放还是平移 const action = e.target === this.zoomHandle ? 'zooming' : 'panning' const { tx, ty } = this.sourceGraph.translate() + const eventData: Util.EventData = { action, clientX: e.clientX, @@ -232,29 +259,44 @@ export class MiniMap extends View implements Graph.Plugin { this.targetGraphTransforming = true this.delegateDocumentEvents(Util.documentEvents, eventData) } - + // 拖动 protected doAction(evt: Dom.MouseMoveEvent) { const e = this.normalizeEvent(evt) const clientX = e.clientX const clientY = e.clientY const data = e.data as Util.EventData switch (data.action) { + // 平移 case 'panning': { const scale = this.sourceGraph.transform.getScale() + const targetScale = this.targetGraph.transform.getScale() + + // 相对于起始位置偏移了多少px const rx = (clientX - data.clientX) * scale.sx const ry = (clientY - data.clientY) * scale.sy + if (this.scroller) { this.graphContainer.scrollLeft = data.scrollLeft + rx / this.ratio this.graphContainer.scrollTop = data.scrollTop + ry / this.ratio } else { + // this.sourceGraph.translate( + // data.translateX - rx / this.ratio, + // data.translateY - ry / this.ratio, + // ) + + // 计算每次的偏移距离 + const x = (rx ) / targetScale.sx + const y = (ry ) / targetScale.sy + + // 现有偏移位置 - 每次偏移距离 this.sourceGraph.translate( - data.translateX - rx / this.ratio, - data.translateY - ry / this.ratio, + data.translateX - x, + data.translateY - y, ) } break } - + // 缩放 case 'zooming': { const startScale = data.scale const startGeometry = data.geometry @@ -284,7 +326,7 @@ export class MiniMap extends View implements Graph.Plugin { this.undelegateDocumentEvents() this.targetGraphTransforming = false } - + // 点击滚动 protected scrollTo(evt: Dom.MouseDownEvent) { const e = this.normalizeEvent(evt) @@ -294,6 +336,8 @@ export class MiniMap extends View implements Graph.Plugin { const ts = this.targetGraph.translate() ts.ty = ts.ty || 0 + console.log('scrollTo ts', ts) + if (e.offsetX == null) { const offset = Dom.offset(this.targetGraph.container) x = e.pageX - offset.left @@ -303,9 +347,40 @@ export class MiniMap extends View implements Graph.Plugin { y = e.offsetY } - const cx = (x - ts.tx) / this.ratio - const cy = (y - ts.ty) / this.ratio - this.sourceGraph.centerPoint(cx, cy) + const sourceScale = this.sourceGraph.transform.getScale() + const targetScale = this.targetGraph.transform.getScale() + + // // const cx = (x - ts.tx) / this.ratio + // // const cy = (y - ts.ty) / this.ratio + + // const originX = (x - ts.tx) + // const originY = (y - ts.ty) + // console.log('scrollTo', this.ratio, originX, originY) + // // 小地图scale缩小,大地图scale被放大, cx就越大;偏移差距也就越大 + // // 小地图scale放大,大地图scale被缩小, cx就越小;偏移差距也就越小 + // const cx = (originX * sourceScale.sx)/ targetScale.sx + // const cy = (originY * sourceScale.sy)/ targetScale.sy + + // console.log('big position', cx, cy) + + const position = Dom.position(this.viewport) + const containerPosition = Dom.position(this.targetGraph.container) + const {width, height} = this.viewport.getBoundingClientRect() + const start = { + x: position.left + width/2, + y: position.top - containerPosition.top + height/2, + } + const cx = (x - start.x) * sourceScale.sx / targetScale.sx + const cy = (y - start.y) * sourceScale.sy / targetScale.sy + + const { tx, ty } = this.sourceGraph.translate() + + this.sourceGraph.translate(tx - cx, ty - cy) + + + + + // this.sourceGraph.centerPoint(cx, cy) } @View.dispose() diff --git a/packages/x6/src/graph/graph.ts b/packages/x6/src/graph/graph.ts index ea57fdf1980..fe558303e4b 100644 --- a/packages/x6/src/graph/graph.ts +++ b/packages/x6/src/graph/graph.ts @@ -1,5 +1,5 @@ -import { Basecoat, NumberExt, Dom, KeyValue } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' +import { Basecoat, NumberExt, Dom, KeyValue } from '@antv/x6-common/es' +import { Point, Rectangle } from '../../../x6-geometry/src/index' import { Model, Collection, Cell, Node, Edge } from '../model' import { CellView } from '../view' import * as Registry from '../registry' @@ -52,6 +52,7 @@ export class Graph extends Basecoat { this.css = new Css(this) this.view = new GraphView(this) this.defs = new Defs(this) + // 坐标(用于画布与客户端/浏览器的坐标转换) this.coord = new Coord(this) this.transform = new Transform(this) this.highlight = new Highlight(this) @@ -68,6 +69,7 @@ export class Graph extends Basecoat { this.renderer = new ViewRenderer(this) this.panning = new Panning(this) this.mousewheel = new Wheel(this) + // 虚拟渲染 this.virtualRender = new VirtualRender(this) this.size = new Size(this) } @@ -824,6 +826,12 @@ export class Graph extends Basecoat { // #region coord + /** + * 当前事件在栅格中的坐标 + * @param x + * @param y + * @returns + */ snapToGrid(p: Point.PointLike): Point snapToGrid(x: number, y: number): Point snapToGrid(x: number | Point.PointLike, y?: number) { @@ -881,7 +889,10 @@ export class Graph extends Basecoat { return this.coord.localToPagePoint(x, y) } - + /** + * 画布坐标转换为客户端坐标(浏览器坐标) + * @param rect + */ clientToLocal(rect: Rectangle.RectangleLike): Rectangle clientToLocal(x: number, y: number, width: number, height: number): Rectangle clientToLocal(p: Point.PointLike): Point @@ -907,7 +918,10 @@ export class Graph extends Basecoat { return this.coord.clientToLocalPoint(x, y) } - + /** + * 浏览器坐标转换为画布端坐标 + * @param rect + */ localToClient(rect: Rectangle.RectangleLike): Rectangle localToClient(x: number, y: number, width: number, height: number): Rectangle localToClient(p: Point.PointLike): Point diff --git a/packages/x6/src/graph/transform.ts b/packages/x6/src/graph/transform.ts index b4178d2c475..1e6d4bc5ffd 100644 --- a/packages/x6/src/graph/transform.ts +++ b/packages/x6/src/graph/transform.ts @@ -1,5 +1,5 @@ -import { Dom, NumberExt } from '@antv/x6-common' -import { Point, Rectangle } from '@antv/x6-geometry' +import { Dom, NumberExt } from '../../../x6-common/src/index' +import { Point, Rectangle } from '../../../x6-geometry/src/index' import { Base } from './base' import { Util } from '../util' import { Cell } from '../model' @@ -322,6 +322,7 @@ export class TransformManager extends Base { options: TransformManager.ScaleContentToFitOptions = {}, translate = true, ) { + console.log('scaleContentToFitImpl', options) let contentBBox let contentLocalOrigin if (options.contentArea) { @@ -367,8 +368,10 @@ export class TransformManager extends Base { }) const currentScale = this.getScale() - + // 新的X轴缩放比(scaleX) = (视口宽 / 内容区域宽) * 当前X轴缩放比 + // 保证区域内容都在视口中展示 let newSX = (fittingBox.width / contentBBox.width) * currentScale.sx + // 新的y轴缩放比(scaleY) = (视口高 / 内容区域高) * 当前Y轴缩放比 let newSY = (fittingBox.height / contentBBox.height) * currentScale.sy if (options.preserveAspectRatio !== false) { @@ -421,8 +424,10 @@ export class TransformManager extends Base { const area = Rectangle.create(rect) const graph = this.graph + // 内容区域位置信息 options.contentArea = area if (options.viewportArea == null) { + // 视口位置信息(视口一般会小于内容区域位置,移动视口可以看内容区域不同位置) options.viewportArea = { x: graph.options.x, y: graph.options.y, @@ -459,6 +464,7 @@ export class TransformManager extends Base { y = cy - y * scale.sy // eslint-disable-line if (ts.tx !== x || ts.ty !== y) { + console.log('centerPoint', x, y) this.translate(x, y) } } diff --git a/packages/x6/src/graph/view.ts b/packages/x6/src/graph/view.ts index 1923f1942a5..a59fcaa2614 100644 --- a/packages/x6/src/graph/view.ts +++ b/packages/x6/src/graph/view.ts @@ -50,6 +50,10 @@ export class GraphView extends View { } delegateEvents() { + // 委托监听this.container dom事件上的交互事件;在对应的handler上再去触发graph上的事件 + // 1. GraphView中将ctor.events事件都绑定在this.container上,回调事件根据配置,指向GraphView上具体的函数 + // 2.this.container中的DOM事件触发之后,根据事件信息,找到对应的CellView子类,然后触发CellView上的事件 + // 3.CellView中的notify,会触发this.graph.trigger事件,从而将事件传递给graph;业务层如果监听了graph上的事件(例如 this.graph.on('cell:added'))),也会收到事件 const ctor = this.constructor as typeof GraphView super.delegateEvents(ctor.events) return this @@ -87,7 +91,11 @@ export class GraphView extends View { return true } - + /** + * 根据element查找对应的view (NodeView/EdgeView) + * @param elem + * @returns + */ protected findView(elem: Element) { return this.graph.findViewByElem(elem) } @@ -126,9 +134,12 @@ export class GraphView extends View { } const localPoint = this.graph.snapToGrid(e.clientX, e.clientY) + // 捕捉到元素(NodeView、EdgeView),触发对应view中的onclick事件 if (view) { + // view事件触发 view.onClick(e, localPoint.x, localPoint.y) } else { + // 没有找到,触发空白点击事件 this.graph.trigger('blank:click', { e, x: localPoint.x, diff --git a/packages/x6/src/index.ts b/packages/x6/src/index.ts index 7b9f0157509..51f31725ac9 100644 --- a/packages/x6/src/index.ts +++ b/packages/x6/src/index.ts @@ -7,7 +7,7 @@ export * from './graph' export * from './config' export * from './util' -export * from '@antv/x6-common' +export * from '../../x6-common/src/index' export * from '@antv/x6-geometry' export { Shape, Registry } diff --git a/packages/x6/src/model/cell.ts b/packages/x6/src/model/cell.ts index 87e94587a7d..a0b66c2cd56 100644 --- a/packages/x6/src/model/cell.ts +++ b/packages/x6/src/model/cell.ts @@ -8,8 +8,8 @@ import { KeyValue, Size, Basecoat, -} from '@antv/x6-common' -import { Rectangle, Point } from '@antv/x6-geometry' +} from '../../../x6-common/src/index' +import { Rectangle, Point } from '../../../x6-geometry/src/index' import { NonUndefined } from 'utility-types' import { Attr } from '../registry' import { Model } from './model' diff --git a/packages/x6/src/model/collection.ts b/packages/x6/src/model/collection.ts index 38c38918cd1..e612b0b041f 100644 --- a/packages/x6/src/model/collection.ts +++ b/packages/x6/src/model/collection.ts @@ -105,6 +105,8 @@ export class Collection extends Basecoat { } this.trigger('added', args) if (!localOptions.dryrun) { + // 元素添加后触发added事件,从而创建对应的CellView对象(NodeView、EdgeView ) + // 会触发model.notify('cell:added')事件,Scheduler 监听了model.on('cell:added')事件,从而在Scheduler中创建对应的CellView对象(NodeView、EdgeView ) cell.notify('added', { ...args }) } }) diff --git a/packages/x6/src/model/edge.ts b/packages/x6/src/model/edge.ts index 1f7ce8b856a..c8427b943c0 100644 --- a/packages/x6/src/model/edge.ts +++ b/packages/x6/src/model/edge.ts @@ -1183,6 +1183,7 @@ export namespace Edge { export function create(options: Metadata) { const shape = options.shape || 'edge' + // 从registry 注册中心获取对应边的构造函数 const Ctor = registry.get(shape) if (Ctor) { return new Ctor(options) diff --git a/packages/x6/src/model/model.ts b/packages/x6/src/model/model.ts index a40912ccd55..92d2474ccb2 100644 --- a/packages/x6/src/model/model.ts +++ b/packages/x6/src/model/model.ts @@ -252,6 +252,7 @@ export class Model extends Basecoat { } createNode(metadata: Node.Metadata) { + // 构建Node模型 return Node.create(metadata) } @@ -262,6 +263,7 @@ export class Model extends Basecoat { } createEdge(metadata: Edge.Metadata) { + // 构建Edge模型 return Edge.create(metadata) } diff --git a/packages/x6/src/model/node.ts b/packages/x6/src/model/node.ts index 0241e930933..a42e216c4b7 100644 --- a/packages/x6/src/model/node.ts +++ b/packages/x6/src/model/node.ts @@ -1188,6 +1188,7 @@ export namespace Node { export function create(options: Metadata) { const shape = options.shape || 'rect' + // 从registry 注册中心获取对应图形的构造函数(自定义的图形也会在registry中注册) const Ctor = registry.get(shape) if (Ctor) { return new Ctor(options) diff --git a/packages/x6/src/renderer/queueJob.ts b/packages/x6/src/renderer/queueJob.ts index 0a6690f00a7..1a9128fcdc3 100644 --- a/packages/x6/src/renderer/queueJob.ts +++ b/packages/x6/src/renderer/queueJob.ts @@ -58,7 +58,9 @@ export class JobQueue { this.queueFlush() } } - + /** + * 同步刷新执行任务队列 + */ flushJobsSync() { this.isFlushPending = false this.isFlushing = true @@ -76,6 +78,11 @@ export class JobQueue { this.isFlushing = false } + /** + * 二分法查找任务插入的位置,优先级高的排列在前 + * @param job + * @returns + */ private findInsertionIndex(job: Job) { let left = 0 let ins = this.queue.length @@ -92,7 +99,9 @@ export class JobQueue { } return ins } - + /** + * 异步任务调度,使用requestIdleCallback/setTimeout实现 + */ private scheduleJob() { if ('requestIdleCallback' in window) { if (this.scheduleId) { diff --git a/packages/x6/src/renderer/renderer.ts b/packages/x6/src/renderer/renderer.ts index 72225ccd224..e733563d151 100644 --- a/packages/x6/src/renderer/renderer.ts +++ b/packages/x6/src/renderer/renderer.ts @@ -7,6 +7,7 @@ import { CellView, EdgeView } from '../view' import { Util } from '../util' export class Renderer extends Base { + // 初始化Renderer时,传入graph;再Renderer中的Scheduler调度器中,监听graph的相关事件,注意这里监听的是model的事件(例如添加节点cell:added),然后进行渲染更新 private readonly schedule: Scheduler = new Scheduler(this.graph) requestViewUpdate(view: CellView, flag: number, options: any = {}) { @@ -34,10 +35,12 @@ export class Renderer extends Base { : elem[0] if (target) { + // 通过当前target元素,一直向上查找元素,直到找到拥有data-cell-id属性的元素,获取id值,在schedule中的views中,通过id找到对应的view const id = this.graph.view.findAttr('data-cell-id', target) if (id) { const views = this.schedule.views if (views[id]) { + // 返回view return views[id].view } } diff --git a/packages/x6/src/renderer/scheduler.ts b/packages/x6/src/renderer/scheduler.ts index 7abddb92696..d5f1fc0d71a 100644 --- a/packages/x6/src/renderer/scheduler.ts +++ b/packages/x6/src/renderer/scheduler.ts @@ -7,6 +7,7 @@ import { FlagManager } from '../view/flag' import { Graph } from '../graph' export class Scheduler extends Disposable { + // 存储所有的NodeView、EdgeView public views: KeyValue = {} public willRemoveViews: KeyValue = {} protected zPivots: KeyValue @@ -186,6 +187,7 @@ export class Scheduler extends Disposable { options, state: Scheduler.ViewState.CREATED, } + // scheduler中的views,存储所有的CellView(NodeView、EdgeView) this.views[id] = viewItem } } @@ -316,6 +318,7 @@ export class Scheduler extends Disposable { if (viewItem) { const zIndex = view.cell.getZIndex() const pivot = this.addZPivot(zIndex) + // 先将view(NodeView/EdgeView)的Dom框架内容插入到container this.container.insertBefore(view.container, pivot) if (!view.cell.isVisible()) { diff --git a/packages/x6/src/view/cell.ts b/packages/x6/src/view/cell.ts index 970c1135f7c..9101389b845 100644 --- a/packages/x6/src/view/cell.ts +++ b/packages/x6/src/view/cell.ts @@ -102,7 +102,7 @@ export class CellView< constructor(cell: Entity, options: Partial = {}) { super() - + // CellView中包含 Model中的Node、Edge this.cell = cell this.options = this.ensureOptions(options) this.graph = this.options.graph diff --git a/packages/x6/src/view/edge.ts b/packages/x6/src/view/edge.ts index 98502e2f17c..864ac81e448 100644 --- a/packages/x6/src/view/edge.ts +++ b/packages/x6/src/view/edge.ts @@ -1403,7 +1403,9 @@ export class EdgeView< } onClick(e: Dom.ClickEvent, x: number, y: number) { + // 触发CellView的click事件 graph.on('cell:click') super.onClick(e, x, y) + // 触发EdgeView的click事件 graph.on('edge:click') this.notify('edge:click', this.getEventArgs(e, x, y)) } diff --git a/packages/x6/src/view/node.ts b/packages/x6/src/view/node.ts index 07ddcc5760f..17b8621a667 100644 --- a/packages/x6/src/view/node.ts +++ b/packages/x6/src/view/node.ts @@ -518,8 +518,11 @@ export class NodeView< } onClick(e: Dom.ClickEvent, x: number, y: number) { + // 触发CellView的click事件 graph.on('cell:click') super.onClick(e, x, y) + // 触发NodeView的click事件 graph.on('node:click'),这个监听会更具体 this.notify('node:click', this.getEventArgs(e, x, y)) + // 触发连接桩点击事件 this.notifyPortEvent('node:port:click', e, { x, y }) } @@ -1052,21 +1055,28 @@ export class NodeView< return area || null } - + /** + * 开始节点拖拽 + * @param e + * @param x + * @param y + * @returns + */ protected startNodeDragging(e: Dom.MouseDownEvent, x: number, y: number) { const targetView = this.getDelegatedView() if (targetView == null || !targetView.can('nodeMovable')) { return this.notifyUnhandledMouseDown(e, x, y) } - + // 往自定义event中传数据 this.setEventData(e, { targetView, action: 'move', }) - + // 当前cell的位置 const position = Point.create(targetView.cell.getPosition()) targetView.setEventData(e, { moving: false, + // 当前点击位置相对于cell的位置的偏移量 offset: position.diff(x, y), restrict: this.getRestrictArea(targetView), }) @@ -1104,7 +1114,12 @@ export class NodeView< this.processEmbedding(e, data) } } - + /** + * 结束时间拖拽 + * @param e + * @param x + * @param y + */ protected stopNodeDragging(e: Dom.MouseUpEvent, x: number, y: number) { const data = this.getEventData(e) if (data.embedding) { diff --git a/packages/x6/src/view/view.ts b/packages/x6/src/view/view.ts index d6d255884de..307526da967 100644 --- a/packages/x6/src/view/view.ts +++ b/packages/x6/src/view/view.ts @@ -1,4 +1,4 @@ -import { Dom, KeyValue, Basecoat } from '@antv/x6-common' +import { Dom, KeyValue, Basecoat } from '../../../x6-common/src/index.ts' import { EventArgs } from '@antv/x6-common/lib/event/types' import { Config } from '../config' import { Markup } from './markup' @@ -111,7 +111,7 @@ export abstract class View extends Basecoat { if (current === this.container) { return null } - + // 向上递归寻找拥有指定属性attrName的元素 current = current.parentNode as Element }