Compare commits

..

13 Commits

95 changed files with 1929 additions and 988 deletions

View File

@ -1,264 +0,0 @@
import React from 'react'
import { Graph, Node, Edge, NodeView } from '@antv/x6'
import { Point, Angle } from '@antv/x6-geometry'
import { Interp } from '@antv/x6-common'
import '../index.less'
class BallView extends NodeView {
protected speed: number = 0
protected angle: number = 0
protected edge: Edge | null
protected init() {
this.cell.transition('attrs/label/opacity', 1, {
delay: 0,
duration: 3000,
timing: 'inout',
interp: function (a: number, b: number) {
return function (t: number) {
return a + b * (1 - Math.abs(1 - 2 * t))
}
},
})
this.cell.on('transition:complete', ({ cell, path }) => {
if (path === 'position' && this.speed > 5) {
this.speed /= cell.prop<number>('bounciness') || 2
this.fly({ angle: 180 - this.angle, speed: this.speed })
}
})
this.cell.on('change:position', ({ cell, current }) => {
const node = cell as any as Node
this.angle = Point.create(node.getPosition()).theta(
node.previous('position'),
)
//this.speed = we are using constant speed for simplicity
if (current) {
if (
current.x < 0 ||
current.x > this.graph.options.width - node.getSize().width
) {
this.angle -= 180
node.position(node.previous('position')!.x, current.y, {
silent: true,
})
cell.stopTransition('position')
}
}
})
}
fly(opts: { speed?: number; angle?: number } = {}) {
const options = {
speed: 100,
angle: 90,
...opts,
}
const pos = this.cell.getPosition()
const size = this.cell.getSize()
const ga = 9.81
const h0 = this.graph.options.height - pos.y - size.height
const v0 = options.speed
const sin1 = Math.sin(Angle.toRad(options.angle))
const flightTime =
(v0 * sin1 +
Math.sqrt(Math.pow(v0, 2) * Math.pow(sin1, 2) + 2 * h0 * ga)) /
ga
this.cell.transition('position', options, {
duration: 100 * flightTime,
interp(
position: Point.PointLike,
params: { speed: number; angle: number },
) {
return function (t: number) {
t = flightTime * t
return {
x:
position.x +
params.speed * t * Math.cos((Math.PI / 180) * params.angle),
y:
position.y -
params.speed * t * Math.sin((Math.PI / 180) * params.angle) +
(ga / 2) * t * t,
}
}
},
})
this.cell.transition('angle', -options.angle, {
duration: 100 * flightTime,
})
this.speed = options.speed
this.angle = options.angle
}
onMouseDown(e: JQuery.MouseDownEvent, x: number, y: number) {
console.log('mousedown1')
// Do not allow drag element while it's still in a transition.
if (this.cell.getTransitions().indexOf('position') > -1) {
console.log('mousedown2')
return
}
this.edge = this.graph.addEdge({
shape: 'edge',
source: this.cell.getBBox().getCenter(),
target: { x, y },
zIndex: -1,
attrs: {
line: {
stroke: 'rgba(0,0,0,0.1)',
strokeWidth: 6,
targetMarker: {
stroke: 'black',
strokeWidth: 2,
d: 'M 20 -10 L 0 0 L 20 10 z',
},
},
},
})
// Change the marker arrow color.
this.edge.on('change:target', ({ cell }) => {
const edge = cell as any as Edge
const sourcePoint = edge.getSourcePoint()!
const targetPoint = edge.getTargetPoint()!
const dist = sourcePoint.distance(targetPoint)
const maxDist = Math.max(
this.graph.options.width,
this.graph.options.height,
)
const interp = Interp.color('#ffffff', '#ff0000')
edge.attr('line/targetMarker/fill', interp(dist / maxDist / Math.sqrt(2)))
})
}
onMouseMove(e: JQuery.MouseMoveEvent, x: number, y: number) {
if (this.edge) {
this.edge.setTarget({ x, y })
}
}
onMouseUp(e: JQuery.MouseUpEvent, x: number, y: number) {
if (!this.edge) {
return
}
const sourcePoint = this.edge.getSourcePoint()!
const targetPoint = this.edge.getTargetPoint()!
this.edge.remove()
this.edge = null
this.fly({
angle: Math.abs(targetPoint.theta(sourcePoint) - 180),
speed: sourcePoint.distance(targetPoint) / 2,
})
}
}
NodeView.registry.register('ball', BallView as any, true)
Node.registry.register(
'ball',
{
view: 'ball',
markup: [
{
tagName: 'text',
selector: 'label',
},
{
tagName: 'image',
selector: 'ball',
},
],
attrs: {
label: {
text: 'Drag me!',
fontSize: 40,
fontWeight: 900,
refX: 0.5,
refY: -20,
textVerticalAnchor: 'middle',
textAnchor: 'middle',
fill: 'white',
strokeWidth: 2,
stroke: 'black',
opacity: 0,
pointerEvents: 'none',
},
ball: {
refWidth: 1,
refHeight: 1,
},
},
},
true,
)
export default class Example extends React.Component {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 650,
height: 400,
grid: 1,
})
graph.addNode({
shape: 'ball',
x: 250,
y: 370,
width: 30,
height: 30,
bounciness: 3,
attrs: {
image: {
'xlink:href':
'data:image/svg+xml;base64,PHN2ZyB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeG1sbnM6c29kaXBvZGk9Imh0dHA6Ly9zb2RpcG9kaS5zb3VyY2Vmb3JnZS5uZXQvRFREL3NvZGlwb2RpLTAuZHRkIiB4bWxuczppbmtzY2FwZT0iaHR0cDovL3d3dy5pbmtzY2FwZS5vcmcvbmFtZXNwYWNlcy9pbmtzY2FwZSIgaWQ9InN2ZzIiIHZpZXdCb3g9IjAgMCA1MTAgNTEwIiB2ZXJzaW9uPSIxLjEiIGlua3NjYXBlOnZlcnNpb249IjAuNDcgcjIyNTgzIiB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBzb2RpcG9kaTpkb2NuYW1lPSJibHVlIGJhbGwuc3ZnIj4KICA8c29kaXBvZGk6bmFtZWR2aWV3IHBhZ2Vjb2xvcj0iI2ZmZmZmZiIgYm9yZGVyY29sb3I9IiM2NjY2NjYiIGJvcmRlcm9wYWNpdHk9IjEiIG9iamVjdHRvbGVyYW5jZT0iMTAiIGdyaWR0b2xlcmFuY2U9IjEwIiBndWlkZXRvbGVyYW5jZT0iMTAiIGlua3NjYXBlOnBhZ2VvcGFjaXR5PSIwIiBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIiBpbmtzY2FwZTp3aW5kb3ctd2lkdGg9IjEwMjQiIGlua3NjYXBlOndpbmRvdy1oZWlnaHQ9IjU0OCIgaWQ9Im5hbWVkdmlldzE2IiBzaG93Z3JpZD0iZmFsc2UiIGlua3NjYXBlOnNuYXAtZ2xvYmFsPSJmYWxzZSIgaW5rc2NhcGU6em9vbT0iMC40MzY2Mzc1MyIgaW5rc2NhcGU6Y3g9IjI1Ni4yMzMxMSIgaW5rc2NhcGU6Y3k9IjI1OC44NTEwNyIgaW5rc2NhcGU6d2luZG93LXg9Ii04IiBpbmtzY2FwZTp3aW5kb3cteT0iLTgiIGlua3NjYXBlOndpbmRvdy1tYXhpbWl6ZWQ9IjEiIGlua3NjYXBlOmN1cnJlbnQtbGF5ZXI9ImxheWVyMSIvPgogIDxkZWZzIGlkPSJkZWZzNCI+CiAgICA8aW5rc2NhcGU6cGF0aC1lZmZlY3QgZWZmZWN0PSJzcGlybyIgaWQ9InBhdGgtZWZmZWN0MzYyMCIgaXNfdmlzaWJsZT0idHJ1ZSIvPgogICAgPGlua3NjYXBlOnBlcnNwZWN0aXZlIHNvZGlwb2RpOnR5cGU9Imlua3NjYXBlOnBlcnNwM2QiIGlua3NjYXBlOnZwX3g9IjAgOiAyNTUgOiAxIiBpbmtzY2FwZTp2cF95PSIwIDogMTAwMCA6IDAiIGlua3NjYXBlOnZwX3o9IjUxMCA6IDI1NSA6IDEiIGlua3NjYXBlOnBlcnNwM2Qtb3JpZ2luPSIyNTUgOiAxNzAgOiAxIiBpZD0icGVyc3BlY3RpdmUxOCIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXJHcmFkaWVudDM1OTQiPgogICAgICA8c3RvcCBpZD0ic3RvcDM1OTYiIHN0b3AtY29sb3I9IiNGRkYiIG9mZnNldD0iMCIvPgogICAgICA8c3RvcCBpZD0ic3RvcDM1OTgiIHN0b3AtY29sb3I9IiMwMDAiIHN0b3Atb3BhY2l0eT0iMC40MTA3MTQzIiBvZmZzZXQ9IjEiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICA8ZmlsdGVyIGlkPSJmaWx0ZXI0MDY3IiBoZWlnaHQ9IjEuMjYwMTk3MiIgd2lkdGg9IjEuMjg0MjYyOCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiB5PSItMC4xMzAwOTg1OCIgeD0iLTAuMTQyMTMxNCI+CiAgICAgIDxmZUdhdXNzaWFuQmx1ciBpZD0iZmVHYXVzc2lhbkJsdXI0MDY5IiBzdGREZXZpYXRpb249IjEuOTEyMzg5NCIvPgogICAgPC9maWx0ZXI+CiAgICA8cmFkaWFsR3JhZGllbnQgaWQ9InJhZGlhbEdyYWRpZW50NDA4MyIgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50MzU5NCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiIGN5PSI0NzkuNTQiIGN4PSIyNzIuMTUiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLTAuMzExNjQ5OTIsMS4zMTAxMDczLC0xLjMxMDE2NjEsLTAuMzExNjYzODksMTA0MS4yNzkxLDE5OS4yMDMwOSkiIHI9IjI1My4yNiIvPgogICAgPGZpbHRlciB4PSItMC4xNDMxMjQ1NSIgeT0iLTAuMTMxMDEwNzYiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiIgd2lkdGg9IjEuMjg2MjQ5IiBoZWlnaHQ9IjEuMjYyMDIxNSIgaWQ9ImZpbHRlcjM2MDEiPgogICAgICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyMy4xMDkyNDgiIGlkPSJmZUdhdXNzaWFuQmx1cjM2MDMiLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciB4PSItMC4xNDIxMzE0IiB5PSItMC4xMzAwOTg1OCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiB3aWR0aD0iMS4yODQyNjI4IiBoZWlnaHQ9IjEuMjYwMTk3MiIgaWQ9ImZpbHRlcjM2MDUiPgogICAgICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyMi45NDg2NzMiIGlkPSJmZUdhdXNzaWFuQmx1cjM2MDciLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciB4PSItMC4xNDIxMzE0IiB5PSItMC4xMzAwOTg1OCIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIiB3aWR0aD0iMS4yODQyNjI4IiBoZWlnaHQ9IjEuMjYwMTk3MiIgaWQ9ImZpbHRlcjM2MDkiPgogICAgICA8ZmVHYXVzc2lhbkJsdXIgc3RkRGV2aWF0aW9uPSIyMi45NDg2NzMiIGlkPSJmZUdhdXNzaWFuQmx1cjM2MTEiLz4KICAgIDwvZmlsdGVyPgogICAgPGZpbHRlciBpbmtzY2FwZTpjb2xsZWN0PSJhbHdheXMiIGlkPSJmaWx0ZXIzNzU2Ij4KICAgICAgPGZlR2F1c3NpYW5CbHVyIGlua3NjYXBlOmNvbGxlY3Q9ImFsd2F5cyIgc3RkRGV2aWF0aW9uPSI3LjMxNTE1MzMiIGlkPSJmZUdhdXNzaWFuQmx1cjM3NTgiLz4KICAgIDwvZmlsdGVyPgogIDwvZGVmcz4KICA8bWV0YWRhdGEgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlIHJkZjpyZXNvdXJjZT0iaHR0cDovL3B1cmwub3JnL2RjL2RjbWl0eXBlL1N0aWxsSW1hZ2UiLz4KICAgICAgICA8ZGM6dGl0bGUvPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZyBpZD0ibGF5ZXIxIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0xNTQuMjg1NjksLTIyOC4wNzY0OSkiPgogICAgPHBhdGggaWQ9InBhdGgyODE4IiBzdHJva2UtbGluZWpvaW49ImJldmVsIiBzdHlsZT0ic3Ryb2tlLWRhc2hhcnJheTpub25lOyIgZD0iTTY2MCw0ODUuNzhhMjUyLjg2LDI1Mi44NiwwLDEsMSwwLC0wLjU2IiB0cmFuc2Zvcm09InRyYW5zbGF0ZSg0LC0yKSIgc3Ryb2tlLWRhc2hvZmZzZXQ9IjAuNDc5IiBzdHJva2U9IiM3ODc4NzgiIHN0cm9rZS1taXRlcmxpbWl0PSI0IiBzdHJva2Utd2lkdGg9IjAuODAwMDAwMDEiIGZpbGw9IiMxMDZkZDUiLz4KICAgIDxwYXRoIGlkPSJwYXRoMzU5MiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCwtMikiIGZpbGw9InVybCgjcmFkaWFsR3JhZGllbnQ0MDgzKSIgZD0iTTY2MCw0ODUuNzhhMjUyLjg2LDI1Mi44NiwwLDEsMSwwLC0wLjU2Ii8+CiAgICA8cGF0aCBpZD0icGF0aDQwNzgiIGQ9Ik02NjAsNDg1Ljc4YTI1Mi44NiwyNTIuODYsMCwxLDEsMCwtMC41NiIgdHJhbnNmb3JtPSJ0cmFuc2xhdGUoNCwtMikiIGZpbGw9InVybCgjcmFkaWFsR3JhZGllbnQ0MDgzKSIvPgogIDwvZz4KPC9zdmc+',
},
},
})
graph.addNode({
shape: 'ball',
x: 400,
y: 350,
width: 50,
height: 50,
bounciness: 1.5,
attrs: {
image: {
'xlink:href':
'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB3aWR0aD0iODAwIiBoZWlnaHQ9IjgwMCIgdmlld0JveD0iLTEwNSAtMTA1IDIxMCAyMTAiPgogICA8ZGVmcz4KICAgICAgPGNsaXBQYXRoIGlkPSJiYWxsIj4KICAgICAgICAgPGNpcmNsZSByPSIxMDAiIHN0cm9rZS13aWR0aD0iMCIvPgogICAgICA8L2NsaXBQYXRoPgogICAgICA8cmFkaWFsR3JhZGllbnQgaWQ9InNoYWRvdzEiIGN4PSIuNCIgY3k9Ii4zIiByPSIuOCI+CiAgICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICAgICAgPHN0b3Agb2Zmc2V0PSIuNCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMSIvPgogICAgICAgIDxzdG9wIG9mZnNldD0iLjgiIHN0b3AtY29sb3I9IiNFRUVFRUUiIHN0b3Atb3BhY2l0eT0iMSIvPgogICAgICA8L3JhZGlhbEdyYWRpZW50PgogICAgICA8cmFkaWFsR3JhZGllbnQgaWQ9InNoYWRvdzIiIGN4PSIuNSIgY3k9Ii41IiByPSIuNSI+CiAgICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSJ3aGl0ZSIgc3RvcC1vcGFjaXR5PSIwIi8+CiAgICAgICAgPHN0b3Agb2Zmc2V0PSIuOCIgc3RvcC1jb2xvcj0id2hpdGUiIHN0b3Atb3BhY2l0eT0iMCIvPgogICAgICAgIDxzdG9wIG9mZnNldD0iLjk5IiBzdG9wLWNvbG9yPSJibGFjayIgc3RvcC1vcGFjaXR5PSIuMyIvPgogICAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iYmxhY2siIHN0b3Atb3BhY2l0eT0iMSIvPgogICAgICA8L3JhZGlhbEdyYWRpZW50PgogICAgICA8ZyBpZD0iYmxhY2tfc3R1ZmYiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsaXAtcGF0aD0idXJsKCNiYWxsKSI+CiAgICAgICAgIDxnIGZpbGw9ImJsYWNrIj4KICAgICAgICAgICAgPHBhdGggZD0iTSA2LC0zMiBRIDI2LC0yOCA0NiwtMTkgUSA1NywtMzUgNjQsLTQ3IFEgNTAsLTY4IDM3LC03NiBRIDE3LC03NSAxLC02OCBRIDQsLTUxIDYsLTMyIi8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gLTI2LC0yIFEgLTQ1LC04IC02MiwtMTEgUSAtNzQsNSAtNzYsMjIgUSAtNjksNDAgLTUwLDU0IFEgLTMyLDQ3IC0xNywzOSBRIC0yMywxNSAtMjYsLTIiLz4KICAgICAgICAgICAgPHBhdGggZD0iTSAtOTUsMjIgUSAtMTAyLDEyIC0xMDIsLTggViA4MCBIIC04NSBRIC05NSw0NSAtOTUsMjIiLz4KICAgICAgICAgICAgPHBhdGggZD0iTSA1NSwyNCBRIDQxLDQxIDI0LDUyIFEgMjgsNjUgMzEsNzkgUSA1NSw3OCA2OCw2NyBRIDc4LDUwIDgwLDM1IFEgNjUsMjggNTUsMjQiLz4KICAgICAgICAgICAgPHBhdGggZD0iTSAwLDEyMCBMIC0zLDk1IFEgLTI1LDkzIC00Miw4MiBRIC01MCw4NCAtNjAsODEiLz4KICAgICAgICAgICAgPHBhdGggZD0iTSAtOTAsLTQ4IFEgLTgwLC01MiAtNjgsLTQ5IFEgLTUyLC03MSAtMzUsLTc3IFEgLTM1LC0xMDAgLTQwLC0xMDAgSCAtMTAwIi8+CiAgICAgICAgICAgIDxwYXRoIGQ9Ik0gMTAwLC01NSBMIDg3LC0zNyBRIDk4LC0xMCA5Nyw1IEwgMTAwLDYiLz4KICAgICAgICAgPC9nPgogICAgICAgICA8ZyBmaWxsPSJub25lIj4KICAgICAgICAgICAgPHBhdGggZD0iTSA2LC0zMiBRIC0xOCwtMTIgLTI2LC0yICAgICAgICAgICAgICAgICAgICAgIE0gNDYsLTE5IFEgNTQsNSA1NSwyNCAgICAgICAgICAgICAgICAgICAgICBNIDY0LC00NyBRIDc3LC00NCA4NywtMzcgICAgICAgICAgICAgICAgICAgICAgTSAzNywtNzYgUSAzOSwtOTAgMzYsLTEwMCAgICAgICAgICAgICAgICAgICAgICBNIDEsLTY4IFEgLTEzLC03NyAtMzUsLTc3ICAgICAgICAgICAgICAgICAgICAgIE0gLTYyLC0xMSBRIC02NywtMjUgLTY4LC00OSAgICAgICAgICAgICAgICAgICAgICBNIC03NiwyMiBRIC04NSwyNCAtOTUsMjIgICAgICAgICAgICAgICAgICAgICAgTSAtNTAsNTQgUSAtNDksNzAgLTQyLDgyICAgICAgICAgICAgICAgICAgICAgIE0gLTE3LDM5IFEgMCw0OCAyNCw1MiAgICAgICAgICAgICAgICAgICAgICBNIDMxLDc5IFEgMjAsOTIgLTMsOTUgICAgICAgICAgICAgICAgICAgICAgTSA2OCw2NyBMIDgwLDgwICAgICAgICAgICAgICAgICAgICAgIE0gODAsMzUgUSA5MCwyNSA5Nyw1ICAgICAgICAgICAgICIvPgogICAgICAgICA8L2c+CiAgICAgIDwvZz4KICAgPC9kZWZzPgogICA8Y2lyY2xlIHI9IjEwMCIgZmlsbD0id2hpdGUiIHN0cm9rZT0ibm9uZSIvPgogICA8Y2lyY2xlIHI9IjEwMCIgZmlsbD0idXJsKCNzaGFkb3cxKSIgc3Ryb2tlPSJub25lIi8+CiAgIDx1c2UgeGxpbms6aHJlZj0iI2JsYWNrX3N0dWZmIiBzdHJva2U9IiNFRUUiIHN0cm9rZS13aWR0aD0iNyIvPgogICA8dXNlIHhsaW5rOmhyZWY9IiNibGFja19zdHVmZiIgc3Ryb2tlPSIjREREIiBzdHJva2Utd2lkdGg9IjQiLz4KICAgPHVzZSB4bGluazpocmVmPSIjYmxhY2tfc3R1ZmYiIHN0cm9rZT0iIzk5OSIgc3Ryb2tlLXdpZHRoPSIyIi8+CiAgIDx1c2UgeGxpbms6aHJlZj0iI2JsYWNrX3N0dWZmIiBzdHJva2U9ImJsYWNrIiBzdHJva2Utd2lkdGg9IjEiLz4KICAgPGNpcmNsZSByPSIxMDAiIGZpbGw9InVybCgjc2hhZG93MikiIHN0cm9rZT0ibm9uZSIvPgo8L3N2Zz4=',
},
},
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div
ref={this.refContainer}
className="x6-graph"
style={{
backgroundImage: 'linear-gradient(to bottom, #00BFFF , #FFFFFF)',
}}
/>
</div>
)
}
}

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Scroller } from '@antv/x6-plugin-scroller'
import { SplitBox } from '@antv/x6-react-components'
import '@antv/x6-react-components/es/split-box/style/index.css'
import '../index.less'
@ -14,7 +15,7 @@ export default class Example extends React.Component {
new Graph({
container: this.graphContainer1,
background: {
color: '#F2F7FA',
color: '#D94111',
},
autoResize: true,
})
@ -22,18 +23,23 @@ export default class Example extends React.Component {
new Graph({
container: this.graphContainer2,
background: {
color: '#F2F7FA',
color: '#90C54C',
},
autoResize: true,
})
new Graph({
const graph = new Graph({
container: this.graphContainer3,
background: {
color: '#F2F7FA',
color: '#0491E4',
},
autoResize: true,
})
graph.use(
new Scroller({
enabled: true,
}),
)
}
refContainer1 = (container: HTMLDivElement) => {

View File

@ -0,0 +1,51 @@
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: 600,
grid: true,
})
const source = graph.addNode({
shape: 'rect',
x: 80,
y: 80,
width: 160,
height: 60,
label: 'source',
})
const target = graph.addNode({
shape: 'rect',
x: 320,
y: 320,
width: 160,
height: 60,
label: 'target',
})
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

@ -3,7 +3,7 @@ import { Graph, Cell } from '@antv/x6'
import '../index.less'
Graph.registerNode(
'event',
'bpmn-event',
{
inherit: 'circle',
attrs: {
@ -18,7 +18,7 @@ Graph.registerNode(
)
Graph.registerNode(
'activity',
'bpmn-activity',
{
inherit: 'rect',
markup: [
@ -61,7 +61,7 @@ Graph.registerNode(
)
Graph.registerNode(
'gateway',
'bpmn-gateway',
{
inherit: 'polygon',
attrs: {
@ -98,14 +98,14 @@ Graph.registerEdge(
const data = [
{
id: '1',
shape: 'event',
shape: 'bpmn-event',
width: 40,
height: 40,
position: { x: 50, y: 180 },
},
{
id: '2',
shape: 'activity',
shape: 'bpmn-activity',
width: 100,
height: 60,
position: { x: 20, y: 280 },
@ -119,7 +119,7 @@ const data = [
},
{
id: '4',
shape: 'gateway',
shape: 'bpmn-gateway',
width: 55,
height: 55,
position: { x: 170, y: 282.5 },
@ -132,7 +132,7 @@ const data = [
},
{
id: '6',
shape: 'activity',
shape: 'bpmn-activity',
width: 100,
height: 60,
position: { x: 300, y: 240 },
@ -140,7 +140,7 @@ const data = [
},
{
id: '7',
shape: 'activity',
shape: 'bpmn-activity',
width: 100,
height: 60,
position: { x: 300, y: 320 },
@ -160,7 +160,7 @@ const data = [
},
{
id: '10',
shape: 'gateway',
shape: 'bpmn-gateway',
width: 55,
height: 55,
position: { x: 460, y: 282.5 },
@ -179,7 +179,7 @@ const data = [
},
{
id: '13',
shape: 'activity',
shape: 'bpmn-activity',
width: 100,
height: 60,
position: { x: 560, y: 280 },
@ -193,7 +193,7 @@ const data = [
},
{
id: '15',
shape: 'event',
shape: 'bpmn-event',
width: 40,
height: 40,
position: { x: 710, y: 290 },

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph, ObjectExt, Cell } from '@antv/x6'
import { Graph, Cell } from '@antv/x6'
import { ObjectExt } from '@antv/x6-common'
import '../index.less'
Graph.registerNode(

View File

@ -1,453 +1,454 @@
// import React from 'react'
// import { Graph, Node, Path, Cell } from '@antv/x6'
// import '@antv/x6-react-shape'
// import '../index.less'
// import './index.less'
// interface NodeStatus {
// id: string
// status: 'default' | 'success' | 'failed' | 'running'
// label?: string
// }
import React from 'react'
import { Graph, Node, Cell } from '@antv/x6'
import { Path } from '@antv/x6-geometry'
import { register } from '@antv/x6-react-shape'
import { Selection } from '@antv/x6-plugin-selection'
import { Snapline } from '@antv/x6-plugin-snapline'
import '../index.less'
import './index.less'
interface NodeStatus {
id: string
status: string
label?: string
}
// const image = {
// logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',
// success:
// 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',
// failed:
// 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',
// running:
// 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',
// }
// export class AlgoNode extends React.Component<{ node?: Node }> {
// shouldComponentUpdate() {
// const { node } = this.props
// if (node) {
// if (node.hasChanged('data')) {
// return true
// }
// }
// return false
// }
const image = {
logo: 'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*evDjT5vjkX0AAAAAAAAAAAAAARQnAQ',
success:
'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*6l60T6h8TTQAAAAAAAAAAAAAARQnAQ',
failed:
'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*SEISQ6My-HoAAAAAAAAAAAAAARQnAQ',
running:
'https://gw.alipayobjects.com/mdn/rms_43231b/afts/img/A*t8fURKfgSOgAAAAAAAAAAAAAARQnAQ',
}
export class AlgoNode extends React.Component<{ node?: Node }> {
shouldComponentUpdate() {
const { node } = this.props
if (node) {
if (node.hasChanged('data')) {
return true
}
}
return false
}
// render() {
// const { node } = this.props
// const data = node?.getData() as NodeStatus
// const { label, status = 'default' } = data
render() {
const { node } = this.props
const data = node?.getData() as NodeStatus
const { label, status = 'default' } = data
// return (
// <div className={`node ${status}`}>
// <img src={image.logo} alt="logo" />
// <span className="label">{label}</span>
// <span className="status">
// {status === 'success' && <img src={image.success} alt="success" />}
// {status === 'failed' && <img src={image.failed} alt="failed" />}
// {status === 'running' && <img src={image.running} alt="running" />}
// </span>
// </div>
// )
// }
// }
return (
<div className={`node ${status}`}>
<img src={image.logo} alt="logo" />
<span className="label">{label}</span>
<span className="status">
{status === 'success' && <img src={image.success} alt="success" />}
{status === 'failed' && <img src={image.failed} alt="failed" />}
{status === 'running' && <img src={image.running} alt="running" />}
</span>
</div>
)
}
}
// Graph.registerNode(
// 'dag-node',
// {
// inherit: 'react-shape',
// width: 180,
// height: 36,
// component: <AlgoNode />,
// ports: {
// groups: {
// top: {
// position: 'top',
// attrs: {
// circle: {
// r: 4,
// magnet: true,
// stroke: '#C2C8D5',
// strokeWidth: 1,
// fill: '#fff',
// },
// },
// },
// bottom: {
// position: 'bottom',
// attrs: {
// circle: {
// r: 4,
// magnet: true,
// stroke: '#C2C8D5',
// strokeWidth: 1,
// fill: '#fff',
// },
// },
// },
// },
// },
// },
// true,
// )
register(AlgoNode, {
shape: 'dag-node',
width: 180,
height: 36,
ports: {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#C2C8D5',
strokeWidth: 1,
fill: '#fff',
},
},
},
},
},
})
// Graph.registerEdge(
// 'dag-edge',
// {
// inherit: 'edge',
// attrs: {
// line: {
// stroke: '#C2C8D5',
// strokeWidth: 1,
// targetMarker: null,
// },
// },
// },
// true,
// )
Graph.registerEdge(
'dag-edge',
{
inherit: 'edge',
attrs: {
line: {
stroke: '#C2C8D5',
strokeWidth: 1,
targetMarker: null,
},
},
},
true,
)
// Graph.registerConnector(
// 'algo-connector',
// (s, e) => {
// const offset = 4
// const deltaY = Math.abs(e.y - s.y)
// const control = Math.floor((deltaY / 3) * 2)
Graph.registerConnector(
'algo-connector',
(s, e) => {
const offset = 4
const deltaY = Math.abs(e.y - s.y)
const control = Math.floor((deltaY / 3) * 2)
// const v1 = { x: s.x, y: s.y + offset + control }
// const v2 = { x: e.x, y: e.y - offset - control }
const v1 = { x: s.x, y: s.y + offset + control }
const v2 = { x: e.x, y: e.y - offset - control }
// return Path.normalize(
// `M ${s.x} ${s.y}
// L ${s.x} ${s.y + offset}
// C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
// L ${e.x} ${e.y}
// `,
// )
// },
// true,
// )
return Path.normalize(
`M ${s.x} ${s.y}
L ${s.x} ${s.y + offset}
C ${v1.x} ${v1.y} ${v2.x} ${v2.y} ${e.x} ${e.y - offset}
L ${e.x} ${e.y}
`,
)
},
true,
)
// const data = [
// {
// id: '1',
// shape: 'dag-node',
// x: 290,
// y: 110,
// data: {
// label: '读数据',
// status: 'success',
// },
// ports: [
// {
// id: '1-1',
// group: 'bottom',
// },
// ],
// },
// {
// id: '2',
// shape: 'dag-node',
// x: 290,
// y: 225,
// data: {
// label: '读数据',
// status: 'success',
// },
// ports: [
// {
// id: '2-1',
// group: 'top',
// },
// {
// id: '2-2',
// group: 'bottom',
// },
// {
// id: '2-3',
// group: 'bottom',
// },
// ],
// },
// {
// id: '3',
// shape: 'dag-node',
// x: 170,
// y: 350,
// data: {
// label: '读数据',
// status: 'success',
// },
// ports: [
// {
// id: '3-1',
// group: 'top',
// },
// {
// id: '3-2',
// group: 'bottom',
// },
// ],
// },
// {
// id: '4',
// shape: 'dag-node',
// x: 450,
// y: 350,
// data: {
// label: '读数据',
// status: 'success',
// },
// ports: [
// {
// id: '4-1',
// group: 'top',
// },
// {
// id: '4-2',
// group: 'bottom',
// },
// ],
// },
// {
// id: '5',
// shape: 'dag-edge',
// source: {
// cell: '1',
// port: '1-1',
// },
// target: {
// cell: '2',
// port: '2-1',
// },
// zIndex: 0,
// },
// {
// id: '6',
// shape: 'dag-edge',
// source: {
// cell: '2',
// port: '2-2',
// },
// target: {
// cell: '3',
// port: '3-1',
// },
// zIndex: 0,
// },
// {
// id: '7',
// shape: 'dag-edge',
// source: {
// cell: '2',
// port: '2-3',
// },
// target: {
// cell: '4',
// port: '4-1',
// },
// zIndex: 0,
// },
// ]
const data = [
{
id: '1',
shape: 'dag-node',
x: 290,
y: 110,
data: {
label: '读数据',
status: 'success',
},
ports: [
{
id: '1-1',
group: 'bottom',
},
],
},
{
id: '2',
shape: 'dag-node',
x: 290,
y: 225,
data: {
label: '读数据',
status: 'success',
},
ports: [
{
id: '2-1',
group: 'top',
},
{
id: '2-2',
group: 'bottom',
},
{
id: '2-3',
group: 'bottom',
},
],
},
{
id: '3',
shape: 'dag-node',
x: 170,
y: 350,
data: {
label: '读数据',
status: 'success',
},
ports: [
{
id: '3-1',
group: 'top',
},
{
id: '3-2',
group: 'bottom',
},
],
},
{
id: '4',
shape: 'dag-node',
x: 450,
y: 350,
data: {
label: '读数据',
status: 'success',
},
ports: [
{
id: '4-1',
group: 'top',
},
{
id: '4-2',
group: 'bottom',
},
],
},
{
id: '5',
shape: 'dag-edge',
source: {
cell: '1',
port: '1-1',
},
target: {
cell: '2',
port: '2-1',
},
zIndex: 0,
},
{
id: '6',
shape: 'dag-edge',
source: {
cell: '2',
port: '2-2',
},
target: {
cell: '3',
port: '3-1',
},
zIndex: 0,
},
{
id: '7',
shape: 'dag-edge',
source: {
cell: '2',
port: '2-3',
},
target: {
cell: '4',
port: '4-1',
},
zIndex: 0,
},
]
// const nodeStatusList = [
// [
// {
// id: '1',
// status: 'running',
// },
// {
// id: '2',
// status: 'default',
// },
// {
// id: '3',
// status: 'default',
// },
// {
// id: '4',
// status: 'default',
// },
// ],
// [
// {
// id: '1',
// status: 'success',
// },
// {
// id: '2',
// status: 'running',
// },
// {
// id: '3',
// status: 'default',
// },
// {
// id: '4',
// status: 'default',
// },
// ],
// [
// {
// id: '1',
// status: 'success',
// },
// {
// id: '2',
// status: 'success',
// },
// {
// id: '3',
// status: 'running',
// },
// {
// id: '4',
// status: 'running',
// },
// ],
// [
// {
// id: '1',
// status: 'success',
// },
// {
// id: '2',
// status: 'success',
// },
// {
// id: '3',
// status: 'success',
// },
// {
// id: '4',
// status: 'failed',
// },
// ],
// ]
// export default class Example extends React.Component {
// private container: HTMLDivElement
const nodeStatusList = [
[
{
id: '1',
status: 'running',
},
{
id: '2',
status: 'default',
},
{
id: '3',
status: 'default',
},
{
id: '4',
status: 'default',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'running',
},
{
id: '3',
status: 'default',
},
{
id: '4',
status: 'default',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'success',
},
{
id: '3',
status: 'running',
},
{
id: '4',
status: 'running',
},
],
[
{
id: '1',
status: 'success',
},
{
id: '2',
status: 'success',
},
{
id: '3',
status: 'success',
},
{
id: '4',
status: 'failed',
},
],
]
export default class Example extends React.Component {
private container: HTMLDivElement
// componentDidMount() {
// const graph: Graph = new Graph({
// container: this.container,
// width: 800,
// height: 600,
// panning: {
// enabled: true,
// eventTypes: ['leftMouseDown', 'mouseWheel'],
// },
// mousewheel: {
// enabled: true,
// modifiers: 'ctrl',
// factor: 1.1,
// maxScale: 1.5,
// minScale: 0.5,
// },
// highlighting: {
// magnetAdsorbed: {
// name: 'stroke',
// args: {
// attrs: {
// fill: '#fff',
// stroke: '#31d0c6',
// strokeWidth: 4,
// },
// },
// },
// },
// connecting: {
// snap: true,
// allowBlank: false,
// allowLoop: false,
// highlight: true,
// connector: 'algo-connector',
// connectionPoint: 'anchor',
// anchor: 'center',
// validateMagnet({ magnet }) {
// return magnet.getAttribute('port-group') !== 'top'
// },
// createEdge() {
// return graph.createEdge({
// shape: 'dag-edge',
// attrs: {
// line: {
// strokeDasharray: '5 5',
// },
// },
// zIndex: -1,
// })
// },
// },
// selecting: {
// enabled: true,
// multiple: true,
// rubberEdge: true,
// rubberNode: true,
// modifiers: 'shift',
// rubberband: true,
// },
// })
componentDidMount() {
const graph: Graph = new Graph({
container: this.container,
width: 800,
height: 600,
panning: {
enabled: true,
eventTypes: ['leftMouseDown', 'mouseWheel'],
},
mousewheel: {
enabled: true,
modifiers: 'ctrl',
factor: 1.1,
maxScale: 1.5,
minScale: 0.5,
},
highlighting: {
magnetAdsorbed: {
name: 'stroke',
args: {
attrs: {
fill: '#fff',
stroke: '#31d0c6',
strokeWidth: 4,
},
},
},
},
connecting: {
snap: true,
allowBlank: false,
allowLoop: false,
highlight: true,
connector: 'algo-connector',
connectionPoint: 'anchor',
anchor: 'center',
validateMagnet({ magnet }) {
return magnet.getAttribute('port-group') !== 'top'
},
createEdge() {
return graph.createEdge({
shape: 'dag-edge',
attrs: {
line: {
strokeDasharray: '5 5',
},
},
zIndex: -1,
})
},
},
})
// graph.on('edge:connected', ({ edge }) => {
// edge.attr({
// line: {
// strokeDasharray: '',
// },
// })
// })
const selection = new Selection({
enabled: true,
multiple: true,
rubberEdge: true,
rubberNode: true,
modifiers: 'shift',
rubberband: true,
})
graph.use(selection)
graph.use(new Snapline({ enabled: true }))
// graph.on('node:change:data', ({ node }) => {
// const edges = graph.getIncomingEdges(node)
// const { status } = node.getData() as NodeStatus
// edges?.forEach((edge) => {
// if (status === 'running') {
// edge.attr('line/strokeDasharray', 5)
// edge.attr('line/style/animation', 'running-line 30s infinite linear')
// } else {
// edge.attr('line/strokeDasharray', '')
// edge.attr('line/style/animation', '')
// }
// })
// })
graph.on('edge:connected', ({ edge }) => {
edge.attr({
line: {
strokeDasharray: '',
},
})
})
// // 初始化节点/边
// const init = (data: Cell.Metadata[]) => {
// const cells: Cell[] = []
// data.forEach((item) => {
// if (item.shape === 'dag-node') {
// cells.push(graph.createNode(item))
// } else {
// cells.push(graph.createEdge(item))
// }
// })
// graph.resetCells(cells)
// }
graph.on('node:change:data', ({ node }) => {
const edges = graph.getIncomingEdges(node)
const { status } = node.getData() as NodeStatus
edges?.forEach((edge) => {
if (status === 'running') {
edge.attr('line/strokeDasharray', 5)
edge.attr('line/style/animation', 'running-line 30s infinite linear')
} else {
edge.attr('line/strokeDasharray', '')
edge.attr('line/style/animation', '')
}
})
})
// // 显示节点状态
// const showNodeStatus = async (statusList: NodeStatus[][]) => {
// const status = statusList.shift()
// status?.forEach((item) => {
// const { id, status } = item
// const node = graph.getCellById(id)
// const data = node.getData() as NodeStatus
// node.setData({
// ...data,
// status: status,
// })
// })
// setTimeout(() => {
// showNodeStatus(statusList)
// }, 3000)
// }
// 初始化节点/边
const init = (data: Cell.Metadata[]) => {
const cells: Cell[] = []
data.forEach((item) => {
if (item.shape === 'dag-node') {
cells.push(graph.createNode(item))
} else {
cells.push(graph.createEdge(item))
}
})
graph.resetCells(cells)
}
// init(data)
// showNodeStatus(nodeStatusList)
// }
// 显示节点状态
const showNodeStatus = async (statusList: NodeStatus[][]) => {
const status = statusList.shift()
status?.forEach((item) => {
const { id, status } = item
const node = graph.getCellById(id)
const data = node.getData() as NodeStatus
node.setData({
...data,
status: status,
})
})
setTimeout(() => {
showNodeStatus(statusList)
}, 3000)
}
// refContainer = (container: HTMLDivElement) => {
// this.container = container
// }
init(data)
showNodeStatus(nodeStatusList)
}
// render() {
// return (
// <div className="x6-graph-wrap">
// <div ref={this.refContainer} className="dag" />
// </div>
// )
// }
// }
refContainer = (container: HTMLDivElement) => {
this.container = container
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="dag" />
</div>
)
}
}

View File

@ -2,6 +2,8 @@ import React from 'react'
import { Graph, Cell, Node } from '@antv/x6'
import { connectors } from '../connector/xmind-definitions'
import Hierarchy from '@antv/hierarchy'
import { Selection } from '@antv/x6-plugin-selection'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import '../index.less'
import './mind.less'
@ -181,13 +183,15 @@ export default class Example extends React.Component {
connecting: {
connectionPoint: 'anchor',
},
selecting: {
enabled: true,
},
keyboard: {
enabled: true,
},
})
const selection = new Selection({
enabled: true,
})
graph.use(selection)
const keyboard = new Keyboard({
enabled: true,
})
graph.use(keyboard)
const render = () => {
const result: HierarchyResult = Hierarchy.mindmap(data, {
@ -346,8 +350,8 @@ export default class Example extends React.Component {
render()
}
})
graph.bindKey(['backspace', 'delete'], () => {
const selectedNodes = graph
keyboard.bindKey(['backspace', 'delete'], () => {
const selectedNodes = selection
.getSelectedCells()
.filter((item) => item.isNode())
if (selectedNodes.length) {
@ -358,9 +362,9 @@ export default class Example extends React.Component {
}
})
graph.bindKey('tab', (e) => {
keyboard.bindKey('tab', (e) => {
e.preventDefault()
const selectedNodes = graph
const selectedNodes = selection
.getSelectedCells()
.filter((item) => item.isNode())
if (selectedNodes.length) {

View File

@ -1,4 +1,5 @@
import { Graph, Path, Point } from '@antv/x6'
import { Graph } from '@antv/x6'
import { Path, Point } from '@antv/x6-geometry'
import { Connector } from '@antv/x6/lib/registry'
export const connectors = {

View File

@ -8,6 +8,51 @@ const dataSource = [
example: 'animation/transition',
description: 'transition 动画',
},
{
key: '2',
example: 'auto-resize',
description: '画布大小自适应',
},
{
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: 'snapline',
description: '对齐线',
},
]
const columns = [

View File

@ -1,5 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Snapline } from '@antv/x6-plugin-snapline'
import '../index.less'
export default class Example extends React.Component {
@ -11,9 +12,14 @@ export default class Example extends React.Component {
width: 800,
height: 600,
grid: true,
snapline: true,
})
const snapline = new Snapline({
enabled: true,
sharp: true,
})
graph.use(snapline)
graph.addNode({
shape: 'rect',
x: 50,

View File

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

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-common",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.18",
"description": "Basic toolkit for x6.",
"main": "lib/index.js",
"module": "es/index.js",

View File

@ -1,4 +1,4 @@
import { Color } from '../../src/color'
import { Color } from '../../color'
describe('Color', () => {
describe('#constructor', () => {

View File

@ -3,7 +3,7 @@ import {
IDisposable,
DisposableSet,
DisposableDelegate,
} from '../../src/common/disposable'
} from '../../common/disposable'
class TestDisposable implements IDisposable {
count = 0

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'
import { DataUri } from '../../src/datauri'
import { DataUri } from '../../datauri'
describe('DataUri', () => {
describe('#isDataUrl', () => {

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'
import { Dom } from '../../src/dom'
import { Dom } from '../../dom'
describe('af', () => {
describe('#requestAnimationFrame', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('attr', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('class', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('css', () => {

View File

@ -1,6 +1,6 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { ObjectExt } from '../../src/object'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
import { ObjectExt } from '../../object'
describe('Dom', () => {
describe('data', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
const wrap = document.createElement('div')
const svgContent =

View File

@ -1,6 +1,6 @@
import sinon from 'sinon'
import { Dom } from '../../src/dom'
import { Core } from '../../src/dom/event/core'
import { Dom } from '../../dom'
import { Core } from '../../dom/event/core'
// import { Hook } from './hook'
describe('EventDom', () => {
@ -30,7 +30,7 @@ describe('EventDom', () => {
return this
}
once(p1: any, p2?: any, p3?: any, p4?: any, p5?: any) {
once(p1: any, p2?: any, p3?: any, p4?: any) {
Dom.Event.once(this.node, p1, p2, p3, p4)
return this
}
@ -610,7 +610,6 @@ describe('EventDom', () => {
expect(spy2.callCount).toEqual(0)
})
// Todo Error: Expected 2 to equal 1.
// it('should apply hook to prevent window to unload', () => {
// const win = new EventDom(window as any)
// const spy1 = sinon.spy(() => {

View File

@ -1,5 +1,5 @@
import { Dom } from '../../src/dom'
import { Vector } from '../../src/vector'
import { Dom } from '../../dom'
import { Vector } from '../../vector'
describe('Dom', () => {
describe('matrix', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('path', () => {

View File

@ -1,4 +1,4 @@
import { Dom } from '../../src/dom'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('#prefix', () => {

View File

@ -1,4 +1,4 @@
import { Dom } from '../../src/dom'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('#clearSelection', () => {

View File

@ -1,4 +1,4 @@
import { Dom } from '../../src/dom'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('#setPrefixedStyle', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
describe('#text', () => {

View File

@ -1,5 +1,5 @@
import { Vector } from '../../src/vector'
import { Dom } from '../../src/dom'
import { Vector } from '../../vector'
import { Dom } from '../../dom'
describe('Dom', () => {
const fixture = document.createElement('div')

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'
import { Events } from '../../src/event'
import { Events } from '../../event'
describe('events', () => {
it('should trigger with context', () => {

View File

@ -1,4 +1,4 @@
import { FunctionExt } from '../../src/function'
import { FunctionExt } from '../../function'
describe('async', () => {
describe('#toDeferredBoolean', () => {

View File

@ -1,5 +1,5 @@
import sinon from 'sinon'
import { FunctionExt } from '../../src/function'
import { FunctionExt } from '../../function'
describe('FunctionExt', () => {
describe('#call', () => {

View File

@ -1,4 +1,4 @@
import { JSONArray, JSONExt, JSONObject, JSONPrimitive } from '../../src/json'
import { JSONArray, JSONExt, JSONObject, JSONPrimitive } from '../../json'
describe('JSONExt', () => {
describe('isPrimitive()', () => {

View File

@ -1,4 +1,4 @@
import { NumberExt } from '../../src/number'
import { NumberExt } from '../../number'
describe('NumberExt', () => {
describe('#mod', () => {

View File

@ -1,4 +1,4 @@
import { ObjectExt } from '../../src/object'
import { ObjectExt } from '../../object'
describe('object', () => {
class Parent {

View File

@ -1,4 +1,4 @@
import { ObjectExt } from '../../src/object'
import { ObjectExt } from '../../object'
describe('Ojbect', () => {
describe('applyMixins', () => {

View File

@ -1,4 +1,4 @@
import { ObjectExt } from '../../src/object'
import { ObjectExt } from '../../object'
describe('Object', () => {
const obj = {

View File

@ -1,4 +1,4 @@
import { StringExt } from '../../src/string'
import { StringExt } from '../../string'
describe('String', () => {
describe('#format', () => {

View File

@ -1,4 +1,4 @@
import { StringExt } from '../../src/string'
import { StringExt } from '../../string'
describe('StringExt', () => {
describe('#StringExt.hashcode', () => {

View File

@ -1,4 +1,4 @@
import { StringExt } from '../../src/string'
import { StringExt } from '../../string'
describe('String', () => {
describe('#getSpellingSuggestion', () => {

View File

@ -1,4 +1,4 @@
import { StringExt } from '../../src/string'
import { StringExt } from '../../string'
describe('string', () => {
describe('#uuid', () => {

View File

@ -1,4 +1,4 @@
import { Text } from '../../src/text'
import { Text } from '../../text'
describe('Text', () => {
describe('#annotate', () => {

View File

@ -1,4 +1,4 @@
import { Text } from '../../src/text'
import { Text } from '../../text'
describe('Text', () => {
describe('#sanitize', () => {

View File

@ -1,4 +1,4 @@
import { Unit } from '../../src'
import { Unit } from '../..'
describe('Unit', () => {
describe('#toPx', () => {

View File

@ -1,4 +1,4 @@
import { Vector } from '../../src/vector'
import { Vector } from '../../vector'
describe('Vector', () => {
describe('#create', () => {

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,5 +1,5 @@
{
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.18",
"name": "@antv/x6-geometry",
"description": "Geometry operations for x6.",
"main": "lib/index.js",

View File

@ -1,7 +1,4 @@
import { Ellipse } from '../src'
import { Line } from '../src'
import { Point } from '../src'
import { Rectangle } from '../src'
import { Ellipse, Line, Point, Rectangle } from '..'
describe('ellipse', () => {
describe('#constructor', () => {

View File

@ -1,6 +1,4 @@
import { Line } from '../src'
import { Point } from '../src'
import { Rectangle } from '../src'
import { Line, Point, Rectangle } from '..'
describe('Line', () => {
describe('#constructor', () => {

View File

@ -1,5 +1,4 @@
import { Path } from '../../src/path'
import { normalizePathData } from '../../src/path'
import { Path, normalizePathData } from '../../path'
describe('Path', () => {
describe('#normalizePathData', () => {

View File

@ -1,4 +1,4 @@
import { Point } from '../src'
import { Point } from '..'
describe('Point', () => {
describe('#constructor', () => {

View File

@ -1,6 +1,4 @@
import { Line } from '../src'
import { Point } from '../src'
import { Polyline } from '../src'
import { Line, Point, Polyline } from '..'
describe('Polyline', () => {
describe('#constructor', () => {

View File

@ -1,7 +1,4 @@
import { Ellipse } from '../src'
import { Line } from '../src'
import { Point } from '../src'
import { Rectangle } from '../src'
import { Ellipse, Line, Point, Rectangle } from '..'
describe('rectangle', () => {
describe('#constructor', () => {

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-keyboard",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.20",
"description": "keyboard plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
@ -33,6 +33,7 @@
"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"
@ -104,7 +105,7 @@
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-common"
"directory": "packages/x6-plugin-keyboard"
},
"publishConfig": {
"access": "public",

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-scroller",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.21",
"description": "scroller plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
@ -34,6 +34,7 @@
"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"
@ -101,7 +102,7 @@
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-common"
"directory": "packages/x6-plugin-scroller"
},
"publishConfig": {
"access": "public",

View File

@ -15,7 +15,7 @@ export class Scroller extends Disposable {
private scrollerImpl: ScrollerImpl
public name = 'scroller'
private get pannable() {
public get pannable() {
if (this.options) {
if (typeof this.options.pannable === 'object') {
return this.options.pannable.enabled
@ -26,13 +26,17 @@ export class Scroller extends Disposable {
return false
}
public get container() {
return this.scrollerImpl.container
}
constructor(public readonly options: Scroller.Options) {
super()
}
public init(graph: Graph) {
this.graph = graph
CssLoader.ensure('scroller', content)
CssLoader.ensure(this.name, content)
this.scrollerImpl = new ScrollerImpl({
...this.options,
@ -280,7 +284,7 @@ export class Scroller extends Disposable {
}
autoScroll(clientX: number, clientY: number) {
this.scrollerImpl.autoScroll(clientX, clientY)
return this.scrollerImpl.autoScroll(clientX, clientY)
}
// #endregion
@ -341,9 +345,8 @@ export class Scroller extends Disposable {
protected preparePanning({ e }: { e: Dom.MouseDownEvent }) {
const allowPanning = this.allowPanning(e, true)
const selection = this.graph.getPlugin('selection') as any
const allowRubberband =
this.allowPanning(e) && selection && selection.allowRubberband(e, true)
if (allowPanning || !allowRubberband) {
const allowRubberband = selection && selection.allowRubberband(e, true)
if (allowPanning || (this.allowPanning(e) && !allowRubberband)) {
this.updateClassName(true)
this.scrollerImpl.startPanning(e)
this.scrollerImpl.once('pan:stop', () => this.updateClassName(false))
@ -371,7 +374,7 @@ export class Scroller extends Disposable {
dispose() {
this.scrollerImpl.dispose()
this.stopListening()
CssLoader.clean('scroller')
CssLoader.clean(this.name)
}
}

View File

@ -48,6 +48,7 @@ export class ScrollerImpl extends View {
super()
this.options = ScrollerImpl.getOptions(options)
this.onUpdate = FunctionExt.debounce(this.onUpdate, 200)
const scale = this.graph.transform.getScale()
this.sx = scale.sx
@ -79,31 +80,6 @@ export class ScrollerImpl extends View {
Dom.before(graphContainer, this.container)
}
// todo copy style
// const style = graphContainer.getAttribute('style')
// if (style) {
// const obj: { [name: string]: string } = {}
// const styles = style.split(';')
// styles.forEach((item) => {
// const section = item.trim()
// if (section) {
// const pair = section.split(':')
// if (pair.length) {
// obj[pair[0].trim()] = pair[1] ? pair[1].trim() : ''
// }
// }
// })
// Object.keys(obj).forEach((key: any) => {
// if (key === 'width' || key === 'height') {
// return
// }
// graphContainer.style[key] = ''
// this.container.style[key] = obj[key]
// })
// }
this.content = document.createElement('div')
Dom.addClass(this.content, this.prefixClassName(ScrollerImpl.contentClass))
Dom.css(this.content, {
@ -185,11 +161,9 @@ export class ScrollerImpl extends View {
}
protected onUpdate() {
if (!this.options.autoResize) {
return
if (this.options.autoResize) {
this.update()
}
this.update()
}
protected delegateBackgroundEvents(events?: View.Events) {
@ -381,76 +355,12 @@ export class ScrollerImpl extends View {
gridWidth: this.options.pageWidth,
gridHeight: this.options.pageHeight,
allowNewOrigin: 'negative',
contentArea: this.calcContextArea(resizeOptions),
...resizeOptions,
}
this.graph.fitToContent(this.getFitToContentOptions(options))
}
protected calcContextArea(
resizeOptions:
| (TransformManager.FitToContentFullOptions & {
direction:
| ScrollerImpl.AutoResizeDirection
| ScrollerImpl.AutoResizeDirection[]
})
| undefined,
) {
const direction = resizeOptions?.direction
if (!direction) {
return this.graph.transform.getContentArea({ useCellGeometry: true })
}
function getCellBBox(cell: Cell) {
let rect = cell.getBBox()
if (rect) {
if (cell.isNode()) {
const angle = cell.getAngle()
if (angle != null && angle !== 0) {
rect = rect.bbox(angle)
}
}
}
return rect
}
const gridWidth = this.options.pageWidth || 1
const gridHeight = this.options.pageHeight || 1
let calculativeCells = this.graph.getCells()
if (!direction.includes('top')) {
calculativeCells = calculativeCells.filter((cell) => {
const bbox = getCellBBox(cell)
return bbox.y >= 0
})
}
if (!direction.includes('left')) {
calculativeCells = calculativeCells.filter((cell) => {
const bbox = getCellBBox(cell)
return bbox.x >= 0
})
}
if (!direction.includes('right')) {
calculativeCells = calculativeCells.filter((cell) => {
const bbox = getCellBBox(cell)
return bbox.x + bbox.width <= gridWidth
})
}
if (!direction.includes('bottom')) {
calculativeCells = calculativeCells.filter((cell) => {
const bbox = getCellBBox(cell)
return bbox.y + bbox.height <= gridHeight
})
}
return this.model.getCellsBBox(calculativeCells) || new Rectangle()
}
protected getFitToContentOptions(
options: TransformManager.FitToContentFullOptions,
) {

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-selection",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.22",
"description": "selection plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
@ -34,6 +34,7 @@
"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"
@ -102,7 +103,7 @@
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-common"
"directory": "packages/x6-plugin-selection"
},
"publishConfig": {
"access": "public",

View File

@ -22,7 +22,7 @@ function compile(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, 'scroller.css'))
compile(path.join(src, 'index.less'), path.join(dist, 'selection.css'))
function toCSSPath(source) {
const dir = path.dirname(source)
@ -61,7 +61,7 @@ function processLessInDir(dir) {
}
function makeStyleModule() {
const source = path.join(dist, 'scroller.css')
const source = path.join(dist, 'selection.css')
const target = path.join(src, 'style/raw.ts')
const content = fs.readFileSync(source, { encoding: 'utf8' })
const prev = fs.existsSync(target)

View File

@ -40,7 +40,7 @@ export class Selection extends Disposable {
public init(graph: Graph) {
this.graph = graph
CssLoader.ensure('scroller', content)
CssLoader.ensure(this.name, content)
this.selectionImpl = new SelectionImpl({
...this.options,
graph,
@ -453,6 +453,7 @@ export class Selection extends Disposable {
dispose() {
this.stopListening()
this.selectionImpl.dispose()
CssLoader.clean(this.name)
}
}

View File

@ -119,7 +119,8 @@ export class SelectionImpl extends View<SelectionImpl.EventArgs> {
options,
}: Collection.EventArgs['node:change:position']) {
const { showNodeSelectionBox, pointerEvents } = this.options
const { ui, selection, translateBy } = options
const { ui, selection, translateBy, snapped } = options
let allowTranslating = !this.translating
/* Scenarios where this method is not called:
@ -135,6 +136,9 @@ export class SelectionImpl extends View<SelectionImpl.EventArgs> {
allowTranslating =
allowTranslating && translateBy && node.id === translateBy
// enabled when snapline snapped
allowTranslating = allowTranslating || snapped
if (allowTranslating) {
this.translating = true
const current = node.position()

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-snapline`
> TODO: description
## Usage

View File

@ -0,0 +1,112 @@
{
"name": "@antv/x6-plugin-snapline",
"version": "2.0.6-beta.20",
"description": "snapline plugin for X6.",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/x6-plugin-snapline.js",
"jsdelivr": "dist/x6-plugin-snapline.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"es",
"lib"
],
"keywords": [
"plugin",
"snapline",
"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-snapline"
},
"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: 'X6PluginSnapline',
format: 'umd',
file: 'dist/x6-plugin-snapline.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, 'snapline.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, 'snapline.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,16 @@
@snapline-prefix-cls: ~'x6-widget-snapline';
.@{snapline-prefix-cls} {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
&-vertical,
&-horizontal {
stroke: #2ecc71;
stroke-width: 1px;
}
}

View File

@ -0,0 +1,149 @@
import { Disposable, CssLoader } from '@antv/x6-common'
import { Graph } from '@antv/x6'
import { SnaplineImpl } from './snapline'
import { content } from './style/raw'
export class Snapline extends Disposable {
private snaplineImpl: SnaplineImpl
public name = 'snapline'
constructor(public readonly options: Snapline.Options) {
super()
}
public init(graph: Graph) {
CssLoader.ensure(this.name, content)
this.snaplineImpl = new SnaplineImpl({
...this.options,
graph,
})
}
// #region api
isSnaplineEnabled() {
return !this.snaplineImpl.disabled
}
enableSnapline() {
this.snaplineImpl.enable()
return this
}
disableSnapline() {
this.snaplineImpl.disable()
return this
}
toggleSnapline(enabled?: boolean) {
if (enabled != null) {
if (enabled !== this.isSnaplineEnabled()) {
if (enabled) {
this.enableSnapline()
} else {
this.disableSnapline()
}
}
} else {
if (this.isSnaplineEnabled()) {
this.disableSnapline()
} else {
this.enableSnapline()
}
return this
}
}
hideSnapline() {
this.snaplineImpl.hide()
return this
}
setSnaplineFilter(filter?: SnaplineImpl.Filter) {
this.snaplineImpl.setFilter(filter)
return this
}
isSnaplineOnResizingEnabled() {
return this.snaplineImpl.options.resizing === true
}
enableSnaplineOnResizing() {
this.snaplineImpl.options.resizing = true
return this
}
disableSnaplineOnResizing() {
this.snaplineImpl.options.resizing = false
return this
}
toggleSnaplineOnResizing(enableOnResizing?: boolean) {
if (enableOnResizing != null) {
if (enableOnResizing !== this.isSnaplineOnResizingEnabled()) {
if (enableOnResizing) {
this.enableSnaplineOnResizing()
} else {
this.disableSnaplineOnResizing()
}
}
} else if (this.isSnaplineOnResizingEnabled()) {
this.disableSnaplineOnResizing()
} else {
this.enableSnaplineOnResizing()
}
return this
}
isSharpSnapline() {
return this.snaplineImpl.options.sharp === true
}
enableSharpSnapline() {
this.snaplineImpl.options.sharp = true
return this
}
disableSharpSnapline() {
this.snaplineImpl.options.sharp = false
return this
}
toggleSharpSnapline(sharp?: boolean) {
if (sharp != null) {
if (sharp !== this.isSharpSnapline()) {
if (sharp) {
this.enableSharpSnapline()
} else {
this.disableSharpSnapline()
}
}
} else if (this.isSharpSnapline()) {
this.disableSharpSnapline()
} else {
this.enableSharpSnapline()
}
return this
}
getSnaplineTolerance() {
return this.snaplineImpl.options.tolerance
}
setSnaplineTolerance(tolerance: number) {
this.snaplineImpl.options.tolerance = tolerance
return this
}
// #endregion
@Disposable.dispose()
dispose() {
this.snaplineImpl.dispose()
CssLoader.clean(this.name)
}
}
export namespace Snapline {
export interface Options extends SnaplineImpl.Options {}
}

View File

@ -0,0 +1,695 @@
import { IDisablable, ArrayExt, FunctionExt, Vector } from '@antv/x6-common'
import { Angle, Point, Rectangle } from '@antv/x6-geometry'
import {
Graph,
EventArgs,
Model,
Node,
CellView,
NodeView,
View,
} from '@antv/x6'
export class SnaplineImpl extends View implements IDisablable {
public readonly options: SnaplineImpl.Options
protected readonly graph: Graph
protected filterShapes: { [type: string]: boolean }
protected filterCells: { [id: string]: boolean }
protected filterFunction: SnaplineImpl.FilterFunction | null
protected offset: Point.PointLike
protected timer: number | null
public container: SVGElement
protected containerWrapper: Vector
protected horizontal: Vector
protected vertical: Vector
protected get model() {
return this.graph.model
}
protected get containerClassName() {
return this.prefixClassName('widget-snapline')
}
protected get verticalClassName() {
return `${this.containerClassName}-vertical`
}
protected get horizontalClassName() {
return `${this.containerClassName}-horizontal`
}
constructor(options: SnaplineImpl.Options & { graph: Graph }) {
super()
const { graph, ...others } = options
this.graph = graph
this.options = { tolerance: 10, ...others }
this.offset = { x: 0, y: 0 }
this.render()
this.parseFilter()
if (!this.disabled) {
this.startListening()
}
}
public get disabled() {
return this.options.enabled !== true
}
enable() {
if (this.disabled) {
this.options.enabled = true
this.startListening()
}
}
disable() {
if (!this.disabled) {
this.options.enabled = false
this.stopListening()
}
}
setFilter(filter?: SnaplineImpl.Filter) {
this.options.filter = filter
this.parseFilter()
}
protected render() {
const container = (this.containerWrapper = new Vector('svg'))
const horizontal = (this.horizontal = new Vector('line'))
const vertical = (this.vertical = new Vector('line'))
container.addClass(this.containerClassName)
horizontal.addClass(this.horizontalClassName)
vertical.addClass(this.verticalClassName)
container.setAttribute('width', '100%')
container.setAttribute('height', '100%')
horizontal.setAttribute('display', 'none')
vertical.setAttribute('display', 'none')
container.append([horizontal, vertical])
if (this.options.className) {
container.addClass(this.options.className)
}
this.container = this.containerWrapper.node
}
protected startListening() {
this.stopListening()
this.graph.on('node:mousedown', this.captureCursorOffset, this)
this.graph.on('node:mousemove', this.snapOnMoving, this)
this.model.on('batch:stop', this.onBatchStop, this)
this.delegateDocumentEvents({
mouseup: 'hide',
touchend: 'hide',
})
}
protected stopListening() {
this.graph.off('node:mousedown', this.captureCursorOffset, this)
this.graph.off('node:mousemove', this.snapOnMoving, this)
this.model.off('batch:stop', this.onBatchStop, this)
this.undelegateDocumentEvents()
}
protected parseFilter() {
this.filterShapes = {}
this.filterCells = {}
this.filterFunction = null
const filter = this.options.filter
if (Array.isArray(filter)) {
filter.forEach((item) => {
if (typeof item === 'string') {
this.filterShapes[item] = true
} else {
this.filterCells[item.id] = true
}
})
} else if (typeof filter === 'function') {
this.filterFunction = filter
}
}
protected onBatchStop({ name, data }: Model.EventArgs['batch:stop']) {
if (name === 'resize') {
this.snapOnResizing(data.cell, data as Node.ResizeOptions)
}
}
captureCursorOffset({ view, x, y }: EventArgs['node:mousedown']) {
const targetView = view.getDelegatedView()
if (targetView && this.isNodeMovable(targetView)) {
const pos = view.cell.getPosition()
this.offset = {
x: x - pos.x,
y: y - pos.y,
}
}
}
protected isNodeMovable(view: CellView) {
return view && view.cell.isNode() && view.can('nodeMovable')
}
protected getRestrictArea(view?: NodeView): Rectangle.RectangleLike | null {
const restrict = this.graph.options.translating.restrict
const area =
typeof restrict === 'function'
? FunctionExt.call(restrict, this.graph, view!)
: restrict
if (typeof area === 'number') {
return this.graph.transform.getGraphArea().inflate(area)
}
if (area === true) {
return this.graph.transform.getGraphArea()
}
return area || null
}
protected snapOnResizing(node: Node, options: Node.ResizeOptions) {
if (
this.options.resizing &&
!options.snapped &&
options.ui &&
options.direction &&
options.trueDirection
) {
const view = this.graph.renderer.findViewByCell(node) as NodeView
if (view && view.cell.isNode()) {
const nodeBbox = node.getBBox()
const nodeBBoxRotated = nodeBbox.bbox(node.getAngle())
const nodeTopLeft = nodeBBoxRotated.getTopLeft()
const nodeBottomRight = nodeBBoxRotated.getBottomRight()
const angle = Angle.normalize(node.getAngle())
const tolerance = this.options.tolerance || 0
let verticalLeft: number | undefined
let verticalTop: number | undefined
let verticalHeight: number | undefined
let horizontalTop: number | undefined
let horizontalLeft: number | undefined
let horizontalWidth: number | undefined
const snapOrigin = {
vertical: 0,
horizontal: 0,
}
const direction = options.direction
const trueDirection = options.trueDirection
const relativeDirection = options.relativeDirection
if (trueDirection.indexOf('right') !== -1) {
snapOrigin.vertical = nodeBottomRight.x
} else {
snapOrigin.vertical = nodeTopLeft.x
}
if (trueDirection.indexOf('bottom') !== -1) {
snapOrigin.horizontal = nodeBottomRight.y
} else {
snapOrigin.horizontal = nodeTopLeft.y
}
this.model.getNodes().some((cell) => {
if (this.isIgnored(node, cell)) {
return false
}
const snapBBox = cell.getBBox().bbox(cell.getAngle())
const snapTopLeft = snapBBox.getTopLeft()
const snapBottomRight = snapBBox.getBottomRight()
const groups = {
vertical: [snapTopLeft.x, snapBottomRight.x],
horizontal: [snapTopLeft.y, snapBottomRight.y],
}
const distances = {} as {
vertical: { position: number; distance: number }[]
horizontal: { position: number; distance: number }[]
}
Object.keys(groups).forEach((k) => {
const key = k as 'vertical' | 'horizontal'
const list = groups[key]
.map((value) => ({
position: value,
distance: Math.abs(value - snapOrigin[key]),
}))
.filter((item) => item.distance <= tolerance)
distances[key] = ArrayExt.sortBy(list, (item) => item.distance)
})
if (verticalLeft == null && distances.vertical.length > 0) {
verticalLeft = distances.vertical[0].position
verticalTop = Math.min(nodeBBoxRotated.y, snapBBox.y)
verticalHeight =
Math.max(nodeBottomRight.y, snapBottomRight.y) - verticalTop
}
if (horizontalTop == null && distances.horizontal.length > 0) {
horizontalTop = distances.horizontal[0].position
horizontalLeft = Math.min(nodeBBoxRotated.x, snapBBox.x)
horizontalWidth =
Math.max(nodeBottomRight.x, snapBottomRight.x) - horizontalLeft
}
return verticalLeft != null && horizontalTop != null
})
this.hide()
let dx = 0
let dy = 0
if (horizontalTop != null || verticalLeft != null) {
if (verticalLeft != null) {
dx =
trueDirection.indexOf('right') !== -1
? verticalLeft - nodeBottomRight.x
: nodeTopLeft.x - verticalLeft
}
if (horizontalTop != null) {
dy =
trueDirection.indexOf('bottom') !== -1
? horizontalTop - nodeBottomRight.y
: nodeTopLeft.y - horizontalTop
}
}
let dWidth = 0
let dHeight = 0
if (angle % 90 === 0) {
if (angle === 90 || angle === 270) {
dWidth = dy
dHeight = dx
} else {
dWidth = dx
dHeight = dy
}
} else {
const quadrant =
angle >= 0 && angle < 90
? 1
: angle >= 90 && angle < 180
? 4
: angle >= 180 && angle < 270
? 3
: 2
if (horizontalTop != null && verticalLeft != null) {
if (dx < dy) {
dy = 0
horizontalTop = undefined
} else {
dx = 0
verticalLeft = undefined
}
}
const rad = Angle.toRad(angle % 90)
if (dx) {
dWidth = quadrant === 3 ? dx / Math.cos(rad) : dx / Math.sin(rad)
}
if (dy) {
dHeight = quadrant === 3 ? dy / Math.cos(rad) : dy / Math.sin(rad)
}
const quadrant13 = quadrant === 1 || quadrant === 3
switch (relativeDirection) {
case 'top':
case 'bottom':
dHeight = dy
? dy / (quadrant13 ? Math.cos(rad) : Math.sin(rad))
: dx / (quadrant13 ? Math.sin(rad) : Math.cos(rad))
break
case 'left':
case 'right':
dWidth = dx
? dx / (quadrant13 ? Math.cos(rad) : Math.sin(rad))
: dy / (quadrant13 ? Math.sin(rad) : Math.cos(rad))
break
default:
break
}
}
switch (relativeDirection) {
case 'top':
case 'bottom':
dWidth = 0
break
case 'left':
case 'right':
dHeight = 0
break
default:
break
}
const gridSize = this.graph.getGridSize()
let newWidth = Math.max(nodeBbox.width + dWidth, gridSize)
let newHeight = Math.max(nodeBbox.height + dHeight, gridSize)
if (options.minWidth && options.minWidth > gridSize) {
newWidth = Math.max(newWidth, options.minWidth)
}
if (options.minHeight && options.minHeight > gridSize) {
newHeight = Math.max(newHeight, options.minHeight)
}
if (options.maxWidth) {
newWidth = Math.min(newWidth, options.maxWidth)
}
if (options.maxHeight) {
newHeight = Math.min(newHeight, options.maxHeight)
}
if (options.preserveAspectRatio) {
if (dHeight < dWidth) {
newHeight = newWidth * (nodeBbox.height / nodeBbox.width)
} else {
newWidth = newHeight * (nodeBbox.width / nodeBbox.height)
}
}
if (newWidth !== nodeBbox.width || newHeight !== nodeBbox.height) {
node.resize(newWidth, newHeight, {
direction,
relativeDirection,
trueDirection,
snapped: true,
snaplines: this.cid,
restrict: this.getRestrictArea(view),
})
if (verticalHeight) {
verticalHeight += newHeight - nodeBbox.height
}
if (horizontalWidth) {
horizontalWidth += newWidth - nodeBbox.width
}
}
const newRotatedBBox = node.getBBox().bbox(angle)
if (
verticalLeft &&
Math.abs(newRotatedBBox.x - verticalLeft) > 1 &&
Math.abs(newRotatedBBox.width + newRotatedBBox.x - verticalLeft) > 1
) {
verticalLeft = undefined
}
if (
horizontalTop &&
Math.abs(newRotatedBBox.y - horizontalTop) > 1 &&
Math.abs(newRotatedBBox.height + newRotatedBBox.y - horizontalTop) > 1
) {
horizontalTop = undefined
}
this.update({
verticalLeft,
verticalTop,
verticalHeight,
horizontalTop,
horizontalLeft,
horizontalWidth,
})
}
}
}
snapOnMoving({ view, e, x, y }: EventArgs['node:mousemove']) {
const targetView: NodeView = view.getEventData(e).delegatedView || view
if (!this.isNodeMovable(targetView)) {
return
}
const node = targetView.cell
const size = node.getSize()
const position = node.getPosition()
const cellBBox = new Rectangle(
x - this.offset.x,
y - this.offset.y,
size.width,
size.height,
)
const angle = node.getAngle()
const nodeCenter = cellBBox.getCenter()
const nodeBBoxRotated = cellBBox.bbox(angle)
const nodeTopLeft = nodeBBoxRotated.getTopLeft()
const nodeBottomRight = nodeBBoxRotated.getBottomRight()
const distance = this.options.tolerance || 0
let verticalLeft: number | undefined
let verticalTop: number | undefined
let verticalHeight: number | undefined
let horizontalTop: number | undefined
let horizontalLeft: number | undefined
let horizontalWidth: number | undefined
let verticalFix = 0
let horizontalFix = 0
this.model.getNodes().some((targetNode) => {
if (this.isIgnored(node, targetNode)) {
return false
}
const snapBBox = targetNode.getBBox().bbox(targetNode.getAngle())
const snapCenter = snapBBox.getCenter()
const snapTopLeft = snapBBox.getTopLeft()
const snapBottomRight = snapBBox.getBottomRight()
if (verticalLeft == null) {
if (Math.abs(snapCenter.x - nodeCenter.x) < distance) {
verticalLeft = snapCenter.x
verticalFix = 0.5
} else if (Math.abs(snapTopLeft.x - nodeTopLeft.x) < distance) {
verticalLeft = snapTopLeft.x
verticalFix = 0
} else if (Math.abs(snapTopLeft.x - nodeBottomRight.x) < distance) {
verticalLeft = snapTopLeft.x
verticalFix = 1
} else if (Math.abs(snapBottomRight.x - nodeBottomRight.x) < distance) {
verticalLeft = snapBottomRight.x
verticalFix = 1
} else if (Math.abs(snapBottomRight.x - nodeTopLeft.x) < distance) {
verticalLeft = snapBottomRight.x
}
if (verticalLeft != null) {
verticalTop = Math.min(nodeBBoxRotated.y, snapBBox.y)
verticalHeight =
Math.max(nodeBottomRight.y, snapBottomRight.y) - verticalTop
}
}
if (horizontalTop == null) {
if (Math.abs(snapCenter.y - nodeCenter.y) < distance) {
horizontalTop = snapCenter.y
horizontalFix = 0.5
} else if (Math.abs(snapTopLeft.y - nodeTopLeft.y) < distance) {
horizontalTop = snapTopLeft.y
} else if (Math.abs(snapTopLeft.y - nodeBottomRight.y) < distance) {
horizontalTop = snapTopLeft.y
horizontalFix = 1
} else if (Math.abs(snapBottomRight.y - nodeBottomRight.y) < distance) {
horizontalTop = snapBottomRight.y
horizontalFix = 1
} else if (Math.abs(snapBottomRight.y - nodeTopLeft.y) < distance) {
horizontalTop = snapBottomRight.y
}
if (horizontalTop != null) {
horizontalLeft = Math.min(nodeBBoxRotated.x, snapBBox.x)
horizontalWidth =
Math.max(nodeBottomRight.x, snapBottomRight.x) - horizontalLeft
}
}
return verticalLeft != null && horizontalTop != null
})
this.hide()
if (horizontalTop != null || verticalLeft != null) {
if (horizontalTop != null) {
nodeBBoxRotated.y =
horizontalTop - horizontalFix * nodeBBoxRotated.height
}
if (verticalLeft != null) {
nodeBBoxRotated.x = verticalLeft - verticalFix * nodeBBoxRotated.width
}
const newCenter = nodeBBoxRotated.getCenter()
const newX = newCenter.x - cellBBox.width / 2
const newY = newCenter.y - cellBBox.height / 2
const dx = newX - position.x
const dy = newY - position.y
if (dx !== 0 || dy !== 0) {
node.translate(dx, dy, {
snapped: true,
restrict: this.getRestrictArea(targetView),
})
if (horizontalWidth) {
horizontalWidth += dx
}
if (verticalHeight) {
verticalHeight += dy
}
}
this.update({
verticalLeft,
verticalTop,
verticalHeight,
horizontalTop,
horizontalLeft,
horizontalWidth,
})
}
}
protected isIgnored(snapNode: Node, targetNode: Node) {
return (
targetNode.id === snapNode.id ||
targetNode.isDescendantOf(snapNode) ||
this.filterShapes[targetNode.shape] ||
this.filterCells[targetNode.id] ||
(this.filterFunction &&
FunctionExt.call(this.filterFunction, this.graph, targetNode))
)
}
protected update(metadata: {
verticalLeft?: number
verticalTop?: number
verticalHeight?: number
horizontalTop?: number
horizontalLeft?: number
horizontalWidth?: number
}) {
// https://en.wikipedia.org/wiki/Transformation_matrix#Affine_transformations
if (metadata.horizontalTop) {
const start = this.graph.localToGraph(
new Point(metadata.horizontalLeft, metadata.horizontalTop),
)
const end = this.graph.localToGraph(
new Point(
metadata.horizontalLeft! + metadata.horizontalWidth!,
metadata.horizontalTop,
),
)
this.horizontal.setAttributes({
x1: this.options.sharp ? `${start.x}` : '0',
y1: `${start.y}`,
x2: this.options.sharp ? `${end.x}` : '100%',
y2: `${end.y}`,
display: 'inherit',
})
} else {
this.horizontal.setAttribute('display', 'none')
}
if (metadata.verticalLeft) {
const start = this.graph.localToGraph(
new Point(metadata.verticalLeft, metadata.verticalTop),
)
const end = this.graph.localToGraph(
new Point(
metadata.verticalLeft,
metadata.verticalTop! + metadata.verticalHeight!,
),
)
this.vertical.setAttributes({
x1: `${start.x}`,
y1: this.options.sharp ? `${start.y}` : '0',
x2: `${end.x}`,
y2: this.options.sharp ? `${end.y}` : '100%',
display: 'inherit',
})
} else {
this.vertical.setAttribute('display', 'none')
}
this.show()
}
protected resetTimer() {
if (this.timer) {
clearTimeout(this.timer)
this.timer = null
}
}
show() {
this.resetTimer()
if (this.container.parentNode == null) {
this.graph.container.appendChild(this.container)
}
return this
}
hide() {
this.resetTimer()
this.vertical.setAttribute('display', 'none')
this.horizontal.setAttribute('display', 'none')
const clean = this.options.clean
const delay = typeof clean === 'number' ? clean : clean !== false ? 3000 : 0
if (delay > 0) {
this.timer = window.setTimeout(() => {
if (this.container.parentNode !== null) {
this.unmount()
}
}, delay)
}
return this
}
protected onRemove() {
this.stopListening()
this.hide()
}
@View.dispose()
dispose() {
this.remove()
}
}
export namespace SnaplineImpl {
export interface Options {
enabled?: boolean
className?: string
tolerance?: number
sharp?: boolean
/**
* Specify if snap on node resizing or not.
*/
resizing?: boolean
clean?: boolean | number
filter?: Filter
}
export type Filter = null | (string | { id: string })[] | FilterFunction
export type FilterFunction = (this: Graph, node: Node) => boolean
}

View File

@ -0,0 +1,20 @@
/* eslint-disable */
/**
* Auto generated file, do not modify it!
*/
export const content = `.x6-widget-snapline {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
pointer-events: none;
}
.x6-widget-snapline-vertical,
.x6-widget-snapline-horizontal {
stroke: #2ecc71;
stroke-width: 1px;
}
`

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-vue-shape",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.20",
"description": "X6 shape for rendering vue components.",
"main": "lib/index.js",
"module": "es/index.js",
@ -110,7 +110,7 @@
"repository": {
"type": "git",
"url": "ssh://git@github.com/antvis/x6.git",
"directory": "packages/x6-react-shape"
"directory": "packages/x6-vue-shape"
},
"publishConfig": {
"access": "public",

View File

@ -1 +0,0 @@
export * from './src'

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6",
"version": "2.0.6-beta.16",
"version": "2.0.6-beta.21",
"description": "JavaScript diagramming library that uses SVG and HTML for rendering.",
"main": "lib/index.js",
"module": "es/index.js",
@ -65,8 +65,8 @@
"@antv/x6-package-json/rollup.json"
],
"dependencies": {
"@antv/x6-common": "^2.0.6-beta.16",
"@antv/x6-geometry": "^2.0.6-beta.16"
"@antv/x6-common": "^2.0.6-beta.18",
"@antv/x6-geometry": "^2.0.6-beta.18"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^20.0.0",

View File

@ -3,7 +3,7 @@ import config from '../../configs/rollup-config'
export default config({
output: [
{
name: 'X6Next',
name: 'X6',
format: 'umd',
file: 'dist/x6.js',
sourcemap: true,

View File

@ -1,6 +1,6 @@
// import { Rectangle, Ellipse, Polyline, Path } from '../../geometry'
import { Util } from '../../src/util'
import { Vector } from '@antv/x6-common'
import { Util } from '../../util'
describe('Util', () => {
const fixture = document.createElement('div')

View File

@ -113,14 +113,27 @@ export class MouseWheel extends Base {
targetScale = NumberExt.clamp(targetScale, minScale, maxScale)
if (targetScale !== currentScale) {
if (this.widgetOptions.zoomAtMousePosition) {
const origin = this.graph.coord.clientToGraphPoint(this.startPos)
this.graph.zoom(targetScale, {
absolute: true,
center: origin.clone(),
})
const scroller = this.graph.getPlugin('scroller') as any
if (scroller) {
if (this.widgetOptions.zoomAtMousePosition) {
const origin = this.graph.coord.clientToLocalPoint(this.startPos)
scroller.zoom(targetScale, {
absolute: true,
center: origin.clone(),
})
} else {
scroller.zoom(targetScale, { absolute: true })
}
} else {
this.graph.zoom(targetScale, { absolute: true })
if (this.widgetOptions.zoomAtMousePosition) {
const origin = this.graph.coord.clientToGraphPoint(this.startPos)
this.graph.zoom(targetScale, {
absolute: true,
center: origin.clone(),
})
} else {
this.graph.zoom(targetScale, { absolute: true })
}
}
}
this.currentScale = null

View File

@ -51,6 +51,7 @@ export namespace Options {
guard: (e: Dom.EventObject, view?: CellView | null) => boolean
onPortRendered?: (args: OnPortRenderedArgs) => void
onEdgeLabelRendered?: (args: OnEdgeLabelRenderedArgs) => void
}
export interface ManualBooleans {
@ -375,6 +376,13 @@ export namespace Options {
contentContainer: Element
contentSelectors?: Markup.Selectors
}
export interface OnEdgeLabelRenderedArgs {
edge: Edge
label: Edge.Label
container: Element
selectors: Markup.Selectors
}
}
export namespace Options {

View File

@ -65,8 +65,12 @@ export class PanningManager extends Base {
}
protected preparePanning({ e }: { e: Dom.MouseDownEvent }) {
// todo 暂时删除 selection 的判断
if (this.allowPanning(e, true)) {
const selection = this.graph.getPlugin('selection') as any
const allowRubberband = selection && selection.allowRubberband(e, true)
if (
this.allowPanning(e, true) ||
(this.allowPanning(e) && !allowRubberband)
) {
this.startPanning(e)
}
}

View File

@ -2,11 +2,27 @@ import { SizeSensor } from '@antv/x6-common'
import { Base } from './base'
export class SizeManager extends Base {
private getScroller() {
const scroller = this.graph.getPlugin('scroller') as any
if (scroller && scroller.options.enabled) {
return scroller
}
return null
}
private getContainer() {
const scroller = this.getScroller()
if (scroller) {
return scroller.container.parentElement
}
return this.graph.container.parentElement
}
private getSensorTarget() {
const autoResize = this.options.autoResize
if (autoResize) {
if (typeof autoResize === 'boolean') {
return this.graph.container.parentElement
return this.getContainer()
}
return autoResize as HTMLElement
}
@ -27,7 +43,12 @@ export class SizeManager extends Base {
}
resize(width?: number, height?: number) {
this.graph.transform.resize(width, height)
const scroller = this.getScroller()
if (scroller) {
scroller.resize(width, height)
} else {
this.graph.transform.resize(width, height)
}
}
@Base.dispose()

View File

@ -397,7 +397,8 @@ export class TransformManager extends Base {
}
getContentArea(options: TransformManager.GetContentAreaOptions = {}) {
if (options.useCellGeometry) {
// use geometry calc default
if (options.useCellGeometry !== false) {
return this.model.getAllCellsBBox() || new Rectangle()
}

View File

@ -69,6 +69,7 @@ export namespace Boundary {
name: 'boundary',
tagName: 'rect',
padding: 10,
useCellGeometry: true,
attrs: {
fill: 'none',
stroke: '#333',

View File

@ -154,6 +154,7 @@ export namespace Button {
export namespace Button {
Button.config<Button.Options>({
name: 'button',
useCellGeometry: true,
events: {
mousedown: 'onMouseDown',
touchstart: 'onMouseDown',
@ -188,6 +189,7 @@ export namespace Button {
],
distance: 60,
offset: 0,
useCellGeometry: true,
onClick({ view, btn }) {
btn.parent.remove()
view.cell.remove({ ui: true, toolId: btn.cid })

View File

@ -5,8 +5,9 @@ export interface Job {
}
export enum JOB_PRIORITY {
Manual = 1,
Render = 2,
RenderEdge = 1,
RenderNode = 2,
Update = 3,
}
let isFlushing = false
@ -106,7 +107,7 @@ function flushJobs() {
function findInsertionIndex(job: Job) {
let start = 0
while (queue[start] && queue[start].priority <= job.priority) {
while (queue[start] && queue[start].priority >= job.priority) {
start += 1
}
return start

View File

@ -14,11 +14,7 @@ export class Renderer extends Base {
}
isViewMounted(view: CellView) {
if (view == null) {
return false
}
return true // todo
return this.schedule.isViewMounted(view)
}
setRenderArea(area?: Rectangle) {

View File

@ -1,4 +1,4 @@
import { KeyValue, Dom } from '@antv/x6-common'
import { KeyValue, Dom, Disposable } from '@antv/x6-common'
import { Rectangle } from '@antv/x6-geometry'
import { Model, Cell } from '../model'
import { View, CellView, NodeView, EdgeView } from '../view'
@ -6,7 +6,7 @@ import { queueJob, queueFlush, clearJobs, JOB_PRIORITY } from './queueJob'
import { FlagManager } from '../view/flag'
import { Graph } from '../graph'
export class Scheduler {
export class Scheduler extends Disposable {
public views: KeyValue<Scheduler.View> = {}
protected zPivots: KeyValue<Comment>
private graph: Graph
@ -21,6 +21,7 @@ export class Scheduler {
}
constructor(graph: Graph) {
super()
this.graph = graph
this.init()
}
@ -74,7 +75,7 @@ export class Scheduler {
viewItem.view,
Scheduler.FLAG_INSERT,
options,
JOB_PRIORITY.Render,
JOB_PRIORITY.Update,
true,
)
}
@ -91,7 +92,7 @@ export class Scheduler {
view: CellView,
flag: number,
options: any = {},
priority: JOB_PRIORITY = JOB_PRIORITY.Manual,
priority: JOB_PRIORITY = JOB_PRIORITY.Update,
flush = true,
) {
const id = view.cell.id
@ -114,17 +115,9 @@ export class Scheduler {
const effectedEdges = this.getEffectedEdges(view)
effectedEdges.forEach((edge) => {
queueJob({
id: edge.id,
priority,
cb: () => {
this.renderViewInArea(edge.view, edge.flag, options)
},
})
this.requestViewUpdate(edge.view, edge.flag, options, priority, false)
})
viewItem.state = Scheduler.ViewState.REQUESTED
if (flush) {
queueFlush()
}
@ -135,6 +128,20 @@ export class Scheduler {
this.flushWaittingViews()
}
isViewMounted(view: CellView) {
if (view == null) {
return false
}
const viewItem = this.views[view.cell.id]
if (!viewItem) {
return false
}
return viewItem.state === Scheduler.ViewState.MOUNTED
}
protected renderViews(cells: Cell[], options: any = {}) {
cells.sort((c1, c2) => {
if (c1.isNode() && c2.isEdge()) {
@ -170,7 +177,7 @@ export class Scheduler {
viewItem.view,
flag,
options,
JOB_PRIORITY.Render,
cell.isNode() ? JOB_PRIORITY.RenderNode : JOB_PRIORITY.RenderEdge,
false,
)
})
@ -211,7 +218,10 @@ export class Scheduler {
const viewItem = this.views[ids[i]]
if (viewItem && viewItem.state === Scheduler.ViewState.WAITTING) {
const { view, flag, options } = viewItem
this.requestViewUpdate(view, flag, options, JOB_PRIORITY.Render, false)
const priority = view.cell.isNode()
? JOB_PRIORITY.RenderNode
: JOB_PRIORITY.RenderEdge
this.requestViewUpdate(view, flag, options, priority, false)
}
}
@ -405,6 +415,7 @@ export class Scheduler {
)
}
@Disposable.dispose()
dispose() {
this.stopListening()
}
@ -419,7 +430,6 @@ export namespace Scheduler {
export enum ViewState {
CREATED,
MOUNTED,
REQUESTED,
WAITTING,
}
export interface View {

View File

@ -165,23 +165,26 @@ export class EdgeView<
this.container.append(ret.fragment)
}
// protected customizeLabels() {
// if (this.containers.labels) {
// const edge = this.cell
// const labels = edge.labels
// for (let i = 0, n = labels.length; i < n; i += 1) {
// const label = labels[i]
// const container = this.labelCache[i]
// const selectors = this.labelSelectors[i]
// this.graph.hook.onEdgeLabelRendered({
// edge,
// label,
// container,
// selectors,
// })
// }
// }
// }
protected customizeLabels() {
if (this.labelContainer) {
const edge = this.cell
const labels = edge.labels
for (let i = 0, n = labels.length; i < n; i += 1) {
const label = labels[i]
const container = this.labelCache[i]
const selectors = this.labelSelectors[i]
const onEdgeLabelRendered = this.graph.options.onEdgeLabelRendered
if (onEdgeLabelRendered) {
onEdgeLabelRendered({
edge,
label,
container,
selectors,
})
}
}
}
}
protected renderLabels() {
const edge = this.cell
@ -245,8 +248,7 @@ export class EdgeView<
}
this.updateLabels()
// todo
// this.customizeLabels()
this.customizeLabels()
return this
}

View File

@ -945,16 +945,15 @@ export class NodeView<
y: number,
cell: Cell,
) {
const cells = [cell]
let cells = [cell]
// todo
// const selection = this.graph.selection.widget
// if (selection && selection.options.movable) {
// const selectedCells = this.graph.getSelectedCells()
// if (selectedCells.includes(cell)) {
// cells = selectedCells.filter((c: Cell) => c.isNode())
// }
// }
const selection = this.graph.getPlugin('selection') as any
if (selection && selection.isSelectionMovable()) {
const selectedCells = selection.getSelectedCells()
if (selectedCells.includes(cell)) {
cells = selectedCells.filter((c: Cell) => c.isNode())
}
}
cells.forEach((c: Cell) => {
this.notify(name, {
@ -1055,11 +1054,10 @@ export class NodeView<
// eslint-disable-next-line
protected autoScrollGraph(x: number, y: number) {
// todo
// const scroller = this.graph.scroller.widget
// if (scroller) {
// scroller.autoScroll(x, y)
// }
const scroller = this.graph.getPlugin('scroller') as any
if (scroller) {
scroller.autoScroll(x, y)
}
}
// #endregion

View File

@ -391,10 +391,15 @@ export namespace View {
}
if (Config.useCSSSelector) {
const validSelector = selector.includes('>')
? `:scope ${selector}`
: selector
return {
isCSSSelector: true,
// $(rootElem).find(selector).toArray() as Element[] todo
elems: Array.prototype.slice.call(rootElem.querySelectorAll(selector)),
// $(rootElem).find(selector).toArray() as Element[]
elems: Array.prototype.slice.call(
rootElem.querySelectorAll(validSelector),
),
}
}

View File

@ -19,6 +19,7 @@ lerna run build --stream --scope @antv/x6-vue-shape \
--scope @antv/x6-react-shape \
--scope @antv/x6-plugin-keyboard \
--scope @antv/x6-plugin-scroller \
--scope @antv/x6-plugin-selection
--scope @antv/x6-plugin-selection \
--scope @antv/x6-plugin-snapline
wait