Compare commits
24 Commits
@antv/x6@2
...
@antv/x6-s
Author | SHA1 | Date | |
---|---|---|---|
7b091f35de | |||
95c72c3033 | |||
0c91218b64 | |||
61c030a162 | |||
5c5f3e5319 | |||
06a8f28c2d | |||
ece198265b | |||
6abd0683ea | |||
fff95806c8 | |||
a069449782 | |||
3b25683529 | |||
45337e4a62 | |||
91fc97791b | |||
cdd0913eee | |||
a696009ede | |||
c808ca6d2b | |||
d3301d33d5 | |||
0029555458 | |||
cc18463c53 | |||
fb8098c1c0 | |||
c250caba6a | |||
019333d79d | |||
33c2e59207 | |||
844ee5fa04 |
@ -37,6 +37,7 @@ Utopia <greatauk11@gmail.com>
|
||||
XLZY <1017866168@qq.com>
|
||||
Xingjian <han>
|
||||
Zhenyu <o>
|
||||
_XiaoTian <istianlei@qq.com>
|
||||
arthur657834 <kingkom7834@126.com>
|
||||
boyu.zlj <boyu.zlj@antgroup.com>
|
||||
breezefaith <nyzhangzc@qq.com>
|
||||
@ -80,6 +81,7 @@ zdc1111 <39116292+zdc1111@users.noreply.github.com>
|
||||
€alix <qq287649920@gmail.com>
|
||||
九思⚡⚡⚡ <2228429150@qq.com>
|
||||
何腾飞 <avrin.live.cn@outlook.com>
|
||||
依枫 <deng25st@163.com>
|
||||
偏右 <afc163@gmail.com>
|
||||
小耀 <jinyue.gjy@antfin.com>
|
||||
崖 <bubkoo.wy@gmail.com>
|
||||
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 12 MiB After Width: | Height: | Size: 12 MiB |
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021-2022 Alipay.inc
|
||||
Copyright (c) 2021-2023 Alipay.inc
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
17
README.md
17
README.md
@ -6,18 +6,17 @@
|
||||
<p align="center"><strong>提供简单易用的节点定制能力和开箱即用的交互组件,方便我们快速搭建流程图、DAG 图、ER 图等图应用</strong></p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/antvis/X6/actions/workflows/ci.yml"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/antvis/x6/ci.yml?branch=master&logo=github&style=flat-square"></a>
|
||||
<!-- <a href="https://app.codecov.io/gh/antvis/X6"><img alt="coverage" src="https://img.shields.io/codecov/c/gh/antvis/x6?logo=codecov&style=flat-square&token=15CO54WYUV"></a> -->
|
||||
<a href="https://lgtm.com/projects/g/antvis/x6/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/antvis/x6.svg?logo=lgtm&style=flat-square"></a>
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Package" src="https://img.shields.io/npm/v/@antv/x6.svg?style=flat-square"></a>
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@antv/x6?logo=npm&style=flat-square"></a>
|
||||
<a href="https://github.com/antvis/X6/actions/workflows/ci.yml"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/antvis/x6/ci.yml?branch=master&style=for-the-badge&logo=github"></a>
|
||||
<!-- <a href="https://app.codecov.io/gh/antvis/X6"><img alt="coverage" src="https://img.shields.io/codecov/c/gh/antvis/x6?logo=codecov&style=for-the-badge&token=15CO54WYUV"></a> -->
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Package" src="https://img.shields.io/npm/v/@antv/x6.svg?logo=npm&style=for-the-badge"></a>
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@antv/x6?logo=npm&style=for-the-badge"></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="/LICENSE"><img src="https://img.shields.io/github/license/antvis/x6?style=flat-square" alt="MIT License"></a>
|
||||
<a href="https://www.typescriptlang.org"><img alt="Language" src="https://img.shields.io/badge/language-TypeScript-blue.svg?style=flat-square"></a>
|
||||
<a href="https://github.com/antvis/x6/pulls"><img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-Welcome-brightgreen.svg?style=flat-square"></a>
|
||||
<a href="https://x6.antv.antgroup.com"><img alt="website" src="https://img.shields.io/static/v1?label=&labelColor=505050&message=website&color=0076D6&style=flat-square&logo=google-chrome&logoColor=0076D6"></a>
|
||||
<a href="/LICENSE"><img src="https://img.shields.io/github/license/antvis/x6?style=for-the-badge" alt="MIT License"></a>
|
||||
<a href="https://www.typescriptlang.org"><img alt="Language" src="https://img.shields.io/badge/language-TypeScript-blue.svg?style=for-the-badge"></a>
|
||||
<a href="https://github.com/antvis/x6/pulls"><img alt="PRs Welcome" src="https://img.shields.io/badge/PRs-Welcome-brightgreen.svg?style=for-the-badge"></a>
|
||||
<a href="https://x6.antv.antgroup.com"><img alt="website" src="https://img.shields.io/static/v1?label=&labelColor=505050&message=website&color=0076D6&style=for-the-badge&logo=google-chrome&logoColor=f5f5f5"></a>
|
||||
</p>
|
||||
|
||||
## 特性
|
||||
|
@ -2,7 +2,7 @@
|
||||
"name": "x6",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"preinstall": "node ./scripts/preinstall",
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"lint:ts": "eslint '**/src/**/*.{js,ts}?(x)' --cache --fix",
|
||||
"lint:style": "stylelint '**/src/**/*.less' --customSyntax postcss-less --cache --fix",
|
||||
"lint": "run-s lint:ts lint:style",
|
||||
|
@ -81,7 +81,10 @@ export class Dnd extends View {
|
||||
|
||||
this.targetModel.startBatch('dnd')
|
||||
Dom.addClass(this.container, 'dragging')
|
||||
Dom.appendTo(this.container, this.options.containerParent || document.body)
|
||||
Dom.appendTo(
|
||||
this.container,
|
||||
this.options.draggingContainer || document.body,
|
||||
)
|
||||
|
||||
this.sourceNode = node
|
||||
this.prepareDragging(node, e.clientX, e.clientY)
|
||||
@ -481,7 +484,7 @@ export namespace Dnd {
|
||||
// duration?: number
|
||||
// easing?: string
|
||||
// }
|
||||
containerParent?: HTMLElement
|
||||
draggingContainer?: HTMLElement
|
||||
/**
|
||||
* dnd tool box container.
|
||||
*/
|
||||
|
@ -1,3 +1,10 @@
|
||||
## @antv/x6-plugin-transform [2.1.5](https://github.com/antvis/x6/compare/@antv/x6-plugin-transform@2.1.4...@antv/x6-plugin-transform@2.1.5) (2022-12-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add defense for view in transform plugin ([#3092](https://github.com/antvis/x6/issues/3092)) ([fb8098c](https://github.com/antvis/x6/commit/fb8098c1c06440dd69f4e93881fd36f7e6de2a56))
|
||||
|
||||
## @antv/x6-plugin-transform [2.1.4](https://github.com/antvis/x6/compare/@antv/x6-plugin-transform@2.1.3...@antv/x6-plugin-transform@2.1.4) (2022-12-07)
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@antv/x6-plugin-transform",
|
||||
"version": "2.1.4",
|
||||
"version": "2.1.5",
|
||||
"description": "transform plugin for X6",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
|
@ -107,7 +107,11 @@ export class TransformImpl extends View<TransformImpl.EventArgs> {
|
||||
|
||||
render() {
|
||||
this.renderHandles()
|
||||
this.view.addClass(Private.NODE_CLS)
|
||||
|
||||
if (this.view) {
|
||||
this.view.addClass(Private.NODE_CLS)
|
||||
}
|
||||
|
||||
Dom.addClass(this.container, this.containerClassName)
|
||||
Dom.toggleClass(
|
||||
this.container,
|
||||
@ -153,7 +157,9 @@ export class TransformImpl extends View<TransformImpl.EventArgs> {
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.view.removeClass(Private.NODE_CLS)
|
||||
if (this.view) {
|
||||
this.view.removeClass(Private.NODE_CLS)
|
||||
}
|
||||
return super.remove()
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
## @antv/x6-vue-shape [2.0.9](https://github.com/antvis/x6/compare/@antv/x6-vue-shape@2.0.8...@antv/x6-vue-shape@2.0.9) (2022-12-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* get graph from right place ([#3078](https://github.com/antvis/x6/issues/3078)) ([844ee5f](https://github.com/antvis/x6/commit/844ee5fa043cbcd788ec1693f88576e797426228))
|
||||
|
||||
## @antv/x6-vue-shape [2.0.7](https://github.com/antvis/x6/compare/@antv/x6-vue-shape@2.0.6...@antv/x6-vue-shape@2.0.7) (2022-12-09)
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@antv/x6-vue-shape",
|
||||
"version": "2.0.7",
|
||||
"version": "2.0.9",
|
||||
"description": "X6 shape for rendering vue components.",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
|
@ -26,6 +26,7 @@ export class VueShapeView extends NodeView<VueShape> {
|
||||
this.unmountVueComponent()
|
||||
const root = this.getComponentContainer()
|
||||
const node = this.cell
|
||||
const graph = this.graph
|
||||
|
||||
if (root) {
|
||||
const { component } = shapeMaps[node.shape]
|
||||
@ -40,13 +41,13 @@ export class VueShapeView extends NodeView<VueShape> {
|
||||
provide() {
|
||||
return {
|
||||
getNode: () => node,
|
||||
getGraph: () => this.graph,
|
||||
getGraph: () => graph,
|
||||
}
|
||||
},
|
||||
})
|
||||
} else if (isVue3) {
|
||||
if (isActive()) {
|
||||
connect(this.targetId(), component, root, node, this.graph)
|
||||
connect(this.targetId(), component, root, node, graph)
|
||||
} else {
|
||||
this.vm = createApp({
|
||||
render() {
|
||||
@ -55,7 +56,7 @@ export class VueShapeView extends NodeView<VueShape> {
|
||||
provide() {
|
||||
return {
|
||||
getNode: () => node,
|
||||
getGraph: () => this.graph,
|
||||
getGraph: () => graph,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
@ -1,3 +1,17 @@
|
||||
## @antv/x6 [2.1.4](https://github.com/antvis/x6/compare/@antv/x6@2.1.3...@antv/x6@2.1.4) (2023-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* optimize rendering logic to prevent loops ([#3108](https://github.com/antvis/x6/issues/3108)) ([45337e4](https://github.com/antvis/x6/commit/45337e4a62224aaffd60fc8b2670a071c5560796))
|
||||
|
||||
## @antv/x6 [2.1.3](https://github.com/antvis/x6/compare/@antv/x6@2.1.2...@antv/x6@2.1.3) (2022-12-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* schedule edge when source and target is not ready ([#3090](https://github.com/antvis/x6/issues/3090)) ([019333d](https://github.com/antvis/x6/commit/019333d79d7f22c44c400f29d501497f4323af1a))
|
||||
|
||||
## @antv/x6 [2.1.1](https://github.com/antvis/x6/compare/@antv/x6@2.1.0...@antv/x6@2.1.1) (2022-12-19)
|
||||
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@antv/x6",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.4",
|
||||
"description": "JavaScript diagramming library that uses SVG and HTML for rendering",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.js",
|
||||
|
@ -219,6 +219,18 @@ export class Scheduler extends Disposable {
|
||||
|
||||
if (result) {
|
||||
console.log('left flag', result) // eslint-disable-line
|
||||
if (
|
||||
cell.isEdge() &&
|
||||
(result & view.getFlag(['source', 'target'])) === 0
|
||||
) {
|
||||
this.queue.queueJob({
|
||||
id,
|
||||
priority: JOB_PRIORITY.RenderEdge,
|
||||
cb: () => {
|
||||
this.updateView(view, flag, options)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -357,7 +357,12 @@ export class NodeView<
|
||||
|
||||
protected updatePorts() {
|
||||
const groups = this.cell.getParsedGroups()
|
||||
Object.keys(groups).forEach((groupName) => this.updatePortGroup(groupName))
|
||||
const groupList = Object.keys(groups)
|
||||
if (groupList.length === 0) {
|
||||
this.updatePortGroup()
|
||||
} else {
|
||||
groupList.forEach((groupName) => this.updatePortGroup(groupName))
|
||||
}
|
||||
}
|
||||
|
||||
protected updatePortGroup(groupName?: string) {
|
||||
|
24058
pnpm-lock.yaml
generated
24058
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
if (!/pnpm/.test(process.env.npm_execpath || '')) {
|
||||
console.warn(
|
||||
`This repository requires using pnpm as the package manager for scripts to work properly.`,
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
if (window) {
|
||||
(window as any).react = require('react');
|
||||
(window as any).reactDom = require('react-dom');
|
||||
(window as any).antd = require('antd');
|
||||
(window as any).dagre = require('dagre');
|
||||
(window as any).x6 = require('@antv/x6');
|
||||
(window as any).x6PluginSnapline = require('@antv/x6-plugin-snapline');
|
||||
(window as any).x6PluginClipboard = require('@antv/x6-plugin-clipboard');
|
||||
(window as any).x6PluginKeyboard = require('@antv/x6-plugin-keyboard');
|
||||
(window as any).x6PluginSelection = require('@antv/x6-plugin-selection');
|
||||
(window as any).x6PluginTransform = require('@antv/x6-plugin-transform');
|
||||
(window as any).x6PluginStencil = require('@antv/x6-plugin-stencil');
|
||||
(window as any).x6PluginHistory = require('@antv/x6-plugin-history');
|
||||
(window as any).x6ReactShape = require('@antv/x6-react-shape');
|
||||
(window as any).layout = require('@antv/layout');
|
||||
(window as any).hierarchy = require('@antv/hierarchy');
|
||||
(window as any).elkjs = require('elkjs/lib/elk.bundled.js');
|
||||
}
|
||||
;(window as any).react = require('react')
|
||||
;(window as any).reactDom = require('react-dom')
|
||||
;(window as any).antd = require('antd')
|
||||
;(window as any).dagre = require('dagre')
|
||||
;(window as any).x6 = require('@antv/x6')
|
||||
;(window as any).x6PluginSnapline = require('@antv/x6-plugin-snapline')
|
||||
;(window as any).x6PluginClipboard = require('@antv/x6-plugin-clipboard')
|
||||
;(window as any).x6PluginKeyboard = require('@antv/x6-plugin-keyboard')
|
||||
;(window as any).x6PluginSelection = require('@antv/x6-plugin-selection')
|
||||
;(window as any).x6PluginTransform = require('@antv/x6-plugin-transform')
|
||||
;(window as any).x6PluginStencil = require('@antv/x6-plugin-stencil')
|
||||
;(window as any).x6PluginHistory = require('@antv/x6-plugin-history')
|
||||
;(window as any).x6ReactShape = require('@antv/x6-react-shape')
|
||||
;(window as any).layout = require('@antv/layout')
|
||||
;(window as any).classnames = require('classnames')
|
||||
;(window as any).hierarchy = require('@antv/hierarchy')
|
||||
;(window as any).elkjs = require('elkjs/lib/elk.bundled.js')
|
||||
}
|
||||
|
@ -1,3 +1,24 @@
|
||||
## @antv/x6-sites [1.3.1](https://github.com/antvis/x6/compare/@antv/x6-sites@1.3.0...@antv/x6-sites@1.3.1) (2023-01-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* optimize rendering logic to prevent loops ([#3108](https://github.com/antvis/x6/issues/3108)) ([45337e4](https://github.com/antvis/x6/commit/45337e4a62224aaffd60fc8b2670a071c5560796))
|
||||
|
||||
# @antv/x6-sites [1.3.0](https://github.com/antvis/X6/compare/@antv/x6-sites@1.2.2...@antv/x6-sites@1.3.0) (2023-01-01)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add data processing dag example ([#3091](https://github.com/antvis/X6/issues/3091)) ([d3301d3](https://github.com/antvis/X6/commit/d3301d33d575269d9219ab1337a2ec1785d61494))
|
||||
|
||||
## @antv/x6-sites [1.2.2](https://github.com/antvis/X6/compare/@antv/x6-sites@1.2.1...@antv/x6-sites@1.2.2) (2022-12-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* schedule edge when source and target is not ready ([#3090](https://github.com/antvis/X6/issues/3090)) ([019333d](https://github.com/antvis/X6/commit/019333d79d7f22c44c400f29d501497f4323af1a))
|
||||
|
||||
# @antv/x6-sites [1.2.0](https://github.com/antvis/X6/compare/@antv/x6-sites@1.1.2...@antv/x6-sites@1.2.0) (2022-12-16)
|
||||
|
||||
|
||||
|
7
sites/x6-sites/LEGAL.md
Normal file
7
sites/x6-sites/LEGAL.md
Normal file
@ -0,0 +1,7 @@
|
||||
Legal Disclaimer
|
||||
|
||||
Within this source code, the comments in Chinese shall be the original, governing version. Any comment in other languages are for reference only. In the event of any conflict between the Chinese language version comments and other language version comments, the Chinese language version shall prevail.
|
||||
|
||||
法律免责声明
|
||||
|
||||
关于代码注释部分,中文注释为官方版本,其它语言注释仅做参考。中文注释可能与其它语言注释存在不一致,当中文注释与其它语言注释存在不一致时,请以中文注释为准。
|
@ -2,9 +2,9 @@
|
||||
title: Graph
|
||||
order: 0
|
||||
redirect_from:
|
||||
+ /zh/docs
|
||||
+ /zh/docs/api
|
||||
+ /zh/docs/api/graph
|
||||
- /zh/docs
|
||||
- /zh/docs/api
|
||||
- /zh/docs/api/graph
|
||||
---
|
||||
|
||||
## 配置
|
||||
@ -13,29 +13,29 @@ redirect_from:
|
||||
new Graph(options: Options)
|
||||
```
|
||||
|
||||
| 选项 | 类型 | 必选 | 默认值 | 描述 |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------ | :--: | ------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| containers | HTMLElement | ✓ | | 画布的容器。 |
|
||||
| width | number | | - | 画布宽度,默认使用容器宽度。 |
|
||||
| height | number | | - | 画布高度,默认使用容器高度。 |
|
||||
| scaling | { min?: number, max?: number } | | { min: 0.01, max: 16 } | 画布的最小最大缩放级别。 |
|
||||
| [autoResize](/zh/docs/tutorial/basic/graph#画布大小) | boolean \| Element \| Document | | `false` | 是否监听容器大小改变,并自动更新画布大小。 |
|
||||
| [panning](/zh/docs/api/graph/panning) | boolean \| `PanningManager.Options` | | `false` | 画布是否可以拖拽平移,默认禁用。 |
|
||||
| [mousewheel](/zh/docs/api/graph/mousewheel) | boolean \| `MouseWheel.Options` | | `false` | 鼠标滚轮缩放,默认禁用。 |
|
||||
| [grid](/zh/docs/api/graph/grid) | boolean \| number \| `GridManager.Options` | | `false` | 网格,默认使用 `10px` 的网格,但不绘制网格背景。 |
|
||||
| [background](/zh/docs/api/graph/background) | false \| `BackgroundManager.Options` | | `false` | 背景,默认不绘制背景。 |
|
||||
| [translating](/zh/docs/api/interacting/interaction#trasnlating) | `Translating.Options` | | { restrict: false } | 限制节点移动。 |
|
||||
| [embedding](/zh/docs/api/interacting/interaction#embedding) | boolean \| `Embedding.Options` | | `false` | 嵌套节点,默认禁用。 |
|
||||
| [connecting](/zh/docs/api/interacting/interaction#connecting) | `Connecting.Options` | | { snap: false, ... } | 连线选项。 |
|
||||
| [highlighting](/zh/docs/api/interacting/interaction#highlighting) | `Highlighting.Options` | | {...} | 高亮选项。 |
|
||||
| [interacting](/zh/docs/api/interacting/interaction#interacting) | `Interacting.Options` | | { edgeLabelMovable: false } | 定制节点和边的交互行为。 |
|
||||
| [magnetThreshold](/zh/docs/api/graph/view#magnetthreshold) | number \| 'onleave' | | `0` | 鼠标移动多少次后才触发连线,或者设置为 `'onleave'` 时表示鼠标移出元素时才触发连线。 |
|
||||
| [moveThreshold](/zh/docs/api/graph/view#movethreshold) | number | | `0` | 触发 `'mousemove'` 事件之前,允许鼠标移动的次数。 |
|
||||
| [clickThreshold](/zh/docs/api/graph/view#clickthreshold) | number | | `0` | 当鼠标移动次数超过指定的数字时,将不触发鼠标点击事件。 |
|
||||
| [preventDefaultContextMenu](/zh/docs/api/graph/view#preventdefaultcontextmenu) | boolean | | `true` | 是否禁用浏览器默认右键菜单。 |
|
||||
| [preventDefaultBlankAction](/zh/docs/api/graph/view#preventdefaultblankaction) | boolean | | `true` | 在画布空白位置响应鼠标事件时,是否禁用鼠标默认行为。 |
|
||||
| [async](/zh/docs/api/graph/view#async) | boolean | | `true` | 是否异步渲染 |
|
||||
| [virtual](/zh/docs/api/graph/view#virtual) | boolean | | `false` | 是否只渲染可视区域内容 |
|
||||
| [onPortRendered](/zh/docs/api/graph/view#onportrendered) | (args: OnPortRenderedArgs) => void | | - | 当某个连接桩渲染完成时触发的回调。 |
|
||||
| [onEdgeLabelRendered](/zh/docs/api/graph/view#onedgelabelrendered) | (args: OnEdgeLabelRenderedArgs) => void | | - | 当边的文本标签渲染完成时触发的回调。 |
|
||||
| [createCellView](/zh/docs/api/graph/view#createcellview) | (this: Graph, cell: Cell) => CellView \| null \| undefined | | - | 是自定义元素的视图。 |
|
||||
| 选项 | 类型 | 必选 | 描述 | 默认值 |
|
||||
| ------------------------------------------------------------------------------ | ------------------------------ | :--: | ------------------------------------------------------------------------------------------------- | ------------------------------------------------- |
|
||||
| containers | HTMLElement | ✓ | 画布的容器。 | |
|
||||
| width | number | | 画布宽度,默认使用容器宽度。 | - |
|
||||
| height | number | | 画布高度,默认使用容器高度。 | - |
|
||||
| scaling | { min?: number, max?: number } | | 画布的最小最大缩放级别。 | { min: 0.01, max: 16 } |
|
||||
| [autoResize](/zh/docs/tutorial/basic/graph#画布大小) | boolean \| Element \| Document | | 是否监听容器大小改变,并自动更新画布大小。 | `false` |
|
||||
| [panning](/zh/docs/api/graph/panning) | boolean \| `PanningManager.Options` | | 画布是否可以拖拽平移,默认禁用。 | `false` |
|
||||
| [mousewheel](/zh/docs/api/graph/mousewheel) | boolean \| `MouseWheel.Options` | | 鼠标滚轮缩放,默认禁用。 | `false` |
|
||||
| [grid](/zh/docs/api/graph/grid) | boolean \| number \| `GridManager.Options` | | 网格,默认使用 `10px` 的网格,但不绘制网格背景。 | `false` |
|
||||
| [background](/zh/docs/api/graph/background) | false \| `BackgroundManager.Options` | | 背景,默认不绘制背景。 | `false` |
|
||||
| [translating](/zh/docs/api/interacting/interaction#trasnlating) | `Translating.Options` | | 限制节点移动。 | { restrict: false } |
|
||||
| [embedding](/zh/docs/api/interacting/interaction#embedding) | boolean \| `Embedding.Options` | | 嵌套节点,默认禁用。 | `false` |
|
||||
| [connecting](/zh/docs/api/interacting/interaction#connecting) | `Connecting.Options` | | 连线选项。 | { snap: false, ... } |
|
||||
| [highlighting](/zh/docs/api/interacting/interaction#highlighting) | `Highlighting.Options` | | 高亮选项。 | {...} |
|
||||
| [interacting](/zh/docs/api/interacting/interaction#interacting) | `Interacting.Options` | | 定制节点和边的交互行为。 | { edgeLabelMovable: false } |
|
||||
| [magnetThreshold](/zh/docs/api/graph/view#magnetthreshold) | number \| 'onleave' | | 鼠标移动多少次后才触发连线,或者设置为 `'onleave'` 时表示鼠标移出元素时才触发连线。 | `0` |
|
||||
| [moveThreshold](/zh/docs/api/graph/view#movethreshold) | number | | 触发 `'mousemove'` 事件之前,允许鼠标移动的次数。 | `0` |
|
||||
| [clickThreshold](/zh/docs/api/graph/view#clickthreshold) | number | | 当鼠标移动次数超过指定的数字时,将不触发鼠标点击事件。 | `0` |
|
||||
| [preventDefaultContextMenu](/zh/docs/api/graph/view#preventdefaultcontextmenu) | boolean | | 是否禁用浏览器默认右键菜单。 | `true` |
|
||||
| [preventDefaultBlankAction](/zh/docs/api/graph/view#preventdefaultblankaction) | boolean | | 在画布空白位置响应鼠标事件时,是否禁用鼠标默认行为。 | `true` |
|
||||
| [async](/zh/docs/api/graph/view#async) | boolean | | 是否异步渲染 | `true` |
|
||||
| [virtual](/zh/docs/api/graph/view#virtual) | boolean | | 是否只渲染可视区域内容 | `false` |
|
||||
| [onPortRendered](/zh/docs/api/graph/view#onportrendered) | (args: OnPortRenderedArgs) => void | | 当某个连接桩渲染完成时触发的回调。 | - |
|
||||
| [onEdgeLabelRendered](/zh/docs/api/graph/view#onedgelabelrendered) | (args: OnEdgeLabelRenderedArgs) => void | | 当边的文本标签渲染完成时触发的回调。 | - |
|
||||
| [createCellView](/zh/docs/api/graph/view#createcellview) | (this: Graph, cell: Cell) => CellView \| null \| undefined | | 是自定义元素的视图。 | - |
|
||||
|
@ -221,11 +221,11 @@ graph.addEdge({
|
||||
|
||||
支持的参数如下表:
|
||||
|
||||
| 参数名 | 参数类型 | 是否必选 | 默认值 | 参数说明 |
|
||||
| --------- | ------------------ | :------: | ------ | -------------------------------------------------------------------------------------------------- | --- | ---- | --- | --------- | ------------------------------------ |
|
||||
| offset | number \| 'center' | 否 | `32` | 路由的第一个点和最后一个点与节点之间的距离。当取值为 `'center'` 时,节点距离的中心作为路由点坐标。 |
|
||||
| min | number | 否 | `16` | 路由的第一个点和最后一个点与节点之间的最小距离。 |
|
||||
| direction | `'T' | 'B' | 'L' | 'R' | 'H' | 'V'` | 否 | undefined | 路由方向,缺省时将自动选择最优方向。 |
|
||||
| 参数名 | 参数类型 | 是否必选 | 默认值 | 参数说明 |
|
||||
| --------- | ---------------------------------------- | -------- | --------- | ------------------------------------------------------------ |
|
||||
| offset | number \|'center' | 否 | `32` | 路由的第一个点和最后一个点与节点之间的距离。当取值为 `'center'` 时,节点距离的中心作为路由点坐标。 |
|
||||
| min | number | 否 | `16` | 路由的第一个点和最后一个点与节点之间的最小距离。 |
|
||||
| direction | `'T'`\|`'B'`\|`'L'`\|`'R'`\|`'H'`\|`'V'` | 否 | undefined | 路由方向,缺省时将自动选择最优方向。 |
|
||||
|
||||
例如:
|
||||
|
||||
|
@ -11,9 +11,9 @@ X6 是基于 HTML 和 SVG 的图编辑引擎,提供低成本的定制能力和
|
||||
如果你还没有使用过 X6, 建议通过 [快速上手](/zh/docs/tutorial/getting-started) 抢先体验 X6 的魅力。
|
||||
|
||||
<p align="left">
|
||||
<a href="https://github.com/antvis/X6/actions/workflows/ci.yml"><img alt="build" src="https://img.shields.io/github/workflow/status/antvis/x6/%F0%9F%91%B7%E3%80%80CI/master?logo=github&style=flat-square"></a>
|
||||
<a href="https://app.codecov.io/gh/antvis/X6"><img alt="coverage" src="https://img.shields.io/codecov/c/gh/antvis/x6?logo=codecov&style=flat-square&token=15CO54WYUV"></a>
|
||||
<a href="https://lgtm.com/projects/g/antvis/x6/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/antvis/x6.svg?logo=lgtm&style=flat-square"></a>
|
||||
<a href="https://github.com/antvis/X6/actions/workflows/ci.yml"><img alt="build" src="https://img.shields.io/github/actions/workflow/status/antvis/x6/ci.yml?branch=master&logo=github&style=flat-square"></a>
|
||||
<!-- <a href="https://app.codecov.io/gh/antvis/X6"><img alt="coverage" src="https://img.shields.io/codecov/c/gh/antvis/x6?logo=codecov&style=flat-square&token=15CO54WYUV"></a>
|
||||
<a href="https://lgtm.com/projects/g/antvis/x6/context:javascript"><img alt="Language grade: JavaScript" src="https://img.shields.io/lgtm/grade/javascript/g/antvis/x6.svg?logo=lgtm&style=flat-square"></a> -->
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Package" src="https://img.shields.io/npm/v/@antv/x6.svg?style=flat-square"></a>
|
||||
<a href="https://www.npmjs.com/package/@antv/x6"><img alt="NPM Downloads" src="https://img.shields.io/npm/dm/@antv/x6?logo=npm&style=flat-square"></a>
|
||||
</p>
|
||||
|
@ -70,11 +70,12 @@ export default () => {
|
||||
|
||||
| 选项 | 类型 | 必选 | 默认值 | 说明 |
|
||||
| -------------------- | ----------------------------------------------------------------------------------- | :--: | ------ | -------------------------------------------------------------------------------------------------------- |
|
||||
| options.target | Graph | ✓️ | | 目标画布。 |
|
||||
| options.getDragNode | (sourceNode: Node, options: GetDragNodeOptions) => Node | | | 拖拽开始时,获取被拖拽的节点,默认克隆 `dnd.start` 传入的节点。 |
|
||||
| options.getDropNode | (draggingNode: Node, options: GetDropNodeOptions) => Node | | | 拖拽结束时,获取放置到目标画布的节点,默认克隆被拖拽的节点。 |
|
||||
| options.validateNode | (droppingNode: Node, options: ValidateNodeOptions) => boolean \| Promins\<boolean\> | | | 拖拽结束时,验证节点是否可以放置到目标画布中。 |
|
||||
| options.dndContainer | HTMLElement | | | 如果设置 `dndContainer`,在 `dndContainer` 上放开鼠标不会放置节点,常用于 `dnd` 容器处于画布上面的场景。 |
|
||||
| target | Graph | ✓️ | | 目标画布。 |
|
||||
| getDragNode | (sourceNode: Node, options: GetDragNodeOptions) => Node | | | 拖拽开始时,获取被拖拽的节点,默认克隆 `dnd.start` 传入的节点。 |
|
||||
| getDropNode | (draggingNode: Node, options: GetDropNodeOptions) => Node | | | 拖拽结束时,获取放置到目标画布的节点,默认克隆被拖拽的节点。 |
|
||||
| validateNode | (droppingNode: Node, options: ValidateNodeOptions) => boolean \| Promins\<boolean\> | | | 拖拽结束时,验证节点是否可以放置到目标画布中。 |
|
||||
| dndContainer | HTMLElement | | | 如果设置 `dndContainer`,在 `dndContainer` 上放开鼠标不会放置节点,常用于 `dnd` 容器处于画布上面的场景。 |
|
||||
| draggingContainer | HTMLElement | | `document.body` | 自定义拖拽画布容器。 |
|
||||
|
||||
## 常见问题
|
||||
|
||||
|
@ -0,0 +1,869 @@
|
||||
import React from 'react'
|
||||
import { Graph, Node, Path, Edge, Platform, StringExt } from '@antv/x6'
|
||||
import { Selection } from '@antv/x6-plugin-selection'
|
||||
import classnames from 'classnames'
|
||||
import insertCss from 'insert-css'
|
||||
import { register } from '@antv/x6-react-shape'
|
||||
import { Tooltip, Dropdown } from 'antd'
|
||||
|
||||
// 节点类型
|
||||
enum NodeType {
|
||||
INPUT = 'INPUT', // 数据输入
|
||||
FILTER = 'FILTER', // 数据过滤
|
||||
JOIN = 'JOIN', // 数据连接
|
||||
UNION = 'UNION', // 数据合并
|
||||
AGG = 'AGG', // 数据聚合
|
||||
OUTPUT = 'OUTPUT', // 数据输出
|
||||
}
|
||||
|
||||
// 元素校验状态
|
||||
enum CellStatus {
|
||||
DEFAULT = 'default',
|
||||
SUCCESS = 'success',
|
||||
ERROR = 'error',
|
||||
}
|
||||
|
||||
// 节点位置信息
|
||||
interface Position {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
// 加工类型列表
|
||||
const PROCESSING_TYPE_LIST = [
|
||||
{
|
||||
type: 'FILTER',
|
||||
name: '数据筛选',
|
||||
},
|
||||
{
|
||||
type: 'JOIN',
|
||||
name: '数据连接',
|
||||
},
|
||||
{
|
||||
type: 'UNION',
|
||||
name: '数据合并',
|
||||
},
|
||||
{
|
||||
type: 'AGG',
|
||||
name: '数据聚合',
|
||||
},
|
||||
|
||||
{
|
||||
type: 'OUTPUT',
|
||||
name: '数据输出',
|
||||
},
|
||||
]
|
||||
|
||||
// 不同节点类型的icon
|
||||
const NODE_TYPE_LOGO = {
|
||||
INPUT:
|
||||
'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*RXnuTpQ22xkAAAAAAAAAAAAADtOHAQ/original', // 数据输入
|
||||
FILTER:
|
||||
'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*ZJ6qToit8P4AAAAAAAAAAAAADtOHAQ/original', // 数据筛选
|
||||
JOIN: 'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*EHqyQoDeBvIAAAAAAAAAAAAADtOHAQ/original', // 数据连接
|
||||
UNION:
|
||||
'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*k4eyRaXv8gsAAAAAAAAAAAAADtOHAQ/original', // 数据合并
|
||||
AGG: 'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*TKG8R6nfYiAAAAAAAAAAAAAADtOHAQ/original', // 数据聚合
|
||||
OUTPUT:
|
||||
'https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*zUgORbGg1HIAAAAAAAAAAAAADtOHAQ/original', // 数据输出
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据起点初始下游节点的位置信息
|
||||
* @param node 起始节点
|
||||
* @param graph
|
||||
* @returns
|
||||
*/
|
||||
const getDownstreamNodePosition = (
|
||||
node: Node,
|
||||
graph: Graph,
|
||||
dx = 250,
|
||||
dy = 100,
|
||||
) => {
|
||||
// 找出画布中以该起始节点为起点的相关边的终点id集合
|
||||
const downstreamNodeIdList: string[] = []
|
||||
graph.getEdges().forEach((edge) => {
|
||||
const originEdge = edge.toJSON()?.data
|
||||
if (originEdge.source === node.id) {
|
||||
downstreamNodeIdList.push(originEdge.target)
|
||||
}
|
||||
})
|
||||
// 获取起点的位置信息
|
||||
const position = node.getPosition()
|
||||
let minX = Infinity
|
||||
let maxY = -Infinity
|
||||
graph.getNodes().forEach((graphNode) => {
|
||||
if (downstreamNodeIdList.indexOf(graphNode.id) > -1) {
|
||||
const nodePosition = graphNode.getPosition()
|
||||
// 找到所有节点中最左侧的节点的x坐标
|
||||
if (nodePosition.x < minX) {
|
||||
minX = nodePosition.x
|
||||
}
|
||||
// 找到所有节点中最x下方的节点的y坐标
|
||||
if (nodePosition.y > maxY) {
|
||||
maxY = nodePosition.y
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
x: minX !== Infinity ? minX : position.x + dx,
|
||||
y: maxY !== -Infinity ? maxY + dy : position.y,
|
||||
}
|
||||
}
|
||||
|
||||
// 根据节点的类型获取ports
|
||||
const getPortsByType = (type: NodeType, nodeId: string) => {
|
||||
let ports = []
|
||||
switch (type) {
|
||||
case NodeType.INPUT:
|
||||
ports = [
|
||||
{
|
||||
id: `${nodeId}-out`,
|
||||
group: 'out',
|
||||
},
|
||||
]
|
||||
break
|
||||
case NodeType.OUTPUT:
|
||||
ports = [
|
||||
{
|
||||
id: `${nodeId}-in`,
|
||||
group: 'in',
|
||||
},
|
||||
]
|
||||
break
|
||||
default:
|
||||
ports = [
|
||||
{
|
||||
id: `${nodeId}-in`,
|
||||
group: 'in',
|
||||
},
|
||||
{
|
||||
id: `${nodeId}-out`,
|
||||
group: 'out',
|
||||
},
|
||||
]
|
||||
break
|
||||
}
|
||||
return ports
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建节点并添加到画布
|
||||
* @param type 节点类型
|
||||
* @param graph
|
||||
* @param position 节点位置
|
||||
* @returns
|
||||
*/
|
||||
export const createNode = (
|
||||
type: NodeType,
|
||||
graph: Graph,
|
||||
position?: Position,
|
||||
) => {
|
||||
if (!graph) {
|
||||
return {}
|
||||
}
|
||||
let newNode = {}
|
||||
const sameTypeNodes = graph
|
||||
.getNodes()
|
||||
.filter((item) => item.getData()?.type === type)
|
||||
const typeName = PROCESSING_TYPE_LIST?.find(
|
||||
(item) => item.type === type,
|
||||
)?.name
|
||||
const id = StringExt.uuid()
|
||||
const node = {
|
||||
id,
|
||||
shape: 'data-processing-dag-node',
|
||||
x: position?.x,
|
||||
y: position?.y,
|
||||
ports: getPortsByType(type, id),
|
||||
data: {
|
||||
name: `${typeName}_${sameTypeNodes.length + 1}`,
|
||||
type,
|
||||
},
|
||||
}
|
||||
newNode = graph.addNode(node)
|
||||
return newNode
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建边并添加到画布
|
||||
* @param source
|
||||
* @param target
|
||||
* @param graph
|
||||
*/
|
||||
const createEdge = (source: string, target: string, graph: Graph) => {
|
||||
const edge = {
|
||||
id: StringExt.uuid(),
|
||||
shape: 'data-processing-curve',
|
||||
source: {
|
||||
cell: source,
|
||||
port: `${source}-out`,
|
||||
},
|
||||
target: {
|
||||
cell: target,
|
||||
port: `${target}-in`,
|
||||
},
|
||||
zIndex: -1,
|
||||
data: {
|
||||
source,
|
||||
target,
|
||||
},
|
||||
}
|
||||
if (graph) {
|
||||
graph.addEdge(edge)
|
||||
}
|
||||
}
|
||||
|
||||
class DataProcessingDagNode extends React.Component<{
|
||||
node: Node
|
||||
}> {
|
||||
state = {
|
||||
plusActionSelected: false,
|
||||
}
|
||||
|
||||
// 创建下游的节点和边
|
||||
createDownstream = (type: NodeType) => {
|
||||
const { node } = this.props
|
||||
const { graph } = node.model || {}
|
||||
if (graph) {
|
||||
// 获取下游节点的初始位置信息
|
||||
const position = getDownstreamNodePosition(node, graph)
|
||||
// 创建下游节点
|
||||
const newNode = createNode(type, graph, position)
|
||||
const source = node.id
|
||||
const target = newNode.id
|
||||
// 创建该节点出发到下游节点的边
|
||||
createEdge(source, target, graph)
|
||||
}
|
||||
}
|
||||
|
||||
// 点击添加下游+号
|
||||
clickPlusDragMenu = (type: NodeType) => {
|
||||
this.createDownstream(type)
|
||||
this.setState({
|
||||
plusActionSelected: false,
|
||||
})
|
||||
}
|
||||
|
||||
// 获取+号下拉菜单
|
||||
getPlusDagMenu = () => {
|
||||
return (
|
||||
<ul>
|
||||
{PROCESSING_TYPE_LIST.map((item) => {
|
||||
const content = (
|
||||
// eslint-disable-next-line
|
||||
<a onClick={() => this.clickPlusDragMenu(item.type)}>
|
||||
<i
|
||||
className="node-mini-logo"
|
||||
style={{ backgroundImage: `url(${NODE_TYPE_LOGO[item.type]})` }}
|
||||
/>
|
||||
|
||||
<span>{item.name}</span>
|
||||
</a>
|
||||
)
|
||||
return (
|
||||
<li className="each-sub-menu" key={item.type}>
|
||||
{content}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
// 添加下游菜单的打开状态变化
|
||||
onPlusDropdownOpenChange = (value: boolean) => {
|
||||
this.setState({
|
||||
plusActionSelected: value,
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标进入矩形主区域的时候显示连接桩
|
||||
onMainMouseEnter = () => {
|
||||
const { node } = this.props
|
||||
// 获取该节点下的所有连接桩
|
||||
const ports = node.getPorts() || []
|
||||
ports.forEach((port) => {
|
||||
node.setPortProp(port.id, 'attrs/circle', {
|
||||
fill: '#fff',
|
||||
stroke: '#85A5FF',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 鼠标离开矩形主区域的时候隐藏连接桩
|
||||
onMainMouseLeave = () => {
|
||||
const { node } = this.props
|
||||
// 获取该节点下的所有连接桩
|
||||
const ports = node.getPorts() || []
|
||||
ports.forEach((port) => {
|
||||
node.setPortProp(port.id, 'attrs/circle', {
|
||||
fill: 'transparent',
|
||||
stroke: 'transparent',
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
const { plusActionSelected } = this.state
|
||||
const { node } = this.props
|
||||
const data = node?.getData()
|
||||
const { name, type, status, statusMsg } = data
|
||||
|
||||
return (
|
||||
<div className="data-processing-dag-node">
|
||||
<div
|
||||
className="main-area"
|
||||
onMouseEnter={this.onMainMouseEnter}
|
||||
onMouseLeave={this.onMainMouseLeave}
|
||||
>
|
||||
<div className="main-info">
|
||||
{/* 节点类型icon */}
|
||||
<i
|
||||
className="node-logo"
|
||||
style={{ backgroundImage: `url(${NODE_TYPE_LOGO[type]})` }}
|
||||
/>
|
||||
<Tooltip title={name} mouseEnterDelay={0.8}>
|
||||
<div className="ellipsis-row node-name">{name}</div>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
{/* 节点状态信息 */}
|
||||
<div className="status-action">
|
||||
{status === CellStatus.ERROR && (
|
||||
<Tooltip title={statusMsg}>
|
||||
<i className="status-icon status-icon-error" />
|
||||
</Tooltip>
|
||||
)}
|
||||
{status === CellStatus.SUCCESS && (
|
||||
<i className="status-icon status-icon-success" />
|
||||
)}
|
||||
|
||||
{/* 节点操作菜单 */}
|
||||
<div className="more-action-container">
|
||||
<i className="more-action" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 添加下游节点 */}
|
||||
{type !== NodeType.OUTPUT && (
|
||||
<div className="plus-dag">
|
||||
<Dropdown
|
||||
dropdownRender={this.getPlusDagMenu}
|
||||
overlayClassName="processing-node-menu"
|
||||
trigger={['click']}
|
||||
placement="bottom"
|
||||
open={plusActionSelected}
|
||||
onOpenChange={this.onPlusDropdownOpenChange}
|
||||
>
|
||||
<i
|
||||
className={classnames('plus-action', {
|
||||
'plus-action-selected': plusActionSelected,
|
||||
})}
|
||||
/>
|
||||
</Dropdown>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
register({
|
||||
shape: 'data-processing-dag-node',
|
||||
width: 212,
|
||||
height: 48,
|
||||
component: DataProcessingDagNode,
|
||||
// port默认不可见
|
||||
ports: {
|
||||
groups: {
|
||||
in: {
|
||||
position: 'left',
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 4,
|
||||
magnet: true,
|
||||
stroke: 'transparent',
|
||||
strokeWidth: 1,
|
||||
fill: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
out: {
|
||||
position: {
|
||||
name: 'right',
|
||||
args: {
|
||||
dx: -32,
|
||||
},
|
||||
},
|
||||
|
||||
attrs: {
|
||||
circle: {
|
||||
r: 4,
|
||||
magnet: true,
|
||||
stroke: 'transparent',
|
||||
strokeWidth: 1,
|
||||
fill: 'transparent',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// 注册连线
|
||||
Graph.registerConnector(
|
||||
'curveConnector',
|
||||
(sourcePoint, targetPoint) => {
|
||||
const hgap = Math.abs(targetPoint.x - sourcePoint.x)
|
||||
const path = new Path()
|
||||
path.appendSegment(
|
||||
Path.createSegment('M', sourcePoint.x - 4, sourcePoint.y),
|
||||
)
|
||||
path.appendSegment(
|
||||
Path.createSegment('L', sourcePoint.x + 12, sourcePoint.y),
|
||||
)
|
||||
// 水平三阶贝塞尔曲线
|
||||
path.appendSegment(
|
||||
Path.createSegment(
|
||||
'C',
|
||||
sourcePoint.x < targetPoint.x
|
||||
? sourcePoint.x + hgap / 2
|
||||
: sourcePoint.x - hgap / 2,
|
||||
sourcePoint.y,
|
||||
sourcePoint.x < targetPoint.x
|
||||
? targetPoint.x - hgap / 2
|
||||
: targetPoint.x + hgap / 2,
|
||||
targetPoint.y,
|
||||
targetPoint.x - 6,
|
||||
targetPoint.y,
|
||||
),
|
||||
)
|
||||
path.appendSegment(
|
||||
Path.createSegment('L', targetPoint.x + 2, targetPoint.y),
|
||||
)
|
||||
|
||||
return path.serialize()
|
||||
},
|
||||
true,
|
||||
)
|
||||
|
||||
Edge.config({
|
||||
markup: [
|
||||
{
|
||||
tagName: 'path',
|
||||
selector: 'wrap',
|
||||
attrs: {
|
||||
fill: 'none',
|
||||
cursor: 'pointer',
|
||||
stroke: 'transparent',
|
||||
strokeLinecap: 'round',
|
||||
},
|
||||
},
|
||||
{
|
||||
tagName: 'path',
|
||||
selector: 'line',
|
||||
attrs: {
|
||||
fill: 'none',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
],
|
||||
connector: { name: 'curveConnector' },
|
||||
attrs: {
|
||||
wrap: {
|
||||
connection: true,
|
||||
strokeWidth: 10,
|
||||
strokeLinejoin: 'round',
|
||||
},
|
||||
line: {
|
||||
connection: true,
|
||||
stroke: '#A2B1C3',
|
||||
strokeWidth: 1,
|
||||
targetMarker: {
|
||||
name: 'classic',
|
||||
size: 6,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
Graph.registerEdge('data-processing-curve', Edge, true)
|
||||
|
||||
const graph: Graph = new Graph({
|
||||
container: document.getElementById('container')!,
|
||||
panning: {
|
||||
enabled: true,
|
||||
eventTypes: ['leftMouseDown', 'mouseWheel'],
|
||||
},
|
||||
mousewheel: {
|
||||
enabled: true,
|
||||
modifiers: 'ctrl',
|
||||
factor: 1.1,
|
||||
maxScale: 1.5,
|
||||
minScale: 0.5,
|
||||
},
|
||||
highlighting: {
|
||||
magnetAdsorbed: {
|
||||
name: 'stroke',
|
||||
args: {
|
||||
attrs: {
|
||||
fill: '#fff',
|
||||
stroke: '#31d0c6',
|
||||
strokeWidth: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
connecting: {
|
||||
snap: true,
|
||||
allowBlank: false,
|
||||
allowLoop: false,
|
||||
highlight: true,
|
||||
sourceAnchor: {
|
||||
name: 'left',
|
||||
args: {
|
||||
dx: Platform.IS_SAFARI ? 4 : 8,
|
||||
},
|
||||
},
|
||||
targetAnchor: {
|
||||
name: 'right',
|
||||
args: {
|
||||
dx: Platform.IS_SAFARI ? 4 : -8,
|
||||
},
|
||||
},
|
||||
createEdge() {
|
||||
return graph.createEdge({
|
||||
shape: 'data-processing-curve',
|
||||
attrs: {
|
||||
line: {
|
||||
strokeDasharray: '5 5',
|
||||
},
|
||||
},
|
||||
zIndex: -1,
|
||||
})
|
||||
},
|
||||
// 连接桩校验
|
||||
validateConnection({ sourceMagnet, targetMagnet }) {
|
||||
// 只能从输出链接桩创建连接
|
||||
if (!sourceMagnet || sourceMagnet.getAttribute('port-group') === 'in') {
|
||||
return false
|
||||
}
|
||||
// 只能连接到输入链接桩
|
||||
if (!targetMagnet || targetMagnet.getAttribute('port-group') !== 'in') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
graph.use(
|
||||
new Selection({
|
||||
enabled: true,
|
||||
multiple: true,
|
||||
rubberEdge: true,
|
||||
rubberNode: true,
|
||||
modifiers: 'shift',
|
||||
rubberband: true,
|
||||
}),
|
||||
)
|
||||
|
||||
// 节点状态列表
|
||||
const nodeStatusList = [
|
||||
{
|
||||
id: 'node-0',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'node-1',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'node-2',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'node-3',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'node-4',
|
||||
status: 'error',
|
||||
statusMsg: '错误信息示例',
|
||||
},
|
||||
]
|
||||
|
||||
// 边状态列表
|
||||
const edgeStatusList = [
|
||||
{
|
||||
id: 'edge-0',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'edge-1',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'edge-2',
|
||||
status: 'success',
|
||||
},
|
||||
{
|
||||
id: 'edge-3',
|
||||
status: 'success',
|
||||
},
|
||||
]
|
||||
|
||||
// 显示节点状态
|
||||
const showNodeStatus = () => {
|
||||
nodeStatusList.forEach((item) => {
|
||||
const { id, status, statusMsg } = item
|
||||
const node = graph.getCellById(id)
|
||||
const data = node.getData() as CellStatus
|
||||
node.setData({
|
||||
...data,
|
||||
status,
|
||||
statusMsg,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 开启边的运行动画
|
||||
const excuteAnimate = () => {
|
||||
graph.getEdges().forEach((edge) => {
|
||||
edge.attr({
|
||||
line: {
|
||||
stroke: '#3471F9',
|
||||
},
|
||||
})
|
||||
edge.attr('line/strokeDasharray', 5)
|
||||
edge.attr('line/style/animation', 'running-line 30s infinite linear')
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭边的动画
|
||||
const stopAnimate = () => {
|
||||
graph.getEdges().forEach((edge) => {
|
||||
edge.attr('line/strokeDasharray', 0)
|
||||
edge.attr('line/style/animation', '')
|
||||
})
|
||||
edgeStatusList.forEach((item) => {
|
||||
const { id, status } = item
|
||||
const edge = graph.getCellById(id)
|
||||
if (status === 'success') {
|
||||
edge.attr('line/stroke', '#52c41a')
|
||||
}
|
||||
if (status === 'error') {
|
||||
edge.attr('line/stroke', '#ff4d4f')
|
||||
}
|
||||
})
|
||||
// 默认选中一个节点
|
||||
graph.select('node-2')
|
||||
}
|
||||
|
||||
fetch('/data/data-processing-dag.json')
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
graph.fromJSON(data)
|
||||
const zoomOptions = {
|
||||
padding: {
|
||||
left: 10,
|
||||
right: 10,
|
||||
},
|
||||
}
|
||||
graph.zoomToFit(zoomOptions)
|
||||
setTimeout(() => {
|
||||
excuteAnimate()
|
||||
}, 2000)
|
||||
setTimeout(() => {
|
||||
showNodeStatus()
|
||||
stopAnimate()
|
||||
}, 3000)
|
||||
})
|
||||
|
||||
insertCss(`
|
||||
.data-processing-dag-node {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.main-area {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 12px;
|
||||
width: 180px;
|
||||
height: 48px;
|
||||
color: rgba(0, 0, 0, 65%);
|
||||
font-size: 12px;
|
||||
font-family: PingFangSC;
|
||||
line-height: 24px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 -1px 4px 0 rgba(209, 209, 209, 50%), 1px 1px 4px 0 rgba(217, 217, 217, 50%);
|
||||
border-radius: 2px;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.main-area:hover {
|
||||
border: 1px solid rgba(0, 0, 0, 10%);
|
||||
box-shadow: 0 -2px 4px 0 rgba(209, 209, 209, 50%), 2px 2px 4px 0 rgba(217, 217, 217, 50%);
|
||||
}
|
||||
|
||||
.node-logo {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
width: 70px;
|
||||
margin-left: 6px;
|
||||
color: rgba(0, 0, 0, 65%);
|
||||
font-size: 12px;
|
||||
font-family: PingFangSC;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.status-action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.status-icon {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.status-icon-error {
|
||||
background: url('https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ')
|
||||
no-repeat center center / 100% 100%;
|
||||
}
|
||||
|
||||
.status-icon-success {
|
||||
background: url('https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ')
|
||||
no-repeat center center / 100% 100%;
|
||||
}
|
||||
|
||||
.more-action-container {
|
||||
margin-left: 12px;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.more-action {
|
||||
display: inline-block;
|
||||
width: 3px;
|
||||
height: 15px;
|
||||
background: url('https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*tFw7SIy-ttQAAAAAAAAAAAAADtOHAQ/original')
|
||||
no-repeat center center / 100% 100%;
|
||||
}
|
||||
|
||||
.plus-dag {
|
||||
visibility: hidden;
|
||||
position: relative;
|
||||
margin-left: 12px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.plus-action {
|
||||
position: absolute;
|
||||
top: calc(50% - 8px);
|
||||
left: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: url('https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*ScX2R4ODfokAAAAAAAAAAAAADtOHAQ/original')
|
||||
no-repeat center center / 100% 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
.plus-action:hover {
|
||||
background-image: url('https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*tRaoS5XhsuQAAAAAAAAAAAAADtOHAQ/original');
|
||||
}
|
||||
|
||||
.plus-action:active,
|
||||
.plus-action-selected {
|
||||
background-image: url('https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*k9cnSaSmlw4AAAAAAAAAAAAADtOHAQ/original');
|
||||
}
|
||||
|
||||
.x6-node-selected .main-area {
|
||||
border-color: #3471f9;
|
||||
}
|
||||
|
||||
.x6-node-selected .plus-dag {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.processing-node-menu {
|
||||
padding: 2px 0;
|
||||
width: 105px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 9px 28px 8px rgba(0, 0, 0, 5%), 0 6px 16px 0 rgba(0, 0, 0, 8%),
|
||||
0 3px 6px -4px rgba(0, 0, 0, 12%);
|
||||
border-radius: 2px;
|
||||
}
|
||||
.processing-node-menu ul {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.processing-node-menu li {
|
||||
list-style:none;
|
||||
}
|
||||
|
||||
.each-sub-menu {
|
||||
padding: 6px 12px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.each-sub-menu:hover {
|
||||
background-color: rgba(0, 0, 0, 4%);
|
||||
}
|
||||
|
||||
.each-sub-menu a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 16px;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 12px;
|
||||
color: rgba(0, 0, 0, 65%);
|
||||
}
|
||||
|
||||
.each-sub-menu span {
|
||||
margin-left: 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.each-disabled-sub-menu a {
|
||||
cursor: not-allowed;
|
||||
color: rgba(0, 0, 0, 35%);
|
||||
}
|
||||
|
||||
.node-mini-logo {
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
@keyframes running-line {
|
||||
to {
|
||||
stroke-dashoffset: -1000;
|
||||
}
|
||||
}
|
||||
`)
|
@ -16,6 +16,14 @@
|
||||
},
|
||||
"screenshot": "https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*RPiGRaSus3UAAAAAAAAAAAAAARQnAQ"
|
||||
},
|
||||
{
|
||||
"filename": "dataProcessingDag.tsx",
|
||||
"title": {
|
||||
"zh": "数据加工 DAG 图",
|
||||
"en": "DAG for Data Processing"
|
||||
},
|
||||
"screenshot": "https://mdn.alipayobjects.com/huamei_f4t1bn/afts/img/A*yRLDQ5KgSO4AAAAAAAAAAAAADtOHAQ/original"
|
||||
},
|
||||
{
|
||||
"filename": "er.ts",
|
||||
"title": {
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@antv/x6-sites",
|
||||
"version": "1.2.0",
|
||||
"version": "2.0.0",
|
||||
"description": "X6 sites deployed on gh-pages",
|
||||
"scripts": {
|
||||
"dev": "dumi dev",
|
||||
@ -33,11 +33,13 @@
|
||||
"@antv/x6-react-shape": "^2.x",
|
||||
"antd": "^4.4.2",
|
||||
"dagre": "^0.8.5",
|
||||
"dumi": "^2.x",
|
||||
"dumi": "2.0.16",
|
||||
"elkjs": "^0.8.2",
|
||||
"highlight.js": "^10.1.2",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0",
|
||||
"react-i18next": "^11.5.0"
|
||||
}
|
||||
"react-i18next": "^11.5.0",
|
||||
"classnames": "^2.2.6"
|
||||
},
|
||||
"repository": "https://github.com/antvis/x6"
|
||||
}
|
||||
|
163
sites/x6-sites/public/data/data-processing-dag.json
Normal file
163
sites/x6-sites/public/data/data-processing-dag.json
Normal file
@ -0,0 +1,163 @@
|
||||
{
|
||||
"nodes": [
|
||||
{
|
||||
"id": "node-0",
|
||||
"shape": "data-processing-dag-node",
|
||||
"x": 0,
|
||||
"y": 100,
|
||||
"ports": [
|
||||
{
|
||||
"id": "node-0-out",
|
||||
"group": "out"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"name": "数据输入_1",
|
||||
"type": "INPUT",
|
||||
"checkStatus": "sucess"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-1",
|
||||
"shape": "data-processing-dag-node",
|
||||
"x": 250,
|
||||
"y": 100,
|
||||
"ports": [
|
||||
{
|
||||
"id": "node-1-in",
|
||||
"group": "in"
|
||||
},
|
||||
{
|
||||
"id": "node-1-out",
|
||||
"group": "out"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"name": "数据筛选_1",
|
||||
"type": "FILTER"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-2",
|
||||
"shape": "data-processing-dag-node",
|
||||
"x": 250,
|
||||
"y": 200,
|
||||
"ports": [
|
||||
{
|
||||
"id": "node-2-out",
|
||||
"group": "out"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"name": "数据输入_2",
|
||||
"type": "INPUT"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-3",
|
||||
"shape": "data-processing-dag-node",
|
||||
"x": 500,
|
||||
"y": 100,
|
||||
"ports": [
|
||||
{
|
||||
"id": "node-3-in",
|
||||
"group": "in"
|
||||
},
|
||||
{
|
||||
"id": "node-3-out",
|
||||
"group": "out"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"name": "数据连接_1",
|
||||
"type": "JOIN"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "node-4",
|
||||
"shape": "data-processing-dag-node",
|
||||
"x": 750,
|
||||
"y": 100,
|
||||
"ports": [
|
||||
{
|
||||
"id": "node-4-in",
|
||||
"group": "in"
|
||||
}
|
||||
],
|
||||
"data": {
|
||||
"name": "数据输出_1",
|
||||
"type": "OUTPUT"
|
||||
}
|
||||
}
|
||||
],
|
||||
"edges": [
|
||||
{
|
||||
"id": "edge-0",
|
||||
"source": {
|
||||
"cell": "node-0",
|
||||
"port": "node-0-out"
|
||||
},
|
||||
"target": {
|
||||
"cell": "node-1",
|
||||
"port": "node-1-in"
|
||||
},
|
||||
"shape": "data-processing-curve",
|
||||
"zIndex": -1,
|
||||
"data": {
|
||||
"source": "node-0",
|
||||
"target": "node-1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "edge-1",
|
||||
"source": {
|
||||
"cell": "node-2",
|
||||
"port": "node-2-out"
|
||||
},
|
||||
"target": {
|
||||
"cell": "node-3",
|
||||
"port": "node-3-in"
|
||||
},
|
||||
"shape": "data-processing-curve",
|
||||
"zIndex": -1,
|
||||
"data": {
|
||||
"source": "node-2",
|
||||
"target": "node-3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "edge-2",
|
||||
"source": {
|
||||
"cell": "node-1",
|
||||
"port": "node-1-out"
|
||||
},
|
||||
"target": {
|
||||
"cell": "node-3",
|
||||
"port": "node-3-in"
|
||||
},
|
||||
"shape": "data-processing-curve",
|
||||
"zIndex": -1,
|
||||
"data": {
|
||||
"source": "node-1",
|
||||
"target": "node-3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "edge-3",
|
||||
"source": {
|
||||
"cell": "node-3",
|
||||
"port": "node-3-out"
|
||||
},
|
||||
"target": {
|
||||
"cell": "node-4",
|
||||
"port": "node-4-in"
|
||||
},
|
||||
"shape": "data-processing-curve",
|
||||
"zIndex": -1,
|
||||
"data": {
|
||||
"source": "node-3",
|
||||
"target": "node-4"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
Reference in New Issue
Block a user