Compare commits

...

2 Commits

Author SHA1 Message Date
12e4ac55d7 chore(release): 🚀 publish 2022-10-19 20:07:08 +08:00
294672b306 feat: add snapline plugin 2022-10-19 20:05:34 +08:00
22 changed files with 1154 additions and 16 deletions

View File

@ -34,7 +34,6 @@ export default class Example extends React.Component {
graph.addEdge({
source,
target,
tools: ['button-remove'],
})
}

View File

@ -48,6 +48,11 @@ const dataSource = [
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.19",
"version": "2.0.6-beta.20",
"npmClient": "yarn",
"useWorkspaces": true,
"command": {

View File

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-keyboard",
"version": "2.0.6-beta.18",
"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,6 +1,6 @@
{
"name": "@antv/x6-plugin-scroller",
"version": "2.0.6-beta.19",
"version": "2.0.6-beta.20",
"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

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-selection",
"version": "2.0.6-beta.19",
"version": "2.0.6-beta.20",
"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

@ -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,6 +1,6 @@
{
"name": "@antv/x6",
"version": "2.0.6-beta.18",
"version": "2.0.6-beta.20",
"description": "JavaScript diagramming library that uses SVG and HTML for rendering.",
"main": "lib/index.js",
"module": "es/index.js",

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

@ -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