Compare commits

..

7 Commits

Author SHA1 Message Date
b041423f06 chore(release): 🚀 publish 2022-10-31 16:47:34 +08:00
39279072c3 chore: 🔧 optimize examples 2022-10-31 16:06:50 +08:00
8d7550413f feat: add html shape 2022-10-30 21:31:01 +08:00
0e39d9447b fix: 🐛 change jobqueue to transient 2022-10-30 19:18:09 +08:00
223a634b83 chore: 🔧 optimize example 2022-10-30 18:48:00 +08:00
269fae9e5e feat: add dnd plugin 2022-10-30 10:01:00 +08:00
8107f6df5d feat: add clipboard plugin 2022-10-29 14:46:12 +08:00
125 changed files with 3226 additions and 9584 deletions

View File

@ -51,10 +51,11 @@ export class AlgoNode extends React.Component<{ node?: Node }> {
}
}
register(AlgoNode, {
register({
shape: 'dag-node',
width: 180,
height: 36,
component: AlgoNode,
ports: {
groups: {
top: {

View File

@ -1,11 +1,16 @@
import React from 'react'
import { Button } from 'antd'
import { Graph } from '@antv/x6'
import { Clipboard } from '@antv/x6-plugin-clipboard'
import { Selection } from '@antv/x6-plugin-selection'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
private graph: Graph
private selection: Selection
private clipboard: Clipboard
componentDidMount() {
const graph = new Graph({
@ -13,14 +18,21 @@ export default class Example extends React.Component {
width: 800,
height: 600,
grid: true,
selecting: {
enabled: true,
},
clipboard: {
enabled: true,
useLocalStorage: true,
},
})
const clipboard = new Clipboard({
enabled: true,
useLocalStorage: true,
})
const selection = new Selection({
enabled: true,
})
const keyboard = new Keyboard({
enabled: true,
})
graph.use(clipboard)
graph.use(selection)
graph.use(keyboard)
graph.addNode({
x: 50,
@ -46,24 +58,19 @@ export default class Example extends React.Component {
attrs: { label: { text: 'C' } },
})
graph.bindKey('meta+c', () => {
const cells = graph.getSelectedCells()
if (cells.length) {
graph.copy(cells)
}
return false
keyboard.bindKey('meta+c', (e) => {
e.preventDefault()
this.onCopy()
})
graph.bindKey('meta+v', () => {
if (!graph.isClipboardEmpty()) {
const cells = graph.paste({ offset: 32 })
graph.cleanSelection()
graph.select(cells)
}
return false
keyboard.bindKey('meta+v', (e) => {
e.preventDefault()
this.onPaste()
})
this.graph = graph
this.selection = selection
this.clipboard = clipboard
}
refContainer = (container: HTMLDivElement) => {
@ -71,18 +78,15 @@ export default class Example extends React.Component {
}
onCopy = () => {
const cells = this.graph.getSelectedCells()
const cells = this.selection.getSelectedCells()
if (cells && cells.length) {
console.log(cells)
this.graph.copy(cells)
this.clipboard.copy(cells)
}
}
onPaste = () => {
if (!this.graph.isClipboardEmpty()) {
this.graph.paste()
} else {
console.log('empty')
if (!this.clipboard.isClipboardEmpty()) {
this.clipboard.paste()
}
}

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, Line, Edge } from '@antv/x6'
import { Graph, Edge } from '@antv/x6'
import { Line } from '@antv/x6-geometry'
import '../index.less'
export default class Example extends React.Component {

View File

@ -1,18 +1,19 @@
import React from 'react'
import { Graph, Point, Path } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Point, Path } from '@antv/x6-geometry'
import '../index.less'
export interface ErRoundedArgs {
export interface OffsetRoundedArgs {
raw?: boolean
radius?: number
offset?: number
}
function erRounded(
function offsetRounded(
sourcePoint: Point.PointLike,
targetPoint: Point.PointLike,
routePoints: Point.PointLike[],
args: ErRoundedArgs,
args: OffsetRoundedArgs,
) {
const path = new Path()
@ -75,7 +76,7 @@ function erRounded(
return args.raw ? path : path.serialize()
}
Graph.registerConnector('erRounded', erRounded, true)
Graph.registerConnector('offsetRounded', offsetRounded, true)
export default class Example extends React.Component {
private container: HTMLDivElement
@ -113,7 +114,7 @@ export default class Example extends React.Component {
source,
target,
connector: {
name: 'erRounded',
name: 'offsetRounded',
args: {
radius: 20,
offset: -20,
@ -125,7 +126,7 @@ export default class Example extends React.Component {
source,
target,
connector: {
name: 'erRounded',
name: 'offsetRounded',
args: {
radius: 20,
offset: 0,
@ -137,7 +138,7 @@ export default class Example extends React.Component {
source,
target,
connector: {
name: 'erRounded',
name: 'offsetRounded',
args: {
radius: 20,
offset: 20,

View File

@ -1,238 +1,176 @@
// import React from 'react'
// import { Button } from 'antd'
// import { Graph, Dom } from '@antv/x6'
// import { Dnd } from '@antv/x6/es/addon/dnd'
// import '../index.less'
// import './index.less'
import React from 'react'
import { Graph, Node } from '@antv/x6'
import { Dnd } from '@antv/x6-plugin-dnd'
import '../index.less'
// export default class Example extends React.Component {
// private graph: Graph
// private dnd: Dnd
// private container: HTMLDivElement
export default class Example extends React.Component {
private graph: Graph
private dnd: Dnd
private container: HTMLDivElement
// componentDidMount() {
// const graph = (this.graph = new Graph({
// container: this.container,
// width: 800,
// height: 800,
// history: true,
// snapline: {
// enabled: true,
// sharp: true,
// },
// grid: {
// visible: true,
// },
// scroller: {
// enabled: true,
// width: 600,
// height: 400,
// pageVisible: true,
// pageBreak: false,
// pannable: true,
// },
// embedding: {
// enabled: true,
// findParent({ node }) {
// const bbox = node.getBBox()
// return this.getNodes().filter((parent) => {
// const targetBBox = parent.getBBox()
// return targetBBox.containsRect(bbox)
// })
// },
// },
// }))
componentDidMount() {
const graph = (this.graph = new Graph({
container: this.container,
width: 800,
height: 800,
grid: {
visible: true,
},
embedding: {
enabled: true,
findParent({ node }) {
const bbox = node.getBBox()
return this.getNodes().filter((parent) => {
const targetBBox = parent.getBBox()
return targetBBox.containsRect(bbox)
})
},
},
}))
// const source = graph.addNode({
// x: 130,
// y: 30,
// width: 200,
// height: 80,
// attrs: {
// label: {
// text: 'Hello',
// fill: '#6a6c8a',
// },
// body: {
// stroke: '#31d0c6',
// strokeWidth: 2,
// },
// },
// })
const source = graph.addNode({
x: 130,
y: 30,
width: 200,
height: 80,
attrs: {
label: {
text: 'Hello',
fill: '#6a6c8a',
},
body: {
stroke: '#31d0c6',
strokeWidth: 2,
},
},
})
// const target = graph.addNode({
// x: 320,
// y: 240,
// width: 100,
// height: 40,
// attrs: {
// label: {
// text: 'World',
// fill: '#6a6c8a',
// },
// body: {
// stroke: '#31d0c6',
// strokeWidth: 2,
// },
// },
// })
const target = graph.addNode({
x: 320,
y: 240,
width: 100,
height: 40,
attrs: {
label: {
text: 'World',
fill: '#6a6c8a',
},
body: {
stroke: '#31d0c6',
strokeWidth: 2,
},
},
})
// graph.addEdge({ source, target })
graph.addEdge({ source, target })
graph.centerContent()
// graph.on('node:change:parent', (args) => {
// console.log('node:change:parent', args)
// })
this.dnd = new Dnd({
target: graph,
})
this.graph = graph
}
// graph.on('node:added', (args) => {
// console.log('node:added', args)
// })
refContainer = (container: HTMLDivElement) => {
this.container = container
}
// graph.centerContent()
startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
const target = e.currentTarget
const type = target.getAttribute('data-type')
let node: Node | undefined
if (type === 'rect') {
node = this.graph.createNode({
shape: 'rect',
width: 100,
height: 40,
attrs: {
label: {
text: 'Rect',
fill: '#6a6c8a',
},
body: {
stroke: '#31d0c6',
strokeWidth: 2,
},
},
})
} else if (type === 'circle') {
node = this.graph.createNode({
shape: 'circle',
width: 60,
height: 60,
attrs: {
label: {
text: 'Circle',
fill: '#6a6c8a',
},
body: {
stroke: '#31d0c6',
strokeWidth: 2,
},
},
})
}
// this.dnd = new Dnd({
// target: graph,
// animation: true,
// getDragNode(sourceNode, options) {
// console.log('getDragNode', sourceNode, options)
// return sourceNode.clone()
// },
// getDropNode(draggingNode, options) {
// console.log('getDropNode', draggingNode, options)
// return draggingNode.clone()
// },
// validateNode(droppingNode, options) {
// console.log('validateNode', droppingNode, options)
if (node) {
this.dnd.start(node, e.nativeEvent as any)
}
}
// return droppingNode.shape === 'html'
// ? new Promise<boolean>((resolve) => {
// const { draggingNode, draggingGraph } = options
// const view = draggingGraph.findView(draggingNode)
// const contentElem = view.findOne('foreignObject > body > div')
// Dom.addClass(contentElem, 'validating')
// setTimeout(() => {
// Dom.removeClass(contentElem, 'validating')
// resolve(true)
// }, 3000)
// })
// : true
// },
// })
// this.graph = graph
// }
// onUndo = () => {
// this.graph.undo()
// }
// onRedo = () => {
// this.graph.redo()
// }
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
// startDrag = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
// const target = e.currentTarget
// const type = target.getAttribute('data-type')
// const node =
// type === 'rect'
// ? this.graph.createNode({
// width: 100,
// height: 40,
// attrs: {
// label: {
// text: 'Rect',
// fill: '#6a6c8a',
// },
// body: {
// stroke: '#31d0c6',
// strokeWidth: 2,
// },
// },
// })
// : this.graph.createNode({
// width: 60,
// height: 60,
// shape: 'html',
// html: () => {
// const wrap = document.createElement('div')
// wrap.style.width = '100%'
// wrap.style.height = '100%'
// wrap.style.display = 'flex'
// wrap.style.alignItems = 'center'
// wrap.style.justifyContent = 'center'
// wrap.style.border = '2px solid rgb(49, 208, 198)'
// wrap.style.background = '#fff'
// wrap.style.borderRadius = '100%'
// wrap.innerText = 'Circle'
// return wrap
// },
// })
// this.dnd.start(node, e.nativeEvent as any)
// }
// render() {
// return (
// <div className="x6-graph-wrap">
// <h1>Dnd</h1>
// <div
// style={{
// position: 'absolute',
// left: 32,
// top: 40,
// width: 200,
// height: 300,
// padding: 16,
// border: '1px solid #f0f0f0',
// display: 'flex',
// flexDirection: 'column',
// alignItems: 'center',
// userSelect: 'none',
// }}
// >
// <div
// data-type="rect"
// onMouseDown={this.startDrag}
// style={{
// width: 100,
// height: 40,
// border: '2px solid #31d0c6',
// textAlign: 'center',
// lineHeight: '40px',
// margin: 16,
// cursor: 'move',
// }}
// >
// Rect
// </div>
// <div
// data-type="circle"
// onMouseDown={this.startDrag}
// style={{
// width: 60,
// height: 60,
// borderRadius: '100%',
// border: '2px solid #31d0c6',
// textAlign: 'center',
// lineHeight: '60px',
// margin: 16,
// cursor: 'move',
// }}
// >
// Circle
// </div>
// </div>
// <div className="x6-graph-tools">
// <Button.Group>
// <Button onClick={this.onUndo}>Undo</Button>
// <Button onClick={this.onRedo}>Redo</Button>
// </Button.Group>
// </div>
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }
render() {
return (
<div className="x6-graph-wrap">
<h1>Dnd</h1>
<div
style={{
position: 'absolute',
left: 32,
top: 40,
width: 200,
height: 300,
padding: 16,
border: '1px solid #f0f0f0',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
userSelect: 'none',
}}
>
<div
data-type="rect"
onMouseDown={this.startDrag}
style={{
width: 100,
height: 40,
border: '2px solid #31d0c6',
textAlign: 'center',
lineHeight: '40px',
margin: 16,
cursor: 'move',
}}
>
Rect
</div>
<div
data-type="circle"
onMouseDown={this.startDrag}
style={{
width: 60,
height: 60,
borderRadius: '100%',
border: '2px solid #31d0c6',
textAlign: 'center',
lineHeight: '60px',
margin: 16,
cursor: 'move',
}}
>
Circle
</div>
</div>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,10 +1,11 @@
import React from 'react'
import { Graph, Path, Point } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Path, Point } from '@antv/x6-geometry'
import '../index.less'
Graph.registerConnector(
'wobble',
(sourcePoint, targetPoint, vertices, args) => {
(sourcePoint, targetPoint, vertices, args: any) => {
const spread = args.spread || 20
const points = [...vertices, targetPoint].map((p) => Point.create(p))
let prev = Point.create(sourcePoint)

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, Shape, Edge, Timing } from '@antv/x6'
import { Graph, Edge } from '@antv/x6'
import { Timing } from '@antv/x6-common'
import '../index.less'
export default class Example extends React.Component {
@ -174,31 +175,6 @@ export default class Example extends React.Component {
},
})
graph.addEdge({
shape: 'shadow-edge',
source: { x: 100, y: 200 },
target: { x: 500, y: 200 },
vertices: [{ x: 300, y: 300 }],
connector: { name: 'smooth' },
markup: Shape.ShadowEdge.getMarkup().slice().reverse().concat({
tagName: 'text',
selector: 'label',
}),
attrs: {
line: {
stroke: '#5654a0',
strokeWith: 3,
},
label: {
textPath: { selector: 'line', startOffset: '50%' },
textAnchor: 'middle',
textVerticalAnchor: 'middle',
text: 'Label Along Path',
fill: 'yellow',
},
},
})
Edge.registry.register('arrow', {
markup: [
{

View File

@ -1,468 +1,399 @@
// import React from 'react'
// import { Graph } from '@antv/x6'
// import { Marker } from '@antv/x6/es/registry'
// import '../index.less'
import React from 'react'
import { Graph } from '@antv/x6'
import { Marker } from '@antv/x6/es/registry'
import '../index.less'
// export default class Example extends React.Component {
// private container: HTMLDivElement
export default class Example extends React.Component {
private container: HTMLDivElement
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 800,
// height: 600,
// interacting: { edgeMovable: false },
// connecting: {
// connectionPoint: {
// name: 'boundary',
// args: {
// extrapolate: true,
// sticky: true,
// },
// },
// validateConnection: function () {
// return false
// },
// },
// })
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
interacting: { edgeMovable: false },
connecting: {
connectionPoint: {
name: 'boundary',
args: {
extrapolate: true,
sticky: true,
},
},
validateConnection: function () {
return false
},
},
})
// graph.addEdge({
// source: { x: 20, y: 20 },
// target: { x: 350, y: 20 },
// attrs: {
// line: {
// stroke: '#222138',
// sourceMarker: {
// name: 'classic',
// fill: '#31d0c6',
// stroke: 'none',
// size: 20,
// },
// targetMarker: {
// name: 'block',
// fill: '#fe854f',
// stroke: 'none',
// size: 20,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 20 },
target: { x: 350, y: 20 },
attrs: {
line: {
stroke: '#222138',
sourceMarker: {
name: 'classic',
fill: '#31d0c6',
stroke: 'none',
size: 20,
},
targetMarker: {
name: 'block',
fill: '#fe854f',
stroke: 'none',
size: 20,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 80 },
// target: { x: 350, y: 80 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'block',
// targetMarker: {
// tagName: 'circle',
// r: 5,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 80 },
target: { x: 350, y: 80 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'block',
targetMarker: {
tagName: 'circle',
r: 5,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 400 },
// target: { x: 280, y: 400 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'block',
// targetMarker: {
// name: 'block',
// width: 12,
// height: 6,
// open: true,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 400 },
target: { x: 280, y: 400 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'block',
targetMarker: {
name: 'block',
width: 12,
height: 6,
open: true,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 430 },
// target: { x: 280, y: 430 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'diamond',
// targetMarker: {
// name: 'diamond',
// width: 12,
// height: 6,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 430 },
target: { x: 280, y: 430 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'diamond',
targetMarker: {
name: 'diamond',
width: 12,
height: 6,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 460 },
// target: { x: 280, y: 460 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'ellipse',
// targetMarker: {
// name: 'ellipse',
// rx: 6,
// ry: 4,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 460 },
target: { x: 280, y: 460 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'ellipse',
targetMarker: {
name: 'ellipse',
rx: 6,
ry: 4,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 490 },
// target: { x: 280, y: 490 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'circle',
// targetMarker: {
// name: 'circlePlus',
// r: 10,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 490 },
target: { x: 280, y: 490 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'circle',
targetMarker: {
name: 'circlePlus',
r: 10,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 520 },
// target: { x: 280, y: 520 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'cross',
// targetMarker: {
// name: 'cross',
// width: 12,
// height: 8,
// offset: -10,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 520 },
target: { x: 280, y: 520 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'cross',
targetMarker: {
name: 'cross',
width: 12,
height: 8,
offset: -10,
},
},
},
})
// graph.addEdge({
// source: { x: 20, y: 550 },
// target: { x: 280, y: 550 },
// attrs: {
// line: {
// stroke: '#fe854f',
// strokeWidth: 1,
// sourceMarker: 'async',
// targetMarker: {
// name: 'async',
// width: 12,
// height: 8,
// offset: -10,
// open: true,
// flip: true,
// },
// },
// },
// })
graph.addEdge({
source: { x: 20, y: 550 },
target: { x: 280, y: 550 },
attrs: {
line: {
stroke: '#fe854f',
strokeWidth: 1,
sourceMarker: 'async',
targetMarker: {
name: 'async',
width: 12,
height: 8,
offset: -10,
open: true,
flip: true,
},
},
},
})
// graph.addEdge({
// source: { x: 10, y: 140 },
// target: { x: 350, y: 140 },
// attrs: {
// line: {
// stroke: '#31d0c6',
// strokeWidth: 3,
// strokeDasharray: '5 2',
// sourceMarker: {
// stroke: '#31d0c6',
// fill: '#31d0c6',
// d: Marker.normalize(
// 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
// ),
// },
// targetMarker: {
// stroke: '#31d0c6',
// fill: '#31d0c6',
// name: 'path',
// d: 'M4.834,4.834L4.833,4.833c-5.889,5.892-5.89,15.443,0.001,21.334s15.44,5.888,21.33-0.002c5.891-5.891,5.893-15.44,0.002-21.33C20.275-1.056,10.725-1.056,4.834,4.834zM25.459,5.542c0.833,0.836,1.523,1.757,2.104,2.726l-4.08,4.08c-0.418-1.062-1.053-2.06-1.912-2.918c-0.859-0.859-1.857-1.494-2.92-1.913l4.08-4.08C23.7,4.018,24.622,4.709,25.459,5.542zM10.139,20.862c-2.958-2.968-2.959-7.758-0.001-10.725c2.966-2.957,7.756-2.957,10.725,0c2.954,2.965,2.955,7.757-0.001,10.724C17.896,23.819,13.104,23.817,10.139,20.862zM5.542,25.459c-0.833-0.837-1.524-1.759-2.105-2.728l4.081-4.081c0.418,1.063,1.055,2.06,1.914,2.919c0.858,0.859,1.855,1.494,2.917,1.913l-4.081,4.081C7.299,26.982,6.379,26.292,5.542,25.459zM8.268,3.435l4.082,4.082C11.288,7.935,10.29,8.571,9.43,9.43c-0.858,0.859-1.494,1.855-1.912,2.918L3.436,8.267c0.58-0.969,1.271-1.89,2.105-2.727C6.377,4.707,7.299,4.016,8.268,3.435zM22.732,27.563l-4.082-4.082c1.062-0.418,2.061-1.053,2.919-1.912c0.859-0.859,1.495-1.857,1.913-2.92l4.082,4.082c-0.58,0.969-1.271,1.891-2.105,2.728C24.623,26.292,23.701,26.983,22.732,27.563z',
// offsetX: 10,
// },
// },
// },
// })
graph.addEdge({
source: { x: 10, y: 140 },
target: { x: 350, y: 140 },
attrs: {
line: {
stroke: '#31d0c6',
strokeWidth: 3,
strokeDasharray: '5 2',
sourceMarker: {
stroke: '#31d0c6',
fill: '#31d0c6',
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
targetMarker: {
stroke: '#31d0c6',
fill: '#31d0c6',
name: 'path',
d: 'M4.834,4.834L4.833,4.833c-5.889,5.892-5.89,15.443,0.001,21.334s15.44,5.888,21.33-0.002c5.891-5.891,5.893-15.44,0.002-21.33C20.275-1.056,10.725-1.056,4.834,4.834zM25.459,5.542c0.833,0.836,1.523,1.757,2.104,2.726l-4.08,4.08c-0.418-1.062-1.053-2.06-1.912-2.918c-0.859-0.859-1.857-1.494-2.92-1.913l4.08-4.08C23.7,4.018,24.622,4.709,25.459,5.542zM10.139,20.862c-2.958-2.968-2.959-7.758-0.001-10.725c2.966-2.957,7.756-2.957,10.725,0c2.954,2.965,2.955,7.757-0.001,10.724C17.896,23.819,13.104,23.817,10.139,20.862zM5.542,25.459c-0.833-0.837-1.524-1.759-2.105-2.728l4.081-4.081c0.418,1.063,1.055,2.06,1.914,2.919c0.858,0.859,1.855,1.494,2.917,1.913l-4.081,4.081C7.299,26.982,6.379,26.292,5.542,25.459zM8.268,3.435l4.082,4.082C11.288,7.935,10.29,8.571,9.43,9.43c-0.858,0.859-1.494,1.855-1.912,2.918L3.436,8.267c0.58-0.969,1.271-1.89,2.105-2.727C6.377,4.707,7.299,4.016,8.268,3.435zM22.732,27.563l-4.082-4.082c1.062-0.418,2.061-1.053,2.919-1.912c0.859-0.859,1.495-1.857,1.913-2.92l4.082,4.082c-0.58,0.969-1.271,1.891-2.105,2.728C24.623,26.292,23.701,26.983,22.732,27.563z',
offsetX: 10,
},
},
},
})
// graph.addEdge({
// source: { x: 400, y: 20 },
// target: { x: 740, y: 20 },
// vertices: [
// { x: 400, y: 60 },
// { x: 550, y: 60 },
// { x: 550, y: 20 },
// ],
// attrs: {
// line: {
// stroke: '#3c4260',
// strokeWidth: 2,
// sourceMarker: {
// fill: '#4b4a67',
// stroke: '#4b4a67',
// d: Marker.normalize(
// 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
// ),
// },
// targetMarker: {
// fill: '#4b4a67',
// stroke: '#4b4a67',
// d: Marker.normalize(
// 'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
// ),
// },
// vertexMarker: {
// tagName: 'circle',
// r: 4,
// strokeWidth: 2,
// fill: 'white',
// },
// },
// },
// })
graph.addEdge({
source: { x: 400, y: 20 },
target: { x: 740, y: 20 },
vertices: [
{ x: 400, y: 60 },
{ x: 550, y: 60 },
{ x: 550, y: 20 },
],
attrs: {
line: {
stroke: '#3c4260',
strokeWidth: 2,
sourceMarker: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
targetMarker: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
vertexMarker: {
tagName: 'circle',
r: 4,
strokeWidth: 2,
fill: 'white',
},
},
},
})
// graph.addEdge({
// source: { x: 440, y: 100 },
// target: { x: 740, y: 100 },
// vertices: [
// { x: 400, y: 140 },
// { x: 550, y: 100 },
// { x: 600, y: 140 },
// ],
// smooth: true,
// attrs: {
// line: {
// stroke: '#7c68fc',
// strokeWidth: 3,
// sourceMarker: {
// stroke: '#7c68fc',
// fill: '#7c68fc',
// d: Marker.normalize(
// 'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z',
// ),
// },
// targetMarker: {
// stroke: '#feb663',
// fill: '#feb663',
// d: Marker.normalize(
// 'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z',
// ),
// },
// },
// },
// })
graph.addEdge({
source: { x: 440, y: 100 },
target: { x: 740, y: 100 },
vertices: [
{ x: 400, y: 140 },
{ x: 550, y: 100 },
{ x: 600, y: 140 },
],
smooth: true,
attrs: {
line: {
stroke: '#7c68fc',
strokeWidth: 3,
sourceMarker: {
stroke: '#7c68fc',
fill: '#7c68fc',
d: Marker.normalize(
'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z',
),
},
targetMarker: {
stroke: '#feb663',
fill: '#feb663',
d: Marker.normalize(
'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z',
),
},
},
},
})
// graph.addEdge({
// shape: 'double-edge',
// source: { x: 10, y: 200 },
// target: { x: 350, y: 200 },
// attrs: {
// line: {
// stroke: '#7c68fc',
// },
// },
// labels: [
// {
// attrs: { text: { text: 'Label' } },
// position: {
// offset: 15,
// distance: 0.5,
// },
// },
// ],
// })
graph.addEdge({
source: { x: 400, y: 200 },
target: { x: 740, y: 200 },
connector: { name: 'smooth' },
attrs: {
line: {
targetMarker: {
d: 'M 0 -5 L -10 0 L 0 5 Z',
},
},
},
labels: [
{
markup: [
{
tagName: 'rect',
selector: 'labelBody',
},
{
tagName: 'text',
selector: 'labelText',
},
],
attrs: {
labelText: {
text: 'First',
fill: '#7c68fc',
fontFamily: 'sans-serif',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refX: -5,
refY: -5,
refWidth: '100%',
refHeight: '100%',
refWidth2: 10,
refHeight2: 10,
stroke: '#7c68fc',
fill: 'white',
strokeWidth: 2,
rx: 5,
ry: 5,
},
},
position: {
distance: 0.3,
options: {
keepGradient: true,
ensureLegibility: true,
},
},
},
{
markup: [
{
tagName: 'ellipse',
selector: 'labelBody',
},
{
tagName: 'text',
selector: 'labelText',
},
],
attrs: {
labelText: {
text: 'Second',
fill: '#31d0c6',
fontFamily: 'sans-serif',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refRx: '70%',
refRy: '80%',
stroke: '#31d0c6',
fill: 'white',
strokeWidth: 2,
},
},
position: {
distance: 0.7,
angle: 45,
},
},
],
})
// graph.addEdge({
// source: { x: 400, y: 200 },
// target: { x: 740, y: 200 },
// connector: { name: 'smooth' },
// attrs: {
// line: {
// targetMarker: {
// d: 'M 0 -5 L -10 0 L 0 5 Z',
// },
// },
// },
// labels: [
// {
// markup: [
// {
// tagName: 'rect',
// selector: 'labelBody',
// },
// {
// tagName: 'text',
// selector: 'labelText',
// },
// ],
// attrs: {
// labelText: {
// text: 'First',
// fill: '#7c68fc',
// fontFamily: 'sans-serif',
// textAnchor: 'middle',
// textVerticalAnchor: 'middle',
// },
// labelBody: {
// ref: 'labelText',
// refX: -5,
// refY: -5,
// refWidth: '100%',
// refHeight: '100%',
// refWidth2: 10,
// refHeight2: 10,
// stroke: '#7c68fc',
// fill: 'white',
// strokeWidth: 2,
// rx: 5,
// ry: 5,
// },
// },
// position: {
// distance: 0.3,
// options: {
// keepGradient: true,
// ensureLegibility: true,
// },
// },
// },
// {
// markup: [
// {
// tagName: 'ellipse',
// selector: 'labelBody',
// },
// {
// tagName: 'text',
// selector: 'labelText',
// },
// ],
// attrs: {
// labelText: {
// text: 'Second',
// fill: '#31d0c6',
// fontFamily: 'sans-serif',
// textAnchor: 'middle',
// textVerticalAnchor: 'middle',
// },
// labelBody: {
// ref: 'labelText',
// refRx: '70%',
// refRy: '80%',
// stroke: '#31d0c6',
// fill: 'white',
// strokeWidth: 2,
// },
// },
// position: {
// distance: 0.7,
// angle: 45,
// },
// },
// ],
// })
// Custom Edge
// -----------
// graph.addEdge({
// shape: 'shadow-edge',
// source: { x: 10, y: 280 },
// target: { x: 440, y: 280 },
// vertices: [
// { x: 150, y: 350 },
// { x: 300, y: 280 },
// ],
// connector: { name: 'smooth' },
// markup: [
// {
// tagName: 'path',
// selector: 'shadow',
// attrs: {
// fill: 'none',
// },
// },
// {
// tagName: 'path',
// selector: 'line',
// attrs: {
// fill: 'none',
// },
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// line: {
// stroke: '#3c4260',
// },
// label: {
// textPath: {
// selector: 'line',
// startOffset: '50%',
// },
// textAnchor: 'middle',
// textVerticalAnchor: 'middle',
// text: 'Label Along Path',
// fill: '#f6f6f6',
// fontSize: 15,
// fontWeight: 'bold',
// fontFamily: 'fantasy',
// },
// },
// })
const node1 = graph.addNode({
shape: 'path',
x: 500,
y: 450,
width: 100,
height: 100,
attrs: {
body: {
fill: '#31d0c6',
refD: 'M 0 20 10 20 10 30 30 30 30 0 40 0 40 40 0 40 z',
},
},
})
// // Custom Edge
// // -----------
graph.addEdge({
source: { x: 300, y: 400 },
target: node1,
attrs: {
line: {
sourceMarker: {
d: 'M 0 0 15 0',
stroke: 'white',
strokeWidth: 3,
},
},
},
})
}
// const node1 = graph.addNode({
// shape: 'path',
// x: 500,
// y: 450,
// width: 100,
// height: 100,
// attrs: {
// body: {
// fill: '#31d0c6',
// refD: 'M 0 20 10 20 10 30 30 30 30 0 40 0 40 40 0 40 z',
// },
// },
// })
refContainer = (container: HTMLDivElement) => {
this.container = container
}
// graph.addEdge({
// source: { x: 300, y: 400 },
// target: node1,
// attrs: {
// line: {
// sourceMarker: {
// d: 'M 0 0 15 0',
// stroke: 'white',
// strokeWidth: 3,
// },
// },
// },
// })
// }
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,75 +1,76 @@
// import React from 'react'
// import { Graph, Point } from '@antv/x6'
// import { Router } from '@antv/x6/es/registry/router'
// import '../index.less'
import React from 'react'
import { Graph } from '@antv/x6'
import { Router } from '@antv/x6/es/registry/router'
import { Point } from '@antv/x6-geometry'
import '../index.less'
// Router.registry.register(
// 'random',
// (vertices, args, view) => {
// const BOUNCES = args.bounces || 20
// const points = vertices.map((p) => Point.create(p))
Router.registry.register(
'random',
(vertices, args, view) => {
const BOUNCES = args.bounces || 20
const points = vertices.map((p) => Point.create(p))
// for (var i = 0; i < BOUNCES; i++) {
// const sourceCorner = view.sourceBBox.getCenter()
// const targetCorner = view.targetBBox.getCenter()
// const randomPoint = Point.random(
// sourceCorner.x,
// targetCorner.x,
// sourceCorner.y,
// targetCorner.y,
// )
// points.push(randomPoint)
// }
for (var i = 0; i < BOUNCES; i++) {
const sourceCorner = view.sourceBBox.getCenter()
const targetCorner = view.targetBBox.getCenter()
const randomPoint = Point.random(
sourceCorner.x,
targetCorner.x,
sourceCorner.y,
targetCorner.y,
)
points.push(randomPoint)
}
// return points
// },
// true,
// )
return points
},
true,
)
// export default class Example extends React.Component {
// private container: HTMLDivElement
export default class Example extends React.Component {
private container: HTMLDivElement
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 1000,
// height: 600,
// grid: 10,
// })
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 600,
grid: 10,
})
// const source = graph.addNode({
// x: 50,
// y: 50,
// width: 120,
// height: 80,
// attrs: { label: { text: 'Source' } },
// })
const source = graph.addNode({
x: 50,
y: 50,
width: 120,
height: 80,
attrs: { label: { text: 'Source' } },
})
// const target = graph.addNode(
// source.clone().translate(600, 400).attr('label/text', 'Target'),
// )
const target = graph.addNode(
source.clone().translate(600, 400).attr('label/text', 'Target'),
)
// graph.addEdge({
// source,
// target,
// router: {
// name: 'random',
// args: {
// bounces: 10,
// },
// },
// })
// }
graph.addEdge({
source,
target,
router: {
name: 'random',
args: {
bounces: 10,
},
},
})
}
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
refContainer = (container: HTMLDivElement) => {
this.container = container
}
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,29 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import '../../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 1400,
})
console.log(graph)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,92 +0,0 @@
import React from 'react'
import { Edge, Graph } from '@antv/x6'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: true,
connecting: {
allowBlank: true,
createEdge() {
return new Edge()
},
},
})
graph.addNode({
shape: 'rect',
x: 80,
y: 80,
width: 160,
height: 60,
label: 'source',
ports: [
{
id: 'a',
},
],
attrs: {
body: {
magnet: true,
},
},
})
graph.addNode({
shape: 'rect',
x: 320,
y: 320,
width: 160,
height: 60,
ports: [
{
id: 'b',
},
],
attrs: {
body: {
magnet: true,
},
},
})
graph.addNode({
shape: 'rect',
x: 520,
y: 60,
width: 160,
height: 60,
ports: [
{
id: 'c',
},
],
attrs: {
body: {
magnet: true,
},
},
})
graph.on('edge:connected', (args) => {
console.log(args)
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,73 +1,7 @@
import React from 'react'
import { Graph, Edge, CellView, EdgeView, Vector } from '@antv/x6'
import { Graph } from '@antv/x6'
import '../index.less'
class CustomEdgeView extends EdgeView {
onDblClick(e: JQuery.DoubleClickEvent, x: number, y: number) {
if (this.cell.getProp('customLinkInteractions')) {
this.addVertex(x, y)
}
}
onContextMenu(e: JQuery.ContextMenuEvent, x: number, y: number) {
if (this.cell.getProp('customLinkInteractions')) {
this.addLabel(x, y, {
reverseDistance: true,
absoluteDistance: true,
})
}
}
}
CustomEdgeView.config<EdgeView.Options>({ doubleTools: true })
EdgeView.registry.register('customEdgeView', CustomEdgeView, true)
const CustomEdge = Edge.define({
name: 'custom-edge',
defaultLabel: {
markup: [
{
tagName: 'circle',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
label: {
text: '%', // default label text
fill: '#ff0000', // default text color
fontSize: 14,
textAnchor: 'middle',
yAlign: 'middle',
pointerEvents: 'none',
},
body: {
ref: 'label',
fill: '#ffffff',
stroke: '#000000',
strokeWidth: 1,
refRCircumscribed: '60%',
refCx: 0,
refCy: 0,
},
},
position: {
distance: 0.5, // place label at midpoint by default
offset: {
y: -20, // offset label by 20px upwards by default
},
options: {
absoluteOffset: true, // keep offset absolute when moving by default
},
},
},
})
Edge.registry.register('customEdge', CustomEdge, true)
export default class Example extends React.Component {
private container: HTMLDivElement
@ -77,27 +11,19 @@ export default class Example extends React.Component {
width: 800,
height: 1400,
grid: 10,
interacting: function (cellView: CellView) {
if (cellView.cell.getProp('customLinkInteractions')) {
return { vertexAdd: false }
}
// all interactions enabled
return true
},
})
const marker = 'M 10 0 L 0 5 L 10 10 z'
// Default connection of two elements.
// -----------------------------------
const r1 = graph.addNode({
shape: 'basic.rect',
size: { width: 70, height: 30 },
position: { x: 335, y: 50 },
shape: 'rect',
width: 70,
height: 30,
x: 200,
y: 50,
attrs: {
rect: { fill: '#1890ff', stroke: '#1890ff' },
body: { fill: '#1890ff', stroke: '#1890ff' },
text: { text: 'box', fill: '#fff', magnet: true },
},
})
@ -109,11 +35,11 @@ export default class Example extends React.Component {
graph.addEdge({
source: r1,
target: r2,
label: 'default',
})
// Custom link interactions.
// -------------------------
// Changing source and target selectors of the edge.
// -------------------------------------------------
var r3 = r1.clone()
graph.addNode(r3)
r3.translate(0, 80)
@ -123,20 +49,13 @@ export default class Example extends React.Component {
r4.translate(300)
graph.addEdge({
shape: 'customEdge',
view: 'customEdgeView',
customLinkInteractions: true,
source: r3,
target: r4,
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
source: { cell: r3.id },
target: { cell: r4.id, selector: 'text' },
label: 'link to selector',
})
// Custom .source-marker and .target-marker.
// -----------------------------------------
// Vertices.
// ---------
var r5 = r3.clone()
graph.addNode(r5)
r5.translate(0, 80)
@ -148,337 +67,53 @@ export default class Example extends React.Component {
graph.addEdge({
source: r5,
target: r6,
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
vertices: [
{ x: 235, y: 280 },
{ x: 535, y: 280 },
],
label: 'vertices',
})
// Changing source and target selectors of the edge.
// -------------------------------------------------
// // Manhattan routing.
// // ------------------
var r7 = r5.clone()
graph.addNode(r7)
r7.translate(0, 80)
r7.translate(0, 100)
var r8 = r7.clone()
graph.addNode(r8)
r8.translate(300)
r8.translate(200, 80)
graph.addEdge({
source: { cell: r7.id },
target: { cell: r8.id, selector: 'text' },
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
source: r7,
target: r8,
vertices: [{ x: 620, y: 325 }],
router: { name: 'metro' },
label: 'metro router',
})
graph.addEdge({
source: r7,
target: r8,
vertices: [{ x: 350, y: 405 }],
router: { name: 'manhattan' },
connector: { name: 'rounded' },
label: 'manhattan router',
})
// Vertices.
// ---------
// // OneSide routing.
// // ----------------
var r9 = r7.clone()
graph.addNode(r9)
r9.translate(0, 80)
r9.translate(0, 150)
var r10 = r9.clone()
graph.addNode(r10)
r10.translate(300)
r10.translate(300, 0)
graph.addEdge({
source: r9,
target: r10,
vertices: [
{ x: 370, y: 470 },
{ x: 670, y: 470 },
],
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
})
// Custom vertex/connection markups. (ADVANCED)
// --------------------------------------------
var r11 = r9.clone()
graph.addNode(r11)
r11.translate(0, 120)
var r12 = r11.clone()
graph.addNode(r12)
r12.translate(300)
graph.addEdge({
source: r11,
target: r12,
vertices: [
{ x: 370, y: 600 },
{ x: 520, y: 640 },
{ x: 670, y: 600 },
],
vertexMarkup: [
'<g class="vertex-group" transform="translate(<%= x %>, <%= y %>)">',
'<image class="vertex" data-index="<%= index %>" xlink:href="https://cdn1.iconfinder.com/data/icons/ecommerce-61/48/eccomerce_-_location-32.png" width="25" height="25" transform="translate(-12.5, -12.5)"/>',
'<rect class="vertex-remove-area" data-index="<%= index %>" fill="red" width="19.5" height="19" transform="translate(11, -26)" rx="3" ry="3" />',
'<path class="vertex-remove" data-index="<%= index %>" transform="scale(.8) translate(9.5, -37)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z">',
'<title>Remove vertex.</title>',
'</path>',
'</g>',
].join(''),
markup: [
'<path class="connection"/>',
'<image class="source-marker" xlink:href="http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png" width="25" height="25"/>',
'<image class="target-marker" xlink:href="http://cdn3.iconfinder.com/data/icons/49handdrawing/24x24/left.png" width="25" height="25"/>',
'<path class="connection-wrap"/>',
'<g class="vertices"/>',
].join(''),
attrs: {
'.connection': {
strokeWidth: 4,
strokeDasharray: [5, 5, 5].join(','),
stroke: 'gray',
},
},
})
// Labels.
// -------
var r13 = r11.clone()
graph.addNode(r13)
r13.translate(0, 230)
var r14 = r13.clone()
graph.addNode(r14)
r14.translate(300)
const edge7 = new CustomEdge({
source: r13,
target: r14,
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
labels: [
{
attrs: {
label: {
text: '1..n',
},
},
position: {
distance: 29, // individual absolute positioning
offset: null, // remove default offset
options: {
absoluteOffset: null, // disable absolute offset when moving
},
},
},
{
markup: [
// individual markup
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
label: {
text: 'X6',
fill: 'white',
fontFamily: 'sans-serif',
textAnchor: 'left',
},
body: {
stroke: 'red',
strokeWidth: 2,
fill: '#F39C12',
rx: 5,
ry: 5,
refWidth: '140%',
refHeight: '140%',
refX: '-20%',
refY: '-20%',
refRCircumscribed: null,
refCx: null,
refCy: null,
},
},
position: {
distance: 0.5,
offset: {
// individual absolute offset
x: 10,
y: 25,
},
// keep default args
},
},
{
markup: [
{
tagName: 'circle',
selector: 'body',
},
{
tagName: 'path',
selector: 'symbol',
},
],
attrs: {
body: {
ref: null,
fill: 'lightgray',
stroke: 'black',
strokeWidth: 2,
r: 15,
refRCircumscribed: null,
refCx: null,
refCy: null,
},
symbol: {
// add attrs for individually added `path`
d: 'M 0 -15 0 -35 20 -35',
stroke: 'black',
strokeWidth: 2,
fill: 'none',
},
},
position: 0.5, // erase default position object, use relative distance
},
{
position: {
distance: 0.89, // individual relative distance
// keep default offset
// keep default args
},
},
],
})
graph.addEdge(edge7)
// Custom tools.
// -------------
var r15 = r13.clone()
graph.addNode(r15)
r15.translate(0, 100)
var r16 = r15.clone()
graph.addNode(r16)
r16.translate(300)
graph.addEdge({
source: r15,
target: r16,
attrs: {
'.source-marker': { d: marker },
'.target-marker': { d: marker },
},
toolMarkup: [
'<g class="edge-tool">',
'<g class="tool-remove" event="edge:remove">',
'<circle r="11" />',
'<path transform="scale(.8) translate(-16, -16)" d="M24.778,21.419 19.276,15.917 24.777,10.415 21.949,7.585 16.447,13.087 10.945,7.585 8.117,10.415 13.618,15.917 8.116,21.419 10.946,24.248 16.447,18.746 21.948,24.248z"/>',
'<title>Remove link.</title>',
'</g>',
'<g event="edge:options">',
'<circle r="11" transform="translate(25)"/>',
'<path fill="white" transform="scale(.55) translate(29, -16)" d="M31.229,17.736c0.064-0.571,0.104-1.148,0.104-1.736s-0.04-1.166-0.104-1.737l-4.377-1.557c-0.218-0.716-0.504-1.401-0.851-2.05l1.993-4.192c-0.725-0.91-1.549-1.734-2.458-2.459l-4.193,1.994c-0.647-0.347-1.334-0.632-2.049-0.849l-1.558-4.378C17.165,0.708,16.588,0.667,16,0.667s-1.166,0.041-1.737,0.105L12.707,5.15c-0.716,0.217-1.401,0.502-2.05,0.849L6.464,4.005C5.554,4.73,4.73,5.554,4.005,6.464l1.994,4.192c-0.347,0.648-0.632,1.334-0.849,2.05l-4.378,1.557C0.708,14.834,0.667,15.412,0.667,16s0.041,1.165,0.105,1.736l4.378,1.558c0.217,0.715,0.502,1.401,0.849,2.049l-1.994,4.193c0.725,0.909,1.549,1.733,2.459,2.458l4.192-1.993c0.648,0.347,1.334,0.633,2.05,0.851l1.557,4.377c0.571,0.064,1.148,0.104,1.737,0.104c0.588,0,1.165-0.04,1.736-0.104l1.558-4.377c0.715-0.218,1.399-0.504,2.049-0.851l4.193,1.993c0.909-0.725,1.733-1.549,2.458-2.458l-1.993-4.193c0.347-0.647,0.633-1.334,0.851-2.049L31.229,17.736zM16,20.871c-2.69,0-4.872-2.182-4.872-4.871c0-2.69,2.182-4.872,4.872-4.872c2.689,0,4.871,2.182,4.871,4.872C20.871,18.689,18.689,20.871,16,20.871z"/>',
'<title>Link options.</title>',
'</g>',
'</g>',
].join(''),
})
// Manhattan routing.
// ------------------
var r17 = r15.clone()
graph.addNode(r17)
r17.translate(0, 100)
var r18 = r17.clone()
graph.addNode(r18)
r18.translate(200, 80)
graph.addEdge({
source: r17,
target: r18,
vertices: [{ x: 700, y: 990 }],
router: { name: 'metro' },
})
graph.addEdge({
source: r17,
target: r18,
vertices: [{ x: 450, y: 1015 }],
router: { name: 'manhattan' },
connector: { name: 'rounded' },
})
// Markers.
// ------------------
var r19 = r17.clone()
graph.addNode(r19)
r19.translate(0, 200)
var r20 = r19.clone()
graph.addNode(r20)
r20.translate(200, 0)
var circleMarker = Vector.create(
'<marker id="circle-marker" markerUnits="userSpaceOnUse" viewBox = "0 0 12 12" refX = "6" refY = "6" markerWidth = "15" markerHeight = "15" stroke = "none" stroke-width = "0" fill = "red" orient = "auto"> <circle r = "5" cx="6" cy="6" fill="blue"/> </marker>',
)
var diamondMarker = Vector.create(
'<marker id="diamond-marker" viewBox = "0 0 5 20" refX = "0" refY = "6" markerWidth = "30" markerHeight = "30" stroke = "none" stroke-width = "0" fill = "red" > <rect x="0" y="0" width = "10" height="10" transform="rotate(45)" /> </marker>',
)
const defs = graph.view.svg.querySelector('defs')!
defs.appendChild(circleMarker.node)
defs.appendChild(diamondMarker.node)
graph.addEdge({
source: r19,
target: r20,
vertices: [
{ x: 400, y: 1080 },
{ x: 600, y: 1080 },
],
attrs: {
'.connection': {
'marker-mid': 'url(#circle-marker)',
},
},
})
graph.addEdge({
source: r19,
target: r20,
vertices: [
{ x: 400, y: 1190 },
{ x: 600, y: 1190 },
],
attrs: {
'.connection': {
'marker-mid': 'url(#diamond-marker)',
},
},
})
// OneSide routing.
// ----------------
var r21 = r19.clone()
graph.addNode(r21)
r21.translate(0, 150)
var r22 = r21.clone()
graph.addNode(r22)
r22.translate(200, 0)
graph.addEdge({
source: r21,
target: r22,
router: { name: 'oneSide', args: { side: 'bottom' } },
label: 'oneSide rounter',
})
}

View File

@ -10,7 +10,6 @@ export default class Example extends React.Component {
container: this.container,
width: 1000,
height: 600,
gridSize: 10,
})
const rect = graph.createNode({

View File

@ -1,154 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 400,
grid: true,
connecting: {
allowNode: false,
allowEdge: true,
allowLoop: false,
allowBlank: false,
allowMulti: 'withPort',
},
})
graph.addNode({
x: 60,
y: 50,
width: 120,
height: 64,
ports: {
groups: {
in: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'top',
},
out: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'bottom',
},
},
items: [
{
id: 'port1',
group: 'in',
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
},
})
graph.addNode({
x: 160,
y: 240,
width: 120,
height: 64,
ports: {
groups: {
in: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'top',
},
out: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'bottom',
},
},
items: [
{
id: 'port1',
group: 'in',
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
},
})
graph.addEdge({
source: [360, 80],
target: [560, 200],
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,39 +1,44 @@
import React from 'react'
import { Graph, Shape } from '@antv/x6'
import { Graph } from '@antv/x6'
import '../../index.less'
Shape.Rect.config({
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#d9d9d9',
strokeWidth: 1,
Graph.registerNode(
'custom-port-rect',
{
inherit: 'rect',
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#d9d9d9',
strokeWidth: 1,
},
},
ports: {
groups: {
in: {
position: { name: 'top' },
},
out: {
position: { name: 'bottom' },
},
},
},
portMarkup: [
{
tagName: 'circle',
selector: 'portBody',
attrs: {
r: 5,
magnet: true,
stroke: '#31d0c6',
fill: '#fff',
strokeWidth: 2,
},
},
],
},
ports: {
groups: {
in: {
position: { name: 'top' },
},
out: {
position: { name: 'bottom' },
},
},
},
portMarkup: [
{
tagName: 'circle',
selector: 'portBody',
attrs: {
r: 5,
magnet: true,
stroke: '#31d0c6',
fill: '#fff',
strokeWidth: 2,
},
},
],
})
true,
)
const magnetAvailabilityHighlighter = {
name: 'stroke',
@ -54,7 +59,6 @@ export default class Example extends React.Component {
container: this.container,
width: 800,
height: 400,
// grid: true,
highlighting: {
magnetAvailable: magnetAvailabilityHighlighter,
},
@ -91,6 +95,7 @@ export default class Example extends React.Component {
})
const source = graph.addNode({
shape: 'custom-port-rect',
x: 40,
y: 40,
width: 100,
@ -104,6 +109,7 @@ export default class Example extends React.Component {
})
const target = graph.addNode({
shape: 'custom-port-rect',
x: 140,
y: 240,
width: 100,
@ -117,6 +123,7 @@ export default class Example extends React.Component {
})
graph.addNode({
shape: 'custom-port-rect',
x: 320,
y: 120,
width: 100,

View File

@ -1,5 +1,7 @@
import React from 'react'
import { Graph, Util, Timing } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Timing } from '@antv/x6-common'
import { Marker } from '@antv/x6/lib/registry/marker'
import '../index.less'
function registerEdgeTool(name: string, inherit: string, options: any) {
@ -131,14 +133,14 @@ export default class Example extends React.Component {
sourceMarker: {
stroke: '#31d0c6',
fill: '#31d0c6',
d: Util.normalizeMarker(
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
targetMarker: {
stroke: '#31d0c6',
fill: '#31d0c6',
d: Util.normalizeMarker(
d: Marker.normalize(
'M4.834,4.834L4.833,4.833c-5.889,5.892-5.89,15.443,0.001,21.334s15.44,5.888,21.33-0.002c5.891-5.891,5.893-15.44,0.002-21.33C20.275-1.056,10.725-1.056,4.834,4.834zM25.459,5.542c0.833,0.836,1.523,1.757,2.104,2.726l-4.08,4.08c-0.418-1.062-1.053-2.06-1.912-2.918c-0.859-0.859-1.857-1.494-2.92-1.913l4.08-4.08C23.7,4.018,24.622,4.709,25.459,5.542zM10.139,20.862c-2.958-2.968-2.959-7.758-0.001-10.725c2.966-2.957,7.756-2.957,10.725,0c2.954,2.965,2.955,7.757-0.001,10.724C17.896,23.819,13.104,23.817,10.139,20.862zM5.542,25.459c-0.833-0.837-1.524-1.759-2.105-2.728l4.081-4.081c0.418,1.063,1.055,2.06,1.914,2.919c0.858,0.859,1.855,1.494,2.917,1.913l-4.081,4.081C7.299,26.982,6.379,26.292,5.542,25.459zM8.268,3.435l4.082,4.082C11.288,7.935,10.29,8.571,9.43,9.43c-0.858,0.859-1.494,1.855-1.912,2.918L3.436,8.267c0.58-0.969,1.271-1.89,2.105-2.727C6.377,4.707,7.299,4.016,8.268,3.435zM22.732,27.563l-4.082-4.082c1.062-0.418,2.061-1.053,2.919-1.912c0.859-0.859,1.495-1.857,1.913-2.92l4.082,4.082c-0.58,0.969-1.271,1.891-2.105,2.728C24.623,26.292,23.701,26.983,22.732,27.563z',
10,
),
@ -162,14 +164,14 @@ export default class Example extends React.Component {
sourceMarker: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: Util.normalizeMarker(
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
targetMarker: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: Util.normalizeMarker(
d: Marker.normalize(
'M5.5,15.499,15.8,21.447,15.8,15.846,25.5,21.447,25.5,9.552,15.8,15.152,15.8,9.552z',
),
},
@ -199,14 +201,14 @@ export default class Example extends React.Component {
sourceMarker: {
stroke: '#7c68fc',
fill: '#7c68fc',
d: Util.normalizeMarker(
d: Marker.normalize(
'M24.316,5.318,9.833,13.682,9.833,5.5,5.5,5.5,5.5,25.5,9.833,25.5,9.833,17.318,24.316,25.682z',
),
},
targetMarker: {
stroke: '#feb663',
fill: '#feb663',
d: Util.normalizeMarker(
d: Marker.normalize(
'M14.615,4.928c0.487-0.986,1.284-0.986,1.771,0l2.249,4.554c0.486,0.986,1.775,1.923,2.864,2.081l5.024,0.73c1.089,0.158,1.335,0.916,0.547,1.684l-3.636,3.544c-0.788,0.769-1.28,2.283-1.095,3.368l0.859,5.004c0.186,1.085-0.459,1.553-1.433,1.041l-4.495-2.363c-0.974-0.512-2.567-0.512-3.541,0l-4.495,2.363c-0.974,0.512-1.618,0.044-1.432-1.041l0.858-5.004c0.186-1.085-0.307-2.6-1.094-3.368L3.93,13.977c-0.788-0.768-0.542-1.525,0.547-1.684l5.026-0.73c1.088-0.158,2.377-1.095,2.864-2.081L14.615,4.928z',
),
},
@ -214,26 +216,6 @@ export default class Example extends React.Component {
},
})
const edge6 = graph.addEdge({
shape: 'double-edge',
source: { x: 10, y: 200 },
target: { x: 350, y: 200 },
attrs: {
line: {
stroke: '#7c68fc',
},
},
labels: [
{
attrs: { text: { text: 'double edge' } },
position: {
offset: 15,
distance: 0.5,
},
},
],
})
const edge7 = graph.addEdge({
source: { x: 400, y: 200 },
target: { x: 740, y: 200 },
@ -324,55 +306,6 @@ export default class Example extends React.Component {
],
})
const edge8 = graph.addEdge({
shape: 'shadow-edge',
source: { x: 10, y: 280 },
target: { x: 440, y: 280 },
vertices: [
{ x: 150, y: 350 },
{ x: 300, y: 280 },
],
connector: 'smooth',
markup: [
{
tagName: 'path',
selector: 'shadow',
attrs: {
fill: 'none',
},
},
{
tagName: 'path',
selector: 'line',
attrs: {
fill: 'none',
},
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
line: {
stroke: '#3c4260',
},
label: {
textPath: {
selector: 'line',
startOffset: '50%',
},
textAnchor: 'middle',
textVerticalAnchor: 'middle',
text: 'Label Along Path',
fill: '#f6f6f6',
fontSize: 15,
fontWeight: 'bold',
fontFamily: 'fantasy',
},
},
})
const edge9 = graph.addEdge({
markup: [
{
@ -804,14 +737,6 @@ export default class Example extends React.Component {
break
}
case edge6: {
items.push('vertices', {
name: 'custom-boundary',
args: { padding: 25 },
})
break
}
case edge7: {
items.push('source-arrowhead', 'target-arrowhead', {
name: 'button-remove',
@ -820,8 +745,7 @@ export default class Example extends React.Component {
break
}
case edge9:
case edge8: {
case edge9: {
items.push({
name: 'vertices',
args: {

View File

@ -0,0 +1,81 @@
import React from 'react'
import { Graph, Edge, EdgeView } from '@antv/x6'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: true,
connecting: {
validateMagnet({ cell, magnet }) {
let count = 0
const connectionCount = magnet.getAttribute('connection-count')
const max = connectionCount
? parseInt(connectionCount, 10)
: Number.MAX_SAFE_INTEGER
const outgoingEdges = graph.getOutgoingEdges(cell)
if (outgoingEdges) {
outgoingEdges.forEach((edge: Edge) => {
const edgeView = graph.findViewByCell(edge) as EdgeView
if (edgeView.sourceMagnet === magnet) {
count += 1
}
})
}
return count < max
},
},
})
graph.addNode({
shape: 'rect',
x: 80,
y: 80,
width: 160,
height: 60,
label: 'source',
ports: [
{
id: 'a',
attrs: {
circle: {
magnet: true,
connectionCount: 3, // 自定义属性,控制连接桩可连接多少条边
},
},
},
{
id: 'b',
attrs: {
circle: {
magnet: true,
connectionCount: 0, // 自定义属性,控制连接桩可连接多少条边
},
},
},
],
attrs: {
body: {
magnet: true,
connectionCount: 2, // 自定义属性,控制节点可连接多少条边
},
},
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, Edge, EdgeView } from '@antv/x6'
import { Graph } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
@ -8,63 +9,135 @@ export default class Example extends React.Component {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
height: 400,
grid: true,
connecting: {
validateMagnet({ cell, magnet }) {
let count = 0
const connectionCount = magnet.getAttribute('connection-count')
const max = connectionCount
? parseInt(connectionCount, 10)
: Number.MAX_SAFE_INTEGER
const outgoingEdges = graph.getOutgoingEdges(cell)
if (outgoingEdges) {
outgoingEdges.forEach((edge: Edge) => {
const edgeView = graph.findViewByCell(edge) as EdgeView
if (edgeView.sourceMagnet === magnet) {
count += 1
}
})
}
return count < max
},
allowNode: false,
allowEdge: true,
allowLoop: false,
allowBlank: false,
allowMulti: 'withPort',
},
})
graph.addNode({
shape: 'rect',
x: 80,
y: 80,
width: 160,
height: 60,
label: 'source',
ports: [
{
id: 'a',
attrs: {
circle: {
magnet: true,
connectionCount: 3, // 自定义属性,控制连接桩可连接多少条边
x: 60,
y: 50,
width: 120,
height: 64,
ports: {
groups: {
in: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'top',
},
out: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'bottom',
},
},
{
id: 'b',
attrs: {
circle: {
magnet: true,
connectionCount: 0, // 自定义属性,控制连接桩可连接多少条边
},
items: [
{
id: 'port1',
group: 'in',
},
},
],
attrs: {
body: {
magnet: true,
connectionCount: 2, // 自定义属性,控制节点可连接多少条边
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
},
})
graph.addNode({
x: 160,
y: 240,
width: 120,
height: 64,
ports: {
groups: {
in: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'top',
},
out: {
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
position: 'bottom',
},
},
items: [
{
id: 'port1',
group: 'in',
},
{
id: 'port2',
group: 'in',
},
{
id: 'port3',
group: 'in',
},
{
id: 'port4',
group: 'out',
},
{
id: 'port5',
group: 'out',
},
],
},
})
graph.addEdge({
source: [360, 80],
target: [560, 200],
})
}
refContainer = (container: HTMLDivElement) => {

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, Node, Color } from '@antv/x6'
import { Graph, Node } from '@antv/x6'
import { Color } from '@antv/x6-common'
import '../index.less'
import './dnd.less'
@ -12,10 +13,6 @@ export default class Example extends React.Component {
width: 880,
height: 600,
grid: true,
selecting: {
enabled: true,
// showNodeSelectionBox: true,
},
embedding: {
enabled: true,
findParent({ node }) {

View File

@ -1,178 +0,0 @@
import React from 'react'
import { Graph, Node } from '@antv/x6'
import '@antv/x6-react-shape'
import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons'
import { Group } from './shape'
import '../../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 600,
grid: true,
})
const createGroup = (
id: string,
x: number,
y: number,
width: number,
height: number,
fill: string,
) => {
const group = new Group({
id,
x,
y,
width,
height,
attrs: {
body: { fill },
},
})
graph.addNode(group)
return group
}
const createNode = (
id: string,
x: number,
y: number,
width: number,
height: number,
fill: string,
) => {
return graph.addNode({
id,
x,
y,
width,
height,
attrs: {
body: {
fill: fill || 'blue',
},
label: {
text: id,
fill: 'white',
refX: 10,
refY: 10,
textAnchor: 'start',
},
},
})
}
const createEdge = (
id: string,
source: string,
target: string,
vertices?: { x: number; y: number }[],
) => {
return graph.addEdge({
id,
source,
target,
vertices: vertices,
label: id,
})
}
const a = createGroup('a', 100, 30, 480, 320, 'lightblue')
const aa = createGroup('aa', 180, 80, 160, 140, 'green')
const aaa = createGroup('aaa', 200, 120, 120, 40, 'gray')
const c = createNode('c', 450, 200, 50, 50, 'orange')
a.addChild(aa)
aa.addChild(aaa)
a.addChild(c)
console.log(aa)
createNode('d', 680, 80, 50, 50, 'black')
const l1 = createEdge('l1', 'aa', 'c') // auto embed to common ancestor `a`
console.log(l1)
createEdge('l3', 'c', 'd')
aa.addChild(
createEdge('l2', 'aa', 'aaa', [
{ x: 50, y: 110 },
{ x: 50, y: 180 },
]),
)
graph.on('node:collapse', ({ node }: { node: Group }) => {
node.toggleCollapse()
const collapsed = node.isCollapsed()
const cells = node.getDescendants()
cells.forEach((node) => {
if (collapsed) {
node.hide()
} else {
node.show()
}
})
})
graph.addNode({
shape: 'react-shape',
x: 320,
y: 420,
width: 160,
height: 60,
component: (node: Node) => {
const data = node.getData<any>() || {}
const collapsed = data.collapsed === true
return (
<div style={{ background: '#f5f5f5', width: '100%', height: '100%' }}>
{collapsed ? (
<PlusSquareOutlined
style={{ cursor: 'pointer' }}
event="react:collapse"
/>
) : (
<MinusSquareOutlined
style={{ cursor: 'pointer' }}
event="react:collapse"
/>
)}
{node.attr('body/fill')}
</div>
)
},
})
graph.on('react:collapse', ({ node }: { node: Node }) => {
const data = node.getData<any>() || {}
const collapsed = !(data.collapsed === true)
node.updateData({ collapsed })
node.resize(collapsed ? 80 : 160, collapsed ? 30 : 60)
const cells = node.getDescendants()
cells.forEach((node) => {
if (collapsed) {
node.hide()
} else {
node.show()
}
})
console.log(node)
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,150 +0,0 @@
import React from 'react'
import { Graph, Cell, CellView } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component {
private info: HTMLDivElement
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 600,
})
const me = (
id: string,
x: number,
y: number,
width: number,
height: number,
fill: string,
) => {
return graph.addNode({
id,
name: id,
x,
y,
width,
height,
attrs: {
body: {
fill: fill || 'blue',
},
label: {
text: id,
fill: 'white',
refX: 10,
refY: 10,
textAnchor: 'start',
},
},
})
}
const ml = (
id: string,
source: string,
target: string,
vertices?: { x: number; y: number }[],
) => {
return graph.addEdge({
id,
source,
target,
name: id,
vertices: vertices,
label: id,
})
}
const a = me('a', 100, 30, 420, 200, 'lightblue')
const aa = me('aa', 130, 50, 160, 140, 'green')
const aaa = me('aaa', 150, 120, 120, 40, 'gray')
const c = me('c', 400, 50, 50, 50, 'orange')
a.addChild(aa)
aa.addChild(aaa)
a.addChild(c)
console.log(aa)
me('d', 620, 50, 50, 50, 'black')
const l1 = ml('l1', 'aa', 'c') // auto embed to common ancestor `a`
console.log(l1)
ml('l3', 'c', 'd')
aa.addChild(
ml('l2', 'aa', 'aaa', [
{ x: 50, y: 110 },
{ x: 50, y: 140 },
]),
)
const model = graph.model
graph.on('cell:mouseleave', this.resetInfo)
graph.on('cell:mouseenter', ({ view }: { view: CellView }) => {
const cell = view.cell
const i: { [key: string]: string } = {}
const toString = (cloned: { [key: string]: Cell }) =>
Object.keys(cloned)
.map((id) => {
const cell = cloned[id]
return cell.prop('name')
})
.join(', ')
let key = `graph.cloneCells([${cell.id}])`
let cloned = model.cloneCells([cell])
i[key] = toString(cloned)
key = `Cell.deepClone(${cell.id})`
cloned = Cell.deepClone(cell)
i[key] = toString(cloned)
key = `${cell.id}.clone({ deep: true })`
cloned = { [cell.id]: cell.clone({ deep: true }) }
i[key] = toString(cloned)
key = `graph.cloneSubGraph([${cell.id}], { deep: true })`
cloned = model.cloneSubGraph([cell], { deep: true })
i[key] = toString(cloned)
key = `graph.getSubGraph([${cell.id}], { deep: true })`
const cells = model.getSubGraph([cell], { deep: true })
i[key] = cells.map((c) => c.prop('name')).join(', ')
key = `graph.getConnectedLinks(${cell.id}, { deep: true })`
const edges = model.getConnectedEdges(cell, { deep: true })
i[key] = edges.map((c) => c.prop('name')).join(', ')
this.info.innerText = JSON.stringify(i, null, '\t')
})
this.resetInfo()
}
resetInfo = () => {
this.info.innerText =
'Hover over cells to see\nhow cloning and graph search works\non nested graphs.'
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
refInfo = (div: HTMLDivElement) => {
this.info = div
}
render() {
return (
<div className="x6-graph-wrap">
<div
ref={this.refInfo}
style={{ position: 'fixed', right: 50, bottom: 50 }}
/>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,56 +1,76 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Checkbox } from 'antd'
import { Graph, Node, Edge } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component {
export default class Example extends React.Component<
Example.Props,
Example.State
> {
private container: HTMLDivElement
private parent: Node
private edge1: Edge
private edge2: Edge
// default embeded
state = { embedEdges: true }
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: 10,
})
const r1 = graph.addNode({
size: { width: 600, height: 240 },
const parent = graph.addNode({
size: { width: 100, height: 30 },
position: { x: 100, y: 40 },
attrs: {
body: { fill: 'orange' },
label: { text: 'Box' },
body: { fill: 'blue' },
label: { text: 'parent', fill: 'white' },
},
})
var r11 = r1.clone()
r11
.resize(240, 120)
.attr({ body: { fill: 'yellow' } })
.translate(24, 24)
.addTo(r1)
var r12 = r11.clone()
r12
.resize(120, 80)
.attr({ body: { fill: 'yellow' } })
.translate(400, 80)
.addTo(r1)
// auto update parent
graph.addEdge({
source: r11,
target: r12,
})
graph
.addEdge({
source: { x: 160, y: 100 },
target: { x: 240, y: 240 },
const child1 = graph
.addNode({
x: 70,
y: 130,
width: 80,
height: 30,
attrs: {
body: { fill: 'lightgreen', rx: 5, ry: 5 },
label: { text: 'child', fill: 'white' },
},
})
.addTo(r1)
.addTo(parent)
const r2 = r1.clone({ deep: true }).translate(0, 300)
graph.addNode(r2)
const child2 = child1.clone().translate(100).addTo(parent)
this.parent = parent
this.edge1 = graph.addEdge({
source: parent,
target: child1,
})
this.edge2 = graph.addEdge({
source: parent,
target: child2,
vertices: [
{ x: 210, y: 75 },
{ x: 190, y: 105 },
],
})
}
onEmbedEdgesChanged = (e: any) => {
const embedEdges = e.target.checked
this.setState({ embedEdges })
if (embedEdges) {
this.parent.embed(this.edge1)
this.parent.embed(this.edge2)
} else {
this.parent.unembed(this.edge1)
this.parent.unembed(this.edge2)
}
}
refContainer = (container: HTMLDivElement) => {
@ -60,8 +80,24 @@ export default class Example extends React.Component {
render() {
return (
<div className="x6-graph-wrap">
<div className="x6-graph-tools">
<Checkbox
checked={this.state.embedEdges}
onChange={this.onEmbedEdgesChanged}
>
Embed Edges
</Checkbox>
</div>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}
// eslint-disable-next-line
namespace Example {
export interface Props {}
export interface State {
embedEdges: boolean
}
}

View File

@ -1,103 +0,0 @@
import React from 'react'
import { Checkbox } from 'antd'
import { Graph, Node, Edge } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component<
Example.Props,
Example.State
> {
private container: HTMLDivElement
private parent: Node
private edge1: Edge
private edge2: Edge
// default embeded
state = { embedEdges: true }
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
})
const parent = graph.addNode({
size: { width: 100, height: 30 },
position: { x: 100, y: 40 },
attrs: {
body: { fill: 'blue' },
label: { text: 'parent', fill: 'white' },
},
})
const child1 = graph
.addNode({
x: 70,
y: 130,
width: 80,
height: 30,
attrs: {
body: { fill: 'lightgreen', rx: 5, ry: 5 },
label: { text: 'child', fill: 'white' },
},
})
.addTo(parent)
const child2 = child1.clone().translate(100).addTo(parent)
this.parent = parent
this.edge1 = graph.addEdge({
source: parent,
target: child1,
})
this.edge2 = graph.addEdge({
source: parent,
target: child2,
vertices: [
{ x: 210, y: 75 },
{ x: 190, y: 105 },
],
})
}
onEmbedEdgesChanged = (e: any) => {
const embedEdges = e.target.checked
this.setState({ embedEdges })
if (embedEdges) {
this.parent.embed(this.edge1)
this.parent.embed(this.edge2)
} else {
this.parent.unembed(this.edge1)
this.parent.unembed(this.edge2)
}
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div className="x6-graph-tools">
<Checkbox
checked={this.state.embedEdges}
onChange={this.onEmbedEdgesChanged}
>
Embed Edges
</Checkbox>
</div>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}
// eslint-disable-next-line
namespace Example {
export interface Props {}
export interface State {
embedEdges: boolean
}
}

View File

@ -1,3 +0,0 @@
.x6-port .collapsed {
cursor: pointer;
}

View File

@ -1,383 +0,0 @@
import React from 'react'
import { Graph, Edge, CellView, Shape } from '@antv/x6'
import '../index.less'
import './index.less'
const EXPANDED_COLOR = '#8CC152'
const COLLAPSED_COLOR = '#FCBB42'
const BASE_COLOR = '#434A54'
class TogglableRect extends Shape.Rect {
onConnectedEdgeVisibleChange(
edge: Edge,
type: Edge.TerminalType,
visible: boolean,
) {
const terminal = edge[type]
const portId = (terminal as Edge.TerminalCellData).port
if (portId && this.isNode()) {
var expand = visible
var collapsedMap: { [portId: string]: number } =
this.prop('collapsed') || {}
if (expand) {
if (isFinite(collapsedMap[portId])) {
collapsedMap[portId]--
}
if (collapsedMap[portId] <= 0) {
delete collapsedMap[portId]
this.portProp(portId, 'collapsed', false)
this.onPortExpand(portId, expand)
}
} else {
if (!collapsedMap[portId]) {
collapsedMap[portId] = 1
this.portProp(portId, 'collapsed', true)
this.onPortExpand(portId, expand)
} else {
collapsedMap[portId]++
}
}
this.prop('collapsed', collapsedMap)
}
}
protected onPortExpand(portId: string, expand: boolean) {
var color = expand ? EXPANDED_COLOR : COLLAPSED_COLOR
var className = expand ? 'expanded' : 'collapsed'
this.portProp(portId, 'attrs/circle/fill', color)
this.portProp(portId, 'attrs/circle/class', className)
}
isPortCollapsed(portId: string) {
var collapsedMap: { [portId: string]: number } =
this.prop('collapsed') || {}
return collapsedMap[portId] > 0
}
expandPort(portId: string) {
if (portId) {
if (this.isPortCollapsed(portId) && this.model) {
const resolve = (edge: Edge) => {
const source = edge.getSource()
const target = edge.getTarget()
let result
if (source && this.id !== (source as Edge.TerminalCellData).cell) {
result = {
opposite: source,
current: target,
}
}
if (target && this.id !== (target as Edge.TerminalCellData).cell) {
result = {
opposite: target,
current: source,
}
}
return result
}
this.model.getConnectedEdges(this).forEach((edge) => {
const ret = resolve(edge as any)
if (
ret &&
(ret.current as Edge.TerminalCellData).port === portId &&
(ret.opposite as Edge.TerminalCellData).cell
) {
const cellId = (ret.opposite as Edge.TerminalCellData).cell
const cell = this.model!.getCell(cellId)
cell && cell.show()
}
})
}
}
}
}
TogglableRect.config({
size: {
width: 100,
height: 100,
},
ports: {
groups: {
in: {
attrs: {
circle: {
magnet: true,
stroke: BASE_COLOR,
fill: EXPANDED_COLOR,
},
},
position: {
name: 'left',
},
},
out: {
attrs: {
circle: {
magnet: true,
stroke: BASE_COLOR,
fill: EXPANDED_COLOR,
},
},
position: 'right',
},
},
},
attrs: {
body: {
refWidth: 1,
refHeight: 1,
fill: '#AAB2BD',
},
label: {
refX: '50%',
refY: '50%',
fontWeight: 'bold',
fontSize: 24,
xAlign: 'middle',
yAlign: 'middle',
fill: '#F5F7FA',
},
},
})
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: 1,
connecting: {
connectionPoint: 'boundary',
validateMagnet: function (cellView: CellView, magnet: Element) {
if (!magnet) {
return true
}
var cell = cellView.cell as any as TogglableRect
var portId = magnet.getAttribute('port')
return portId ? !cell.isPortCollapsed(portId) : true
},
},
magnetThreshold: 'onleave',
clickThreshold: 5,
})
graph.on('node:click', ({ view }) => {
view.cell.hide()
})
graph.on('node:magnet:click', ({ e, view, magnet }) => {
e.stopPropagation()
var portId = magnet.getAttribute('port')
if (portId) {
const rect = view.cell as any as TogglableRect
rect.expandPort(portId)
}
})
graph.on('cell:change:visible', ({ cell, current }) => {
console.log(cell)
if (cell.isEdge()) {
const visible = current !== false
const sourceCell = cell.getSourceCell()
if (sourceCell) {
const rect = sourceCell as any as TogglableRect
rect.onConnectedEdgeVisibleChange(cell, 'source', visible)
}
const targetCell = cell.getTargetCell()
if (targetCell) {
const rect = targetCell as any as TogglableRect
rect.onConnectedEdgeVisibleChange(cell, 'target', visible)
}
}
})
const a = new TogglableRect({
id: 'a',
x: 200,
y: 40,
attrs: { label: { text: 'a' } },
ports: {
items: [
{ group: 'in', id: 'in1', type: 'a' },
{ group: 'in', id: 'in2', type: 'o' },
{ group: 'out', id: 'out1' },
{ group: 'out', id: 'out2' },
],
},
})
a.addTo(graph)
const aa = new TogglableRect({
id: 'aa',
x: 400,
y: 40,
attrs: { label: { text: 'aa' } },
ports: {
items: [
{ group: 'in', id: 'in1' },
{ group: 'in', id: 'in2' },
{ group: 'out', id: 'out1' },
{ group: 'out', id: 'out2' },
],
},
})
aa.addTo(graph)
const aaa = new TogglableRect({
id: 'aaa',
x: 550,
y: 120,
attrs: { label: { text: 'aaa' } },
ports: {
items: [
{ group: 'in', id: 'in1' },
{ group: 'out', id: 'out1' },
],
},
})
aaa.addTo(graph)
const b = new TogglableRect({
id: 'b',
x: 200,
y: 200,
attrs: { label: { text: 'b' } },
ports: {
items: [
{ group: 'in', id: 'in1' },
{ group: 'in', id: 'in2' },
{ group: 'out', id: 'out1' },
{ group: 'out', id: 'out2' },
],
},
})
b.addTo(graph)
const bb = b
.clone()
.prop('id', 'bb')
.attr({ label: { text: 'bb' } })
.setPosition(400, 200)
bb.addTo(graph)
const bbb = b
.clone()
.prop('id', 'bbb')
.attr({ label: { text: 'bbb' } })
.setPosition(400, 350)
bbb.addTo(graph)
const x = new TogglableRect({
id: 'x',
x: 60,
y: 400,
attrs: { label: { text: 'x' } },
ports: {
items: [
{ group: 'out', id: 'out1' },
{ group: 'out', id: 'out2' },
],
},
})
x.addTo(graph)
const y = x
.clone()
.prop('id', 'y')
.attr({ label: { text: 'y' } })
.setPosition(30, 80)
y.addTo(graph)
graph.addEdge({
source: { cell: a.id, port: 'out1' },
target: { cell: aa.id, port: 'in1' },
})
graph.addEdge({
sourceCell: aa,
targetCell: aaa,
sourcePort: 'out2',
targetPort: 'in1',
})
graph.addEdge({
sourceCell: b.id,
targetCell: bb.id,
sourcePort: 'out1',
targetPort: 'in1',
})
graph.addEdge({
sourceCell: b,
targetCell: bbb,
sourcePort: 'out2',
targetPort: 'in1',
})
graph.addEdge({
sourceCell: b,
targetCell: aaa,
sourcePort: 'out1',
targetPort: 'in1',
})
graph.addEdge({
sourceCell: aaa,
sourcePort: 'out1',
targetPoint: { x: 700, y: 100 },
})
graph.addEdge({
sourceCell: bbb,
targetCell: x,
sourcePort: 'in1',
targetPort: 'out2',
})
graph.addEdge({
sourceCell: b,
targetCell: x,
sourcePort: 'in2',
targetPort: 'out1',
})
graph.addEdge({
sourceCell: b,
targetCell: y,
sourcePort: 'in1',
targetPort: 'out2',
})
graph.addEdge({
sourceCell: a,
targetCell: y,
sourcePort: 'in1',
targetPort: 'out1',
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,913 +0,0 @@
import dagre from 'dagre'
const data = {
fathers: [
{
id: 0,
married: false,
name: 'Eric Taylor',
// sons: [
// {
// age: 21,
// name: 'Jose',
// },
// {
// age: 1,
// name: 'James',
// },
// {
// age: 17,
// name: 'Gary',
// },
// {
// age: 29,
// name: 'Thomas',
// },
// {
// age: 3,
// name: 'John',
// },
// {
// age: 10,
// name: 'Michael',
// },
// {
// age: 13,
// name: 'Jose',
// },
// {
// age: 26,
// name: 'James',
// },
// {
// age: 19,
// name: 'Christopher',
// },
// {
// age: 20,
// name: 'George',
// },
// ],
daughters: [
{
age: 30,
name: 'Sarah',
},
{
age: 6,
name: 'Cynthia',
},
{
age: 15,
name: 'Linda',
},
{
age: 7,
name: 'Barbara',
},
{
age: 18,
name: 'Margaret',
},
],
},
// {
// id: 1,
// married: true,
// name: 'Michael Taylor',
// sons: [
// {
// age: 18,
// name: 'Timothy',
// },
// {
// age: 3,
// name: 'George',
// },
// ],
// daughters: [
// {
// age: 12,
// name: 'Karen',
// },
// {
// age: 22,
// name: 'Patricia',
// },
// {
// age: 4,
// name: 'Linda',
// },
// {
// age: 24,
// name: 'Jennifer',
// },
// {
// age: 8,
// name: 'Laura',
// },
// {
// age: 23,
// name: 'Kimberly',
// },
// {
// age: 11,
// name: 'Anna',
// },
// ],
// },
// {
// id: 2,
// married: false,
// name: 'James Lopez',
// sons: [
// {
// age: 2,
// name: 'Ronald',
// },
// ],
// daughters: [
// {
// age: 28,
// name: 'Karen',
// },
// ],
// },
// {
// id: 3,
// married: false,
// name: 'Charles Thomas',
// sons: [
// {
// age: 12,
// name: 'Michael',
// },
// {
// age: 24,
// name: 'Anthony',
// },
// {
// age: 14,
// name: 'Eric',
// },
// {
// age: 8,
// name: 'Steven',
// },
// ],
// daughters: [
// {
// age: 10,
// name: 'Nancy',
// },
// {
// age: 12,
// name: 'Angela',
// },
// {
// age: 18,
// name: 'Deborah',
// },
// {
// age: 3,
// name: 'Cynthia',
// },
// {
// age: 26,
// name: 'Patricia',
// },
// ],
// },
// {
// id: 4,
// married: false,
// name: 'Donald Martinez',
// sons: [
// {
// age: 21,
// name: 'Timothy',
// },
// {
// age: 19,
// name: 'James',
// },
// {
// age: 22,
// name: 'Christopher',
// },
// {
// age: 24,
// name: 'Michael',
// },
// {
// age: 24,
// name: 'Edward',
// },
// {
// age: 24,
// name: 'Charles',
// },
// {
// age: 11,
// name: 'Jason',
// },
// ],
// daughters: [
// {
// age: 13,
// name: 'Jennifer',
// },
// {
// age: 20,
// name: 'Margaret',
// },
// {
// age: 16,
// name: 'Sharon',
// },
// {
// age: 25,
// name: 'Carol',
// },
// {
// age: 30,
// name: 'Carol',
// },
// {
// age: 23,
// name: 'Donna',
// },
// ],
// },
// {
// id: 5,
// married: true,
// name: 'Timothy Lewis',
// sons: [
// {
// age: 15,
// name: 'Ronald',
// },
// {
// age: 29,
// name: 'Robert',
// },
// {
// age: 15,
// name: 'Richard',
// },
// ],
// daughters: [
// {
// age: 27,
// name: 'Linda',
// },
// {
// age: 5,
// name: 'Carol',
// },
// {
// age: 17,
// name: 'Helen',
// },
// {
// age: 27,
// name: 'Margaret',
// },
// {
// age: 4,
// name: 'Ruth',
// },
// {
// age: 22,
// name: 'Michelle',
// },
// {
// age: 8,
// name: 'Elizabeth',
// },
// ],
// },
// {
// id: 6,
// married: true,
// name: 'Daniel Martin',
// sons: [
// {
// age: 10,
// name: 'Brian',
// },
// {
// age: 4,
// name: 'Gary',
// },
// ],
// daughters: [
// {
// age: 24,
// name: 'Cynthia',
// },
// {
// age: 18,
// name: 'Melissa',
// },
// {
// age: 18,
// name: 'Elizabeth',
// },
// {
// age: 11,
// name: 'Margaret',
// },
// {
// age: 18,
// name: 'Shirley',
// },
// {
// age: 17,
// name: 'Sandra',
// },
// {
// age: 2,
// name: 'Brenda',
// },
// {
// age: 8,
// name: 'Elizabeth',
// },
// {
// age: 2,
// name: 'Elizabeth',
// },
// {
// age: 25,
// name: 'Kimberly',
// },
// ],
// },
// {
// id: 7,
// married: false,
// name: 'Kevin Walker',
// sons: [
// {
// age: 8,
// name: 'Brian',
// },
// {
// age: 31,
// name: 'Kenneth',
// },
// {
// age: 1,
// name: 'David',
// },
// {
// age: 22,
// name: 'John',
// },
// {
// age: 30,
// name: 'Thomas',
// },
// {
// age: 19,
// name: 'Matthew',
// },
// {
// age: 31,
// name: 'Thomas',
// },
// ],
// daughters: [
// {
// age: 13,
// name: 'Barbara',
// },
// {
// age: 22,
// name: 'Donna',
// },
// {
// age: 11,
// name: 'Michelle',
// },
// ],
// },
// {
// id: 8,
// married: false,
// name: 'Jose Taylor',
// sons: [
// {
// age: 21,
// name: 'Richard',
// },
// {
// age: 3,
// name: 'Thomas',
// },
// {
// age: 26,
// name: 'Charles',
// },
// ],
// daughters: [
// {
// age: 5,
// name: 'Jessica',
// },
// {
// age: 11,
// name: 'Nancy',
// },
// {
// age: 3,
// name: 'Maria',
// },
// {
// age: 26,
// name: 'Cynthia',
// },
// {
// age: 16,
// name: 'Helen',
// },
// ],
// },
// {
// id: 9,
// married: false,
// name: 'Michael Young',
// sons: [
// {
// age: 3,
// name: 'George',
// },
// {
// age: 25,
// name: 'Jeffrey',
// },
// {
// age: 7,
// name: 'Edward',
// },
// {
// age: 18,
// name: 'Anthony',
// },
// {
// age: 31,
// name: 'Eric',
// },
// {
// age: 8,
// name: 'Donald',
// },
// {
// age: 31,
// name: 'Christopher',
// },
// {
// age: 23,
// name: 'Brian',
// },
// {
// age: 18,
// name: 'George',
// },
// ],
// daughters: [
// {
// age: 12,
// name: 'Susan',
// },
// {
// age: 4,
// name: 'Elizabeth',
// },
// {
// age: 13,
// name: 'Shirley',
// },
// {
// age: 17,
// name: 'Barbara',
// },
// {
// age: 12,
// name: 'Susan',
// },
// ],
// },
// {
// id: 10,
// married: false,
// name: 'Jason Miller',
// sons: [
// {
// age: 11,
// name: 'Mark',
// },
// {
// age: 30,
// name: 'Kevin',
// },
// {
// age: 14,
// name: 'Edward',
// },
// {
// age: 22,
// name: 'Thomas',
// },
// {
// age: 1,
// name: 'Jose',
// },
// {
// age: 18,
// name: 'Daniel',
// },
// {
// age: 2,
// name: 'Jeffrey',
// },
// {
// age: 20,
// name: 'Christopher',
// },
// ],
// daughters: [
// {
// age: 26,
// name: 'Dorothy',
// },
// {
// age: 8,
// name: 'Angela',
// },
// {
// age: 28,
// name: 'Elizabeth',
// },
// {
// age: 11,
// name: 'Laura',
// },
// {
// age: 23,
// name: 'Karen',
// },
// {
// age: 0,
// name: 'Brenda',
// },
// ],
// },
// {
// id: 11,
// married: true,
// name: 'Larry Miller',
// sons: [
// {
// age: 16,
// name: 'James',
// },
// {
// age: 21,
// name: 'Jeffrey',
// },
// {
// age: 15,
// name: 'Scott',
// },
// {
// age: 24,
// name: 'Matthew',
// },
// {
// age: 24,
// name: 'Anthony',
// },
// {
// age: 7,
// name: 'Anthony',
// },
// ],
// daughters: [
// {
// age: 17,
// name: 'Karen',
// },
// {
// age: 26,
// name: 'Lisa',
// },
// {
// age: 18,
// name: 'Cynthia',
// },
// {
// age: 27,
// name: 'Susan',
// },
// {
// age: 13,
// name: 'Michelle',
// },
// {
// age: 21,
// name: 'Lisa',
// },
// {
// age: 10,
// name: 'Maria',
// },
// {
// age: 17,
// name: 'Barbara',
// },
// ],
// },
// {
// id: 12,
// married: true,
// name: 'Christopher Thomas',
// sons: [
// {
// age: 22,
// name: 'William',
// },
// {
// age: 7,
// name: 'Paul',
// },
// ],
// daughters: [
// {
// age: 12,
// name: 'Amy',
// },
// {
// age: 1,
// name: 'Melissa',
// },
// {
// age: 4,
// name: 'Mary',
// },
// {
// age: 26,
// name: 'Anna',
// },
// {
// age: 24,
// name: 'Donna',
// },
// {
// age: 30,
// name: 'Jessica',
// },
// {
// age: 8,
// name: 'Lisa',
// },
// {
// age: 5,
// name: 'Maria',
// },
// {
// age: 7,
// name: 'Jessica',
// },
// ],
// },
// {
// id: 13,
// married: true,
// name: 'Daniel Johnson',
// sons: [
// {
// age: 29,
// name: 'Gary',
// },
// {
// age: 30,
// name: 'Frank',
// },
// ],
// daughters: [
// {
// age: 31,
// name: 'Patricia',
// },
// {
// age: 23,
// name: 'Melissa',
// },
// {
// age: 10,
// name: 'Shirley',
// },
// {
// age: 10,
// name: 'Kimberly',
// },
// {
// age: 15,
// name: 'Maria',
// },
// {
// age: 11,
// name: 'Ruth',
// },
// {
// age: 26,
// name: 'Linda',
// },
// {
// age: 13,
// name: 'Jennifer',
// },
// {
// age: 8,
// name: 'Jennifer',
// },
// ],
// },
// {
// id: 14,
// married: false,
// name: 'Daniel Martinez',
// sons: [
// {
// age: 26,
// name: 'Eric',
// },
// {
// age: 26,
// name: 'Joseph',
// },
// {
// age: 5,
// name: 'Richard',
// },
// {
// age: 28,
// name: 'Gary',
// },
// ],
// daughters: [
// {
// age: 15,
// name: 'Maria',
// },
// {
// age: 25,
// name: 'Kimberly',
// },
// {
// age: 27,
// name: 'Lisa',
// },
// ],
// },
// {
// id: 15,
// married: false,
// name: 'Edward Jackson',
// sons: [
// {
// age: 6,
// name: 'William',
// },
// {
// age: 16,
// name: 'Robert',
// },
// {
// age: 27,
// name: 'Jason',
// },
// ],
// daughters: [
// {
// age: 24,
// name: 'Carol',
// },
// {
// age: 17,
// name: 'Laura',
// },
// {
// age: 30,
// name: 'Shirley',
// },
// {
// age: 3,
// name: 'Deborah',
// },
// {
// age: 24,
// name: 'Sharon',
// },
// {
// age: 23,
// name: 'Jessica',
// },
// {
// age: 19,
// name: 'Angela',
// },
// {
// age: 10,
// name: 'Cynthia',
// },
// {
// age: 0,
// name: 'Patricia',
// },
// {
// age: 18,
// name: 'Jennifer',
// },
// ],
// },
],
}
let counter = 0
function getNodeMetadata(label: string) {
counter += 1
return {
id: counter,
shape: 'tree-node',
width: isNaN(+`${label}`) ? 70 : 28,
height: 26,
leaf: false,
attrs: {
label: {
textWrap: {
text: label,
},
},
},
}
}
function formatData(output: any, data: any, parent?: any) {
const { nodes, edges } = output
Object.keys(data).forEach((key) => {
const val = data[key]
const child = getNodeMetadata(key)
nodes.push(child)
if (parent) {
edges.push({
source: parent.id,
target: child.id,
shape: 'tree-edge',
})
}
if (Array.isArray(val) || typeof val === 'object') {
formatData(output, val, child)
} else {
const sub = getNodeMetadata(val)
const edge = {
source: child.id,
target: sub.id,
shape: 'tree-edge',
}
sub.leaf = true
nodes.push(sub)
edges.push(edge)
}
})
}
export function getData() {
const output = { nodes: [], edges: [] }
formatData(output, data)
var g = new dagre.graphlib.Graph({})
g.setGraph({ rankdir: 'LR', ranksep: 100, nodesep: 30 })
g.setDefaultEdgeLabel(() => ({}))
output.nodes.forEach((node: any) => {
g.setNode(node.id, { ...node })
})
output.edges.forEach((edge: any) => {
g.setEdge(edge.source, edge.target)
})
dagre.layout(g)
return {
nodes: g.nodes().map((id: string) => g.node(id)),
edges: output.edges,
}
}

View File

@ -1,94 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { getData } from './data'
import { TreeNode, TreeEdge } from './shape'
import '../../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
frozen: true,
async: true,
interacting: false,
grid: 1,
sorting: 'approx',
background: {
color: '#F3F7F6',
},
scroller: {
enabled: true,
},
connecting: {
anchor: 'orth',
connectionPoint: 'boundary',
router: {
name: 'er',
args: {
direction: 'H',
},
},
},
minimap: {
enabled: true,
container: document.getElementById('minimap')!,
},
})
graph.zoomTo(0.8)
var start = new Date().getTime()
const data = getData()
const nodes = data.nodes.map(({ leaf, ...metadata }: any) => {
const node = new TreeNode(metadata)
if (leaf) {
node.toggleButtonVisibility(leaf === false)
}
return node
})
const edges = data.edges.map(
(edge: any) => new TreeEdge({ source: edge.source, target: edge.target }),
)
graph.resetCells([...nodes, ...edges])
graph.unfreeze({
progress({ done }) {
if (done) {
const time = new Date().getTime() - start
console.log(time)
graph.unfreeze({ batchSize: 50 })
}
},
})
graph.on('node:collapse', ({ node }) => {
const treeNode = node as TreeNode
treeNode.toggleCollapse()
const collapsed = treeNode.isCollapsed()
const nodes = graph.getSuccessors(node) as TreeNode[]
nodes.forEach((node) => {
node.toggleVisible(collapsed)
})
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
<div id="minimap" />
</div>
)
}
}

View File

@ -1,125 +0,0 @@
import { Node, Edge, Shape } from '@antv/x6'
export class TreeNode extends Node {
private collapsed: boolean = false
protected postprocess() {
this.toggleCollapse(false)
}
isCollapsed() {
return this.collapsed === true
}
toggleButtonVisibility(visible: boolean) {
this.attr('buttonGroup', { display: visible ? 'block' : 'none' })
}
toggleCollapse(collapsed: boolean) {
const target = collapsed == null ? !this.collapsed : collapsed
if (!target) {
this.attr('buttonSign', { d: 'M 1 5 9 5 M 5 1 5 9', strokeWidth: 1.6 })
} else {
this.attr('buttonSign', { d: 'M 2 5 8 5', strokeWidth: 1.8 })
}
this.collapsed = target
}
}
TreeNode.config({
zIndex: 2,
markup: [
{
tagName: 'g',
selector: 'buttonGroup',
children: [
{
tagName: 'rect',
selector: 'button',
attrs: {
'pointer-events': 'visiblePainted',
},
},
{
tagName: 'path',
selector: 'buttonSign',
attrs: {
fill: 'none',
'pointer-events': 'none',
},
},
],
},
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
refWidth: '100%',
refHeight: '100%',
strokeWidth: 1,
fill: '#ffffff',
stroke: '#a0a0a0',
},
label: {
textWrap: {
ellipsis: true,
width: -10,
},
textAnchor: 'middle',
textVerticalAnchor: 'middle',
refX: '50%',
refY: '50%',
fontSize: 12,
},
buttonGroup: {
refX: '100%',
refY: '50%',
},
button: {
fill: '#4C65DD',
stroke: 'none',
x: -10,
y: -10,
height: 20,
width: 30,
rx: 10,
ry: 10,
cursor: 'pointer',
event: 'node:collapse',
},
buttonSign: {
refX: 5,
refY: -5,
stroke: '#FFFFFF',
strokeWidth: 1.6,
},
},
})
export class TreeEdge extends Shape.Edge {
isHidden() {
var node = this.getTargetNode() as TreeNode
return !node || !node.isVisible()
}
}
TreeEdge.config({
zIndex: 1,
attrs: {
line: {
stroke: '#a0a0a0',
strokeWidth: 1,
targetMarker: null,
},
},
})
Node.registry.register('tree-node', TreeNode, true)
Edge.registry.register('tree-edge', TreeEdge, true)

View File

@ -1,122 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import {
Connector,
IntermediateEvent,
UndevelopedEvent,
BasicEvent,
ExternalEvent,
ConditioningEvent,
} from './shapes'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 800,
connecting: {
connectionPoint: {
name: 'boundary',
args: { extrapolate: true },
},
connector: 'rounded',
router: 'orth',
},
sorting: 'approx',
async: true,
interacting: false,
frozen: true,
})
var events = [
IntermediateEvent.create('Fall from Scaffolding').gate('inhibit'),
IntermediateEvent.create('Fall from the Scaffolding', 'and').gate('and'),
IntermediateEvent.create('Safety Belt Not Working', 'or').gate('or'),
IntermediateEvent.create('Fall By Accident', 'or').gate('or'),
IntermediateEvent.create('Broken By Equipment', 'or').gate('or'),
IntermediateEvent.create('Did not Wear Safety Belt', 'or').gate('or'),
UndevelopedEvent.create('Slip and Fall'),
UndevelopedEvent.create('Lose Balance'),
UndevelopedEvent.create('Upholder Broken'),
BasicEvent.create('Safety Belt Broken'),
BasicEvent.create('Forgot to Wear'),
ExternalEvent.create('Take off When Walking'),
ConditioningEvent.create('Height and Ground Condition'),
]
var links = [
Connector.create(events[0], events[1]),
Connector.create(events[1], events[2]),
Connector.create(events[1], events[3]),
Connector.create(events[2], events[4]),
Connector.create(events[2], events[5]),
Connector.create(events[3], events[6]),
Connector.create(events[3], events[7]),
Connector.create(events[4], events[8]),
Connector.create(events[4], events[9]),
Connector.create(events[5], events[10]),
Connector.create(events[5], events[11]),
Connector.create(events[0], events[12]),
]
// function layout() {
// const autoLayoutElements: v1.Node[] = []
// const manualLayoutElements: v1.Node[] = []
// graph.model.getNodes().forEach(cell => {
// if (cell instanceof ConditioningEvent) {
// manualLayoutElements.push(cell)
// } else {
// autoLayoutElements.push(cell)
// }
// })
// // Automatic Layout
// v1.layout.DirectedGraph.layout(
// graph.model.getSubGraph(autoLayoutElements),
// {
// setVertices: true,
// marginX: 20,
// marginY: 20,
// },
// )
// // Manual Layout
// manualLayoutElements.forEach(node => {
// const neighbor = graph.model.getNeighbors(node, { incoming: true })[0]
// if (!neighbor) {
// return
// }
// const neighborPosition = neighbor.getBBox().getBottomRight()
// node.setPosition(
// neighborPosition.x + 20,
// neighborPosition.y - node.getSize().height / 2 - 20,
// )
// })
// }
graph.model.resetCells([...events, ...links] as any)
graph.unfreeze()
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div
style={{
width: '100%',
height: '100%',
backgroundColor: '#ffffff',
}}
>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,298 +0,0 @@
// import { Rect, Edge } from '@antv/x6/es/shape/standard'
// export class Event extends Rect {
// static create(label: string) {
// return new Event({
// attrs: {
// label: {
// text: label,
// },
// },
// })
// }
// }
// Event.config({
// zIndex: 3,
// attrs: {
// root: {
// pointerEvents: 'bounding-box',
// },
// body: {
// strokeWidth: 2,
// fillOpacity: 0.2,
// },
// label: {
// textWrap: {
// height: -20,
// width: -20,
// ellipsis: true,
// },
// refX: '50%',
// refY: '50%',
// fontSize: 16,
// fontFamily: 'sans-serif',
// fill: '#333333',
// textAnchor: 'middle',
// textVerticalAnchor: 'middle',
// },
// },
// })
// export class IntermediateEvent extends Event {
// static create(label: string, type?: string) {
// return new IntermediateEvent({
// attrs: {
// label: {
// text: label,
// },
// gate: {
// gateType: type,
// },
// },
// })
// }
// gateTypes = {
// or: 'M -20 0 C -20 -15 -10 -30 0 -30 C 10 -30 20 -15 20 0 C 10 -6 -10 -6 -20 0',
// xor: 'M -20 0 C -20 -15 -10 -30 0 -30 C 10 -30 20 -15 20 0 C 10 -6 -10 -6 -20 0 M -20 0 0 -30 M 0 -30 20 0',
// and: 'M -20 0 C -20 -25 -10 -30 0 -30 C 10 -30 20 -25 20 0 Z',
// priority_and:
// 'M -20 0 C -20 -25 -10 -30 0 -30 C 10 -30 20 -25 20 0 Z M -20 0 0 -30 20 0',
// inhibit: 'M -10 0 -20 -15 -10 -30 10 -30 20 -15 10 0 Z',
// transfer: 'M -20 0 20 0 0 -30 z',
// }
// gate(): string
// gate(type: string): this
// gate(type?: string) {
// if (type === undefined) {
// return this.attr<string>(['gate', 'gateType'])
// }
// this.attr(['gate'], {
// gateType: type,
// title: type.toUpperCase() + ' Gate',
// })
// return this
// }
// }
// IntermediateEvent.config(
// {
// size: {
// width: 100,
// height: 100,
// },
// markup: [
// {
// tagName: 'path',
// selector: 'gate',
// },
// {
// tagName: 'rect',
// selector: 'body',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// root: {
// title: 'Intermediate Event',
// },
// body: {
// refWidth: '100%',
// refHeight: -40,
// stroke: '#3c4260',
// fill: '#3c4260',
// },
// gate: {
// event: 'element:gate:click',
// gateType: 'xor',
// stroke: '#7c68fc',
// fill: '#7c68fc',
// fillOpacity: 0.2,
// strokeWidth: 2,
// refX: '50%',
// refY: '100%',
// fillRule: 'nonzero',
// cursor: 'pointer',
// },
// label: {
// textWrap: {
// height: -40,
// width: -10,
// },
// refY2: -20,
// },
// },
// },
// {
// gateType: {
// set(type: string) {
// var data = this.cell.gateTypes[type]
// return { d: data ? data + ' M 0 -30 0 -40' : 'M 0 0 0 0' }
// },
// },
// },
// )
// export class ExternalEvent extends Event {}
// ExternalEvent.config({
// size: {
// width: 80,
// height: 100,
// },
// markup: [
// {
// tagName: 'path',
// selector: 'body',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// root: {
// title: 'External Event',
// },
// body: {
// refD: 'M 0 0 10 -10 20 0 20 40 0 40 Z',
// stroke: '#fe854f',
// fill: '#fe854f',
// },
// },
// })
// export class UndevelopedEvent extends Event {}
// UndevelopedEvent.config({
// size: {
// width: 140,
// height: 80,
// },
// markup: [
// {
// tagName: 'path',
// selector: 'body',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// root: {
// title: 'Undeveloped Event',
// },
// body: {
// refD: 'M -1 0 0 1 1 0 0 -1 Z',
// stroke: '#feb663',
// fill: '#feb663',
// },
// },
// })
// export class BasicEvent extends Event {}
// BasicEvent.config({
// zIndex: 3,
// size: {
// width: 80,
// height: 80,
// },
// markup: [
// {
// tagName: 'circle',
// selector: 'body',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// root: {
// title: 'Basic Event',
// },
// body: {
// refCx: '50%',
// refCy: '50%',
// refR: '50%',
// stroke: '#30d0c6',
// fill: '#30d0c6',
// },
// },
// })
// export class ConditioningEvent extends Event {}
// ConditioningEvent.config({
// zIndex: 2,
// size: {
// width: 140,
// height: 80,
// },
// markup: [
// {
// tagName: 'ellipse',
// selector: 'body',
// },
// {
// tagName: 'text',
// selector: 'label',
// },
// ],
// attrs: {
// root: {
// title: 'Conditioning Event',
// },
// body: {
// refCx: '50%',
// refCy: '50%',
// refRx: '50%',
// refRy: '50%',
// stroke: '#7c68fc',
// fill: '#7c68fc',
// fillOpacity: 0.2,
// },
// },
// })
// export class Connector extends Edge {
// static create(event1: Event, event2: Event) {
// return new Connector({
// zIndex: 1,
// source: {
// cell: event1.id,
// selector: event1 instanceof IntermediateEvent ? 'gate' : 'body',
// },
// target: {
// cell: event2.id,
// selector: 'body',
// },
// })
// }
// }
// Connector.config({
// attrs: {
// line: {
// connection: true,
// stroke: '#333333',
// strokeWidth: 2,
// strokeLinejoin: 'round',
// },
// },
// markup: [
// {
// tagName: 'path',
// selector: 'line',
// attrs: {
// fill: 'none',
// 'pointer-events': 'none',
// },
// },
// ],
// })

View File

@ -1,4 +1,6 @@
import { Graph, FunctionExt, Vector, Rectangle } from '@antv/x6'
import { Graph } from '@antv/x6'
import { FunctionExt, Vector } from '@antv/x6-common'
import { Rectangle } from '@antv/x6-geometry'
import { FitToContentCard } from './fit-card'
import { ScaleContentToFitCard } from './scale-card'

View File

@ -1,5 +1,4 @@
import React from 'react'
import { Grid } from '@antv/x6/es/definition/grid'
import { Input, Select, Slider, Card, Row, Col } from 'antd'
export class GridCard extends React.Component<GridCard.Props, GridCard.State> {
@ -232,11 +231,11 @@ export class GridCard extends React.Component<GridCard.Props, GridCard.State> {
export namespace GridCard {
export interface Props {
onGridSizeChange: (size: number) => void
onChange: (res: Grid.NativeItem) => void
onChange: (res: any) => void
}
export interface State {
type: Grid.NativeNames
type: any
size: number
color: string
thickness: number

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, Rectangle } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Rectangle } from '@antv/x6-geometry'
import '../index.less'
import './index.less'
import { render } from './render'
@ -77,12 +78,12 @@ export default class Example extends React.Component<
}),
})
})
.on('translate', ({ origin: { x, y } }) => {
.on('translate', ({ tx, ty }) => {
this.effect.hideAll()
this.setState({
attrs: getAttrs({
originX: x,
originY: y,
originX: tx,
originY: ty,
}),
})
})
@ -107,13 +108,11 @@ export default class Example extends React.Component<
this.container = container
}
onBackgroundChanged = (
options: Graph.BackgroundManager.BackgroundOptions,
) => {
onBackgroundChanged = (options: Graph.BackgroundManager.Options) => {
this.graph.drawBackground(options)
}
onGridChanged = (options: Graph.GridManager.NativeItem) => {
onGridChanged = (options: Graph.GridManager.Options) => {
this.graph.drawGrid(options)
}
@ -126,7 +125,7 @@ export default class Example extends React.Component<
}
onGraphOriginChanged = (ox: number, oy: number) => {
this.graph.setOrigin(ox, oy)
this.graph.translate(ox, oy)
this.setState((prevState) => ({
attrs: {
...prevState.attrs,

View File

@ -1,212 +0,0 @@
{
"groups": [
{
"id": "group1",
"shape": "react-shape",
"x": 376,
"y": 202,
"width": 250,
"height": 150,
"component": "group",
"children": [],
"zIndex": -1,
"isGroup": true,
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"stroke": "#c5c5c5",
"strokeWidth": 1
}
}
}
},
"items": [
{
"id": "group1-1",
"group": "in"
}
]
}
}
],
"nodes": [
{
"id": "1",
"shape": "rect",
"x": 440,
"y": 240,
"width": 160,
"height": 32,
"attrs": {
"text": {
"text": "inside node 1"
},
"body": {
"fill": "#ffffff",
"stroke": "#5f95ff",
"rx": 4,
"ry": 4
}
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"stroke": "#5f95ff",
"strokeWidth": 1
}
}
}
},
"items": [
{
"id": "1-1",
"group": "in"
}
]
},
"group": "group1",
"zIndex": 1
},
{
"id": "2",
"shape": "rect",
"x": 440,
"y": 290,
"width": 160,
"height": 32,
"attrs": {
"text": {
"text": "inside node 2"
},
"body": {
"fill": "#ffffff",
"stroke": "#5f95ff",
"rx": 4,
"ry": 4
}
},
"ports": {
"groups": {
"in": {
"position": "left",
"attrs": {
"circle": {
"r": 4,
"stroke": "#5f95ff",
"strokeWidth": 1
}
}
}
},
"items": [
{
"id": "2-1",
"group": "in"
}
]
},
"group": "group1",
"zIndex": 2
},
{
"id": "3",
"shape": "rect",
"x": 154,
"y": 262,
"width": 160,
"height": 32,
"attrs": {
"text": {
"text": "outside node"
},
"body": {
"fill": "#ffffff",
"stroke": "#5f95ff",
"rx": 4,
"ry": 4
}
},
"ports": {
"groups": {
"in": {
"position": "right",
"attrs": {
"circle": {
"r": 4,
"stroke": "#5f95ff",
"strokeWidth": 1
}
}
}
},
"items": [
{
"id": "3-1",
"group": "in"
}
]
},
"zIndex": 3
}
],
"edges": [
{
"id": "edge1",
"attrs": {
"line": {
"stroke": "#a2b1c3"
}
},
"source": {
"cell": "3",
"port": "3-1"
},
"target": {
"cell": "1",
"port": "1-1"
},
"originSource": {
"cell": "3",
"port": "3-1"
},
"originTarget": {
"cell": "1",
"port": "1-1"
},
"for": ""
},
{
"id": "edge2",
"attrs": {
"line": {
"stroke": "#a2b1c3"
}
},
"source": {
"cell": "3",
"port": "3-1"
},
"target": {
"cell": "2",
"port": "2-1"
},
"originSource": {
"cell": "3",
"port": "3-1"
},
"originTarget": {
"cell": "2",
"port": "2-1"
},
"for": ""
}
]
}

View File

@ -1,28 +0,0 @@
.group {
width: 100%;
height: 100%;
background: #fafafa;
border: 1px solid #c5c5c5;
border-radius: 4px;
}
.group .header {
width: 100%;
height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
}
.group img {
margin-left: 12px;
margin-right: 8px;
}
.group .btn {
display: flex;
justify-content: center;
align-items: center;
width: 16px;
height: 16px;
margin: 1px 8px 0 0;
border: 1px solid #c5c5c5;
border-radius: 2px;
}

View File

@ -1,310 +1,175 @@
// import React from 'react'
// import { Graph, Node } from '@antv/x6'
// import '@antv/x6-react-shape'
// import data from './data.json'
// import '../index.less'
// import './index.less'
import React from 'react'
import { Graph, Node } from '@antv/x6'
import '@antv/x6-react-shape'
import { PlusSquareOutlined, MinusSquareOutlined } from '@ant-design/icons'
import { Group } from './shape'
import '../index.less'
// //#region react component
// interface IProps {
// node?: Node
// }
// interface IState {
// collapsed: boolean
// }
// class GroupComponent extends React.Component<IProps, IState> {
// state = {
// collapsed: false,
// }
export default class Example extends React.Component {
private container: HTMLDivElement
// shouldComponentUpdate(nextProps: IProps, nextState: IState) {
// return nextState.collapsed !== this.state.collapsed
// }
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 600,
grid: true,
})
// onCollapse = () => {
// const node = this.props.node
// const target = !this.state.collapsed
const createGroup = (
id: string,
x: number,
y: number,
width: number,
height: number,
fill: string,
) => {
const group = new Group({
id,
x,
y,
width,
height,
attrs: {
body: { fill },
},
})
graph.addNode(group)
return group
}
// if (node) {
// const cells = node.getChildren()
// if (cells) {
// cells.forEach((cell: Node) => {
// if (target) {
// cell.hide()
// } else {
// cell.show()
// }
// })
// }
// if (target) {
// node.prop('previousSize', node.size())
// node.size(160, 32)
// } else {
// const previousSize = node.prop('previousSize')
// node.size(previousSize.width, previousSize.height)
// }
// }
const createNode = (
id: string,
x: number,
y: number,
width: number,
height: number,
fill: string,
) => {
return graph.addNode({
id,
x,
y,
width,
height,
attrs: {
body: {
fill: fill || 'blue',
},
label: {
text: id,
fill: 'white',
refX: 10,
refY: 10,
textAnchor: 'start',
},
},
})
}
// this.setState({
// collapsed: target,
// })
// }
const createEdge = (
id: string,
source: string,
target: string,
vertices?: { x: number; y: number }[],
) => {
return graph.addEdge({
id,
source,
target,
vertices: vertices,
label: id,
})
}
// render() {
// return (
// <div className="group">
// <div className="header">
// <span>
// <img
// src="https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*rYacTYE0PR0AAAAAAAAAAAAAARQnAQ"
// alt="group"
// />
// <span>Group</span>
// </span>
// <span className="btn" onClick={this.onCollapse}>
// {this.state.collapsed ? '+' : '-'}
// </span>
// </div>
// </div>
// )
// }
// }
// Graph.registerReactComponent('group', <GroupComponent />, true)
// //#endregion
const a = createGroup('a', 100, 30, 480, 320, 'lightblue')
const aa = createGroup('aa', 180, 80, 160, 140, 'green')
const aaa = createGroup('aaa', 200, 120, 120, 40, 'gray')
const c = createNode('c', 450, 200, 50, 50, 'orange')
// export default class Example extends React.Component {
// private graph: Graph
// private container: HTMLDivElement
a.addChild(aa)
aa.addChild(aaa)
a.addChild(c)
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 800,
// height: 600,
// grid: true,
// connecting: {
// connector: 'smooth',
// },
// })
createNode('d', 680, 80, 50, 50, 'black')
// this.graph = graph
// this.initShape()
// this.initEvent()
// }
const l1 = createEdge('l1', 'aa', 'c') // auto embed to common ancestor `a`
console.log(l1)
createEdge('l3', 'c', 'd')
aa.addChild(
createEdge('l2', 'aa', 'aaa', [
{ x: 50, y: 110 },
{ x: 50, y: 180 },
]),
)
// initShape = () => {
// const nodes = data.nodes
// const edges = data.edges
// const groups = data.groups
// const newEdges: typeof edges = []
graph.on('node:collapse', ({ node }: { node: Group }) => {
node.toggleCollapse()
const collapsed = node.isCollapsed()
const cells = node.getDescendants()
cells.forEach((node) => {
if (collapsed) {
node.hide()
} else {
node.show()
}
})
})
// const getNode = (nodeId: string) => {
// if (nodeId) {
// return nodes.find((node) => node.id === nodeId)
// }
// return null
// }
graph.addNode({
shape: 'react-shape',
x: 320,
y: 420,
width: 160,
height: 60,
component: (node: Node) => {
const data = node.getData<any>() || {}
const collapsed = data.collapsed === true
return (
<div style={{ background: '#f5f5f5', width: '100%', height: '100%' }}>
{collapsed ? (
<PlusSquareOutlined
style={{ cursor: 'pointer' }}
event="react:collapse"
/>
) : (
<MinusSquareOutlined
style={{ cursor: 'pointer' }}
event="react:collapse"
/>
)}
// const getGroup = (groupId: string | undefined) => {
// if (groupId) {
// return groups.find((group) => group.id === groupId)
// }
// return null
// }
{node.attr('body/fill')}
</div>
)
},
})
// // 将连接到群组内部节点的连线进行拆分
// // source target op
// // √ X source->群组->target
// // X √ source->群组->target
// // √ √ source->群组1->群组2->target
// edges.forEach((edge) => {
// const sourceNodeId =
// typeof edge.source === 'string' ? edge.source : edge.source.cell
// const targetNodeId =
// typeof edge.target === 'string' ? edge.target : edge.target.cell
// const sourceNode = getNode(sourceNodeId)
// const targetNode = getNode(targetNodeId)
// const sourceGroup = getGroup(sourceNode?.group)
// const targetGroup = getGroup(targetNode?.group)
graph.on('react:collapse', ({ node }: { node: Node }) => {
const data = node.getData<any>() || {}
const collapsed = !(data.collapsed === true)
node.updateData({ collapsed })
node.resize(collapsed ? 80 : 160, collapsed ? 30 : 60)
const cells = node.getDescendants()
cells.forEach((node) => {
if (collapsed) {
node.hide()
} else {
node.show()
}
})
})
}
// if (sourceGroup !== targetGroup) {
// if (sourceGroup && targetGroup) {
// const sourceGroupPort = {
// cell: sourceGroup.id,
// port: sourceGroup.ports.items[0].id,
// }
// const targetGroupPort = {
// cell: targetGroup.id,
// port: targetGroup.ports.items[0].id,
// }
// newEdges.push(
// ...[
// {
// ...edge,
// source: edge.source,
// target: sourceGroupPort,
// id: `${edge.id}_1`,
// for: edge.id,
// },
// {
// ...edge,
// source: targetGroupPort,
// target: edge.target,
// id: `${edge.id}_2`,
// for: edge.id,
// },
// ],
// )
// edge.source = sourceGroupPort
// edge.target = targetGroupPort
refContainer = (container: HTMLDivElement) => {
this.container = container
}
// const sourceChildren = sourceGroup.children as string[]
// const targetChildren = targetGroup.children as string[]
// sourceChildren.push(sourceNode!.id)
// targetChildren.push(targetNode!.id)
// } else if (sourceGroup) {
// const sourceGroupPort = {
// cell: sourceGroup.id,
// port: sourceGroup.ports.items[0].id,
// }
// newEdges.push(
// ...[
// {
// ...edge,
// source: edge.source,
// target: sourceGroupPort,
// id: `${edge.id}_1`,
// for: edge.id,
// },
// ],
// )
// edge.source = sourceGroupPort
// const children = sourceGroup.children as string[]
// children.push(sourceNode!.id)
// } else if (targetGroup) {
// const targetGroupPort = {
// cell: targetGroup.id,
// port: targetGroup.ports.items[0].id,
// }
// newEdges.push(
// ...[
// {
// ...edge,
// source: targetGroupPort,
// target: edge.target,
// id: `${edge.id}_1`,
// for: edge.id,
// },
// ],
// )
// edge.target = targetGroupPort
// const children = targetGroup.children as string[]
// children.push(targetNode!.id)
// }
// }
// })
// this.graph.addNodes([...nodes, ...groups])
// this.graph.addEdges([...edges, ...newEdges])
// }
// initEvent = () => {
// const graph = this.graph
// graph.on('node:moving', ({ node }) => {
// const isGroup = node.prop('isGroup')
// if (isGroup) {
// node.prop('originPosition', node.getPosition())
// return
// }
// const groupId = node.prop('group')
// const group = graph.getNodes().find((node) => node.id === groupId)
// if (!group) {
// return
// }
// let hasChange = false
// let originSize = group.prop('originSize')
// if (originSize == null) {
// originSize = group.size()
// group.prop('originSize', originSize)
// }
// let originPosition = group.prop('originPosition')
// if (originPosition == null) {
// originPosition = group.position()
// group.prop('originPosition', originPosition)
// }
// let x = originPosition.x
// let y = originPosition.y
// let cornerX = originPosition.x + originSize.width
// let cornerY = originPosition.y + originSize.height
// const childs = group.getChildren()
// if (childs) {
// childs.forEach((child) => {
// const bbox = child.getBBox().inflate(32)
// const corner = bbox.getCorner()
// if (bbox.x < x) {
// x = bbox.x
// hasChange = true
// }
// if (bbox.y < y) {
// y = bbox.y
// hasChange = true
// }
// if (corner.x > cornerX) {
// cornerX = corner.x
// hasChange = true
// }
// if (corner.y > cornerY) {
// cornerY = corner.y
// hasChange = true
// }
// })
// }
// if (hasChange) {
// group.prop({
// position: { x, y },
// size: { width: cornerX - x, height: cornerY - y },
// })
// }
// })
// }
// toJson = () => {
// const res = this.graph.toJSON()
// const cells = res.cells
// res.cells = cells
// .filter((cell) => !cell.for)
// .map((cell) => {
// if (cell.shape === 'edge') {
// return {
// ...cell,
// source: cell.originSource || cell.source,
// target: cell.originTarget || cell.target,
// }
// }
// return cell
// })
// return res
// }
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,91 +0,0 @@
// import React from 'react'
// import { Graph } from '@antv/x6'
// import { Halo } from '@antv/x6/es/addon/halo'
// import '../index.less'
// import '../../../../../packages/x6/src/addon/halo/index.less'
// export default class Example extends React.Component {
// private container: HTMLDivElement
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 800,
// height: 600,
// grid: 1,
// })
// graph.addNode({
// shape: 'rect',
// x: 50,
// y: 50,
// width: 100,
// height: 40,
// attrs: { label: { text: 'A' } },
// })
// graph.addNode({
// shape: 'circle',
// x: 250,
// y: 80,
// width: 50,
// height: 50,
// attrs: { label: { text: 'B' } },
// })
// graph.addNode({
// shape: 'ellipse',
// x: 350,
// y: 150,
// width: 80,
// height: 40,
// attrs: { label: { text: 'C' } },
// })
// graph.on('node:resize', (args) => {
// console.log('node:resize', args)
// })
// graph.on('node:resizing', (args) => {
// console.log('node:resizing', args)
// })
// graph.on('node:resized', (args) => {
// console.log('node:resized', args)
// })
// graph.on('node:rotate', (args) => {
// console.log('node:rotate', args)
// })
// graph.on('node:rotating', (args) => {
// console.log('node:rotating', args)
// })
// graph.on('node:rotated', (args) => {
// console.log('node:rotated', args)
// })
// graph.on('node:mouseup', ({ view }) => {
// const halo = new Halo({
// view,
// // type: 'toolbar',
// // pie: { sliceAngle: 360 / 7 },
// })
// console.log(halo)
// })
// }
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }

View File

@ -0,0 +1,10 @@
.custom-html {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-size: 16px;
border: 1px solid #000;
}

View File

@ -1,7 +1,21 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Graph, Cell, Shape } from '@antv/x6'
import '../index.less'
import './index.less'
Shape.HTML.register({
shape: 'custom-html',
width: 160,
height: 80,
effect: ['data'],
html(cell: Cell) {
const data = cell.getData()
const div = document.createElement('div')
div.className = 'custom-html'
div.innerHTML = data.time
return div
},
})
export default class Example extends React.Component {
private container: HTMLDivElement
@ -13,50 +27,26 @@ export default class Example extends React.Component {
grid: true,
})
const source = graph.addNode({
shape: 'html',
const node = graph.addNode({
shape: 'custom-html',
x: 80,
y: 80,
width: 160,
height: 60,
html: () => {
const wrap = document.createElement('div')
wrap.style.width = '100%'
wrap.style.height = '100%'
wrap.style.background = '#f0f0f0'
wrap.style.display = 'flex'
wrap.style.justifyContent = 'center'
wrap.style.alignItems = 'center'
wrap.innerText = 'hello'
return wrap
data: {
time: Date.now(),
},
})
console.log(graph.toJSON())
const wrap = document.createElement('div')
wrap.style.width = '100%'
wrap.style.height = '100%'
wrap.style.background = '#f0f0f0'
wrap.style.display = 'flex'
wrap.style.justifyContent = 'center'
wrap.style.alignItems = 'center'
const change = () => {
setTimeout(() => {
node.setData({
time: Date.now(),
})
change()
}, 1000)
}
wrap.innerText = 'world'
const target = graph.addNode({
shape: 'html',
x: 320,
y: 320,
width: 160,
height: 60,
html: wrap,
})
graph.addEdge({
source,
target,
})
change()
}
refContainer = (container: HTMLDivElement) => {

View File

@ -1,61 +0,0 @@
import React from 'react'
import { Graph, Cell } from '@antv/x6'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: true,
})
const node = graph.addNode({
shape: 'html',
x: 80,
y: 80,
width: 160,
height: 60,
data: {
time: new Date().toString(),
},
html: {
render(node: Cell) {
const data = node.getData() as any
return `<div>
<span>${data.time}</span>
</div>`
},
shouldComponentUpdate(node: Cell) {
return node.hasChanged('data')
},
},
})
const change = () => {
setTimeout(() => {
node.setData({
time: new Date().toString(),
})
change()
}, 1000)
}
change()
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,140 +1,143 @@
// import React from 'react'
// import { Node, Vector, Graph, Point, Line, Polyline } from '@antv/x6'
// import { Connector } from '@antv/x6/es/registry/connector'
// import '../index.less'
import React from 'react'
import { Node, Graph } from '@antv/x6'
import { Vector } from '@antv/x6-common'
import { Point, Line, Polyline } from '@antv/x6-geometry'
import { Connector } from '@antv/x6/es/registry/connector'
import '../index.less'
// function random(max: number, min: number) {
// return Math.floor(Math.random() * (max - min)) + min
// }
function random(max: number, min: number) {
return Math.floor(Math.random() * (max - min)) + min
}
// export default class Example extends React.Component {
// private container: HTMLDivElement
export default class Example extends React.Component {
private container: HTMLDivElement
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 800,
// height: 600,
// grid: 1,
// translating: {
// restrict: {
// x: 50,
// y: 50,
// width: 700,
// height: 500,
// },
// },
// })
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: 1,
translating: {
restrict: {
x: 50,
y: 50,
width: 700,
height: 500,
},
},
async: false,
})
// function createCircle(x: number, y: number, group: 'inner' | 'outer') {
// const node = graph.addNode({
// shape: 'circle',
// size: { width: 20, height: 20 },
// position: { x: x, y: y },
// group: group,
// attrs: {
// body: {
// strokeWidth: 3,
// fill: group === 'inner' ? '#af9bff' : '#31d0c6',
// stroke: group === 'inner' ? '#7c68fc' : '#009d93',
// },
// },
// })
function createCircle(x: number, y: number, group: 'inner' | 'outer') {
const node = graph.addNode({
shape: 'circle',
size: { width: 20, height: 20 },
position: { x: x, y: y },
group: group,
attrs: {
body: {
strokeWidth: 3,
fill: group === 'inner' ? '#af9bff' : '#31d0c6',
stroke: group === 'inner' ? '#7c68fc' : '#009d93',
},
},
})
// node.on('change:position', updateBoundaries)
// }
node.on('change:position', updateBoundaries)
}
// function createBoundary(color: string) {
// var boundary = Vector.create('path').attr({
// fill: color,
// 'fill-opacity': 0.2,
// stroke: color,
// 'stroke-width': 3,
// })
function createBoundary(color: string) {
var boundary = Vector.create('path').attr({
fill: color,
'fill-opacity': 0.2,
stroke: color,
'stroke-width': 3,
})
// Vector.create(graph.view.stage).prepend(boundary)
Vector.create(graph.view.stage).prepend(boundary)
// return boundary
// }
return boundary
}
// function updateBoundaries() {
// var padding = 10
function updateBoundaries() {
var padding = 10
// var innerPoints = getPointsByGroup('inner', padding)
// var outerPoints = getPointsByGroup('outer', padding)
var innerPoints = getPointsByGroup('inner', padding)
var outerPoints = getPointsByGroup('outer', padding)
// var innerHullPoints = convexHullAlgorithm(innerPoints)
// var innerBoundaryPoints = getPaddedPoints(innerHullPoints, padding)
// var outerHullPoints = convexHullAlgorithm(
// outerPoints.concat(innerBoundaryPoints),
// )
var innerHullPoints = convexHullAlgorithm(innerPoints)
var innerBoundaryPoints = getPaddedPoints(innerHullPoints, padding)
var outerHullPoints = convexHullAlgorithm(
outerPoints.concat(innerBoundaryPoints),
)
// innerBoundary.attr('d', createData(innerHullPoints))
// outerBoundary.attr('d', createData(outerHullPoints))
// }
innerBoundary.attr('d', createData(innerHullPoints))
outerBoundary.attr('d', createData(outerHullPoints))
}
// function getPointsByGroup(group: 'inner' | 'outer', padding: number) {
// var node = graph.model.getNodes().filter((node) => {
// return node.getProp('group') === group
// })
function getPointsByGroup(group: 'inner' | 'outer', padding: number) {
var node = graph.model.getNodes().filter((node) => {
return node.getProp('group') === group
})
// return node.reduce<Point[]>((memo, el) => {
// return memo.concat(getNodeCornerPoints(el, padding))
// }, [])
// }
return node.reduce<Point[]>((memo, el) => {
return memo.concat(getNodeCornerPoints(el, padding))
}, [])
}
// function getNodeCornerPoints(node: Node, padding: number = 0) {
// var bbox = node.getBBox().inflate(padding)
// return [bbox.origin, bbox.bottomLeft, bbox.corner, bbox.topRight]
// }
function getNodeCornerPoints(node: Node, padding: number = 0) {
var bbox = node.getBBox().inflate(padding)
return [bbox.origin, bbox.bottomLeft, bbox.corner, bbox.topRight]
}
// function getPaddedPoints(points: Point[], padding: number = 0) {
// return points.reduce<Point[]>((memo, point) => {
// memo.push(
// point.clone().translate(padding, padding),
// point.clone().translate(-padding, padding),
// point.clone().translate(padding, -padding),
// point.clone().translate(-padding, -padding),
// )
// return memo
// }, [])
// }
function getPaddedPoints(points: Point[], padding: number = 0) {
return points.reduce<Point[]>((memo, point) => {
memo.push(
point.clone().translate(padding, padding),
point.clone().translate(-padding, padding),
point.clone().translate(padding, -padding),
point.clone().translate(-padding, -padding),
)
return memo
}, [])
}
// function createData(points: Point[], radius?: number) {
// var origin = new Line(points[0], points[points.length - 1]).getCenter()
// return Connector.presets.rounded.call(this, origin, origin, points, {
// radius: radius || 30,
// })
// }
function createData(points: Point[], radius?: number) {
var origin = new Line(points[0], points[points.length - 1]).getCenter()
return Connector.presets.rounded.call(this, origin, origin, points, {
radius: radius || 30,
})
}
// function convexHullAlgorithm(points: Point[]) {
// return new Polyline(points).toHull().points
// }
function convexHullAlgorithm(points: Point[]) {
return new Polyline(points).toHull().points
}
// // bootstrap
// // ---------
// Array.from({ length: 10 }).forEach((_, i) => {
// var x = random(100, 700)
// var y = random(100, 500)
// createCircle(x, y, i % 3 === 0 ? 'inner' : 'outer')
// })
// bootstrap
// ---------
Array.from({ length: 10 }).forEach((_, i) => {
var x = random(100, 700)
var y = random(100, 500)
createCircle(x, y, i % 3 === 0 ? 'inner' : 'outer')
})
// // create boundaries around elements
// var innerBoundary = createBoundary('#fe854f')
// var outerBoundary = createBoundary('#feb663')
// create boundaries around elements
var innerBoundary = createBoundary('#fe854f')
var outerBoundary = createBoundary('#feb663')
// updateBoundaries()
// }
updateBoundaries()
}
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
refContainer = (container: HTMLDivElement) => {
this.container = container
}
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// </div>
// )
// }
// }
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -3,57 +3,150 @@ import { Table } from 'antd'
import './index.less'
const dataSource = [
// graph
{
key: '1',
example: 'animation/transition',
description: 'transition 动画',
example: 'graph',
description: '画布',
},
{
example: 'position',
description: '画布定位',
},
{
key: '2',
example: 'auto-resize',
description: '画布大小自适应',
},
// node
{
example: 'html',
description: 'HTML 节点',
},
{
example: 'group',
description: '群组',
},
{
example: 'embed/dnd',
description: '嵌入',
},
{
example: 'react',
description: 'React 节点',
},
{
example: 'react/portal',
description: 'Portal 使用方式',
},
// port
{
example: 'ports/defaults',
description: '连接桩增删',
},
{
example: 'ports/connected',
description: '定义连接桩形状',
},
// edge
{
example: 'edge',
description: '连线',
},
{
example: 'router',
description: 'Manhattan 路由',
},
{
example: 'edge/tool/arrowhead',
description: '箭头工具',
},
{
example: 'edge/tool/button',
description: '按钮工具',
},
{
example: 'edge/custom-connector',
description: '自定义连接器',
},
{
example: 'edge/custom-router',
description: '自定义路由',
},
{
example: 'edge/native-marker',
description: '内置箭头',
},
{
example: 'edge/custom-marker',
description: '自定义箭头',
},
{
example: 'edge/edge-editor',
description: '路径编辑器',
},
{
example: 'connector/offset-rounded',
description: '带偏移的圆角连接器',
},
{
example: 'connector/xmind-curve',
description: '脑图连接器',
},
// case
{
key: '3',
example: 'case/bpmn',
description: 'BPMN 图',
},
{
key: '4',
example: 'case/class',
description: '类图',
},
{
key: '5',
example: 'case/dag',
description: 'DAG 图',
},
{
key: '6',
example: 'case/elk',
description: 'ELK 图',
},
{
key: '7',
example: 'case/er',
description: 'ER 图',
},
{
key: '8',
example: 'case/mind',
description: '脑图',
},
{
key: '9',
example: 'case/swimlane',
description: '泳道图',
},
{
key: '10',
example: 'org',
description: '组织架构图',
},
// plugin
{
example: 'snapline',
description: '对齐线',
},
]
{
example: 'clipboard',
description: '剪切板',
},
{
example: 'keyboard',
description: '快捷键',
},
{
example: 'dnd',
description: 'Dnd',
},
// animation
{
example: 'animation/transition',
description: '动画',
},
].map((item, index) => ({ key: index, ...item }))
const columns = [
{

View File

@ -1,6 +1,7 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Selection } from '@antv/x6-plugin-selection'
import '../index.less'
export default class Example extends React.Component {
@ -12,29 +13,12 @@ export default class Example extends React.Component {
width: 800,
height: 600,
grid: true,
// selecting: {
// enabled: true,
// showNodeSelectionBox: true,
// },
// clipboard: {
// enabled: true,
// },
// keyboard: {
// enabled: true,
// global: false,
// },
})
graph.use(
new Keyboard({
enabled: true,
}),
)
const keyboard = graph.getPlugin('keyboard') as Keyboard
keyboard.bindKey('backspace', () => {
console.log('backspace')
})
const selection = new Selection({ enabled: true })
const keyboard = new Keyboard({ enabled: true })
graph.use(selection)
graph.use(keyboard)
graph.addNode({
x: 50,
@ -60,30 +44,9 @@ export default class Example extends React.Component {
attrs: { label: { text: 'C' } },
})
// graph.bindKey('meta+c', () => {
// const cells = graph.getSelectedCells()
// if (cells.length) {
// graph.copy(cells)
// }
// return false
// })
// graph.bindKey('meta+v', () => {
// if (!graph.isClipboardEmpty()) {
// const cells = graph.paste({ offset: 32 })
// graph.resetSelection(cells)
// }
// console.log(graph.toJSON())
// return false
// })
// graph.bindKey('backspace', () => {
// graph.removeCells(graph.getSelectedCells())
// })
// graph.on('selection:changed', ({ selected }) => {
// console.log(selected)
// })
keyboard.bindKey('backspace', () => {
graph.removeCells(selection.getSelectedCells())
})
}
refContainer = (container: HTMLDivElement) => {

View File

@ -1,591 +0,0 @@
/* eslint-disable @typescript-eslint/no-use-before-define */
import {
Graph,
Node,
Edge,
Shape,
EdgeView,
Line,
Angle,
Registry,
} from '@antv/x6'
Graph.registerNode(
'angle-node',
Shape.Rect.define({
width: 120,
height: 120,
zIndex: 1,
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#d9d9d9',
strokeWidth: 1,
rx: 2,
ry: 2,
},
},
}),
true,
)
Graph.registerEdge(
'angle-edge',
{
markup: [
{
tagName: 'path',
selector: 'wrap',
attrs: {
fill: 'none',
cursor: 'pointer',
stroke: 'transparent',
strokeLinecap: 'round',
},
},
{
tagName: 'path',
selector: 'line',
attrs: {
fill: 'none',
pointerEvents: 'none',
},
},
{
tagName: 'path',
selector: 'sourceAngle',
groupSelector: 'angles',
attrs: {
cursor: 'pointer',
},
},
{
tagName: 'path',
selector: 'targetAngle',
groupSelector: 'angles',
attrs: {
cursor: 'pointer',
},
},
{
tagName: 'text',
selector: 'sourceAngleLabel',
groupSelector: 'angleLabels',
},
{
tagName: 'text',
selector: 'targetAngleLabel',
groupSelector: 'angleLabels',
},
],
attrs: {
wrapper: {
connection: true,
strokeWidth: 10,
strokeLinejoin: 'round',
},
line: {
connection: true,
stroke: '#333333',
strokeWidth: 2,
strokeLinejoin: 'round',
},
angles: {
stroke: '#333333',
fill: 'none',
strokeWidth: 1,
angle: {
radius: 40,
pie: false,
},
},
sourceAngle: {
angle: {
type: 'source',
},
},
targetAngle: {
angle: {
type: 'target',
},
},
angleLabels: {
textAnchor: 'middle',
textVerticalAnchor: 'middle',
fill: '#333',
fontSize: 11,
fontFamily: 'sans-serif',
angleText: {
distance: 23,
precision: 0,
},
},
sourceAngleLabel: {
angleText: {
type: 'source',
},
},
targetAngleLabel: {
angleText: {
type: 'target',
},
},
},
attrHooks: {
angle: {
set(val, { view }) {
const defaults = { d: 'M 0 0 0 0' }
if (val == null || typeof val !== 'object') {
return defaults
}
const attr = val as {} as AngleEdge.AngleOptions
var angleRadius = attr.radius || 40
var angleStart = attr.start || 'self'
var anglePie = attr.pie || false
var angleDirection = attr.direction || 'small'
const meta = AngleEdge.getArcMeta(view as EdgeView, attr.type, {
angle: attr.value,
radius: angleRadius,
start: angleStart,
direction: angleDirection,
})
if (meta == null) {
return defaults
}
var connectionPoint = meta.connectionPoint,
linkArcPoint = meta.arcPoint1,
otherArcPoint = meta.arcPoint2,
arcAngle = meta.arcAngle,
largeArcFlag = meta.largeArcFlag,
sweepFlag = meta.sweepFlag
const segments = [
`M ${linkArcPoint.x} ${linkArcPoint.y}`,
// A rx ry x-axis-rotation large-arc-flag sweep-flag x y
`A ${angleRadius} ${angleRadius} ${arcAngle} ${largeArcFlag} ${sweepFlag} ${otherArcPoint.x} ${otherArcPoint.y}`,
]
if (anglePie) {
segments.push(`L ${connectionPoint.x} ${connectionPoint.y} Z`)
}
return {
d: segments.join(' '),
}
},
},
angleText: {
set(val, options) {
let text = ''
const view = options.view as EdgeView
const attr = val as {} as AngleEdge.AngleTextOptions
let meta = AngleEdge.getArcMeta(view, attr.type, { radius: 40 })
if (meta) {
text = AngleEdge.getAngleText({
angle: meta.angleBetween,
precision: attr.precision,
})
}
const raw = Registry.Attr.presets.text as Registry.Attr.SetDefinition
raw.set.call(this, text, options)
// update text position
const distance = attr.distance || 60
meta = AngleEdge.getArcMeta(view, attr.type, { radius: distance })
if (!meta) {
return {}
}
var line: Line
const connectionPoint = meta.connectionPoint
const arcPoint1 = meta.arcPoint1
const arcPoint2 = meta.arcPoint2
const angleBetween = meta.angleBetween
const largeArcFlag = meta.largeArcFlag
if (Math.abs(angleBetween - 180) < 1e-6) {
const p = arcPoint1
.clone()
.rotate(largeArcFlag ? 90 : -90, connectionPoint)
line = new Line(connectionPoint, p).setLength(distance)
} else {
const c = new Line(arcPoint1, arcPoint2).getCenter()
line = new Line(connectionPoint, c).setLength(distance)
largeArcFlag && line.scale(-1, -1, line.start)
}
const pos = line.end
const angle = Angle.normalize(((line.angle() + 90) % 180) - 90)
return {
transform: `translate(${pos.x}, ${pos.y}) rotate(${angle})`,
}
},
},
},
},
true,
)
// eslint-disable-next-line
namespace Cache {
function ensure(view: EdgeView) {
const cacheKey = 'angleData'
const cache = view.getDataOfElement(view.container)
if (!(cacheKey in cache)) {
cache[cacheKey] = {}
}
return cache[cacheKey] as Object as {
[key: string]: AngleEdge.Metadata | null
}
}
export function get(
view: EdgeView,
type: Edge.TerminalType,
): AngleEdge.Metadata | null {
const cache = ensure(view)
return cache[type] || null
}
export function set(
view: EdgeView,
type: Edge.TerminalType,
meta: AngleEdge.Metadata | null,
) {
const cache = ensure(view)
cache[type] = meta
return meta
}
}
// eslint-disable-next-line
namespace AngleEdge {
export type AngleStart = 'self' | 'source' | 'target'
export type AngleDirection = 'clockwise' | 'anticlockwise' | 'small' | 'large'
export type Metadata = ReturnType<typeof calc>
export interface AngleOptions {
type: Edge.TerminalType
start?: AngleStart
direction?: AngleDirection
radius?: number
value?: number
pie?: boolean
}
export interface AngleTextOptions {
type: Edge.TerminalType
precision?: number
distance?: number
}
export function getArcMeta(
edgeView: EdgeView,
terminalType: Edge.TerminalType,
options: {
angle?: number
radius: number
start?: AngleStart
direction?: AngleDirection
},
): Metadata | null {
let meta = Cache.get(edgeView, terminalType)
if (meta) {
return meta
}
const isValidAngle = typeof options.angle === 'number'
const terminalView = edgeView.getTerminalView(terminalType)
if (!terminalView && !isValidAngle) {
return null
}
var isTarget = terminalType === 'target'
var tangent = edgeView.getTangentAtRatio(isTarget ? 1 : 0)
if (!tangent) {
return null
}
if (isTarget) {
tangent.scale(-1, -1, tangent.start)
}
// angle is specified
if (typeof options.angle === 'number') {
const ref = tangent.clone().rotate(-options.angle, tangent.start)
meta = calc(tangent, ref, {
start: 'target',
radius: options.radius,
direction: options.angle < 0 ? 'clockwise' : 'anticlockwise',
})
} else if (terminalView) {
const terminalCell = terminalView.cell
if (terminalCell.isEdge()) {
// terminal is another edge
const terminalEdgeView = terminalView as EdgeView
const p = terminalEdgeView.getClosestPointLength(tangent.start)!
const ref = terminalEdgeView.getTangentAtLength(p)!
meta = calc(tangent, ref, options)
} else if (terminalCell.isNode()) {
// terminal is node
const magnet = edgeView.getTerminalMagnet(terminalType) as SVGElement
if (!magnet) {
return null
}
const angleRadius = options.radius
const angleDirection = options.direction
const terminalNode = terminalCell as Node
const bbox = terminalNode.getBBox()
const angle = terminalNode.getAngle()
const center = bbox.getCenter()
const magnetBBox = terminalView.getUnrotatedBBoxOfElement(magnet)
const end = tangent.clone().setLength(1).end.rotate(angle, center)
if (magnetBBox.containsPoint(end)) {
return null
}
let sweepFlag = 0
let tx = 0
let ty = 0
let arcAngle = tangent.angle() - angle
if (end.y > magnetBBox.y + magnetBBox.height || end.y < magnetBBox.y) {
arcAngle += 90
tx = angleRadius
} else {
ty = angleRadius
}
arcAngle = Angle.normalize(arcAngle)
const quadrant = Math.floor(arcAngle / 90)
switch (quadrant) {
case 0:
sweepFlag = 1
break
case 1:
sweepFlag = 0
break
case 2:
tx *= -1
ty *= -1
sweepFlag = 1
break
case 3:
tx *= -1
ty *= -1
sweepFlag = 0
}
let sweep = false
switch (angleDirection) {
case 'large':
sweep = true
break
case 'anticlockwise':
sweep = 0 === quadrant || 2 === quadrant
break
case 'clockwise':
sweep = 1 === quadrant || 3 === quadrant
break
}
if (sweep) {
tx *= -1
ty *= -1
sweepFlag ^= 1
}
const startLine = tangent.setLength(angleRadius)
const connectionPoint = startLine.start
const arcPoint1 = startLine.end
const arcPoint2 = connectionPoint
.clone()
.translate(tx, ty)
.rotate(-angle, connectionPoint)
let angleBetween = connectionPoint.angleBetween(arcPoint1, arcPoint2)
if (180 < angleBetween) {
angleBetween = 360 - angleBetween
}
meta = {
connectionPoint: connectionPoint,
arcPoint1: arcPoint1,
arcPoint2: arcPoint2,
sweepFlag: sweepFlag,
largeArcFlag: 0,
arcAngle: arcAngle,
angleBetween: angleBetween,
}
}
}
Cache.set(edgeView, terminalType, meta)
return meta
}
function calc(
line: Line,
ref: Line,
options: {
radius: number
start?: AngleStart
direction?: AngleDirection
},
) {
const angleStart = options.start
const angleRadius = options.radius
let angleDirection = options.direction
const startLine = line.setLength(angleRadius)
const endLine = ref.setLength(angleRadius)
const arcAngle = line.angle()
const connectionPoint = startLine.start
const arcPoint1 = startLine.end
let arcPoint2 = endLine.end
let angleBetween = connectionPoint.angleBetween(arcPoint1, arcPoint2)
let sweepFlag = 1
let largeArcFlag = 0
const quadrant = Math.floor(angleBetween / 90)
let reflect = false
let antifix = false
let normalize = true
switch (angleStart) {
case 'target': {
if (angleDirection === 'small') {
angleDirection = angleBetween < 180 ? 'clockwise' : 'anticlockwise'
}
if (angleDirection === 'large') {
angleDirection = 180 < angleBetween ? 'clockwise' : 'anticlockwise'
}
switch (angleDirection) {
case 'anticlockwise':
if (0 < angleBetween && angleBetween < 180) {
largeArcFlag ^= 1
}
antifix = true
break
case 'clockwise':
default:
if (180 <= angleBetween) {
largeArcFlag ^= 1
}
sweepFlag ^= 1
break
}
normalize = false
break
}
case 'source': {
if (angleDirection === 'small') {
angleDirection = 180 < angleBetween ? 'clockwise' : 'anticlockwise'
} else if (angleDirection === 'large') {
angleDirection = angleBetween < 180 ? 'clockwise' : 'anticlockwise'
}
switch (angleDirection) {
case 'anticlockwise': {
if (180 < angleBetween) {
largeArcFlag ^= 1
}
sweepFlag = 1
antifix = true
break
}
default:
case 'clockwise': {
if (angleBetween < 180) {
largeArcFlag ^= 1
}
sweepFlag = 0
break
}
}
normalize = false
reflect = true
angleBetween = Angle.normalize(angleBetween + 180)
break
}
case 'self':
default: {
switch (angleDirection) {
case 'anticlockwise': {
reflect = 0 === quadrant || 1 === quadrant
sweepFlag = 1
antifix = true
break
}
case 'clockwise': {
reflect = 2 === quadrant || 3 === quadrant
sweepFlag = 0
antifix = false
break
}
case 'small': {
reflect = 1 === quadrant || 2 === quadrant
sweepFlag = 0 === quadrant || 2 === quadrant ? 0 : 1
antifix = 1 === quadrant || 3 === quadrant
break
}
case 'large': {
reflect = 0 === quadrant || 3 === quadrant
sweepFlag = 1 === quadrant || 3 === quadrant ? 0 : 1
antifix = 0 === quadrant || 2 === quadrant
break
}
}
}
}
if (reflect) {
arcPoint2 = arcPoint2.reflection(connectionPoint)
}
if (normalize && 180 <= angleBetween) {
angleBetween = Angle.normalize(angleBetween - 180)
}
if (antifix) {
angleBetween = Angle.normalize((normalize ? 180 : 360) - angleBetween)
}
return {
connectionPoint: connectionPoint,
arcPoint1: arcPoint1,
arcPoint2: arcPoint2,
largeArcFlag: largeArcFlag,
sweepFlag: sweepFlag,
arcAngle: arcAngle,
angleBetween: angleBetween,
}
}
export function getAngleText(
options: { angle?: number; precision?: number } = {},
) {
const angle = options.angle != null ? options.angle : 0
const decimalPoints = options.precision == null ? 2 : options.precision
return `${angle.toFixed(decimalPoints)}°`
}
}

View File

@ -1,332 +0,0 @@
import React from 'react'
import { Graph, Cell, Edge, Angle } from '@antv/x6'
import './angle-shape'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
async: true,
frozen: true,
rotating: {
enabled: true,
},
resizing: {
enabled: true,
},
})
const rect1 = graph.addNode({ shape: 'angle-node', x: 420, y: 40 })
const rect2 = graph.addNode({ shape: 'angle-node', x: 420, y: 460 })
const rect3 = graph.addNode({ shape: 'angle-node', x: 80, y: 240 })
const edge1 = graph.addEdge({
shape: 'angle-edge',
source: {
cell: rect1,
anchor: { name: 'bottom', args: { rotate: true } },
connectionPoint: { name: 'anchor' },
},
target: {
cell: rect2,
anchor: { name: 'top', args: { rotate: true } },
connectionPoint: { name: 'anchor' },
},
attrs: {
line: {
stroke: '#464554',
strokeWidth: 2,
targetMarker: {
tagName: 'circle',
r: 2,
},
sourceMarker: {
tagName: 'circle',
r: 2,
},
},
angles: {
stroke: '#8D8DB6',
strokeWidth: 2,
strokeDasharray: '2,4',
},
angleLabels: {
fill: '#8D8DB6',
},
},
})
const edge2 = graph.addEdge({
shape: 'angle-edge',
source: {
cell: rect3,
anchor: { name: 'center' },
connectionPoint: { name: 'boundary' },
},
target: {
cell: edge1,
anchor: { name: 'ratio' },
connectionPoint: { name: 'anchor' },
},
attrs: {
line: {
strokeWidth: 2,
stroke: '#464554',
targetMarker: {
tagName: 'circle',
r: 3,
},
sourceMarker: {
tagName: 'circle',
r: 2,
},
},
sourceAngle: {
stroke: '#4666E5',
strokeWidth: 3,
angle: {
direction: 'small',
},
},
targetAngle: {
stroke: '#4666E5',
strokeWidth: 3,
angle: {
start: 'target',
direction: 'clockwise',
},
},
angleLabels: {
fill: '#334AA6',
fontWeight: 'bold',
},
},
})
const edge3 = graph.addEdge({
shape: 'angle-edge',
source: { x: 680, y: 100 },
target: { x: 680, y: 500 },
attrs: {
line: {
strokeWidth: 2,
stroke: '#464554',
sourceMarker: {
name: 'path',
strokeWidth: 3,
d: 'M 0 -5 0 5',
},
targetMarker: null,
},
targetAngle: {
stroke: '#4666E5',
fill: '#859AEE',
strokeWidth: 2,
angle: {
start: 'target',
direction: 'clockwise',
radius: 50,
value: 90,
pie: true,
},
},
targetAngleLabel: {
fill: '#FFFFFF',
fontWeight: 'bold',
angleText: {
distance: 30,
},
},
},
})
graph.unfreeze()
graph.on('edge:mouseup', ({ view, edge }) => {
console.log('edge:mouseup')
graph.removeTools()
const tools: Cell.ToolItem[] = [
{
name: 'boundary',
args: {
attrs: {
stroke: '#6B6A76',
strokeDasharray: '1, 3',
strokeWidth: 1,
},
},
},
]
if (edge === edge1) {
tools.push(
{
name: 'source-anchor',
args: {
resetAnchor: false,
restrictArea: false,
},
},
{
name: 'target-anchor',
args: {
resetAnchor: false,
restrictArea: false,
},
},
)
}
if (edge === edge2) {
tools.push(
{
name: 'button',
args: {
markup: [
{
tagName: 'circle',
attrs: {
r: 10,
cursor: 'pointer',
fill: '#464554',
stroke: '#F3F7F6',
strokeWidth: 1,
},
},
{
tagName: 'path',
attrs: {
d: 'M -4 -0.8 L -7.2 2.4 L -4 5.6 L -4 3.2 L 1.6 3.2 L 1.6 1.6 L -4 1.6 L -4 -0.8 Z M 7.2 -2.4 L 4 -5.6 L 4 -3.2 L -1.6 -3.2 L -1.6 -1.6 L 4 -1.6 L 4 0.8 L 7.2 -2.4 Z',
cursor: 'pointer',
strokeWidth: 2,
fill: '#fff',
stroke: 'none',
},
},
],
distance: -40,
onClick({ cell }: { cell: Edge }) {
const path = 'targetAngle/angle/direction'
const directions = ['clockwise', 'anticlockwise']
const direction = cell.attr<string>(path)
const newDirection =
directions[
(directions.indexOf(direction) + 1) % directions.length
]
cell.attr(path, newDirection)
},
},
},
{
name: 'button',
args: {
markup: [
{
tagName: 'circle',
attrs: {
r: 10,
cursor: 'pointer',
fill: '#464554',
stroke: '#F3F7F6',
strokeWidth: 1,
},
},
{
tagName: 'path',
attrs: {
d: 'M -4 -0.8 L -7.2 2.4 L -4 5.6 L -4 3.2 L 1.6 3.2 L 1.6 1.6 L -4 1.6 L -4 -0.8 Z M 7.2 -2.4 L 4 -5.6 L 4 -3.2 L -1.6 -3.2 L -1.6 -1.6 L 4 -1.6 L 4 0.8 L 7.2 -2.4 Z',
cursor: 'pointer',
strokeWidth: 2,
fill: '#fff',
stroke: 'none',
},
},
],
distance: 40,
onClick({ cell }: { cell: Edge }) {
const path = 'sourceAngle/angle/direction'
const directions = ['small', 'large']
const direction = cell.attr<string>(path)
const newDirection =
directions[
(directions.indexOf(direction) + 1) % directions.length
]
cell.attr(path, newDirection)
},
},
},
{
name: 'source-anchor',
args: {
resetAnchor: false,
},
},
{
name: 'target-anchor',
args: {
resetAnchor: false,
},
},
)
}
if (edge === edge3) {
tools.push({
name: 'button',
args: {
markup: [
{
tagName: 'circle',
attrs: {
r: 10,
cursor: 'pointer',
fill: '#464554',
stroke: '#F3F7F6',
strokeWidth: 1,
},
},
{
tagName: 'path',
attrs: {
d: 'M -5 0 5 0 M 0 -5 0 5',
cursor: 'pointer',
strokeWidth: 2,
fill: 'none',
stroke: '#fff',
},
},
],
distance: -50,
onClick({ cell }: { cell: Edge }) {
const path = 'targetAngle/angle/value'
const angle = cell.attr<number>(path)
cell.attr(path, Angle.normalize(angle + 10))
},
},
})
}
edge.addTools(tools)
})
graph.on('blank:mouseup', () => graph.removeTools())
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,238 +0,0 @@
/* eslint-disable @typescript-eslint/no-namespace */
import { Graph, Shape, EdgeView, Edge, Registry, Point, Angle } from '@antv/x6'
Graph.registerNode(
'distance-node',
Shape.Rect.define({
width: 120,
height: 120,
zIndex: 1,
attrs: {
body: {
fill: '#f5f5f5',
stroke: '#d9d9d9',
strokeWidth: 1,
rx: 2,
ry: 2,
},
},
}),
true,
)
namespace DistanceEdge {
export interface DistanceAnchorOptions {
type: Edge.TerminalType
}
export interface DistanceTextOptions {
unit?: string
precision?: number
ratio?: number
offset?: number
}
export function getDistanceText(
view: EdgeView,
options: DistanceTextOptions = {},
) {
const unit = options.unit || ''
const precision = options.precision || 0
const length = view.getConnectionLength()
return length != null ? `${length.toFixed(precision)}${unit}` : ''
}
}
export const DistanceEdgeBase = Graph.registerEdge(
'distance-edge',
{
markup: [
{
tagName: 'path',
selector: 'sourceAnchorLine',
groupSelector: 'anchorLines',
},
{
tagName: 'path',
selector: 'targetAnchorLine',
groupSelector: 'anchorLines',
},
{
tagName: 'path',
selector: 'wrap',
groupSelector: 'lines',
attrs: {
fill: 'none',
cursor: 'pointer',
stroke: 'transparent',
strokeLinecap: 'round',
},
},
{
tagName: 'path',
selector: 'line',
groupSelector: 'lines',
attrs: {
fill: 'none',
pointerEvents: 'none',
},
},
{
tagName: 'text',
selector: 'distanceLabel',
},
],
attrs: {
lines: {
connection: true,
strokeLinejoin: 'round',
},
line: {
stroke: '#333333',
strokeWidth: 2,
targetMarker: {
tagName: 'path',
strokeWidth: 2,
d: 'M 0 10 0 -10 M 10 10 0 0 10 -10',
fill: 'none',
},
sourceMarker: {
tagName: 'path',
strokeWidth: 2,
d: 'M 0 10 0 -10 M 10 10 0 0 10 -10',
fill: 'none',
},
},
wrapper: {
strokeWidth: 10,
},
anchorLines: {
stroke: '#333333',
strokeWidth: 1,
strokeDasharray: '1,2',
},
sourceAnchorLine: {
distanceAnchor: {
type: 'source',
},
},
targetAnchorLine: {
distanceAnchor: {
type: 'target',
},
},
distanceLabel: {
fontFamily: 'sans-serif',
fontWeight: 'lighter',
fontSize: 14,
textAnchor: 'middle',
textVerticalAnchor: 'middle',
distanceText: {
unit: 'px',
precision: 0,
ratio: 0.5,
offset: 12,
},
},
},
attrHooks: {
distanceAnchor: {
set(val, { view }) {
if (typeof val === 'object') {
const attr = val as {} as DistanceEdge.DistanceAnchorOptions
const edgeView = view as EdgeView
const anchor = edgeView.getTerminalAnchor(attr.type)
const kont = edgeView.getTerminalConnectionPoint(attr.type)
return {
d: `M ${anchor.x} ${anchor.y} ${kont.x} ${kont.y}`,
}
}
},
},
distanceText: {
set(val, options) {
const view = options.view as EdgeView
const attr = val as {} as DistanceEdge.DistanceTextOptions
const text = DistanceEdge.getDistanceText(view, attr)
const raw = Registry.Attr.presets.text as Registry.Attr.SetDefinition
raw.set.call(this, text, options)
// label position
const ratio = attr.ratio || 0.5
const offset = attr.offset || 0
const tangent = view.getTangentAtRatio(ratio)
let start: Point
let angle: number
if (tangent) {
angle = tangent.vector().vectorAngle(new Point(1, 0))
start = tangent.start
} else {
start = view.path.start!
angle = 0
}
let y = offset
let transform: string
if (angle === 0) {
transform = `translate(${start.x},${start.y})`
} else {
const fixed = Angle.normalize(((angle + 90) % 180) - 90)
if (angle !== fixed) {
y = -offset
}
transform = `translate(${start.x},${start.y}) rotate(${fixed})`
}
return { transform, y }
},
},
},
},
true,
)
Graph.registerEdge(
'distance-edge-normal',
DistanceEdgeBase.define({
zIndex: 2,
attrs: {
distanceLabel: {
fill: '#464554',
distanceText: {
precision: 1,
},
},
line: {
stroke: '#464554',
},
anchorLines: {
strokeDasharray: 'none',
stroke: '#D2D2D2',
},
},
}),
true,
)
Graph.registerEdge(
'distance-edge-main',
DistanceEdgeBase.define({
zIndex: 3,
attrs: {
distanceLabel: {
fill: '#4666E5',
distanceText: {
precision: 1,
},
},
line: {
stroke: '#4666E5',
},
anchorLines: {
strokeDasharray: 'none',
stroke: '#D2D2D2',
},
},
}),
true,
)

View File

@ -1,255 +0,0 @@
import React from 'react'
import { Graph, EdgeView } from '@antv/x6'
import './distance-shape'
import '../index.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1080,
height: 720,
async: true,
frozen: true,
rotating: {
enabled: true,
},
resizing: {
enabled: true,
},
interacting: {
edgeMovable: false,
},
checkView({ view }) {
const cell = view.cell
if (cell.isEdge()) {
const edgeView = view as EdgeView
if (
view.container.parentNode &&
edgeView.getConnectionLength() === 0
) {
return false
}
if (cell.prop<boolean>('showOnRotated')) {
const target = cell.getTargetCell()
if (target && target.isNode()) {
return target.getAngle() % 90 > 0
}
return false
}
}
return true
},
})
const rect1 = graph.addNode({ shape: 'distance-node', x: 500, y: 400 })
const rect2 = graph.addNode({ shape: 'distance-node', x: 100, y: 100 })
const rect3 = graph.addNode({
shape: 'distance-node',
x: 800,
y: 300,
angle: 30,
})
// #region Node Measurements
graph.addEdge({
shape: 'distance-edge-main',
showOnRotated: true,
source: {
cell: rect3,
anchor: { name: 'topRight', args: { dy: -50 } },
connectionPoint: { name: 'anchor' },
},
target: {
cell: rect3,
anchor: { name: 'topLeft', args: { dy: -50 } },
connectionPoint: { name: 'anchor' },
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect3,
anchor: { name: 'topRight', args: { rotate: true } },
connectionPoint: { name: 'anchor', args: { offset: { y: -25 } } },
},
target: {
cell: rect3,
anchor: { name: 'topLeft', args: { rotate: true } },
connectionPoint: { name: 'anchor', args: { offset: { y: 25 } } },
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect3,
anchor: { name: 'topLeft', args: { rotate: true } },
connectionPoint: { name: 'anchor', args: { offset: { y: -25 } } },
},
target: {
cell: rect3,
anchor: { name: 'bottomLeft', args: { rotate: true } },
connectionPoint: { name: 'anchor', args: { offset: { y: 25 } } },
},
})
// #endregion
// #region Distance Between Nodes
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect2,
anchor: { name: 'bottomRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'right', alignOffset: 30 },
},
},
target: {
cell: rect1,
anchor: { name: 'topRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'right', alignOffset: 30 },
},
},
})
graph.addEdge({
shape: 'distance-edge-main',
source: {
cell: rect2,
anchor: { name: 'bottomLeft' },
connectionPoint: {
name: 'anchor',
args: { align: 'bottom', alignOffset: 60 },
},
},
target: {
cell: rect1,
anchor: { name: 'bottomRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'bottom', alignOffset: 60 },
},
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect2,
anchor: { name: 'bottomRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'bottom', alignOffset: 30 },
},
},
target: {
cell: rect1,
anchor: { name: 'bottomLeft' },
connectionPoint: {
name: 'anchor',
args: { align: 'bottom', alignOffset: 30 },
},
},
})
graph.addEdge({
shape: 'distance-edge-main',
source: {
cell: rect2,
anchor: { name: 'topRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'right', alignOffset: 60 },
},
},
target: {
cell: rect1,
anchor: { name: 'bottomRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'right', alignOffset: 60 },
},
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect2,
anchor: { name: 'bottomLeft' },
connectionPoint: {
name: 'anchor',
args: { align: 'left', alignOffset: 60 },
},
},
target: {
cell: rect1,
anchor: { name: 'topLeft' },
connectionPoint: {
name: 'anchor',
args: { align: 'left', alignOffset: 60 },
},
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect2,
anchor: { name: 'topRight' },
connectionPoint: {
name: 'anchor',
args: { align: 'top', alignOffset: 60 },
},
},
target: {
cell: rect1,
anchor: { name: 'topLeft' },
connectionPoint: {
name: 'anchor',
args: { align: 'top', alignOffset: 60 },
},
},
})
graph.addEdge({
shape: 'distance-edge-normal',
source: {
cell: rect2,
anchor: { name: 'bottomRight' },
connectionPoint: { name: 'anchor', args: { offset: { y: 60 } } },
},
target: {
cell: rect1,
anchor: { name: 'topLeft' },
connectionPoint: { name: 'anchor', args: { offset: { y: -60 } } },
},
})
// #endregion
graph.unfreeze()
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,6 +1,7 @@
import React from 'react'
import { Select } from 'antd'
import { Graph, Cell, Node, Color, Dom } from '@antv/x6'
import { Graph, Cell, Node } from '@antv/x6'
import { Color, Dom } from '@antv/x6-common'
import dagre from 'dagre'
import './shape'
import '../index.less'
@ -27,8 +28,6 @@ export default class Example extends React.Component<
container: this.container,
width: 1000,
height: 600,
scroller: true,
snapline: true,
interacting: false,
})
@ -84,27 +83,31 @@ export default class Example extends React.Component<
}
setup() {
this.graph.on('node:add', ({ e, node }) => {
e.stopPropagation()
const bg = Color.randomHex()
const member = this.createNode(
'Employee',
'New Employee',
Math.random() < 0.5 ? male : female,
bg,
Color.invert(bg, true),
)
this.graph.freeze()
this.graph.addCell([member, this.createEdge(node, member)])
this.layout()
})
this.graph.on(
'node:add',
({ e, node }: { e: Dom.ClickEvent; node: Node }) => {
e.stopPropagation()
const bg = Color.randomHex()
const member = this.createNode(
'Employee',
'New Employee',
Math.random() < 0.5 ? male : female,
bg,
Color.invert(bg, true),
)
this.graph.addCell([member, this.createEdge(node, member)])
this.layout()
},
)
this.graph.on('node:delete', ({ e, node }) => {
e.stopPropagation()
this.graph.freeze()
this.graph.removeCell(node)
this.layout()
})
this.graph.on(
'node:delete',
({ e, node }: { e: Dom.ClickEvent; node: Node }) => {
e.stopPropagation()
this.graph.removeCell(node)
this.layout()
},
)
}
layout() {
@ -122,17 +125,15 @@ export default class Example extends React.Component<
})
edges.forEach((edge) => {
const source = edge.getSource()
const target = edge.getTarget()
g.setEdge(source.cell, target.cell)
const source = edge.getSourceCellId()
const target = edge.getTargetCellId()
g.setEdge(source, target)
})
dagre.layout(g)
this.graph.freeze()
g.nodes().forEach((id) => {
const node = this.graph.getCell(id) as Node
g.nodes().forEach((id: string) => {
const node = this.graph.getCellById(id) as Node
if (node) {
const pos = g.node(id)
node.position(pos.x, pos.y)
@ -145,8 +146,6 @@ export default class Example extends React.Component<
const sourceBBox = source.getBBox()
const targetBBox = target.getBBox()
console.log(sourceBBox, targetBBox)
if (
(rankdir === 'LR' || rankdir === 'RL') &&
sourceBBox.y !== targetBBox.y
@ -178,11 +177,7 @@ export default class Example extends React.Component<
} else {
edge.setVertices([])
}
console.log(sourceBBox, targetBBox, edge.getVertices())
})
this.graph.unfreeze()
}
createNode(

View File

@ -1,4 +1,6 @@
import { ObjectExt, Dom, Vector, Point, View } from '@antv/x6'
import { View } from '@antv/x6'
import { Point } from '@antv/x6-geometry'
import { ObjectExt, Dom, Vector } from '@antv/x6-common'
// need: <meta http-equiv="x-ua-compatible" content="IE=Edge" />
export class PathDrawer extends View {

View File

@ -1,4 +1,5 @@
import { Point, Vector } from '@antv/x6'
import { Point } from '@antv/x6-geometry'
import { Vector } from '@antv/x6-common'
import React from 'react'
import '../index.less'

View File

@ -1,321 +0,0 @@
import React from 'react'
import { Checkbox, InputNumber, Button } from 'antd'
import { Graph, Cell, Node, Shape, View, Rectangle } from '@antv/x6'
import '../index.less'
function random(max: number, min: number) {
return Math.floor(Math.random() * (max - min)) + min
}
const viewportTemplate = new Shape.Rect({
zIndex: 3,
size: { width: 200, height: 200 },
position: { x: 100, y: 100 },
attrs: {
body: {
fill: 'rgba(255,255,255,0.35)',
stroke: 'rgba(255,0,0,0.8)',
strokeWidth: 10,
pointerEvents: 'all',
},
},
})
class Loader extends View {
bar: HTMLDivElement
constructor() {
super()
this.container = document.createElement('div')
this.$(this.container).css({
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 20,
background: 'white',
border: '1px solid black',
padding: 2,
})
this.bar = document.createElement('div')
this.$(this.bar).css({
height: '100%',
width: '0%',
background: 'blue',
transition: 'width 0.2s',
})
}
progress(value: number) {
this.bar.style.width = Math.min(Math.max(value, 0), 1) * 100 + '%'
}
}
export default class Example extends React.Component<
Example.Props,
Example.State
> {
private container: HTMLDivElement
private graph: Graph
private loader: Loader = new Loader()
private windowBBox: Rectangle
private viewport: Node
state: Example.State = {
customViewport: true,
padding: 100,
keepRendered: false,
keepDragged: false,
count: 1000,
columns: 40,
batch: 1000,
}
componentDidMount() {
const graph = (this.graph = new Graph({
container: this.container,
width: 1000,
height: 600,
grid: 1,
async: true,
frozen: true,
sorting: 'approx',
connecting: {
anchor: 'nodeCenter',
connectionPoint: 'boundary',
},
checkView: ({ view, unmounted }) => {
if (this.state.keepDragged && view.cid === draggedCid) {
return true
}
if (this.state.keepRendered && unmounted) {
return true
}
if (this.state.customViewport) {
var viewportBBox = this.viewport.getBBox()
return viewportBBox.isIntersectWithRect(
view.cell.getBBox().inflate(this.state.padding),
)
} else {
if (view.cell === this.viewport) {
return false
}
return this.windowBBox.isIntersectWithRect(
view.cell.getBBox().inflate(this.state.padding),
)
}
},
}))
graph.on('render:done', ({ stats }) => {
console.table(stats)
})
// Dragged view is always visible
let draggedCid: string | null = null
graph.on({
'cell:mousedown': function ({ view }) {
draggedCid = view.cid
},
'cell:mouseup': function () {
draggedCid = null
},
})
window.onscroll = () => {
this.setWindowBBox()
}
window.onresize = () => {
this.setWindowBBox()
}
this.setWindowBBox()
this.restart()
}
randomColor() {
return (
'hsl(' +
random(171, 181) +
',' +
random(58, 72) +
'%,' +
random(45, 55) +
'%)'
)
}
restart() {
this.loader.progress(0)
document.body.appendChild(this.loader.container)
console.time('perf-all')
var count = this.state.count
var columnCount = this.state.columns
var nodes = Array.from({ length: count }, (_, index) => {
var row = Math.floor(index / columnCount)
var column = index % columnCount
return new Shape.Rect({
zIndex: 2,
size: { width: 30, height: 20 },
position: { x: column * 50, y: row * 50 },
attrs: {
body: { fill: this.randomColor() },
label: { text: index },
},
})
})
var edges = nodes.map((target, index) => {
if (index === 0) {
return null
}
var source = nodes[index - 1]
return new Shape.Edge({
zIndex: 1,
source: { cell: source.id },
target: { cell: target.id },
})
})
edges.shift()
this.viewport = viewportTemplate.clone() as any as Node
console.time('perf-reset')
this.graph.freeze()
const cells = [...nodes, ...edges, this.viewport] as Cell[]
this.graph.model.resetCells(cells)
// this.graph.fitToContent({ useCellGeometry: true, padding: 10 })
console.timeEnd('perf-reset')
console.time('perf-dump')
this.graph.unfreeze({
batchSize: this.state.batch,
progress: ({ done, current, total }) => {
var progress = current / total
console.log(Math.round(progress * 100) + '%')
if (done) {
console.timeEnd('perf-dump')
console.timeEnd('perf-all')
this.graph.unfreeze()
// this.loader.remove()
} else {
this.loader.progress(progress)
}
},
})
}
setWindowBBox() {
this.windowBBox = this.graph.pageToLocal(
window.scrollX,
window.scrollY,
window.innerWidth,
window.innerHeight,
)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
onCountChange = (count) => this.setState({ count })
onColumnsChange = (columns) => this.setState({ columns })
onBatchChange = (batch) => this.setState({ batch })
onRestartClick = () => this.restart()
onCustomViewport = (e) => this.setState({ customViewport: e.target.checked })
onPaddingChange = (e) =>
this.setState({ padding: e.target.checked ? 100 : 1 })
onKeepRenderedChange = (e) =>
this.setState({ keepRendered: e.target.checked })
onKeepDraggedChange = (e) => this.setState({ keepDragged: e.target.checked })
render() {
return (
<div
className="x6-graph-wrap"
style={{
padding: 24,
}}
>
<div
className="x6-graph-tools"
style={{ width: '100%', userSelect: 'none' }}
>
<Checkbox
checked={this.state.customViewport}
onChange={this.onCustomViewport}
>
Custom Viewport
</Checkbox>
<Checkbox
checked={this.state.padding > 1}
onChange={this.onPaddingChange}
>
Padding
</Checkbox>
<Checkbox
checked={this.state.keepRendered}
onChange={this.onKeepRenderedChange}
>
Keep Rendered
</Checkbox>
<Checkbox
checked={this.state.keepDragged}
onChange={this.onKeepDraggedChange}
style={{ marginRight: 32 }}
>
Keep Dragged
</Checkbox>
Count
<InputNumber
value={this.state.count}
onChange={this.onCountChange}
style={{ marginLeft: 4, marginRight: 16 }}
/>
Columns
<InputNumber
value={this.state.columns}
onChange={this.onColumnsChange}
style={{ marginLeft: 4, marginRight: 16 }}
/>
Batch Size
<InputNumber
value={this.state.batch}
onChange={this.onBatchChange}
style={{ marginLeft: 4, marginRight: 16 }}
/>
<Button onClick={this.onRestartClick}>Restart</Button>
</div>
<div
ref={this.refContainer}
className="x6-graph"
style={{
border: '1px solid #000',
boxShadow: 'none',
}}
/>
</div>
)
}
}
// eslint-disable-next-line
export namespace Example {
export interface Props {}
export interface State {
customViewport: boolean
padding: number
keepRendered: boolean
keepDragged: boolean
count: number
columns: number
batch: number
}
}

View File

@ -156,8 +156,6 @@ export default class Example extends React.Component {
width: 1000,
height: 1000,
grid: 1,
async: true,
sorting: 'approx',
background: {
color: '#000000',
},
@ -165,10 +163,9 @@ export default class Example extends React.Component {
// efficient drawing
var rectSize = 18
var center = graph.getArea().getCenter()
var center = graph.getGraphArea().getCenter()
var radius = 1000 / 2
var nodes: ConveyorNode[] = []
for (var i = 0; i < 10; i++) {
nodes.push(...createCircle(center, radius - 50 - i * 30, rectSize))
}
@ -198,7 +195,7 @@ export default class Example extends React.Component {
}
}
setInterval(updateConveyor, 1)
setInterval(updateConveyor, 100)
}
refContainer = (container: HTMLDivElement) => {

View File

@ -1,5 +1,7 @@
import React from 'react'
import { Graph, Node, Dom, Rectangle } from '@antv/x6'
import { Graph, Node, Util } from '@antv/x6'
import { Dom } from '@antv/x6-common'
import { Rectangle } from '@antv/x6-geometry'
import { Button } from 'antd'
import '../index.less'
@ -14,8 +16,6 @@ export default class Example extends React.Component {
width: 800,
height: 600,
grid: true,
resizing: true,
rotating: true,
panning: true,
mousewheel: true,
})
@ -250,7 +250,10 @@ export default class Example extends React.Component {
s = performance.now()
const newMatrixList = q.map((item) =>
Dom.getMatrixByElementAttr(item as SVGElement, container as SVGElement),
Dom.getTransformToParentElement(
item as SVGElement,
container as SVGElement,
),
)
console.log('new getMatrixOfElement spend:', performance.now() - s)
@ -265,13 +268,11 @@ export default class Example extends React.Component {
// bbox
s = performance.now()
const oldBoundingList = q.map((item) => Dom.getBBox(item as SVGElement))
const oldBoundingList = q.map((item) => Util.getBBox(item as SVGElement))
console.log('old getBoundingRectOfElement spend:', performance.now() - s)
s = performance.now()
const newBoundingList = q.map((item) =>
Dom.getBBoxByElementAttr(item as SVGElement),
)
const newBoundingList = q.map((item) => Util.getBBoxV2(item as SVGElement))
console.log('new getBoundingRectOfElement spend:', performance.now() - s)
isSame = oldBoundingList.every((item, index) => {

View File

@ -1,102 +0,0 @@
[
{
"num": 200,
"time": 0.379,
"type": "async"
},
{
"num": 400,
"time": 0.481,
"type": "async"
},
{
"num": 600,
"time": 0.569,
"type": "async"
},
{
"num": 800,
"time": 0.681,
"type": "async"
},
{
"num": 1000,
"time": 0.79,
"type": "async"
},
{
"num": 1200,
"time": 0.915,
"type": "async"
},
{
"num": 1400,
"time": 1.056,
"type": "async"
},
{
"num": 1600,
"time": 1.24,
"type": "async"
},
{
"num": 1800,
"time": 1.388,
"type": "async"
},
{
"num": 2000,
"time": 1.497,
"type": "async"
},
{
"num": 200,
"time": 0.224,
"type": "sync"
},
{
"num": 400,
"time": 0.355,
"type": "sync"
},
{
"num": 600,
"time": 0.489,
"type": "sync"
},
{
"num": 800,
"time": 0.579,
"type": "sync"
},
{
"num": 1000,
"time": 0.764,
"type": "sync"
},
{
"num": 1200,
"time": 0.858,
"type": "sync"
},
{
"num": 1400,
"time": 0.992,
"type": "sync"
},
{
"num": 1600,
"time": 1.126,
"type": "sync"
},
{
"num": 1800,
"time": 1.282,
"type": "sync"
},
{
"num": 2000,
"time": 1.763,
"type": "sync"
}
]

View File

@ -1,149 +0,0 @@
import React from 'react'
import { Graph, Cell, Node as N, Edge as E } from '@antv/x6'
import '../../../index.less'
type Result = { [key: string]: number[] }
const NODE_NUM = 500 // 节点数量
const MAX_EDGE_NUM = 2000 // 最大边数量
const ITERATIONS = 5 // 迭代 5 次求平均数
const STEP = 200 // 每次增加 200 条边
const ASYNC = false
const result: Result = {} as any
N.registry.register(
'performance_normal_node',
{
inherit: 'circle',
size: {
width: 24,
height: 24,
},
attrs: {
body: {
fill: '#855af2',
stroke: 'transparent',
},
text: {
fill: '#fff',
},
},
},
true,
)
E.registry.register(
'performance_normal_edge',
{
attrs: {
line: {
stroke: '#ccc',
strokeWidth: 1,
targetMarker: null,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 1000,
grid: 1,
async: ASYNC,
})
function mockCells(num: number) {
const nodes: Cell[] = []
const edges: Cell[] = []
Array.from({ length: NODE_NUM }).forEach((_, n) => {
const x = Math.round(Math.random() * 900) + 50
const y = Math.round(Math.random() * 900) + 50
const a = graph.createNode({
x,
y,
id: `${n}`,
shape: 'performance_normal_node',
label: `${n + 1}`,
})
nodes.push(a)
})
Array.from({ length: num }).forEach(() => {
const a = graph.createEdge({
shape: 'performance_normal_edge',
source: `${Math.floor(Math.random() * 500)}`,
target: `${Math.floor(Math.random() * 500)}`,
})
edges.push(a)
})
return {
nodes,
edges,
}
}
function test(num: number, iterations: number) {
const { nodes, edges } = mockCells(num)
graph.model.resetCells(nodes)
const startTime = new Date().getTime()
graph.model.addCells(edges)
if (ASYNC) {
graph.once('render:done', showResult)
} else {
showResult()
}
function showResult() {
const duration = (new Date().getTime() - startTime) / 1000
if (result[num]) {
result[num].push(duration)
} else {
result[num] = [duration]
}
if (iterations < ITERATIONS) {
test(num, iterations + 1)
} else if (num < MAX_EDGE_NUM) {
test(num + STEP, 0)
} else {
output()
}
}
}
function output() {
const res = Object.keys(result).map((key: string) => ({
num: parseInt(key, 10),
time: parseFloat(
(result[key].reduce((pre, cur) => pre + cur, 0) / ITERATIONS).toFixed(
3,
),
),
type: ASYNC ? 'async' : 'sync',
}))
document.getElementById('result')!.innerText = JSON.stringify(res)
}
test(200, 0)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div id="result" style={{ paddingLeft: 8, paddingBottom: 8 }} />
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,102 +0,0 @@
[
{
"num": 500,
"time": 0.285,
"type": "sync"
},
{
"num": 1000,
"time": 0.477,
"type": "sync"
},
{
"num": 1500,
"time": 0.744,
"type": "sync"
},
{
"num": 2000,
"time": 0.964,
"type": "sync"
},
{
"num": 2500,
"time": 1.347,
"type": "sync"
},
{
"num": 3000,
"time": 1.561,
"type": "sync"
},
{
"num": 3500,
"time": 1.831,
"type": "sync"
},
{
"num": 4000,
"time": 2.14,
"type": "sync"
},
{
"num": 4500,
"time": 2.715,
"type": "sync"
},
{
"num": 5000,
"time": 2.848,
"type": "sync"
},
{
"num": 500,
"time": 0.276,
"type": "async"
},
{
"num": 1000,
"time": 0.429,
"type": "async"
},
{
"num": 1500,
"time": 0.677,
"type": "async"
},
{
"num": 2000,
"time": 0.897,
"type": "async"
},
{
"num": 2500,
"time": 1.356,
"type": "async"
},
{
"num": 3000,
"time": 1.584,
"type": "async"
},
{
"num": 3500,
"time": 2.045,
"type": "async"
},
{
"num": 4000,
"time": 2.347,
"type": "async"
},
{
"num": 4500,
"time": 2.705,
"type": "async"
},
{
"num": 5000,
"time": 3.072,
"type": "async"
}
]

View File

@ -1,118 +0,0 @@
import React from 'react'
import { Graph, Cell, Node as N } from '@antv/x6'
import '../../../index.less'
type Result = { [key: string]: number[] }
const MAX_NODE_NUM = 5000 // 最大节点数量
const ITERATIONS = 5 // 迭代 5 次求平均数
const STEP = 500 // 每次增加 500 节点
const ASYNC = true
const result: Result = {} as any
const Node = N.registry.register(
'performance_normal_node',
{
inherit: 'circle',
size: {
width: 24,
height: 24,
},
attrs: {
body: {
fill: '#855af2',
stroke: 'transparent',
},
text: {
fill: '#fff',
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 1000,
grid: 1,
async: ASYNC,
})
function mockCells(num: number) {
const node = new Node()
const cells: Cell[] = []
Array.from({ length: num }).forEach((_, n) => {
const x = Math.round(Math.random() * 900) + 50
const y = Math.round(Math.random() * 900) + 50
const a = node
.clone()
.position(x, y)
.attr('label/text', n + 1)
cells.push(a)
})
return cells
}
function test(num: number, iterations: number) {
const cells = mockCells(num)
const startTime = new Date().getTime()
graph.model.resetCells(cells)
if (ASYNC) {
graph.once('render:done', recordResult)
} else {
recordResult()
}
function recordResult() {
const duration = (new Date().getTime() - startTime) / 1000
if (result[num]) {
result[num].push(duration)
} else {
result[num] = [duration]
}
if (iterations < ITERATIONS) {
test(num, iterations + 1)
} else if (num < MAX_NODE_NUM) {
test(num + STEP, 0)
} else {
output()
}
}
}
function output() {
const res = Object.keys(result).map((key: string) => ({
num: parseInt(key, 10),
time: parseFloat(
(result[key].reduce((pre, cur) => pre + cur, 0) / ITERATIONS).toFixed(
3,
),
),
type: ASYNC ? 'async' : 'sync',
}))
document.getElementById('result')!.innerText = JSON.stringify(res)
}
test(500, 0)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div id="result" style={{ paddingLeft: 8, paddingBottom: 8 }} />
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,102 +0,0 @@
[
{
"num": 500,
"time": 0.594,
"type": "sync"
},
{
"num": 1000,
"time": 1.005,
"type": "sync"
},
{
"num": 1500,
"time": 1.514,
"type": "sync"
},
{
"num": 2000,
"time": 2.014,
"type": "sync"
},
{
"num": 2500,
"time": 2.775,
"type": "sync"
},
{
"num": 3000,
"time": 3.103,
"type": "sync"
},
{
"num": 3500,
"time": 3.689,
"type": "sync"
},
{
"num": 4000,
"time": 4.844,
"type": "sync"
},
{
"num": 4500,
"time": 4.972,
"type": "sync"
},
{
"num": 5000,
"time": 6.49,
"type": "sync"
},
{
"num": 500,
"time": 0.546,
"type": "async"
},
{
"num": 1000,
"time": 0.934,
"type": "async"
},
{
"num": 1500,
"time": 1.37,
"type": "async"
},
{
"num": 2000,
"time": 1.867,
"type": "async"
},
{
"num": 2500,
"time": 2.976,
"type": "async"
},
{
"num": 3000,
"time": 3.687,
"type": "async"
},
{
"num": 3500,
"time": 4.809,
"type": "async"
},
{
"num": 4000,
"time": 5.251,
"type": "async"
},
{
"num": 4500,
"time": 6.628,
"type": "async"
},
{
"num": 5000,
"time": 7.04,
"type": "async"
}
]

View File

@ -1,184 +0,0 @@
import React from 'react'
import { Graph, Cell, Node as N } from '@antv/x6'
import '../../../index.less'
type Result = { [key: string]: number[] }
const MAX_NODE_NUM = 5000 // 最大节点数量
const ITERATIONS = 5 // 迭代 5 次求平均数
const STEP = 500 // 每次增加 500 节点
const ASYNC = true
const result: Result = {} as any
const Node = N.registry.register(
'performance_normal_node',
{
inherit: 'circle',
size: {
width: 24,
height: 24,
},
attrs: {
body: {
fill: '#855af2',
stroke: 'transparent',
},
text: {
fill: '#fff',
},
},
ports: {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 6,
magnet: true,
stroke: '#31d0c6',
strokeWidth: 2,
fill: '#fff',
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
],
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 1000,
grid: 1,
async: ASYNC,
})
function mockCells(num: number) {
const node = new Node()
const cells: Cell[] = []
Array.from({ length: num }).forEach((_, n) => {
const x = Math.round(Math.random() * 900) + 50
const y = Math.round(Math.random() * 900) + 50
const a = node
.clone()
.position(x, y)
.attr('label/text', n + 1)
cells.push(a)
})
return cells
}
function test(num: number, iterations: number) {
const cells = mockCells(num)
const startTime = new Date().getTime()
graph.model.resetCells(cells)
if (ASYNC) {
graph.once('render:done', recordResult)
} else {
recordResult()
}
function recordResult() {
const duration = (new Date().getTime() - startTime) / 1000
if (result[num]) {
result[num].push(duration)
} else {
result[num] = [duration]
}
if (iterations < ITERATIONS) {
test(num, iterations + 1)
} else if (num < MAX_NODE_NUM) {
test(num + STEP, 0)
} else {
output()
}
}
}
function output() {
const res = Object.keys(result).map((key: string) => ({
num: parseInt(key, 10),
time: parseFloat(
(result[key].reduce((pre, cur) => pre + cur, 0) / ITERATIONS).toFixed(
3,
),
),
type: ASYNC ? 'async' : 'sync',
}))
document.getElementById('result')!.innerText = JSON.stringify(res)
}
test(500, 0)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div id="result" style={{ paddingLeft: 8, paddingBottom: 8 }} />
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,102 +0,0 @@
[
{
"num": 500,
"time": 0.232,
"type": "sync"
},
{
"num": 1000,
"time": 0.344,
"type": "sync"
},
{
"num": 1500,
"time": 0.565,
"type": "sync"
},
{
"num": 2000,
"time": 0.707,
"type": "sync"
},
{
"num": 2500,
"time": 0.903,
"type": "sync"
},
{
"num": 3000,
"time": 1.147,
"type": "sync"
},
{
"num": 3500,
"time": 1.36,
"type": "sync"
},
{
"num": 4000,
"time": 1.589,
"type": "sync"
},
{
"num": 4500,
"time": 1.848,
"type": "sync"
},
{
"num": 5000,
"time": 2.332,
"type": "sync"
},
{
"num": 500,
"time": 0.39,
"type": "async"
},
{
"num": 1000,
"time": 0.633,
"type": "async"
},
{
"num": 1500,
"time": 0.903,
"type": "async"
},
{
"num": 2000,
"time": 1.163,
"type": "async"
},
{
"num": 2500,
"time": 1.831,
"type": "async"
},
{
"num": 3000,
"time": 2.172,
"type": "async"
},
{
"num": 3500,
"time": 2.739,
"type": "async"
},
{
"num": 4000,
"time": 3.073,
"type": "async"
},
{
"num": 4500,
"time": 3.7,
"type": "async"
},
{
"num": 5000,
"time": 4.065,
"type": "async"
}
]

View File

@ -1,130 +0,0 @@
import React from 'react'
import { Graph, Cell, Node } from '@antv/x6'
import '@antv/x6-react-shape'
import '../../../index.less'
type Result = { [key: string]: number[] }
const MAX_NODE_NUM = 5000 // 最大节点数量
const ITERATIONS = 5 // 迭代 5 次求平均数
const STEP = 500 // 每次增加 500 节点
const ASYNC = true
const result: Result = {} as any
class MyComponent extends React.Component<{ node?: Node; text: string }> {
shouldComponentUpdate() {
const node = this.props.node
if (node) {
if (node.hasChanged('data')) {
return true
}
}
return false
}
render() {
return (
<div
style={{
width: '100%',
height: '100%',
textAlign: 'center',
borderRadius: 12,
backgroundColor: '#855af2',
}}
>
{this.props.text}
</div>
)
}
}
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 1000,
grid: 1,
async: ASYNC,
})
function mockCells(num: number) {
const cells: Cell[] = []
Array.from({ length: num }).forEach((_, n) => {
const x = Math.round(Math.random() * 900) + 50
const y = Math.round(Math.random() * 900) + 50
const a = graph.createNode({
shape: 'react-shape',
x,
y,
width: 24,
height: 24,
component: <MyComponent text={`${n}`} />,
})
cells.push(a)
})
return cells
}
function test(num: number, iterations: number) {
const cells = mockCells(num)
const startTime = new Date().getTime()
graph.model.resetCells(cells)
if (ASYNC) {
graph.once('render:done', recordResult)
} else {
recordResult()
}
function recordResult() {
const duration = (new Date().getTime() - startTime) / 1000
if (result[num]) {
result[num].push(duration)
} else {
result[num] = [duration]
}
if (iterations < ITERATIONS) {
test(num, iterations + 1)
} else if (num < MAX_NODE_NUM) {
test(num + STEP, 0)
} else {
output()
}
}
}
function output() {
const res = Object.keys(result).map((key: string) => ({
num: parseInt(key, 10),
time: parseFloat(
(result[key].reduce((pre, cur) => pre + cur, 0) / ITERATIONS).toFixed(
3,
),
),
type: ASYNC ? 'async' : 'sync',
}))
document.getElementById('result')!.innerText = JSON.stringify(res)
}
test(500, 0)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div id="result" style={{ paddingLeft: 8, paddingBottom: 8 }} />
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,102 +0,0 @@
[
{
"num": 500,
"time": 1.167,
"type": "async"
},
{
"num": 1000,
"time": 1.969,
"type": "async"
},
{
"num": 1500,
"time": 2.944,
"type": "async"
},
{
"num": 2000,
"time": 3.886,
"type": "async"
},
{
"num": 2500,
"time": 6.275,
"type": "async"
},
{
"num": 3000,
"time": 7.236,
"type": "async"
},
{
"num": 3500,
"time": 9.368,
"type": "async"
},
{
"num": 4000,
"time": 10.251,
"type": "async"
},
{
"num": 4500,
"time": 12.526,
"type": "async"
},
{
"num": 5000,
"time": 13.472,
"type": "async"
},
{
"num": 500,
"time": 0.768,
"type": "sync"
},
{
"num": 1000,
"time": 1.353,
"type": "sync"
},
{
"num": 1500,
"time": 1.951,
"type": "sync"
},
{
"num": 2000,
"time": 2.604,
"type": "sync"
},
{
"num": 2500,
"time": 3.522,
"type": "sync"
},
{
"num": 3000,
"time": 4.03,
"type": "sync"
},
{
"num": 3500,
"time": 4.803,
"type": "sync"
},
{
"num": 4000,
"time": 6.168,
"type": "sync"
},
{
"num": 4500,
"time": 7.369,
"type": "sync"
},
{
"num": 5000,
"time": 8.581,
"type": "sync"
}
]

View File

@ -1,204 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Graph, Cell, Node as N, Markup } from '@antv/x6'
import '../../../index.less'
type Result = { [key: string]: number[] }
const MAX_NODE_NUM = 5000 // 最大节点数量
const ITERATIONS = 5 // 迭代 5 次求平均数
const STEP = 500 // 每次增加 500 节点
const ASYNC = false
const result: Result = {} as any
const Node = N.registry.register(
'performance_normal_node',
{
inherit: 'circle',
size: {
width: 24,
height: 24,
},
attrs: {
body: {
fill: '#855af2',
stroke: 'transparent',
},
text: {
fill: '#fff',
},
},
portMarkup: [Markup.getForeignObjectMarkup()],
ports: {
groups: {
top: {
position: 'top',
attrs: {
fo: {
width: 10,
height: 10,
x: -5,
y: -5,
magnet: 'true',
},
},
},
right: {
position: 'right',
attrs: {
fo: {
width: 10,
height: 10,
x: -5,
y: -5,
magnet: 'true',
},
},
},
bottom: {
position: 'bottom',
attrs: {
fo: {
width: 10,
height: 10,
x: -5,
y: -5,
magnet: 'true',
},
},
},
left: {
position: 'left',
attrs: {
fo: {
width: 10,
height: 10,
x: -5,
y: -5,
magnet: 'true',
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
],
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 1000,
height: 1000,
grid: 1,
async: ASYNC,
onPortRendered(args) {
const selectors = args.contentSelectors
const container = selectors && selectors.foContent
if (container) {
ReactDOM.render(
<div
style={{
width: '100%',
height: '100%',
border: '1px solid #808080',
borderRadius: '100%',
background: '#eee',
}}
/>,
container as HTMLElement,
)
}
},
})
function mockCells(num: number) {
const node = new Node()
const cells: Cell[] = []
Array.from({ length: num }).forEach((_, n) => {
const x = Math.round(Math.random() * 900) + 50
const y = Math.round(Math.random() * 900) + 50
const a = node
.clone()
.position(x, y)
.attr('label/text', n + 1)
cells.push(a)
})
return cells
}
function test(num: number, iterations: number) {
const cells = mockCells(num)
const startTime = new Date().getTime()
graph.model.resetCells(cells)
if (ASYNC) {
graph.once('render:done', recordResult)
} else {
recordResult()
}
function recordResult() {
const duration = (new Date().getTime() - startTime) / 1000
if (result[num]) {
result[num].push(duration)
} else {
result[num] = [duration]
}
if (iterations < ITERATIONS) {
test(num, iterations + 1)
} else if (num < MAX_NODE_NUM) {
test(num + STEP, 0)
} else {
output()
}
}
}
function output() {
const res = Object.keys(result).map((key: string) => ({
num: parseInt(key, 10),
time: parseFloat(
(result[key].reduce((pre, cur) => pre + cur, 0) / ITERATIONS).toFixed(
3,
),
),
type: ASYNC ? 'async' : 'sync',
}))
document.getElementById('result')!.innerText = JSON.stringify(res)
}
test(500, 0)
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div id="result" style={{ paddingLeft: 8, paddingBottom: 8 }} />
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,383 +0,0 @@
// import React from 'react'
// import { Graph, Color } from '@antv/x6'
// import '@antv/x6-react-shape'
// import { Button } from 'antd'
// import '../index.less'
// Graph.registerNode(
// 'org-node',
// {
// width: 260,
// height: 88,
// markup: [
// {
// tagName: 'rect',
// attrs: {
// class: 'card',
// },
// },
// {
// tagName: 'image',
// attrs: {
// class: 'image',
// },
// },
// {
// tagName: 'text',
// attrs: {
// class: 'rank',
// },
// },
// {
// tagName: 'text',
// attrs: {
// class: 'name',
// },
// },
// {
// tagName: 'g',
// attrs: {
// class: 'btn add',
// },
// children: [
// {
// tagName: 'circle',
// attrs: {
// class: 'add',
// },
// },
// {
// tagName: 'text',
// attrs: {
// class: 'add',
// },
// },
// ],
// },
// {
// tagName: 'g',
// attrs: {
// class: 'btn del',
// },
// children: [
// {
// tagName: 'circle',
// attrs: {
// class: 'del',
// },
// },
// {
// tagName: 'text',
// attrs: {
// class: 'del',
// },
// },
// ],
// },
// ],
// attrs: {
// '.card': {
// rx: 10,
// ry: 10,
// refWidth: '100%',
// refHeight: '100%',
// fill: '#FFF',
// stroke: '#000',
// strokeWidth: 0,
// pointerEvents: 'visiblePainted',
// },
// '.image': {
// x: 16,
// y: 16,
// width: 56,
// height: 56,
// opacity: 0.7,
// },
// '.rank': {
// refX: 0.95,
// refY: 0.5,
// fontFamily: 'Courier New',
// fontSize: 13,
// textAnchor: 'end',
// textVerticalAnchor: 'middle',
// },
// '.name': {
// refX: 0.95,
// refY: 0.7,
// fontFamily: 'Arial',
// fontSize: 14,
// fontWeight: '600',
// textAnchor: 'end',
// },
// '.btn.add': {
// refDx: -16,
// refY: 16,
// event: 'node:add',
// },
// '.btn.del': {
// refDx: -44,
// refY: 16,
// event: 'node:delete',
// },
// '.btn > circle': {
// r: 10,
// fill: 'transparent',
// stroke: '#333',
// strokeWidth: 1,
// },
// '.btn.add > text': {
// fontSize: 20,
// fontWeight: 800,
// stroke: '#000',
// x: -5.5,
// y: 7,
// fontFamily: 'Times New Roman',
// text: '+',
// },
// '.btn.del > text': {
// fontSize: 28,
// fontWeight: 500,
// stroke: '#000',
// x: -4.5,
// y: 6,
// fontFamily: 'Times New Roman',
// text: '-',
// },
// },
// },
// true,
// )
// Graph.registerReactComponent(
// 'custom',
// <div
// style={{
// display: 'flex',
// alignItems: 'center',
// border: '1px solid #1890ff',
// backgroundColor: 'rgba(227,244,255,.9)',
// boxShadow: '0 0 3px 3px rgb(64 169 255 / 20%)',
// borderRadius: '16px',
// }}
// >
// <div>
// <svg
// width="1em"
// height="1em"
// viewBox="0 0 16 16"
// xmlns="http://www.w3.org/2000/svg"
// >
// <path
// d="M9.636 1c.133 0 .26.053.355.147l3.362 3.364a.5.5 0 01.147.353V14.5a.5.5 0 01-.5.5H3a.5.5 0 01-.5-.5v-13A.5.5 0 013 1h6.636zM8.42 9.2c-1.032.048-1.572.593-1.621 1.637.039.93.583 1.465 1.48 1.61l.554.753.902-.273-.613-.61.138-.057c.572-.274.752-.869.738-1.423-.026-1.036-.594-1.58-1.578-1.637zm-3.546-.4c-.786.043-1.211.56-1.274 1.125-.021.524.32 1.02.959 1.155.236.04.495.105.692.184.252.101.34.215.33.4-.02.267-.2.416-.534.431-.357.017-.582-.204-.645-.677l-.802.108c.168.76.467 1.259 1.43 1.274.88.013 1.335-.51 1.366-1.166-.01-.37-.114-.642-.471-.862a3.35 3.35 0 00-.818-.324c-.425-.11-.634-.263-.613-.447.02-.205.15-.393.455-.4.303-.007.476.093.644.415l.806-.214C6.264 9.145 5.703 8.78 4.875 8.8zm6.351.4H10.4v3.2h2.4v-.76h-1.574V9.2zm-2.834.705c.448 0 .821.254.842.934-.02.69-.37.901-.842.901-.452 0-.836-.231-.856-.9.03-.67.42-.935.856-.935zm1.014-7.752v2.94h2.94l-2.94-2.94z"
// fill="#1890FF"
// fillRule="nonzero"
// ></path>
// </svg>
// </div>
// <div>SCQL脚本-1</div>
// </div>,
// true,
// )
// export default class Example extends React.Component {
// private container: HTMLDivElement
// private graph: Graph
// componentDidMount() {
// const graph = new Graph({
// container: this.container,
// width: 1600,
// height: 1000,
// grid: true,
// async: false,
// })
// this.graph = graph
// }
// addNodes = () => {
// const nodes = []
// for (let i = 0; i < 500; i++) {
// nodes.push({
// id: i + '',
// x: Math.floor(Math.random() * 1600),
// y: Math.floor(Math.random() * 1000),
// shape: 'org-node',
// attrs: {
// '.card': { fill: Color.randomHex() },
// '.image': {
// xlinkHref:
// 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ',
// },
// '.rank': {
// fill: '#31d0c6',
// text: `${i}`,
// },
// '.name': {
// fill: '#000',
// text: `${i}`,
// },
// '.btn > circle': { stroke: '#000' },
// '.btn > text': { fill: '#000', stroke: '#000' },
// },
// })
// }
// const start = performance.now()
// this.graph.addNodes(nodes)
// console.log('addNodes', performance.now() - start)
// }
// addEdges = () => {
// const edges = []
// for (let i = 0; i < 500; i++) {
// edges.push({
// source: Math.floor(Math.random() * 500) + '',
// target: Math.floor(Math.random() * 500) + '',
// })
// }
// const start = performance.now()
// this.graph.addEdges(edges)
// console.log('addEdges', performance.now() - start)
// }
// addNodesAndEdges = () => {
// this.addNodes()
// this.addEdges()
// }
// addNodesWithPorts = () => {
// const nodes = []
// for (let i = 0; i < 500; i++) {
// nodes.push({
// id: i + '',
// x: Math.floor(Math.random() * 1600),
// y: Math.floor(Math.random() * 1000),
// shape: 'org-node',
// attrs: {
// '.card': { fill: '#31d0c6' },
// '.image': {
// xlinkHref:
// 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*kUy8SrEDp6YAAAAAAAAAAAAAARQnAQ',
// },
// '.rank': {
// fill: '#31d0c6',
// text: '123',
// },
// '.name': {
// fill: '#000',
// text: 'abc',
// },
// '.btn > circle': { stroke: '#000' },
// '.btn > text': { fill: '#000', stroke: '#000' },
// },
// ports: {
// groups: {
// top: {
// position: 'top',
// attrs: {
// circle: {
// r: 6,
// magnet: true,
// stroke: '#31d0c6',
// strokeWidth: 2,
// fill: '#fff',
// },
// },
// },
// right: {
// position: 'right',
// attrs: {
// circle: {
// r: 6,
// magnet: true,
// stroke: '#31d0c6',
// strokeWidth: 2,
// fill: '#fff',
// },
// },
// },
// bottom: {
// position: 'bottom',
// attrs: {
// circle: {
// r: 6,
// magnet: true,
// stroke: '#31d0c6',
// strokeWidth: 2,
// fill: '#fff',
// },
// },
// },
// left: {
// position: 'left',
// attrs: {
// circle: {
// r: 6,
// magnet: true,
// stroke: '#31d0c6',
// strokeWidth: 2,
// fill: '#fff',
// },
// },
// },
// },
// items: [
// {
// group: 'top',
// id: i + `_port_top`,
// },
// {
// group: 'right',
// id: i + `_port_right`,
// },
// {
// group: 'bottom',
// id: i + `_port_bottom`,
// },
// {
// group: 'left',
// id: i + `_port_left`,
// },
// ],
// },
// })
// }
// const start = performance.now()
// this.graph.addNodes(nodes)
// console.log('addNodesWithPorts', performance.now() - start)
// }
// addReactNodes = () => {
// const nodes = []
// for (let i = 0; i < 500; i++) {
// nodes.push({
// id: i + '',
// x: Math.floor(Math.random() * 1600),
// y: Math.floor(Math.random() * 1000),
// shape: 'react-shape',
// component: 'custom',
// width: 160,
// height: 30,
// })
// }
// const start = performance.now()
// this.graph.addNodes(nodes)
// console.log('addReactNodes', performance.now() - start)
// }
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="x6-graph" />
// <Button onClick={this.addNodes}>addNodes</Button>
// <Button onClick={this.addEdges}>addEdges</Button>
// <Button onClick={this.addNodesAndEdges}>addNodesAndEdges</Button>
// <Button onClick={this.addNodesWithPorts}>addNodesWithPorts</Button>
// <Button onClick={this.addReactNodes}>addReactNodes</Button>
// </div>
// )
// }
// }

View File

@ -1,175 +0,0 @@
import React from 'react'
import { Graph, Shape } from '@antv/x6'
Shape.Rect.config({
ports: {
groups: {
in: {
position: { name: 'top' },
},
out: {
position: { name: 'bottom' },
},
},
},
portMarkup: [
{
tagName: 'circle',
selector: 'portBody',
attrs: {
magnet: 'true',
r: 6,
fill: '#fff',
stroke: '#000',
'stroke-width': 2,
},
},
],
})
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const magnetAvailabilityHighlighter = {
name: 'stroke',
args: {
padding: 3,
attrs: {
strokeWidth: 3,
stroke: '#52c41a',
},
},
}
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: true,
highlighting: {
magnetAvailable: magnetAvailabilityHighlighter,
},
connecting: {
allowBlank: false,
snap: true,
highlight: true,
validateMagnet({ magnet }) {
return magnet.getAttribute('port-group') !== 'in'
},
validateConnection({
sourceView,
sourceMagnet,
targetView,
targetMagnet,
}) {
// 不允许连接到自己
if (sourceView === targetView) {
return false
}
// 只能从输出链接桩创建连接
if (
!sourceMagnet ||
sourceMagnet.getAttribute('port-group') === 'in'
) {
return false
}
// 只能连接到输入链接桩
if (
!targetMagnet ||
targetMagnet.getAttribute('port-group') !== 'in'
) {
return false
}
return true
},
},
})
const source = graph.addNode({
x: 40,
y: 40,
width: 100,
height: 40,
label: 'Source',
ports: [
{ id: 'in-1', group: 'in' },
{ id: 'in-2', group: 'in' },
{ id: 'out-1', group: 'out' },
{ id: 'out-2', group: 'out' },
],
})
const target = graph.addNode({
x: 140,
y: 240,
width: 100,
height: 40,
label: 'Target',
ports: [
{ id: 'in-1', group: 'in' },
{ id: 'in-2', group: 'in' },
{ id: 'out-1', group: 'out' },
{ id: 'out-2', group: 'out' },
],
})
graph.addNode({
x: 320,
y: 120,
width: 100,
height: 40,
label: 'Hello',
ports: [
{ id: 'in-1', group: 'in' },
{ id: 'in-2', group: 'in' },
{ id: 'out-1', group: 'out' },
{ id: 'out-2', group: 'out' },
],
})
graph.addEdge({
source: { cell: source.id, port: 'out-2' },
target: { cell: target.id, port: 'in-1' },
tools: [
{
name: 'source-arrowhead',
},
{
name: 'target-arrowhead',
},
],
})
// graph.on('edge:mouseenter', ({ cell }) => {
// cell.addTools([
// {
// name: 'source-arrowhead',
// },
// {
// name: 'target-arrowhead',
// },
// ])
// })
// graph.on('edge:mouseleave', ({ cell }) => {
// cell.removeTools()
// })
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}
}

View File

@ -4,7 +4,7 @@ import { Path } from '@antv/x6-geometry'
import '../index.less'
Graph.registerConnector(
'ports-connected-connector',
'algo-connector',
(s, e) => {
const offset = 4
const deltaY = Math.abs(e.y - s.y)
@ -25,7 +25,7 @@ Graph.registerConnector(
)
Graph.registerNode(
'ports-connected-node',
'algo-node',
{
width: 144,
height: 28,
@ -107,7 +107,7 @@ export default class Example extends React.Component {
width: 800,
height: 600,
connecting: {
connector: 'ports-connected-connector',
connector: 'algo-connector',
createEdge() {
return this.createEdge({
attrs: {
@ -124,7 +124,7 @@ export default class Example extends React.Component {
})
graph.addNode({
shape: 'ports-connected-node',
shape: 'algo-node',
width: 144,
height: 28,
x: 200,
@ -144,7 +144,7 @@ export default class Example extends React.Component {
})
graph.addNode({
shape: 'ports-connected-node',
shape: 'algo-node',
x: 200,
y: 400,
width: 144,

View File

@ -16,7 +16,7 @@ export default class Example extends React.Component {
})
const rect = graph.addNode({
shape: 'basic.rect',
shape: 'rect',
x: 280,
y: 120,
width: 100,
@ -25,9 +25,17 @@ export default class Example extends React.Component {
attrs: {
rect: { stroke: '#31d0c6', strokeWidth: 2 },
},
ports: {
groups: {
left: {
position: 'left',
},
},
},
})
rect.addPort({
group: 'left',
attrs: {
circle: {
magnet: true,
@ -39,6 +47,7 @@ export default class Example extends React.Component {
})
rect.addPort({
group: 'left',
attrs: {
circle: {
magnet: true,
@ -50,6 +59,7 @@ export default class Example extends React.Component {
})
rect.addPort({
group: 'left',
attrs: {
circle: {
magnet: true,
@ -61,7 +71,7 @@ export default class Example extends React.Component {
})
const circle = graph.addNode({
shape: 'basic.circle',
shape: 'circle',
x: 100,
y: 165,
width: 60,
@ -91,6 +101,7 @@ export default class Example extends React.Component {
onAddPort = () => {
this.rect.addPort({
group: 'left',
attrs: {
circle: {
magnet: true,

View File

@ -59,6 +59,7 @@ MyShape.config({
},
body: {
fill: '#f5f5f5',
stroke: '#ccc',
},
},
ports: {

View File

@ -19,28 +19,16 @@ export default class Example extends React.Component {
})
const rect = graph.addNode({
shape: 'basic.rect',
markup:
'<g class="rotatable"><g class="scalable"><rect class="main"/></g><rect class="inner"/></g>',
shape: 'rect',
x: 130,
y: 30,
width: 80,
height: 150,
attrs: {
'.main': {
width: 80,
height: 150,
stroke: '#31d0c6',
'stroke-width': 2,
},
'.inner': {
width: 60,
height: 130,
'ref-x': 10,
'ref-y': 10,
stroke: '#31d0c6',
'stroke-width': 2,
fill: '#7c68fc',
ports: {
groups: {
left: {
position: 'left',
},
},
},
})
@ -52,6 +40,7 @@ export default class Example extends React.Component {
rect.addPort({
zIndex,
id: `${portIndex}`,
group: 'left',
attrs: {
circle: {
r: 20,

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Scroller } from '@antv/x6-plugin-scroller'
import { Button } from 'antd'
import '../index.less'
@ -9,6 +10,7 @@ export default class Example extends React.Component {
private graph1: Graph
private graph2: Graph
private scroller: Scroller
componentDidMount() {
this.graph1 = new Graph({
container: this.container1,
@ -22,11 +24,11 @@ export default class Example extends React.Component {
width: 600,
height: 400,
grid: true,
scroller: {
enabled: true,
},
})
this.scroller = new Scroller({ enabled: true })
this.graph2.use(this.scroller)
const data = [
{
id: '1',
@ -134,12 +136,12 @@ export default class Example extends React.Component {
onZoom = (factor: number, options?: any) => {
this.graph1.zoom(factor, options)
this.graph2.zoom(factor, options)
this.scroller.zoom(factor, options)
}
onZoomTo = (factor: number, options?: any) => {
this.graph1.zoomTo(factor, options)
this.graph2.zoomTo(factor, options)
this.scroller.zoomTo(factor, options)
}
onZoomToRect = () => {
@ -149,7 +151,7 @@ export default class Example extends React.Component {
width: 300,
height: 150,
})
this.graph2.zoomToRect({
this.scroller.zoomToRect({
x: 120,
y: 60,
width: 300,
@ -159,34 +161,34 @@ export default class Example extends React.Component {
onZoomToFit = () => {
this.graph1.zoomToFit()
this.graph2.zoomToFit()
this.scroller.zoomToFit()
}
onCenterPoint = () => {
this.graph1.centerPoint(100, 50)
this.graph2.centerPoint(100, 50)
this.scroller.centerPoint(100, 50)
}
onCenter = () => {
this.graph1.center()
this.graph2.center()
this.scroller.center()
}
onCenterContent = () => {
this.graph1.centerContent()
this.graph2.centerContent()
this.scroller.centerContent()
}
onCenterCell = () => {
const cell1 = this.graph1.getCellById('1')
const cell2 = this.graph2.getCellById('1')
this.graph1.centerCell(cell1)
this.graph2.centerCell(cell2)
this.scroller.centerCell(cell2)
}
onPositionPoint = () => {
this.graph1.positionPoint({ x: 50, y: 60 }, 100, 100)
this.graph2.positionPoint({ x: 50, y: 60 }, 100, 100)
this.scroller.positionPoint({ x: 50, y: 60 }, 100, 100)
}
onPositionRect = () => {
@ -197,19 +199,19 @@ export default class Example extends React.Component {
height: 60,
}
this.graph1.positionRect(r, 'top')
this.graph2.positionRect(r, 'top')
this.scroller.positionRect(r, 'top')
}
onPositionContent = () => {
this.graph1.positionContent('center')
this.graph2.positionContent('center')
this.scroller.positionContent('center')
}
onPositionCell = () => {
const cell1 = this.graph1.getCellById('1')
const cell2 = this.graph2.getCellById('1')
this.graph1.positionCell(cell1, 'center')
this.graph2.positionCell(cell2, 'center')
this.scroller.positionCell(cell2, 'center')
}
render() {

View File

@ -1,77 +0,0 @@
import React from 'react'
import { Graph, Node } from '@antv/x6'
import { ReactShape, register } from '@antv/x6-react-shape'
import '../index.less'
import './index.less'
class GroupNode extends ReactShape {
isGroup() {
return true
}
}
Graph.registerNode('group-node', GroupNode, true)
const NodeComponent = ({ node }: { node: Node }) => {
const data = node.getData()
return (
<div className="react-algo-node">
<img
src="https://gw.alipayobjects.com/zos/bmw-prod/d9f3b597-3a2e-49c3-8469-64a1168ed779.svg"
alt=""
/>
<span>{data.name}</span>
</div>
)
}
register(NodeComponent, {
shape: 'algo-node-3',
width: 144,
height: 28,
effect: ['data'],
inherit: 'group-node',
})
export default class Example extends React.Component {
private container: HTMLDivElement
private count = 0
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
})
const node = graph.createNode({
shape: 'algo-node-3',
x: 80,
y: 80,
data: {
name: '逻辑回归',
},
})
console.log(node.isGroup())
const update = () => {
node.setData({ name: `逻辑回归 ${(this.count += 1)}` })
setTimeout(update, 1000)
}
update()
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -18,11 +18,12 @@ const NodeComponent = ({ node }: { node: Node }) => {
)
}
register(NodeComponent, {
register({
shape: 'algo-node-1',
width: 144,
height: 28,
effect: ['data'],
component: NodeComponent,
})
export default class Example extends React.Component {

View File

@ -1,111 +0,0 @@
import React from 'react'
import { Graph, Node, Color } from '@antv/x6'
import '@antv/x6-react-shape'
import '../index.less'
class MyComponent extends React.Component<{ node?: Node; text: string }> {
shouldComponentUpdate() {
const node = this.props.node
if (node) {
if (node.hasChanged('data')) {
return true
}
}
return false
}
render() {
const color = Color.randomHex()
return (
<div
// onMouseDown={() => {
// console.log('div')
// }}
style={{
color: Color.invert(color, true),
width: '100%',
height: '100%',
textAlign: 'center',
lineHeight: '60px',
background: color,
}}
>
{this.props.text}
</div>
)
}
}
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
selecting: {
enabled: true,
},
keyboard: {
enabled: true,
},
})
graph.bindKey('backspace', () => {
graph.removeCells(graph.getSelectedCells())
})
const source = graph.addNode({
shape: 'react-shape',
x: 80,
y: 80,
width: 160,
height: 60,
data: {},
xxx: {},
component: <MyComponent text="Source" />,
})
const target = graph.addNode({
shape: 'react-shape',
x: 320,
y: 320,
width: 180,
height: 80,
component: (node) => {
return (
<div
style={{
width: '100%',
height: '100%',
border: '1px solid #eee',
padding: 8,
}}
>
<input />
</div>
)
},
})
graph.addEdge({
source,
target,
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,6 +1,7 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Graph, Dom } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Dom } from '@antv/x6-common'
import { Button } from 'antd'
import '../index.less'

View File

@ -1,317 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom'
import { Graph, Shape, Node } from '@antv/x6'
import { Dom } from '@antv/x6-common'
import { Tooltip } from 'antd'
import classnames from 'classnames'
import '@antv/x6-react-shape'
import './port.less'
import '../index.less'
Shape.Rect.define({
shape: 'rect-port',
attrs: {
body: {
strokeWidth: 1,
stroke: '#108ee9',
fill: '#fff',
rx: 15,
ry: 15,
},
},
portMarkup: [
{
tagName: 'foreignObject',
selector: 'fo',
attrs: {
width: 10,
height: 10,
x: -5,
y: -5,
magnet: 'true',
},
children: [
{
ns: Dom.ns.xhtml,
tagName: 'body',
selector: 'foBody',
attrs: {
xmlns: Dom.ns.xhtml,
},
style: {
width: '100%',
height: '100%',
},
children: [
{
tagName: 'div',
selector: 'content',
style: {
width: '100%',
height: '100%',
},
},
],
},
],
},
],
})
Graph.registerConnector(
'pai',
(s, t) => {
const offset = 4
const control = 80
const v1 = { x: s.x, y: s.y + offset + control }
const v2 = { x: t.x, y: t.y - offset - control }
return `M ${s.x} ${s.y}
L ${s.x} ${s.y + offset}
C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${t.x} ${t.y - offset}
L ${t.x} ${t.y}
`
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
onPortRendered(args) {
// console.log(args)
const port = args.port
const contentSelectors = args.contentSelectors
const container = contentSelectors && contentSelectors.content
if (container) {
ReactDOM.render(
<Tooltip title="port">
<div
className={classnames('my-port', {
connected: port.connected,
})}
/>
</Tooltip>,
container,
)
}
},
highlighting: {
nodeAvailable: {
name: 'className',
args: {
className: 'available',
},
},
magnetAvailable: {
name: 'className',
args: {
className: 'available',
},
},
magnetAdsorbed: {
name: 'className',
args: {
className: 'adsorbed',
},
},
},
connecting: {
snap: true,
allowBlank: false,
highlight: true,
sourceAnchor: 'bottom',
targetAnchor: 'center',
connectionPoint: 'anchor',
connector: 'pai',
// connector: 'smooth',
createEdge() {
return new Shape.Edge({
attrs: {
line: {
strokeDasharray: '5 5',
stroke: '#808080',
strokeWidth: 1,
targetMarker: {
name: 'block',
args: {
size: '6',
},
},
},
},
})
},
validateMagnet({ magnet }) {
return magnet.getAttribute('port-group') !== 'in'
},
validateConnection({
sourceView,
targetView,
sourceMagnet,
targetMagnet,
}) {
// 不允许连接到自己
if (sourceView === targetView) {
return false
}
// 只能从输出链接桩创建连接
if (
!sourceMagnet ||
sourceMagnet.getAttribute('port-group') === 'in'
) {
return false
}
// 只能连接到输入链接桩
if (
!targetMagnet ||
targetMagnet.getAttribute('port-group') !== 'in'
) {
return false
}
// 判断目标链接桩是否可连接
const portId = targetMagnet.getAttribute('port')!
const node = targetView.cell as Node
const port = node.getPort(portId)
if (port && port.connected) {
return false
}
return true
},
},
})
graph.on('edge:connected', (args) => {
const edge = args.edge
const node = args.currentCell as Node
const elem = args.currentMagnet as HTMLElement
const portId = elem.getAttribute('port') as string
// 触发 port 重新渲染
node.setPortProp(portId, 'connected', true)
// 更新连线样式
edge.attr({
line: {
strokeDasharray: '',
targetMarker: '',
},
})
})
graph.addNode({
shape: 'rect-port',
width: 160,
height: 30,
x: 360,
y: 180,
label: '归一化',
ports: {
items: [
{ group: 'in', id: 'in1' },
{ group: 'in', id: 'in2' },
{ group: 'out', id: 'out1' },
{ group: 'out', id: 'out2' },
],
groups: {
in: {
position: { name: 'top' },
zIndex: 1,
},
out: {
position: { name: 'bottom' },
zIndex: 1,
},
},
},
})
const source = graph.addNode({
shape: 'rect-port',
width: 160,
height: 30,
x: 40,
y: 40,
label: 'SQL',
ports: {
items: [
{ group: 'in', id: 'in1' },
{ group: 'in', id: 'in2' },
{ group: 'out', id: 'out1' },
],
groups: {
in: {
position: { name: 'top' },
zIndex: 1,
},
out: {
position: { name: 'bottom' },
zIndex: 1,
},
},
},
})
const target = graph.addNode({
shape: 'rect-port',
width: 160,
height: 30,
x: 240,
y: 380,
label: '序列化',
ports: {
items: [
{ group: 'in', id: 'in1', connected: true },
{ group: 'in', id: 'in2' },
{ group: 'out', id: 'out1' },
],
groups: {
in: {
position: { name: 'top' },
zIndex: 1,
},
out: {
position: { name: 'bottom' },
zIndex: 1,
},
},
},
// data: {
// connection: { in1: true },
// },
})
graph.addEdge({
source: { cell: source.id, port: 'out1' },
target: { cell: target.id, port: 'in1' },
attrs: {
line: {
stroke: '#808080',
strokeWidth: 1,
targetMarker: '',
},
},
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -22,11 +22,12 @@ const NodeComponent = () => {
)
}
register(NodeComponent, {
register({
shape: 'algo-node-2',
width: 144,
height: 28,
effect: [],
component: NodeComponent,
})
export default class Example extends React.Component {
@ -71,7 +72,7 @@ export default class Example extends React.Component {
</ThemeContext.Provider>
<div className="x6-graph-tools">
<Button onClick={this.changeTheme}>
{this.state.theme === 'light' ? 'Light' : 'Dark'}
{this.state.theme === 'light' ? 'Dark' : 'Light'}
</Button>
</div>
<div ref={this.refContainer} className="x6-graph" />

View File

@ -1,39 +0,0 @@
.react-shape-ports {
position: relative;
.in-ports,
.out-ports {
position: absolute;
left: 0;
right: 0;
> span {
position: absolute;
display: inline-block;
width: 12px;
height: 12px;
border: 2px solid #ccc;
border-radius: 6px;
margin-left: -6px;
top: -6px;
cursor: pointer;
background-color: #e9e9e9;
&:nth-child(1) {
left: 33.33%;
}
&:nth-child(2) {
left: 66.67%;
}
}
}
.in-ports {
top: 0;
}
.out-ports {
bottom: 0;
}
}

View File

@ -1,104 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Color } from '@antv/x6-common'
import '@antv/x6-react-shape'
import '../index.less'
import './ports.less'
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
})
const sourceColor = Color.randomHex()
const targetColor = Color.randomHex()
const source = graph.addNode({
shape: 'react-shape',
x: 80,
y: 80,
width: 160,
height: 60,
attrs: {
'.': {
magnet: false,
},
},
component: (
<div
className="react-shape-ports"
style={{
color: Color.invert(sourceColor, true),
width: '100%',
height: '100%',
textAlign: 'center',
lineHeight: '60px',
background: sourceColor,
}}
>
<div key="text">Source</div>
<div className="in-ports" key="in-ports">
<span id="1-in-port-1" key="in-port-1" magnet="true" />
<span id="1-in-port-2" key="in-port-2" magnet="true" />
</div>
<div className="out-ports" key="out-ports">
<span id="1-out-port-1" key="out-port-1" magnet="true" />
<span id="1-out-port-2" key="out-port-2" magnet="true" />
</div>
</div>
),
})
const target = graph.addNode({
shape: 'react-shape',
x: 320,
y: 320,
width: 160,
height: 60,
component: (
<div
className="react-shape-ports"
style={{
color: Color.invert(targetColor, true),
width: '100%',
height: '100%',
textAlign: 'center',
lineHeight: '60px',
background: targetColor,
}}
>
<div key="text">Target</div>
<div className="in-ports" key="in-ports">
<span id="2-in-port-1" key="in-port-1" magnet="true" />
<span id="2-in-port-2" key="in-port-2" magnet="true" />
</div>
<div className="out-ports" key="out-ports">
<span id="2-out-port-1" key="out-port-1" magnet="true" />
<span id="2-out-port-2" key="out-port-2" magnet="true" />
</div>
</div>
),
})
graph.addEdge({
source: { cell: source, selector: '[id="out-port-2"]' },
target: { cell: target },
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,6 +1,8 @@
import React from 'react'
import { Button } from 'antd'
import { Graph, NodeView, DataUri } from '@antv/x6'
import { Graph, NodeView } from '@antv/x6'
import { Scroller } from '@antv/x6-plugin-scroller'
import { Selection } from '@antv/x6-plugin-selection'
import '../index.less'
import './index.less'
@ -29,37 +31,20 @@ export default class Example extends React.Component {
private graph: Graph
private graphContainer: HTMLDivElement
private minimapContainer: HTMLDivElement
private scroller: any
private scroller: Scroller
private selection: Selection
componentDidMount() {
const graph = new Graph({
this.graph = new Graph({
container: this.graphContainer,
width: 800,
height: 500,
resizing: true,
background: {
color: '#f5f5f5',
},
grid: {
visible: true,
},
selecting: {
enabled: true,
rubberband: true,
modifiers: 'shift',
},
scroller: {
enabled: true,
// width: 600,
// height: 400,
pageVisible: true,
pageBreak: true,
pannable: {
enabled: true,
eventTypes: ['leftMouseDown', 'rightMouseDown'],
},
// modifiers: 'shift',
},
minimap: {
enabled: true,
container: this.minimapContainer,
@ -89,9 +74,24 @@ export default class Example extends React.Component {
},
})
this.scroller = graph.scroller.widget
this.scroller = new Scroller({
enabled: true,
pageVisible: true,
pageBreak: true,
pannable: {
enabled: true,
eventTypes: ['leftMouseDown', 'rightMouseDown'],
},
})
this.selection = new Selection({
enabled: true,
rubberband: true,
modifiers: 'shift',
})
this.graph.use(this.scroller)
this.graph.use(this.selection)
const rect = graph.addNode({
const rect = this.graph.addNode({
shape: 'rect',
x: 300,
y: 300,
@ -104,11 +104,7 @@ export default class Example extends React.Component {
ports: [{}],
})
rect.on('removed', () => {
console.log('rect was removed')
})
const circle = graph.addNode({
const circle = this.graph.addNode({
shape: 'circle',
x: 400,
y: 400,
@ -120,18 +116,10 @@ export default class Example extends React.Component {
},
})
graph.addEdge({
this.graph.addEdge({
source: rect,
target: circle,
})
// graph.removeCell(rect)
// graph.centerContent()
// graph.resize(300, 200)
// graph.zoomToFit()
this.graph = graph
graph.positionCell(rect, 'left', { padding: { left: 100 } })
}
refContainer = (container: HTMLDivElement) => {
@ -143,14 +131,11 @@ export default class Example extends React.Component {
}
onCenterClick = () => {
this.graph.center()
// this.graph.center({ padding: { left: 300 } })
// this.graph.centerPoint(0, 0)
// this.graph.positionPoint({ x: 0, y: 0 }, 100, 100)
this.scroller.center()
}
onCenterContentClick = () => {
this.graph.centerContent()
this.scroller.centerContent()
}
onZoomOutClick = () => {
@ -166,9 +151,9 @@ export default class Example extends React.Component {
}
onDownload = () => {
this.graph.toPNG((datauri: string) => {
DataUri.downloadDataUri(datauri, 'chart.png')
})
// this.graph.toPNG((datauri: string) => {
// DataUri.downloadDataUri(datauri, 'chart.png')
// })
}
render() {

View File

@ -1,71 +0,0 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Scroller } from '@antv/x6-plugin-scroller'
import '../index.less'
import './index.less'
export default class Example extends React.Component {
private graph: Graph
private graphContainer: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.graphContainer,
width: 800,
height: 500,
background: {
color: '#f5f5f5',
},
grid: {
visible: true,
},
mousewheel: {
enabled: true,
// fixed: false,
modifiers: ['ctrl', 'meta'],
minScale: 0.5,
maxScale: 2,
},
})
graph.use(
new Scroller({
enabled: true,
// width: 600,
// height: 400,
pageVisible: true,
pageBreak: true,
pannable: {
enabled: true,
eventTypes: ['leftMouseDown', 'rightMouseDown'],
},
}),
)
graph.addNode({
shape: 'rect',
x: 300,
y: 300,
width: 90,
height: 60,
attrs: {
rect: { fill: '#31D0C6', stroke: '#4B4A67', 'stroke-width': 2 },
text: { text: 'rect', fill: 'white' },
},
ports: [{}],
})
}
refContainer = (container: HTMLDivElement) => {
this.graphContainer = container
}
render() {
return (
<div className="x6-graph-wrap">
<h1>Scroller</h1>
<div ref={this.refContainer} className="x6-graph" />
</div>
)
}
}

View File

@ -1,5 +1,5 @@
{
"version": "2.0.6-beta.24",
"version": "2.0.6-beta.26",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2022 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,5 @@
# `x6-plugin-clipboard`
> TODO: description
## Usage

View File

@ -0,0 +1,111 @@
{
"name": "@antv/x6-plugin-clipboard",
"version": "2.0.6-beta.26",
"description": "clipboard plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/x6-plugin-clipboard.js",
"jsdelivr": "dist/x6-plugin-clipboard.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"es",
"lib"
],
"keywords": [
"plugin",
"clipboard",
"x6",
"antv"
],
"scripts": {
"clean:build": "rimraf dist es lib",
"clean:coverage": "rimraf ./test/coverage",
"clean": "run-p clean:build clean:coverage",
"lint": "eslint 'src/**/*.{js,ts}?(x)' --fix",
"build:esm": "tsc --module esnext --target es2015 --outDir ./es",
"build:cjs": "tsc --module commonjs --target es2015 --outDir ./lib",
"build:umd": "rollup -c",
"build:dev": "run-p build:cjs build:esm",
"build:watch": "yarn build:esm --w",
"build:watch:esm": "yarn build:esm --w",
"build:watch:cjs": "yarn build:cjs --w",
"build": "run-p build:dev build:umd",
"prebuild": "run-s lint clean",
"coveralls": "cat ./test/coverage/lcov.info | coveralls",
"test": "echo \"no test case\"",
"pretest": "run-p clean:coverage",
"prepare": "run-s test build",
"precommit": "lint-staged"
},
"lint-staged": {
"src/**/*.ts": [
"eslint --fix"
]
},
"inherits": [
"@antv/x6-package-json/cli.json",
"@antv/x6-package-json/eslint.json",
"@antv/x6-package-json/rollup.json"
],
"peerDependencies": {
"@antv/x6": ">=2.0.6-beta.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@types/mousetrap": "^1.6.5",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"coveralls": "^3.1.1",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-unicorn": "^36.0.0",
"less": "^4.1.1",
"lint-staged": "^11.1.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.6",
"prettier": "^2.4.0",
"pretty-quick": "^3.1.1",
"rimraf": "^3.0.2",
"rollup": "^2.56.3",
"rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-filesize": "^9.1.1",
"rollup-plugin-postcss": "^4.0.1",
"rollup-plugin-progress": "^1.1.2",
"rollup-plugin-terser": "^7.0.2",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
},
"author": {
"name": "bubkoo",
"email": "bubkoo.wy@gmail.com"
},
"contributors": [],
"license": "MIT",
"homepage": "https://github.com/antvis/x6",
"bugs": {
"url": "https://github.com/antvis/x6/issues"
},
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-plugin-clipboard"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}

View File

@ -0,0 +1,17 @@
import config from '../../configs/rollup-config'
export default config({
output: [
{
name: 'X6PluginClipboard',
format: 'umd',
file: 'dist/x6-plugin-clipboard.js',
sourcemap: true,
globals: {
'@antv/x6': 'X6',
'@antv/x6-common': 'X6Common',
},
},
],
external: ['@antv/x6', '@antv/x6-common'],
})

View File

@ -0,0 +1,159 @@
import { ArrayExt } from '@antv/x6-common'
import { Config, Graph, Cell, Node, Edge, Model } from '@antv/x6'
export class ClipboardImpl {
protected options: ClipboardImpl.Options
public cells: Cell[] = []
copy(
cells: Cell[],
graph: Graph | Model,
options: ClipboardImpl.CopyOptions = {},
) {
this.options = { ...options }
const model = Model.isModel(graph) ? graph : graph.model
const cloned = model.cloneSubGraph(cells, options)
// sort asc by cell type
this.cells = ArrayExt.sortBy(
Object.keys(cloned).map((key) => cloned[key]),
(cell) => (cell.isEdge() ? 2 : 1),
)
this.serialize(options)
}
cut(
cells: Cell[],
graph: Graph | Model,
options: ClipboardImpl.CopyOptions = {},
) {
this.copy(cells, graph, options)
const model = Graph.isGraph(graph) ? graph.model : graph
model.batchUpdate('cut', () => {
cells.forEach((cell) => cell.remove())
})
}
paste(graph: Graph | Model, options: ClipboardImpl.PasteOptions = {}) {
const localOptions = { ...this.options, ...options }
const { offset, edgeProps, nodeProps } = localOptions
let dx = 20
let dy = 20
if (offset) {
dx = typeof offset === 'number' ? offset : offset.dx
dy = typeof offset === 'number' ? offset : offset.dy
}
this.deserialize(localOptions)
const cells = this.cells
cells.forEach((cell) => {
cell.model = null
cell.removeProp('zIndex')
if (dx || dy) {
cell.translate(dx, dy)
}
if (nodeProps && cell.isNode()) {
cell.prop(nodeProps)
}
if (edgeProps && cell.isEdge()) {
cell.prop(edgeProps)
}
})
const model = Graph.isGraph(graph) ? graph.model : graph
model.batchUpdate('paste', () => {
model.addCells(this.cells)
})
this.copy(cells, graph, options)
return cells
}
serialize(options: ClipboardImpl.PasteOptions) {
if (options.useLocalStorage !== false) {
Storage.save(this.cells)
}
}
deserialize(options: ClipboardImpl.PasteOptions) {
if (options.useLocalStorage) {
const cells = Storage.fetch()
if (cells) {
this.cells = cells
}
}
}
isEmpty() {
return this.cells.length <= 0
}
clean() {
this.options = {}
this.cells = []
Storage.clean()
}
}
export namespace ClipboardImpl {
export interface Options {
useLocalStorage?: boolean
}
export interface CopyOptions extends Options {
deep?: boolean
}
export interface PasteOptions extends Options {
/**
* Set of properties to be set on each copied node on every `paste()` call.
* It is defined as an object. e.g. `{ zIndex: 1 }`.
*/
nodeProps?: Node.Properties
/**
* Set of properties to be set on each copied edge on every `paste()` call.
* It is defined as an object. e.g. `{ zIndex: 1 }`.
*/
edgeProps?: Edge.Properties
/**
* An increment that is added to the pasted cells position on every
* `paste()` call. It can be either a number or an object with `dx`
* and `dy` attributes. It defaults to `{ dx: 20, dy: 20 }`.
*/
offset?: number | { dx: number; dy: number }
}
}
namespace Storage {
const LOCAL_STORAGE_KEY = `${Config.prefixCls}.clipboard.cells`
export function save(cells: Cell[]) {
if (window.localStorage) {
const data = cells.map((cell) => cell.toJSON())
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(data))
}
}
export function fetch() {
if (window.localStorage) {
const raw = localStorage.getItem(LOCAL_STORAGE_KEY)
const cells = raw ? JSON.parse(raw) : []
if (cells) {
return Model.fromJSON(cells)
}
}
}
export function clean() {
if (window.localStorage) {
localStorage.removeItem(LOCAL_STORAGE_KEY)
}
}
}

View File

@ -0,0 +1,154 @@
import { IDisablable, Disposable } from '@antv/x6-common'
import { Cell, Graph } from '@antv/x6'
import { ClipboardImpl } from './clipboard'
export class Clipboard extends Disposable implements IDisablable {
private clipboardImpl: ClipboardImpl
private graph: Graph
public name = 'clipboard'
constructor(public readonly options: Clipboard.Options) {
super()
}
init(graph: Graph) {
this.graph = graph
this.clipboardImpl = new ClipboardImpl()
this.clipboardImpl.deserialize(this.options)
}
// #region api
isClipboardEnabled() {
return !this.disabled
}
enableClipboard() {
this.enable()
return this
}
disableClipboard() {
this.disable()
return this
}
toggleClipboard(enabled?: boolean) {
if (enabled != null) {
if (enabled !== this.isClipboardEnabled()) {
if (enabled) {
this.enableClipboard()
} else {
this.disableClipboard()
}
}
} else if (this.isClipboardEnabled()) {
this.disableClipboard()
} else {
this.enableClipboard()
}
return this
}
isClipboardEmpty() {
return this.isEmpty()
}
getCellsInClipboard() {
return this.cells
}
cleanClipboard() {
this.clean()
return this
}
copy(cells: Cell[], options: Clipboard.CopyOptions = {}) {
if (!this.disabled) {
this.clipboardImpl.copy(cells, this.graph, {
...this.commonOptions,
...options,
})
this.graph.trigger('clipboard:changed', { cells })
}
}
cut(cells: Cell[], options: Clipboard.CopyOptions = {}) {
if (!this.disabled) {
this.clipboardImpl.cut(cells, this.graph, {
...this.commonOptions,
...options,
})
this.graph.trigger('clipboard:changed', { cells })
}
}
paste(options: Clipboard.PasteOptions = {}, graph: Graph = this.graph) {
if (!this.disabled) {
return this.clipboardImpl.paste(graph, {
...this.commonOptions,
...options,
})
}
return []
}
// #endregion
private get commonOptions() {
const { enabled, ...others } = this.options
return others
}
private get cells() {
return this.clipboardImpl.cells
}
get disabled() {
return this.options.enabled !== true
}
enable() {
if (this.disabled) {
this.options.enabled = true
}
}
disable() {
if (!this.disabled) {
this.options.enabled = false
}
}
private clean(force?: boolean) {
if (!this.disabled || force) {
this.clipboardImpl.clean()
this.graph.trigger('clipboard:changed', { cells: [] })
}
}
private isEmpty() {
return this.clipboardImpl.isEmpty()
}
@Disposable.dispose()
dispose() {
this.clean(true)
}
}
export namespace Clipboard {
export interface ClipboardEventArgs {
'clipboard:changed': {
cells: Cell[]
}
}
export interface Options extends ClipboardImpl.Options {
enabled?: boolean
}
export interface CopyOptions extends ClipboardImpl.CopyOptions {}
export interface PasteOptions extends ClipboardImpl.PasteOptions {}
}

View File

@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2022 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
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -0,0 +1,5 @@
# `x6-plugin-dnd`
> TODO: description
## Usage

View File

@ -0,0 +1,112 @@
{
"name": "@antv/x6-plugin-dnd",
"version": "2.0.6-beta.26",
"description": "dnd plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/x6-plugin-dnd.js",
"jsdelivr": "dist/x6-plugin-dnd.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"es",
"lib"
],
"keywords": [
"plugin",
"dnd",
"x6",
"antv"
],
"scripts": {
"clean:build": "rimraf dist es lib",
"clean:coverage": "rimraf ./test/coverage",
"clean": "run-p clean:build clean:coverage",
"lint": "eslint 'src/**/*.{js,ts}?(x)' --fix",
"build:less": "node ./scripts/style",
"build:esm": "tsc --module esnext --target es2015 --outDir ./es",
"build:cjs": "tsc --module commonjs --target es2015 --outDir ./lib",
"build:umd": "rollup -c",
"build:dev": "run-p build:less build:cjs build:esm",
"build:watch": "yarn build:esm --w",
"build:watch:esm": "yarn build:esm --w",
"build:watch:cjs": "yarn build:cjs --w",
"build": "run-p build:dev build:umd",
"prebuild": "run-s lint clean",
"coveralls": "cat ./test/coverage/lcov.info | coveralls",
"test": "echo \"no test case\"",
"pretest": "run-p clean:coverage",
"prepare": "run-s test build",
"precommit": "lint-staged"
},
"lint-staged": {
"src/**/*.ts": [
"eslint --fix"
]
},
"inherits": [
"@antv/x6-package-json/cli.json",
"@antv/x6-package-json/eslint.json",
"@antv/x6-package-json/rollup.json"
],
"peerDependencies": {
"@antv/x6": ">=2.0.6-beta.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^13.0.4",
"@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^8.2.5",
"@types/mousetrap": "^1.6.5",
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"coveralls": "^3.1.1",
"eslint": "^7.32.0",
"eslint-config-airbnb-base": "^14.2.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-eslint-comments": "^3.2.0",
"eslint-plugin-import": "^2.24.2",
"eslint-plugin-jest": "^24.4.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-promise": "^5.1.0",
"eslint-plugin-react": "^7.25.1",
"eslint-plugin-react-hooks": "^4.2.0",
"eslint-plugin-unicorn": "^36.0.0",
"less": "^4.1.1",
"lint-staged": "^11.1.2",
"npm-run-all": "^4.1.5",
"postcss": "^8.3.6",
"prettier": "^2.4.0",
"pretty-quick": "^3.1.1",
"rimraf": "^3.0.2",
"rollup": "^2.56.3",
"rollup-plugin-auto-external": "^2.0.0",
"rollup-plugin-filesize": "^9.1.1",
"rollup-plugin-postcss": "^4.0.1",
"rollup-plugin-progress": "^1.1.2",
"rollup-plugin-terser": "^7.0.2",
"ts-node": "^10.2.1",
"tslib": "^2.3.1",
"typescript": "^4.4.3"
},
"author": {
"name": "bubkoo",
"email": "bubkoo.wy@gmail.com"
},
"contributors": [],
"license": "MIT",
"homepage": "https://github.com/antvis/x6",
"bugs": {
"url": "https://github.com/antvis/x6/issues"
},
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-plugin-dnd"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}

View File

@ -0,0 +1,18 @@
import config from '../../configs/rollup-config'
export default config({
output: [
{
name: 'X6PluginDnd',
format: 'umd',
file: 'dist/x6-plugin-dnd.js',
sourcemap: true,
globals: {
'@antv/x6': 'X6',
'@antv/x6-common': 'X6Common',
'@antv/x6-geometry': 'X6Geometry',
},
},
],
external: ['@antv/x6', '@antv/x6-common', '@antv/x6-geometry'],
})

View File

@ -0,0 +1,85 @@
#!/usr/bin/env node
const fs = require('fs')
const os = require('os')
const path = require('path')
const fse = require('fs-extra')
const cp = require('child_process')
const cwd = process.cwd()
const es = path.join(cwd, 'es')
const lib = path.join(cwd, 'lib')
const src = path.join(cwd, 'src')
const dist = path.join(cwd, 'dist')
function compile(source, target) {
let cmd = './node_modules/.bin/lessc'
if (os.type() === 'Windows_NT') {
cmd = path.join(cwd, './node_modules/.bin/lessc.cmd')
}
cp.execFileSync(cmd, [source, target])
}
compile(path.join(src, 'index.less'), path.join(es, 'index.css'))
compile(path.join(src, 'index.less'), path.join(lib, 'index.css'))
compile(path.join(src, 'index.less'), path.join(dist, 'dnd.css'))
function toCSSPath(source) {
const dir = path.dirname(source)
const file = `${path.basename(source, '.less')}.css`
return path.join(dir, file)
}
// Copy less files
function processLessInDir(dir) {
const stat = fs.statSync(dir)
if (stat) {
if (stat.isDirectory()) {
fs.readdir(dir, (err, files) => {
files.forEach((file) => {
processLessInDir(path.join(dir, file))
})
})
} else {
const ext = path.extname(dir)
if (ext === '.less' || ext === '.css') {
fse.copySync(dir, path.join(es, path.relative(src, dir)))
fse.copySync(dir, path.join(lib, path.relative(src, dir)))
}
if (ext === '.less') {
let source = path.join(es, path.relative(src, dir))
let target = toCSSPath(source)
compile(dir, target)
source = path.join(lib, path.relative(src, dir))
target = toCSSPath(source)
compile(dir, target)
}
}
}
}
function makeStyleModule() {
const source = path.join(dist, 'dnd.css')
const target = path.join(src, 'style/raw.ts')
const content = fs.readFileSync(source, { encoding: 'utf8' })
const prev = fs.existsSync(target)
? fs.readFileSync(target, { encoding: 'utf8' })
: null
const curr = `/* eslint-disable */
/**
* Auto generated file, do not modify it!
*/
export const content = \`${content}\`
`
if (prev !== curr) {
fs.writeFileSync(target, curr)
}
}
processLessInDir(src)
makeStyleModule()

View File

@ -0,0 +1,25 @@
@dnd-prefix-cls: ~'x6-widget-dnd';
.@{dnd-prefix-cls} {
position: absolute;
top: -10000px;
left: -10000px;
z-index: 999999;
display: none;
cursor: move;
opacity: 0.7;
pointer-events: 'cursor';
&.dragging {
display: inline-block;
}
&.dragging * {
pointer-events: none !important;
}
.x6-graph {
background: transparent;
box-shadow: none;
}
}

View File

@ -0,0 +1,511 @@
import { Util, Rectangle, Point } from '@antv/x6-geometry'
import { FunctionExt, Dom, CssLoader } from '@antv/x6-common'
import { Cell, Node, View, NodeView, Graph, EventArgs } from '@antv/x6'
import { content } from './style/raw'
export class Dnd extends View {
public name = 'dnd'
public readonly options: Dnd.Options
public readonly draggingGraph: Graph
protected sourceNode: Node | null
protected draggingNode: Node | null
protected draggingView: NodeView | null
protected draggingBBox: Rectangle
protected geometryBBox: Rectangle
protected candidateEmbedView: NodeView | null
protected delta: Point | null
protected padding: number | null
protected snapOffset: Point.PointLike | null
protected originOffset: null | { left: number; top: number }
protected get targetScroller() {
const target = this.options.target
const scroller = target.getPlugin<any>('scroller')
return scroller
}
protected get targetGraph() {
return this.options.target
}
protected get targetModel() {
return this.targetGraph.model
}
protected get snapline() {
const target = this.options.target
const snapline = target.getPlugin<any>('snapline')
return snapline
}
constructor(options: Partial<Dnd.Options> & { target: Graph }) {
super()
CssLoader.ensure(this.name, content)
this.options = {
...Dnd.defaults,
...options,
} as Dnd.Options
this.container = document.createElement('div')
Dom.addClass(this.container, this.prefixClassName('widget-dnd'))
this.draggingGraph = new Graph({
...this.options.delegateGraphOptions,
container: document.createElement('div'),
width: 1,
height: 1,
async: false,
})
Dom.append(this.container, this.draggingGraph.container)
}
start(node: Node, evt: Dom.MouseDownEvent | MouseEvent) {
const e = evt as Dom.MouseDownEvent
e.preventDefault()
this.targetModel.startBatch('dnd')
Dom.addClass(this.container, 'dragging')
Dom.appendTo(this.container, this.options.containerParent || document.body)
this.sourceNode = node
this.prepareDragging(node, e.clientX, e.clientY)
const local = this.updateNodePosition(e.clientX, e.clientY)
if (this.isSnaplineEnabled()) {
this.snapline.captureCursorOffset({
e,
node,
cell: node,
view: this.draggingView!,
x: local.x,
y: local.y,
})
this.draggingNode!.on('change:position', this.snap, this)
}
this.delegateDocumentEvents(Dnd.documentEvents, e.data)
}
protected isSnaplineEnabled() {
return this.snapline && this.snapline.isSnaplineEnabled()
}
protected prepareDragging(
sourceNode: Node,
clientX: number,
clientY: number,
) {
const draggingGraph = this.draggingGraph
const draggingModel = draggingGraph.model
const draggingNode = this.options.getDragNode(sourceNode, {
sourceNode,
draggingGraph,
targetGraph: this.targetGraph,
})
draggingNode.position(0, 0)
let padding = 5
if (this.isSnaplineEnabled()) {
padding += this.snapline.options.tolerance || 0
}
if (this.isSnaplineEnabled() || this.options.scaled) {
const scale = this.targetGraph.transform.getScale()
draggingGraph.scale(scale.sx, scale.sy)
padding *= Math.max(scale.sx, scale.sy)
} else {
draggingGraph.scale(1, 1)
}
this.clearDragging()
// if (this.options.animation) {
// this.$container.stop(true, true)
// }
draggingModel.resetCells([draggingNode])
const delegateView = draggingGraph.findViewByCell(draggingNode) as NodeView
delegateView.undelegateEvents()
delegateView.cell.off('changed')
draggingGraph.fitToContent({
padding,
allowNewOrigin: 'any',
})
const bbox = delegateView.getBBox()
this.geometryBBox = delegateView.getBBox({ useCellGeometry: true })
this.delta = this.geometryBBox.getTopLeft().diff(bbox.getTopLeft())
this.draggingNode = draggingNode
this.draggingView = delegateView
this.draggingBBox = draggingNode.getBBox()
this.padding = padding
this.originOffset = this.updateGraphPosition(clientX, clientY)
}
protected updateGraphPosition(clientX: number, clientY: number) {
const scrollTop =
document.body.scrollTop || document.documentElement.scrollTop
const delta = this.delta!
const nodeBBox = this.geometryBBox
const padding = this.padding || 5
const offset = {
left: clientX - delta.x - nodeBBox.width / 2 - padding,
top: clientY - delta.y - nodeBBox.height / 2 - padding + scrollTop,
}
if (this.draggingGraph) {
Dom.css(this.container, {
left: `${offset.left}px`,
top: `${offset.top}px`,
})
}
return offset
}
protected updateNodePosition(x: number, y: number) {
const local = this.targetGraph.clientToLocal(x, y)
const bbox = this.draggingBBox!
local.x -= bbox.width / 2
local.y -= bbox.height / 2
this.draggingNode!.position(local.x, local.y)
return local
}
protected snap({
cell,
current,
options,
}: Cell.EventArgs['change:position']) {
const node = cell as Node
if (options.snapped) {
const bbox = this.draggingBBox
node.position(bbox.x + options.tx, bbox.y + options.ty, { silent: true })
this.draggingView!.translate()
node.position(current!.x, current!.y, { silent: true })
this.snapOffset = {
x: options.tx,
y: options.ty,
}
} else {
this.snapOffset = null
}
}
protected onDragging(evt: Dom.MouseMoveEvent) {
const draggingView = this.draggingView
if (draggingView) {
evt.preventDefault()
const e = this.normalizeEvent(evt)
const clientX = e.clientX
const clientY = e.clientY
this.updateGraphPosition(clientX, clientY)
const local = this.updateNodePosition(clientX, clientY)
const embeddingMode = this.targetGraph.options.embedding.enabled
const isValidArea =
(embeddingMode || this.isSnaplineEnabled()) &&
this.isInsideValidArea({
x: clientX,
y: clientY,
})
if (embeddingMode) {
draggingView.setEventData(e, {
graph: this.targetGraph,
candidateEmbedView: this.candidateEmbedView,
})
const data = draggingView.getEventData<any>(e)
if (isValidArea) {
draggingView.processEmbedding(e, data)
} else {
draggingView.clearEmbedding(data)
}
this.candidateEmbedView = data.candidateEmbedView
}
// update snapline
if (this.isSnaplineEnabled()) {
if (isValidArea) {
this.snapline.snapOnMoving({
e,
view: draggingView!,
x: local.x,
y: local.y,
} as EventArgs['node:mousemove'])
} else {
this.snapline.hide()
}
}
}
}
protected onDragEnd(evt: Dom.MouseUpEvent) {
const draggingNode = this.draggingNode
if (draggingNode) {
const e = this.normalizeEvent(evt)
const draggingView = this.draggingView
const draggingBBox = this.draggingBBox
const snapOffset = this.snapOffset
let x = draggingBBox.x
let y = draggingBBox.y
if (snapOffset) {
x += snapOffset.x
y += snapOffset.y
}
draggingNode.position(x, y, { silent: true })
const ret = this.drop(draggingNode, { x: e.clientX, y: e.clientY })
const callback = (node: null | Node) => {
if (node) {
this.onDropped(draggingNode)
if (this.targetGraph.options.embedding.enabled && draggingView) {
draggingView.setEventData(e, {
cell: node,
graph: this.targetGraph,
candidateEmbedView: this.candidateEmbedView,
})
draggingView.finalizeEmbedding(e, draggingView.getEventData<any>(e))
}
} else {
this.onDropInvalid()
}
this.candidateEmbedView = null
this.targetModel.stopBatch('dnd')
}
if (FunctionExt.isAsync(ret)) {
// stop dragging
this.undelegateDocumentEvents()
ret.then(callback) // eslint-disable-line
} else {
callback(ret)
}
}
}
protected clearDragging() {
if (this.draggingNode) {
this.sourceNode = null
this.draggingNode.remove()
this.draggingNode = null
this.draggingView = null
this.delta = null
this.padding = null
this.snapOffset = null
this.originOffset = null
this.undelegateDocumentEvents()
}
}
protected onDropped(draggingNode: Node) {
if (this.draggingNode === draggingNode) {
this.clearDragging()
Dom.removeClass(this.container, 'dragging')
Dom.remove(this.container)
}
}
protected onDropInvalid() {
const draggingNode = this.draggingNode
if (draggingNode) {
this.onDropped(draggingNode)
// todo
// const anim = this.options.animation
// if (anim) {
// const duration = (typeof anim === 'object' && anim.duration) || 150
// const easing = (typeof anim === 'object' && anim.easing) || 'swing'
// this.draggingView = null
// this.$container.animate(this.originOffset!, duration, easing, () =>
// this.onDropped(draggingNode),
// )
// } else {
// this.onDropped(draggingNode)
// }
}
}
protected isInsideValidArea(p: Point.PointLike) {
let targetRect: Rectangle
let dndRect: Rectangle | null = null
const targetGraph = this.targetGraph
const targetScroller = this.targetScroller
if (this.options.dndContainer) {
dndRect = this.getDropArea(this.options.dndContainer)
}
const isInsideDndRect = dndRect && dndRect.containsPoint(p)
if (targetScroller) {
if (targetScroller.options.autoResize) {
targetRect = this.getDropArea(targetScroller.container)
} else {
const outter = this.getDropArea(targetScroller.container)
targetRect = this.getDropArea(targetGraph.container).intersectsWithRect(
outter,
)!
}
} else {
targetRect = this.getDropArea(targetGraph.container)
}
return !isInsideDndRect && targetRect && targetRect.containsPoint(p)
}
protected getDropArea(elem: Element) {
const offset = Dom.offset(elem)!
const scrollTop =
document.body.scrollTop || document.documentElement.scrollTop
const scrollLeft =
document.body.scrollLeft || document.documentElement.scrollLeft
return Rectangle.create({
x:
offset.left +
parseInt(Dom.css(elem, 'border-left-width')!, 10) -
scrollLeft,
y:
offset.top +
parseInt(Dom.css(elem, 'border-top-width')!, 10) -
scrollTop,
width: elem.clientWidth,
height: elem.clientHeight,
})
}
protected drop(draggingNode: Node, pos: Point.PointLike) {
if (this.isInsideValidArea(pos)) {
const targetGraph = this.targetGraph
const targetModel = targetGraph.model
const local = targetGraph.clientToLocal(pos)
const sourceNode = this.sourceNode!
const droppingNode = this.options.getDropNode(draggingNode, {
sourceNode,
draggingNode,
targetGraph: this.targetGraph,
draggingGraph: this.draggingGraph,
})
const bbox = droppingNode.getBBox()
local.x += bbox.x - bbox.width / 2
local.y += bbox.y - bbox.height / 2
const gridSize = this.snapOffset ? 1 : targetGraph.getGridSize()
droppingNode.position(
Util.snapToGrid(local.x, gridSize),
Util.snapToGrid(local.y, gridSize),
)
droppingNode.removeZIndex()
const validateNode = this.options.validateNode
const ret = validateNode
? validateNode(droppingNode, {
sourceNode,
draggingNode,
droppingNode,
targetGraph,
draggingGraph: this.draggingGraph,
})
: true
if (typeof ret === 'boolean') {
if (ret) {
targetModel.addCell(droppingNode, { stencil: this.cid })
return droppingNode
}
return null
}
return FunctionExt.toDeferredBoolean(ret).then((valid) => {
if (valid) {
targetModel.addCell(droppingNode, { stencil: this.cid })
return droppingNode
}
return null
})
}
return null
}
protected onRemove() {
if (this.draggingGraph) {
this.draggingGraph.view.remove()
this.draggingGraph.dispose()
}
}
@View.dispose()
dispose() {
this.remove()
CssLoader.clean(this.name)
}
}
export namespace Dnd {
export interface Options {
target: Graph
/**
* Should scale the dragging node or not.
*/
scaled?: boolean
delegateGraphOptions?: Graph.Options
// animation?:
// | boolean
// | {
// duration?: number
// easing?: string
// }
containerParent?: HTMLElement
/**
* dnd tool box container.
*/
dndContainer?: HTMLElement
getDragNode: (sourceNode: Node, options: GetDragNodeOptions) => Node
getDropNode: (draggingNode: Node, options: GetDropNodeOptions) => Node
validateNode?: (
droppingNode: Node,
options: ValidateNodeOptions,
) => boolean | Promise<boolean>
}
export interface GetDragNodeOptions {
sourceNode: Node
targetGraph: Graph
draggingGraph: Graph
}
export interface GetDropNodeOptions extends GetDragNodeOptions {
draggingNode: Node
}
export interface ValidateNodeOptions extends GetDropNodeOptions {
droppingNode: Node
}
export const defaults: Partial<Options> = {
// animation: false,
getDragNode: (sourceNode) => sourceNode.clone(),
getDropNode: (draggingNode) => draggingNode.clone(),
}
export const documentEvents = {
mousemove: 'onDragging',
touchmove: 'onDragging',
mouseup: 'onDragEnd',
touchend: 'onDragEnd',
touchcancel: 'onDragEnd',
}
}

View File

@ -0,0 +1,27 @@
/* eslint-disable */
/**
* Auto generated file, do not modify it!
*/
export const content = `.x6-widget-dnd {
position: absolute;
top: -10000px;
left: -10000px;
z-index: 999999;
display: none;
cursor: move;
opacity: 0.7;
pointer-events: 'cursor';
}
.x6-widget-dnd.dragging {
display: inline-block;
}
.x6-widget-dnd.dragging * {
pointer-events: none !important;
}
.x6-widget-dnd .x6-graph {
background: transparent;
box-shadow: none;
}
`

View File

@ -0,0 +1,3 @@
{
"extends": "../../tsconfig.json"
}

Some files were not shown because too many files have changed in this diff Show More