diff --git a/sites/x6-sites/docs/api/registry/attr.zh.md b/sites/x6-sites/docs/api/registry/attr.zh.md index 8ca2e975b79..3fbb9244aa9 100644 --- a/sites/x6-sites/docs/api/registry/attr.zh.md +++ b/sites/x6-sites/docs/api/registry/attr.zh.md @@ -1,6 +1,6 @@ --- title: 属性 -order: 11 +order: 13 redirect_from: - /zh/docs - /zh/docs/api diff --git a/sites/x6-sites/docs/api/registry/filter.zh.md b/sites/x6-sites/docs/api/registry/filter.zh.md index ff00afbbaac..b4fb161e220 100644 --- a/sites/x6-sites/docs/api/registry/filter.zh.md +++ b/sites/x6-sites/docs/api/registry/filter.zh.md @@ -1,6 +1,6 @@ --- title: 滤镜 -order: 12 +order: 15 redirect_from: - /zh/docs - /zh/docs/api diff --git a/sites/x6-sites/docs/api/registry/highlighter.zh.md b/sites/x6-sites/docs/api/registry/highlighter.zh.md index 1b8f46c448b..a2c4f7f800b 100644 --- a/sites/x6-sites/docs/api/registry/highlighter.zh.md +++ b/sites/x6-sites/docs/api/registry/highlighter.zh.md @@ -1,6 +1,6 @@ --- title: 高亮器 -order: 11 +order: 14 redirect_from: - /zh/docs - /zh/docs/api diff --git a/sites/x6-sites/docs/api/registry/port-label-layout.zh.md b/sites/x6-sites/docs/api/registry/port-label-layout.zh.md index 5df5125fb1c..923d28f19a4 100644 --- a/sites/x6-sites/docs/api/registry/port-label-layout.zh.md +++ b/sites/x6-sites/docs/api/registry/port-label-layout.zh.md @@ -1,6 +1,6 @@ --- title: PortLabelLayout -order: 14 +order: 12 redirect_from: - /zh/docs - /zh/docs/api diff --git a/sites/x6-sites/docs/api/registry/port-layout.zh.md b/sites/x6-sites/docs/api/registry/port-layout.zh.md index 97d017ca42d..65f4b01229a 100644 --- a/sites/x6-sites/docs/api/registry/port-layout.zh.md +++ b/sites/x6-sites/docs/api/registry/port-layout.zh.md @@ -1,6 +1,6 @@ --- -title: PortLayout -order: 13 +title: 连接桩布局 +order: 11 redirect_from: - /zh/docs - /zh/docs/api @@ -51,9 +51,7 @@ graph.addNode( 下面我们一起来看看如何使用内置的连接桩布局算法,以及如何自定并注册自定义布局算法。 -## presets - -在 `Registry.PortLayout.presets` 命名空间下提供了以下几个内置的布局算法。 +## 内置布局 ### absolute @@ -100,7 +98,7 @@ graph.addNode({ }) ``` - + ### left, right, top, bottom @@ -145,7 +143,7 @@ graph.addNode({ }) ``` - + ### line @@ -200,7 +198,7 @@ graph.addNode({ }) ``` - + ### ellipse @@ -257,7 +255,7 @@ Array.from({ length: 10 }).forEach((_, index) => { }) ``` - + ### ellipseSpread @@ -312,9 +310,9 @@ Array.from({ length: 36 }).forEach(function (_, index) { }) ``` - + -## registry +## 自定义连接桩布局 连接桩布局算法是一个函数具有如下签名的函数,返回每个连接桩相对于节点的相对位置。例如,某节点在画布的位置是 `{ x: 30, y: 40 }`,如果返回的某个连接桩的位置是 `{ x: 2, y: 4 }`,那么该连接桩渲染到画布后的位置是 `{ x: 32, y: 44 }`。 @@ -351,47 +349,11 @@ function sin(portsPositionArgs, elemBBox) { 布局算法实现后,需要注册到系统,注册后就可以像内置布局算法那样来使用。 -### register - -```ts -register(entities: { [name: string]: Definition }, force?: boolean): void -register(name: string, entity: Definition, force?: boolean): Definition -``` - -注册自定义布局算法。 - -### unregister - -```ts -unregister(name: string): Definition | null -``` - -删除注册的自定义布局算法。 - -实际上,我们将 `registry` 的 `register` 和 `unregister` 方法分别挂载为 `Graph` 的两个静态方法 `Graph.registerPortLayout` 和 `Graph.unregisterPortLayout`,所以我们定义的正弦布局可以像下面这样注册到系统: ```ts Graph.registerPortLayout('sin', sin) ``` -或者: - -```ts -Graph.registerPortLayout('sin', (portsPositionArgs, elemBBox) => { - return portsPositionArgs.map((_, index) => { - const step = -Math.PI / 8 - const y = Math.sin(index * step) * 50 - return { - position: { - x: index * 12, - y: y + elemBBox.height, - }, - angle: 0, - } - }) -}) -``` - 注册以后,我们就可以像内置布局算法那样来使用: ```ts @@ -429,4 +391,4 @@ Array.from({ length: 24 }).forEach(() => { }) ``` - + diff --git a/sites/x6-sites/src/api/port-label-layout/inside-outside/index.less b/sites/x6-sites/src/api/port-label-layout/inside-outside/index.less new file mode 100644 index 00000000000..419153c6251 --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/inside-outside/index.less @@ -0,0 +1,42 @@ +.port-label-inside-outside-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/inside-outside/index.tsx b/sites/x6-sites/src/api/port-label-layout/inside-outside/index.tsx new file mode 100644 index 00000000000..bb2a4cc83ef --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/inside-outside/index.tsx @@ -0,0 +1,98 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + shape: 'ellipse', + x: 70, + y: 85, + width: 260, + height: 100, + attrs: { + label: { + text: 'outside', + fill: '#888', + fontSize: 12, + }, + body: { + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fff', + rx: 6, + ry: 6, + }, + }, + ports: { + groups: { + a: { + position: { + name: 'ellipseSpread', + args: { + compensateRotate: true, + }, + }, + label: { + position: { + name: 'outside', + }, + }, + attrs: { + circle: { + fill: '#ffffff', + stroke: '#8f8f8f', + strokeWidth: 1, + r: 10, + magnet: true, + }, + text: { + fill: '#6a6c8a', + fontSize: 12, + }, + }, + }, + }, + }, + }) + + Array.from({ length: 10 }).forEach((_, index) => { + this.node.addPort({ attrs: { text: { text: `P ${index}` } }, group: 'a' }) + }) + } + + onAttrsChanged = ({ position, offset }: State) => { + this.node.prop('ports/groups/a/label/position', { + name: position, + args: { offset }, + }) + + this.node.attr('label/text', position) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/inside-outside/settings.tsx b/sites/x6-sites/src/api/port-label-layout/inside-outside/settings.tsx new file mode 100644 index 00000000000..2b58eb40b1e --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/inside-outside/settings.tsx @@ -0,0 +1,103 @@ +import React from 'react' +import { Radio, Slider, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + position: string + offset: number +} + +export class Settings extends React.Component { + state: State = { + position: 'outside', + offset: 15, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onOffsetChanged = (offset: number) => { + this.setState({ offset }, () => { + this.notifyChange() + }) + } + + onPositionChange = (e: any) => { + this.setState( + { + position: e.target.value, + }, + () => { + this.notifyChange() + }, + ) + } + + render() { + const radioStyle = { + display: 'block', + height: '36px', + lineHeight: '36px', + } + + return ( + + + + + + inside + + + outside + + + insideOriented + + + outsideOriented + + + + + + + offset + + + + + +
{this.state.offset}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/radial/index.less b/sites/x6-sites/src/api/port-label-layout/radial/index.less new file mode 100644 index 00000000000..51d1104836b --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/radial/index.less @@ -0,0 +1,42 @@ +.port-label-radial-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/radial/index.tsx b/sites/x6-sites/src/api/port-label-layout/radial/index.tsx new file mode 100644 index 00000000000..afccd64d79f --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/radial/index.tsx @@ -0,0 +1,98 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + shape: 'ellipse', + x: 70, + y: 50, + width: 260, + height: 100, + attrs: { + label: { + text: 'outside', + fill: '#888', + fontSize: 12, + }, + body: { + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fff', + rx: 6, + ry: 6, + }, + }, + ports: { + groups: { + a: { + position: { + name: 'ellipseSpread', + args: { + compensateRotate: true, + }, + }, + label: { + position: { + name: 'outside', + }, + }, + attrs: { + circle: { + fill: '#ffffff', + stroke: '#8f8f8f', + strokeWidth: 1, + r: 10, + magnet: true, + }, + text: { + fill: '#6a6c8a', + fontSize: 12, + }, + }, + }, + }, + }, + }) + + Array.from({ length: 10 }).forEach((_, index) => { + this.node.addPort({ attrs: { text: { text: `P ${index}` } }, group: 'a' }) + }) + } + + onAttrsChanged = ({ position, offset }: State) => { + this.node.prop('ports/groups/a/label/position', { + name: position, + args: { offset }, + }) + + this.node.attr('label/text', position) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/radial/settings.tsx b/sites/x6-sites/src/api/port-label-layout/radial/settings.tsx new file mode 100644 index 00000000000..291df7bfd1e --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/radial/settings.tsx @@ -0,0 +1,97 @@ +import React from 'react' +import { Radio, Slider, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + position: string + offset: number +} + +export class Settings extends React.Component { + state: State = { + position: 'radial', + offset: 20, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onOffsetChanged = (offset: number) => { + this.setState({ offset }, () => { + this.notifyChange() + }) + } + + onPositionChange = (e: any) => { + this.setState( + { + position: e.target.value, + }, + () => { + this.notifyChange() + }, + ) + } + + render() { + const radioStyle = { + display: 'block', + height: '36px', + lineHeight: '36px', + } + + return ( + + + + + + radial + + + radialOriented + + + + + + + offset + + + + + +
{this.state.offset}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/side/index.less b/sites/x6-sites/src/api/port-label-layout/side/index.less new file mode 100644 index 00000000000..dda8d3dbf27 --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/side/index.less @@ -0,0 +1,42 @@ +.port-label-side-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/side/index.tsx b/sites/x6-sites/src/api/port-label-layout/side/index.tsx new file mode 100644 index 00000000000..fba03ce290d --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/side/index.tsx @@ -0,0 +1,108 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + shape: 'rect', + x: 80, + y: 68, + width: 240, + height: 80, + attrs: { + label: { + text: 'left', + fill: '#6a6c8a', + }, + body: { + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fff', + rx: 6, + ry: 6, + }, + }, + ports: { + groups: { + a: { + position: { + name: 'top', + args: { + dr: 0, + dx: 0, + dy: -10, + }, + }, + label: { + position: { + name: 'left', + }, + }, + attrs: { + circle: { + fill: '#ffffff', + stroke: '#8f8f8f', + strokeWidth: 1, + r: 10, + }, + text: { + fill: '#6a6c8a', + }, + }, + }, + }, + }, + }) + + Array.from({ length: 3 }).forEach((_, index) => { + const label = + index === 2 + ? { + position: { args: { x: 20, y: -20 } }, + } + : {} + const stroke = index === 2 ? { stroke: 'red' } : {} + const fill = index === 2 ? { fill: 'red' } : {} + this.node.addPort({ + label, + group: 'a', + attrs: { + circle: { magnet: true, ...stroke }, + text: { text: `P${index}`, ...fill }, + }, + }) + }) + } + + onAttrsChanged = ({ position }: State) => { + this.node.prop('ports/groups/a/label/position/name', position) + this.node.attr('label/text', position) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-label-layout/side/settings.tsx b/sites/x6-sites/src/api/port-label-layout/side/settings.tsx new file mode 100644 index 00000000000..a6ca02615f3 --- /dev/null +++ b/sites/x6-sites/src/api/port-label-layout/side/settings.tsx @@ -0,0 +1,94 @@ +import React from 'react' +import { Radio, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + position: string + dx: number + dy: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + position: 'left', + dx: 0, + dy: 0, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onDxChanged = (dx: number) => { + this.setState({ dx }, () => { + this.notifyChange() + }) + } + + onDyChanged = (dy: number) => { + this.setState({ dy }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + onPositionChange = (e: any) => { + this.setState( + { + position: e.target.value, + }, + () => { + this.notifyChange() + }, + ) + } + + render() { + const radioStyle = { + display: 'block', + height: '36px', + lineHeight: '36px', + } + + return ( + + + + + + left + + + right + + + top + + + bottom + + + + + + ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/absolute/index.less b/sites/x6-sites/src/api/port-layout/absolute/index.less new file mode 100644 index 00000000000..14d47bea90a --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/absolute/index.less @@ -0,0 +1,42 @@ +.port-absolute-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-layout/absolute/index.tsx b/sites/x6-sites/src/api/port-layout/absolute/index.tsx new file mode 100644 index 00000000000..71d7e57e47e --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/absolute/index.tsx @@ -0,0 +1,129 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + x: 120, + y: 48, + width: 280, + height: 120, + markup: [ + { + tagName: 'rect', + selector: 'body', + }, + ], + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + }, + ports: { + groups: { + group1: { + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + position: { + name: 'absolute', + }, + }, + }, + items: [ + { + id: 'port1', + group: 'group1', + args: { x: 0, y: 60 }, + attrs: { + text: { text: '{ x: 0, y: 60 }' }, + }, + }, + { + id: 'port2', + group: 'group1', + args: { x: 0.6, y: 32, angle: 45 }, + markup: [ + { + tagName: 'path', + selector: 'path', + }, + ], + zIndex: 10, + attrs: { + path: { + d: 'M -6 -8 L 0 8 L 6 -8 Z', + magnet: true, + fill: '#8f8f8f', + }, + text: { text: '{ x: 0.6, y: 32, angle: 45 }', fill: '#888' }, + }, + }, + { + id: 'port3', + group: 'group1', + args: { x: '100%', y: '100%' }, + attrs: { + text: { text: "{ x: '100%', y: '100%' }" }, + }, + label: { + position: { + name: 'right', + }, + }, + }, + ], + }, + }) + } + + onAttrsChanged = (args: State) => { + this.node.portProp('port2', { + args, + attrs: { + text: { text: `{ x: ${args.x}, y: ${args.y}, angle: ${args.angle} }` }, + }, + } as any) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/absolute/settings.tsx b/sites/x6-sites/src/api/port-layout/absolute/settings.tsx new file mode 100644 index 00000000000..78186ad5deb --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/absolute/settings.tsx @@ -0,0 +1,100 @@ +import React from 'react' +import { Slider, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + x: number + y: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + x: 0.6, + y: 32, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onXChanged = (x: number) => { + this.setState({ x }, () => { + this.notifyChange() + }) + } + + onYChanged = (y: number) => { + this.setState({ y }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + render() { + return ( + + + + x + + + + + +
{this.state.x}
+ +
+ + + y + + + + + +
{this.state.y}
+ +
+ + + angle + + + + + +
{this.state.angle}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse-spread/index.less b/sites/x6-sites/src/api/port-layout/ellipse-spread/index.less new file mode 100644 index 00000000000..c774001d8f1 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse-spread/index.less @@ -0,0 +1,42 @@ +.port-ellipse-spread-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse-spread/index.tsx b/sites/x6-sites/src/api/port-layout/ellipse-spread/index.tsx new file mode 100644 index 00000000000..93e40851f24 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse-spread/index.tsx @@ -0,0 +1,122 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + x: 120, + y: 90, + width: 360, + height: 200, + shape: 'ellipse', + + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + }, + ports: { + groups: { + group1: { + markup: [ + { + tagName: 'rect', + selector: 'rect', + }, + { + tagName: 'circle', + selector: 'dot', + }, + ], + attrs: { + rect: { + magnet: true, + stroke: '#8f8f8f', + fill: 'rgba(255,255,255,0.8)', + strokeWidth: 1, + width: 16, + height: 16, + x: -8, + y: -8, + }, + dot: { + fill: '#8f8f8f', + r: 2, + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + label: { + position: 'radial', + }, + position: { + name: 'ellipseSpread', + args: { + start: 45, + }, + }, + }, + }, + }, + }) + + Array.from({ length: 10 }).forEach((_, index) => { + this.node.addPort({ + id: `${index}`, + group: 'group1', + attrs: { text: { text: index } }, + }) + }) + + this.node.portProp('0', { + attrs: { + rect: { stroke: '#8f8f8f' }, + dot: { fill: '#8f8f8f' }, + }, + }) + } + + onAttrsChanged = ({ start, compensateRotate, ...args }: State) => { + this.node.prop('ports/groups/group1/position/args', { + start, + compensateRotate, + }) + + this.node.portProp('0', { + args, + } as any) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse-spread/settings.tsx b/sites/x6-sites/src/api/port-layout/ellipse-spread/settings.tsx new file mode 100644 index 00000000000..609a62f8b79 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse-spread/settings.tsx @@ -0,0 +1,171 @@ +import React from 'react' +import { Slider, Checkbox, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + start: number + compensateRotate: boolean + dr: number + dx: number + dy: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + start: 45, + compensateRotate: false, + dr: 0, + dx: 0, + dy: 0, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onStartChanged = (start: number) => { + this.setState({ start }, () => { + this.notifyChange() + }) + } + + onCompensateRotateChange = (e: any) => { + this.setState({ compensateRotate: e.target.checked }, () => { + this.notifyChange() + }) + } + + onDxChanged = (dx: number) => { + this.setState({ dx }, () => { + this.notifyChange() + }) + } + + onDrChanged = (dr: number) => { + this.setState({ dr }, () => { + this.notifyChange() + }) + } + + onDyChanged = (dy: number) => { + this.setState({ dy }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + render() { + return ( + + + + start + + + + + +
{this.state.start}
+ +
+ + + + compensateRotate + + + + + + dr + + + + + +
{this.state.dr}
+ +
+ + + dx + + + + + +
{this.state.dx}
+ +
+ + + dy + + + + + +
{this.state.dy}
+ +
+ + + angle + + + + + +
{this.state.angle}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse/index.less b/sites/x6-sites/src/api/port-layout/ellipse/index.less new file mode 100644 index 00000000000..86d4864e992 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse/index.less @@ -0,0 +1,42 @@ +.port-ellipse-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse/index.tsx b/sites/x6-sites/src/api/port-layout/ellipse/index.tsx new file mode 100644 index 00000000000..218b39f8f1f --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse/index.tsx @@ -0,0 +1,147 @@ +import React from 'react' +import { Graph, Node, Point } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +function getPathData(deg: number) { + const center = new Point(180, 100) + const start = new Point(180, 0) + const ratio = center.x / center.y + const p = start + .clone() + .rotate(90 - deg, center) + .scale(ratio, 1, center) + return `M ${center.x} ${center.y} ${p.x} ${p.y}` +} + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + x: 120, + y: 90, + width: 360, + height: 200, + shape: 'ellipse', + markup: [ + { tagName: 'ellipse', selector: 'body' }, + { tagName: 'text', selector: 'label' }, + { tagName: 'path', selector: 'line' }, + ], + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + line: { + d: getPathData(45), + stroke: '#8f8f8f', + strokeDasharray: '5 5', + }, + }, + ports: { + groups: { + group1: { + markup: [ + { + tagName: 'rect', + selector: 'rect', + }, + { + tagName: 'circle', + selector: 'dot', + }, + ], + attrs: { + rect: { + magnet: true, + stroke: '#8f8f8f', + fill: 'rgba(255,255,255,0.8)', + strokeWidth: 1, + width: 16, + height: 16, + x: -8, + y: -8, + }, + dot: { + fill: '#8f8f8f', + r: 2, + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + label: { + position: 'radial', + }, + position: { + name: 'ellipse', + args: { + start: 45, + }, + }, + }, + }, + }, + }) + + Array.from({ length: 10 }).forEach((_, index) => { + this.node.addPort({ + id: `${index}`, + group: 'group1', + attrs: { text: { text: index } }, + }) + }) + + this.node.portProp('0', { + attrs: { + rect: { stroke: '#8f8f8f' }, + dot: { fill: '#8f8f8f' }, + }, + }) + } + + onAttrsChanged = ({ start, step, compensateRotate, ...args }: State) => { + this.node.prop('ports/groups/group1/position/args', { + start, + step, + compensateRotate, + }) + + this.node.attr({ + line: { d: getPathData(start) }, + }) + + this.node.portProp('0', { + args, + } as any) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/ellipse/settings.tsx b/sites/x6-sites/src/api/port-layout/ellipse/settings.tsx new file mode 100644 index 00000000000..267fde79d28 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/ellipse/settings.tsx @@ -0,0 +1,196 @@ +import React from 'react' +import { Slider, Checkbox, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + start: number + step: number + compensateRotate: boolean + dr: number + dx: number + dy: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + start: 45, + step: 20, + compensateRotate: false, + dr: 0, + dx: 0, + dy: 0, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onStartChanged = (start: number) => { + this.setState({ start }, () => { + this.notifyChange() + }) + } + + onStepChanged = (step: number) => { + this.setState({ step }, () => { + this.notifyChange() + }) + } + + onCompensateRotateChange = (e: any) => { + this.setState({ compensateRotate: e.target.checked }, () => { + this.notifyChange() + }) + } + + onDxChanged = (dx: number) => { + this.setState({ dx }, () => { + this.notifyChange() + }) + } + + onDrChanged = (dr: number) => { + this.setState({ dr }, () => { + this.notifyChange() + }) + } + + onDyChanged = (dy: number) => { + this.setState({ dy }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + render() { + return ( + + + + start + + + + + +
{this.state.start}
+ +
+ + + step + + + + + +
{this.state.step}
+ +
+ + + + compensateRotate + + + + + + dr + + + + + +
{this.state.dr}
+ +
+ + + dx + + + + + +
{this.state.dx}
+ +
+ + + dy + + + + + +
{this.state.dy}
+ +
+ + + angle + + + + + +
{this.state.angle}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/line/index.less b/sites/x6-sites/src/api/port-layout/line/index.less new file mode 100644 index 00000000000..521da07b3c0 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/line/index.less @@ -0,0 +1,42 @@ +.port-line-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-layout/line/index.tsx b/sites/x6-sites/src/api/port-layout/line/index.tsx new file mode 100644 index 00000000000..88aa6bea7fb --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/line/index.tsx @@ -0,0 +1,127 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + x: 160, + y: 80, + width: 280, + height: 120, + markup: [ + { + tagName: 'rect', + selector: 'body', + }, + { + tagName: 'path', + selector: 'line', + }, + ], + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + line: { + d: 'M 0 0 280 120', + stroke: '#8f8f8f', + strokeDasharray: '5 5', + }, + }, + ports: { + groups: { + group1: { + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8f8f8f', + strokeWidth: 2, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + position: { + name: 'line', + args: { + start: { x: 0, y: 0 }, + end: { x: 280, y: 120 }, + }, + }, + }, + }, + items: [ + { + id: 'port1', + group: 'group1', + }, + { + id: 'port2', + group: 'group1', + args: { angle: 45 }, + markup: [ + { + tagName: 'path', + selector: 'path', + }, + ], + attrs: { + path: { + d: 'M -6 -8 L 0 8 L 6 -8 Z', + magnet: true, + fill: '#8f8f8f', + }, + }, + }, + { + id: 'port3', + group: 'group1', + args: {}, + }, + ], + }, + }) + } + + onAttrsChanged = ({ strict, ...args }: State) => { + this.node.prop('ports/groups/group1/position', { + name: 'line', + args: { strict }, + }) + this.node.portProp('port2', { args } as any) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/line/settings.tsx b/sites/x6-sites/src/api/port-layout/line/settings.tsx new file mode 100644 index 00000000000..0c6afdfb979 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/line/settings.tsx @@ -0,0 +1,122 @@ +import React from 'react' +import { Slider, Switch, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + strict: boolean + dx: number + dy: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + strict: false, + dx: 0, + dy: 0, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onStrictChange = (strict: boolean) => { + this.setState({ strict }, () => { + this.notifyChange() + }) + } + + onDxChanged = (dx: number) => { + this.setState({ dx }, () => { + this.notifyChange() + }) + } + + onDyChanged = (dy: number) => { + this.setState({ dy }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + render() { + return ( + + + + strict + + + + + + + + dx + + + + + +
{this.state.dx}
+ +
+ + + dy + + + + + +
{this.state.dy}
+ +
+ + + angle + + + + + +
{this.state.angle}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/side/index.less b/sites/x6-sites/src/api/port-layout/side/index.less new file mode 100644 index 00000000000..22bb7935836 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/side/index.less @@ -0,0 +1,42 @@ +.port-side-app { + display: flex; + padding: 0; + padding: 16px 8px; + font-family: sans-serif; + + .app-side { + bottom: 0; + padding: 0 8px; + } + + .app-content { + flex: 1; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card { + box-shadow: 0 0 10px 1px #e9e9e9; + } + + .ant-card-head-title { + text-align: center; + } + + .ant-row { + margin: 16px 0; + text-align: left; + } + + .slider-value { + display: inline-block; + margin-left: 8px; + padding: 3px 7px; + color: #333; + font-size: 12px; + line-height: 1.25; + background: #eee; + border-radius: 10px; + } +} diff --git a/sites/x6-sites/src/api/port-layout/side/index.tsx b/sites/x6-sites/src/api/port-layout/side/index.tsx new file mode 100644 index 00000000000..8c7b8fd5084 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/side/index.tsx @@ -0,0 +1,110 @@ +import React from 'react' +import { Graph, Node } from '@antv/x6' +import { Settings, State } from './settings' +import './index.less' + +export default class Example extends React.Component { + private container: HTMLDivElement + private node: Node + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + this.node = graph.addNode({ + x: 160, + y: 110, + width: 280, + height: 120, + attrs: { + body: { + refWidth: '100%', + refHeight: '100%', + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + label: { text: 'left', fill: '#888' }, + }, + ports: { + groups: { + group1: { + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fff', + }, + text: { + fontSize: 12, + fill: '#888', + }, + }, + position: { + name: 'left', + }, + }, + }, + items: [ + { + id: 'port1', + group: 'group1', + }, + { + id: 'port2', + group: 'group1', + args: { angle: 45 }, + markup: [ + { + tagName: 'path', + selector: 'path', + }, + ], + attrs: { + path: { + d: 'M -6 -8 L 0 8 L 6 -8 Z', + magnet: true, + fill: '#8f8f8f', + }, + }, + }, + { + id: 'port3', + group: 'group1', + args: {}, + }, + ], + }, + }) + } + + onAttrsChanged = ({ position, strict, ...args }: State) => { + this.node.prop('ports/groups/group1/position', { + name: position, + args: { strict }, + }) + this.node.attr('label/text', position) + this.node.portProp('port2', { args } as any) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+ +
+
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/side/settings.tsx b/sites/x6-sites/src/api/port-layout/side/settings.tsx new file mode 100644 index 00000000000..0ebdbfff5e2 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/side/settings.tsx @@ -0,0 +1,151 @@ +import React from 'react' +import { Radio, Switch, Slider, Card, Row, Col } from 'antd' + +export interface Props { + onChange: (state: State) => void +} + +export interface State { + position: string + strict: boolean + dx: number + dy: number + angle: number +} + +export class Settings extends React.Component { + state: State = { + position: 'left', + strict: false, + dx: 0, + dy: 0, + angle: 45, + } + + notifyChange() { + this.props.onChange(this.state) + } + + onDxChanged = (dx: number) => { + this.setState({ dx }, () => { + this.notifyChange() + }) + } + + onDyChanged = (dy: number) => { + this.setState({ dy }, () => { + this.notifyChange() + }) + } + + onAngleChanged = (angle: number) => { + this.setState({ angle }, () => { + this.notifyChange() + }) + } + + onStrictChange = (strict: boolean) => { + this.setState({ strict }, () => { + this.notifyChange() + }) + } + + onPositionChange = (e: any) => { + this.setState( + { + position: e.target.value, + }, + () => { + this.notifyChange() + }, + ) + } + + render() { + return ( + + + + position + + + + left + right + top + bottom + + + + + + strict + + + + + + + + dx + + + + + +
{this.state.dx}
+ +
+ + + dy + + + + + +
{this.state.dy}
+ +
+ + + angle + + + + + +
{this.state.angle}
+ +
+
+ ) + } +} diff --git a/sites/x6-sites/src/api/port-layout/sin/index.less b/sites/x6-sites/src/api/port-layout/sin/index.less new file mode 100644 index 00000000000..0df96e61630 --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/sin/index.less @@ -0,0 +1,14 @@ +.port-sin-app { + display: flex; + width: 100%; + padding: 0; + font-family: sans-serif; + + .app-content { + flex: 1; + height: 240px; + margin-right: 8px; + margin-left: 8px; + box-shadow: 0 0 10px 1px #e9e9e9; + } +} diff --git a/sites/x6-sites/src/api/port-layout/sin/index.tsx b/sites/x6-sites/src/api/port-layout/sin/index.tsx new file mode 100644 index 00000000000..faa6c7c9cfa --- /dev/null +++ b/sites/x6-sites/src/api/port-layout/sin/index.tsx @@ -0,0 +1,80 @@ +import React from 'react' +import { Graph } from '@antv/x6' +import './index.less' + +Graph.registerPortLayout( + 'sin', + (portsPositionArgs, elemBBox) => { + return portsPositionArgs.map((_, index) => { + const step = -Math.PI / 8 + const y = Math.sin(index * step) * 50 + return { + position: { + x: index * 12, + y: y + elemBBox.height, + }, + angle: 0, + } + }) + }, + true, +) + +export default class Example extends React.Component { + private container: HTMLDivElement + + componentDidMount() { + const graph = new Graph({ + container: this.container, + background: { + color: '#F2F7FA', + }, + }) + + const rect = graph.addNode({ + x: 120, + y: 40, + width: 280, + height: 120, + attrs: { + body: { + fill: '#fff', + stroke: '#8f8f8f', + strokeWidth: 1, + }, + }, + ports: { + groups: { + sin: { + attrs: { + circle: { + r: 6, + magnet: true, + stroke: '#8f8f8f', + strokeWidth: 1, + fill: '#fe854f', + }, + }, + position: 'sin', + }, + }, + }, + }) + + Array.from({ length: 24 }).forEach(() => { + rect.addPort({ group: 'sin' }) + }) + } + + refContainer = (container: HTMLDivElement) => { + this.container = container + } + + render() { + return ( +
+
+
+ ) + } +}