Compare commits

..

46 Commits

Author SHA1 Message Date
2571cb4ceb chore: update license () 2023-02-27 16:19:16 +08:00
d83bcea863 chore: update contributors [skip ci] 2023-02-27 07:59:22 +00:00
38bdf65dde feat: support angular shape for 2.x ()
* feat: add angular shape

* docs: add angular shape docs

* docs: update docs

* fix: 修复无法在 shape 内部安装依赖的问题
2023-02-27 15:59:01 +08:00
f5db2bb415 chore: update contributors [skip ci] 2023-02-27 07:43:52 +00:00
a3aa2a0da2 fix: fix editor-tool for edge () 2023-02-27 15:43:29 +08:00
2bce01dee8 chore: update CONTRIBUTORS [skip ci] 2023-02-27 03:42:56 +00:00
a30b6632a0 feat: add view:mounted and view:unmounted event () 2023-02-27 11:41:22 +08:00
7656b38735 fix: cannot be invoked without new ()
fix Class constructor xx cannot be invoked without 'new'
2023-02-27 11:35:37 +08:00
d953a732b3 chore(release): release 1 package [skip ci]
[@antv/x6-sites@1.5.5](https://github.com/antvis/X6/releases/tag/%40antv/x6-sites%401.5.5)
2023-02-27 03:05:34 +00:00
6317493347 docs: optimize examples style for doc () 2023-02-27 10:54:45 +08:00
8fc0b72b66 chore: update contributors [skip ci] 2023-02-27 01:29:39 +00:00
18fca29bd7 chore: update contributors [skip ci] 2023-02-24 12:33:15 +00:00
7e61014b65 docs: set version list to 2.x and 1.x () 2023-02-24 20:32:48 +08:00
8dd2b66633 chore(release): release 3 packages [skip ci]
[@antv/x6-common@2.0.10](https://www.npmjs.com/package/@antv/x6-common/v/2.0.10)
[@antv/x6-common@2.0.10](https://github.com/antvis/X6/releases/tag/%40antv/x6-common%402.0.10)

[@antv/x6-plugin-transform@2.1.6](https://www.npmjs.com/package/@antv/x6-plugin-transform/v/2.1.6)
[@antv/x6-plugin-transform@2.1.6](https://github.com/antvis/X6/releases/tag/%40antv/x6-plugin-transform%402.1.6)

[@antv/x6-sites@1.5.4](https://github.com/antvis/X6/releases/tag/%40antv/x6-sites%401.5.4)
2023-02-24 08:56:41 +00:00
76fb1acf74 fix: add textLength & lengthAdjust to CASE_SENSITIVE_ATTR () 2023-02-24 16:45:35 +08:00
709a141e28 fix: transform active-handle class should remove when active removed ()
fix: transform
2023-02-24 16:42:49 +08:00
2ee81fd94b chore: update CONTRIBUTORS [skip ci] 2023-02-23 15:00:09 +00:00
054eb998fa chore: update contributors [skip ci] 2023-02-23 14:53:56 +00:00
e32227ae31 docs: fix warning display style () 2023-02-23 22:53:33 +08:00
278b798c0a chore: update CONTRIBUTORS [skip ci] 2023-02-23 13:37:37 +00:00
c4e11dc98f chore: update contributors [skip ci] 2023-02-23 13:29:51 +00:00
df8466aa33 doc: 修复graph文档 () 2023-02-23 21:29:31 +08:00
1b958d626f chore: release x6@2.5.1 () 2023-02-23 11:17:21 +08:00
946582c242 fix: add judgment for edge view () 2023-02-23 11:12:42 +08:00
482d77ad09 chore(release): release 1 package [skip ci]
[@antv/x6-sites@1.5.3](https://github.com/antvis/X6/releases/tag/%40antv/x6-sites%401.5.3)
2023-02-23 03:01:42 +00:00
3103c11991 chore: change docsearch appid () 2023-02-23 10:50:48 +08:00
3c9093f3ec chore: release 2 packages () 2023-02-23 10:50:29 +08:00
8a89caab27 chore: update contributors [skip ci] 2023-02-23 01:29:28 +00:00
ed999c630c docs: fix a broken link in model.zh.md () 2023-02-22 21:03:46 +08:00
34bcd12038 chore: update CONTRIBUTORS [skip ci] 2023-02-22 08:09:25 +00:00
9138978918 fix: leading reset render area when init ()
feat: refactor the virtual rendering logic
2023-02-22 16:02:44 +08:00
46f4c8ac2a chore: update CONTRIBUTORS [skip ci] 2023-02-21 07:09:05 +00:00
7519b11e66 chore: update contributors [skip ci] 2023-02-21 07:01:22 +00:00
5882b6a599 docs: fix typo in stencil.md () 2023-02-21 15:01:00 +08:00
9781fb48f5 chore: update CONTRIBUTORS [skip ci] 2023-02-20 07:21:00 +00:00
c8a03ed2ba chore: update contributors [skip ci] 2023-02-20 07:14:36 +00:00
b944419572 docs: fix typo in labels.zh.md () 2023-02-20 15:14:14 +08:00
bebc5652d1 chore: update contributors [skip ci] 2023-02-19 01:29:30 +00:00
8daa2c9b98 chore(release): release 3 packages [skip ci]
[@antv/x6-common@2.0.8](https://www.npmjs.com/package/@antv/x6-common/v/2.0.8)
[@antv/x6-common@2.0.8](https://github.com/antvis/X6/releases/tag/%40antv/x6-common%402.0.8)

[@antv/x6-example-features@2.1.1](https://github.com/antvis/X6/releases/tag/%40antv/x6-example-features%402.1.1)

[@antv/x6-sites@1.5.2](https://github.com/antvis/X6/releases/tag/%40antv/x6-sites%401.5.2)
2023-02-18 22:39:19 +00:00
c510756fe4 fix: mindmap demo duplicate node id, close () 2023-02-19 06:28:20 +08:00
9b4fa86daa fix: fix typo for dom event handlers () 2023-02-19 06:28:02 +08:00
7e86ba90d6 chore: update contributors [skip ci] 2023-02-18 01:29:04 +00:00
f27bec6bb8 chore: update CONTRIBUTORS [skip ci] 2023-02-17 15:37:04 +00:00
c38006a358 docs: history插件文档补充stackSize属性的说明 () 2023-02-17 23:28:02 +08:00
5f2783aade chore(release): release 2 packages [skip ci]
[@antv/x6-example-features@2.1.0](https://github.com/antvis/X6/releases/tag/%40antv/x6-example-features%402.1.0)

[@antv/x6-plugin-history@2.2.0](https://www.npmjs.com/package/@antv/x6-plugin-history/v/2.2.0)
[@antv/x6-plugin-history@2.2.0](https://github.com/antvis/X6/releases/tag/%40antv/x6-plugin-history%402.2.0)
2023-02-17 13:57:56 +00:00
fba531064a feat: history add max stack size ()
* feat: init

* feat: add demo

* feat: add demo

* feat: add demo

* feat: limit undoStack size

* feat: 0 means not limit

---------

Co-authored-by: lijianqiang.seven <lijianqiang.seven@bytedance.com>
Co-authored-by: zhangzirui.1993 <zhangzirui.1993@bytedance.com>
2023-02-17 21:46:59 +08:00
79 changed files with 45054 additions and 18556 deletions
CONTRIBUTORSCONTRIBUTORS.svg
examples/x6-example-features
package.json
packages
x6-angular-shape
x6-common
x6-geometry
x6-plugin-clipboard
x6-plugin-dnd
x6-plugin-export
x6-plugin-history
x6-plugin-keyboard
x6-plugin-minimap
x6-plugin-scroller
x6-plugin-selection
x6-plugin-snapline
x6-plugin-stencil
x6-plugin-transform
x6-react-components
x6-react-shape
x6-vue-shape
x6
pnpm-lock.yaml
sites/x6-sites
tsconfig.json

@ -8,12 +8,14 @@ Draco <Draco.coder@gmail.com>
Eve-Sama <17764594863@163.com>
Eve-Sama <948832626@qq.com>
Gossypol <31892817+gossypol@users.noreply.github.com>
HQidea <HQidea@users.noreply.github.com>
ImgBotApp <ImgBotHelp@gmail.com>
Indomi <indomi126@gmail.com>
James Tsang <wtzeng1@gmail.com>
Jógvan Olsen <jogvanolsen@hotmail.com>
Ken Geis <geis.ken@gmail.com>
Kent Wood <minzojian@hotmail.com>
Ko.Rei <32183014+Ko-Rei@users.noreply.github.com>
Limbo <49612796+JUST-Limbo@users.noreply.github.com>
Lixu <37231473+wflixu@users.noreply.github.com>
Lloyd Zhou <lloydzhou@users.noreply.github.com>
@ -57,6 +59,8 @@ kelin.zrh <34393362+AricZhu@users.noreply.github.com>
kingshuaishuai <ken.wang@mrs.ai>
kio <1421104933@qq.com>
lijing666 <lijing241@yeah.net>
linkun <33945539+linkun-wang@users.noreply.github.com>
linkun <linkun0922@163.com>
lopn <lopnxrp@126.com>
luchunwei <luchunwei@gmail.com>
luzhuang <364439895@qq.com>
@ -66,6 +70,7 @@ newbyvector <vectorse@126.com>
niexq <1879633916@qq.com>
niexq <niexq@firstgrid.cn>
njshuisheng <34205271+njshuisheng@users.noreply.github.com>
nobugforever <84232410+mengYu-Jin@users.noreply.github.com>
pengxingjian.pxj <pengxingjian.pxj@alibaba-inc.com>
pfdgithub <pfdgithub@users.noreply.github.com>
qingchi <qinky94@163.com>
@ -78,6 +83,7 @@ wenbei <38773084+wb-wenbei@users.noreply.github.com>
wgf <34190465+evelope@users.noreply.github.com>
wind X <35559153+XueMeijing@users.noreply.github.com>
wjqsummer <52412389+wjqsummer@users.noreply.github.com>
wseven7677 <caoyu_92@126.com>
wtzeng1 <wtzeng1@gmail.com>
x6-bot <x6-bot@users.noreply.github.com>
xrkffgg <xrkffgg@gmail.com>
@ -91,6 +97,7 @@ zdc1111 <39116292+zdc1111@users.noreply.github.com>
小耀 <jinyue.gjy@antfin.com>
崖 <bubkoo.wy@gmail.com>
崖崖崖 <bubkoo.wy@gmail.com>
张子睿 <411489774@qq.com>
文瑀 <wenyu.jqq@antfin.com>
映月 <38279397+orientMoon@users.noreply.github.com>
杨凌 <89915256@qq.com>

File diff suppressed because one or more lines are too long

Before

(image error) Size: 13 MiB

After

(image error) Size: 14 MiB

@ -1,3 +1,17 @@
## @antv/x6-example-features [2.1.1](https://github.com/antvis/X6/compare/@antv/x6-example-features@2.1.0...@antv/x6-example-features@2.1.1) (2023-02-18)
### Bug Fixes
* mindmap demo duplicate node id, close [#3256](https://github.com/antvis/X6/issues/3256) ([#3257](https://github.com/antvis/X6/issues/3257)) ([c510756](https://github.com/antvis/X6/commit/c510756fe4e96c8e7471c2fb558e6019ec69b057))
# @antv/x6-example-features [2.1.0](https://github.com/antvis/X6/compare/@antv/x6-example-features@2.0.3...@antv/x6-example-features@2.1.0) (2023-02-17)
### Features
* history add max stack size ([#3253](https://github.com/antvis/X6/issues/3253)) ([fba5310](https://github.com/antvis/X6/commit/fba531064ad8027c451a81b60d5efd7f7314a0fa))
## @antv/x6-example-features [2.0.2](https://github.com/antvis/X6/compare/@antv/x6-example-features@2.0.1...@antv/x6-example-features@2.0.2) (2023-01-17)

@ -1,7 +1,7 @@
{
"private": true,
"name": "@antv/x6-example-features",
"version": "2.0.2",
"version": "2.1.1",
"scripts": {
"start": "umi dev",
"build": "umi build",

@ -309,9 +309,14 @@ export default class Example extends React.Component {
if (dataItem) {
let item: MindMapData | null = null
const length = dataItem.children ? dataItem.children.length : 0
let nid = `${id}-${length + 1}`
if (graph.hasCell(nid)) {
// 如果通过length + 1拼接出来的节点id在画布中存在了就在id后面加上随机数
nid = nid + Math.random()
}
if (type === 'topic') {
item = {
id: `${id}-${length + 1}`,
id: nid,
type: 'topic-branch',
label: `分支主题${length + 1}`,
width: 100,
@ -319,7 +324,7 @@ export default class Example extends React.Component {
}
} else if (type === 'topic-branch') {
item = {
id: `${id}-${length + 1}`,
id: nid,
type: 'topic-child',
label: `子主题${length + 1}`,
width: 60,

@ -0,0 +1,104 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Keyboard } from '@antv/x6-plugin-keyboard'
import { Selection } from '@antv/x6-plugin-selection'
import { History } from '@antv/x6-plugin-history'
import '../index.less'
export default class Example extends React.Component<
{},
{ graph: Graph | undefined }
> {
private container: HTMLDivElement
componentDidMount() {
const graph = new Graph({
container: this.container,
width: 800,
height: 600,
grid: true,
})
this.setState({ graph })
const selection = new Selection({ enabled: true })
const keyboard = new Keyboard({ enabled: true })
const history = new History({ enabled: true, stackSize: 5 })
graph.use(selection)
graph.use(keyboard)
graph.use(history)
graph.addNode({
x: 50,
y: 50,
width: 100,
height: 40,
attrs: { label: { text: 'A' } },
})
graph.addNode({
x: 250,
y: 50,
width: 100,
height: 40,
attrs: { label: { text: 'B' } },
})
graph.addNode({
x: 350,
y: 150,
width: 100,
height: 40,
attrs: { label: { text: 'C' } },
})
keyboard.bindKey('backspace', () => {
graph.removeCells(selection.getSelectedCells())
})
keyboard.bindKey('command+z', () => {
this.undo()
})
keyboard.bindKey('command+shift+z', () => {
this.redo()
})
}
refContainer = (container: HTMLDivElement) => {
this.container = container
}
enablePlugins = () => {
const { graph } = this.state
graph?.enablePlugins('keyboard')
}
disablePlugins = () => {
const { graph } = this.state
graph?.disablePlugins('keyboard')
}
undo = () => {
const { graph } = this.state
const history = graph?.getPlugin('history') as History
history?.undo()
}
redo = () => {
const { graph } = this.state
const history = graph?.getPlugin('history') as History
history?.redo()
}
render() {
return (
<div className="x6-graph-wrap">
<div ref={this.refContainer} className="x6-graph" />
<button onClick={this.enablePlugins}>enable</button>
<button onClick={this.disablePlugins}>disable</button>
<button onClick={this.undo}>undo</button>
<button onClick={this.redo}>redo</button>
</div>
)
}
}

@ -179,6 +179,10 @@ const dataSource = [
example: 'animation/transition',
description: '动画',
},
{
example: 'history',
description: '时光回溯',
},
].map((item, index) => ({ key: index, ...item }))
const columns = [

@ -51,7 +51,7 @@
"prettier --write --ignore-unknown"
],
"*.less": [
"stylelint --syntax less --fix"
"stylelint --customSyntax postcss-less --fix"
],
"*.js": [
"prettier --write"

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
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.

@ -0,0 +1,136 @@
# @antv/x6-angular-shape
## 渲染节点
我们提供了一个独立的包 `@antv/x6-angular-shape` 以支持将 Angular 的组件/模板作为节点进行渲染。
### Component 渲染
```ts
@Component({
selector: 'app-node',
templateUrl: './node.component.html',
styleUrls: ['./node.component.scss'],
})
export class NodeComponent implements AfterViewInit, OnChanges {
@Input() value: string;
}
```
```ts
import { register } from "@antv/x6-angular-shape";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
ngAfterViewInit(): void {
register({
shape: 'custom-angular-component-node',
width: 120,
height: 20,
content: NodeComponent,
injector: this.injector,
});
this.graph.addNode({
shape: 'custom-angular-component-node',
x: 100,
y: 100,
data: {
// Input 的参数必须放在这里
ngArguments: {
value: '糟糕糟糕 Oh my god',
},
},
});
}
}
```
### TemplateRef 渲染
```html
<ng-template #template let-data="ngArguments">
<section class="template-container">
<span class="value">{{ data.value }}</span>
</section>
</ng-template>
```
```ts
import { register } from "@antv/x6-angular-shape";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
@ViewChild('template') template: TemplateRef<{}>;
ngAfterViewInit(): void {
register({
shape: 'custom-angular-template-node',
width: 120,
height: 20,
content: this.template,
injector: this.injector,
});
this.graph.addNode({
shape: 'custom-angular-template-node',
x: 100,
y: 100,
data: {
ngArguments: {
value: '魔法怎么失灵啦',
},
},
});
}
}
```
## 更新节点
无论是使用 Component 还是 TemplateRef, 都是一样的更新方式.
```ts
node.setData({
ngArguments: {
value: '晚风中闪过 几帧从前啊',
},
});
```
## 有 demo 吗?
有的, 因为X6渲染节点部分与框架是解耦的. 因此 `x6-angular-shape` 包并非是直接在源代码里改的, 而是在一个单独的Angular环境中开发的. 该 demo 还提供了多种节点类型的性能测试, 详情请参考[Eve-Sama/x6-angular-shape](https://github.com/Eve-Sama/x6-angular-shape)、[X6与G6的性能对比, 以及X6多节点类型下的FPS临界点讨论](https://github.com/antvis/X6/issues/3266)
## FAQ
### 为什么输入属性不能直接放在 data 中而需要放在 ngArguments 中? 且为什么不叫 ngInput?
因为并非所有 `node.data` 中的属性都是输入属性, 所以遍历 `data` 中的所有属性进行赋值是不合适的. 至于为什么叫 `ngArguments` 主要是有两点考虑.
- 1.x版本中已经这么用了, 沿用该API可以降低用户升级成本
- `Input` 的概念其实是来自 `Component`, 而 `TemplateRef` 中是 `context`, 在二者的基础上抽象一个 `Arguments` 的概念更通用些
### 2.x版本的 x6-angular-shape 相比较1.x版本有什么新特性吗?
实现思路其实和之前是差不多的. 但是确实有几个点值得一提.
#### demo更聚焦
1.x版本的 demo 除了渲染组件外, 还写了连线、清除等一系列案例. 看似扩展, 实则眼花缭乱. 作为 `x6-angular-shape`的 demo, 2.x版本更加聚焦, 更加聚焦于shape的使用与性能测试等, 与插件无关的内容请查看X6官网.
#### 功能更稳定
在1.x版本中, 虽然实现了功能, 但是使用场景的思考不够全面. 比如`ngArguments`的变化, 对 `TemplateRef`的场景并不生效. 虽然对`Component`生效但是无法触发`ngOnChanges`. 而在新版本中, 这些问题都将不复存在.
### 版本要求
你的Angular版本至少在14及以上才可以. 14以下需要用 hack 的方式实现一些特性, 比较麻烦. 暂时不提供, 如有需要可提issue, 我再专门介绍下如何实现.

@ -0,0 +1,65 @@
{
"name": "@antv/x6-angular-shape",
"version": "2.0.0",
"description": "X6 shape for rendering angular components.",
"main": "lib/index.js",
"module": "es/index.js",
"unpkg": "dist/index.js",
"jsdelivr": "dist/index.js",
"types": "lib/index.d.ts",
"files": [
"dist",
"es",
"lib"
],
"keywords": [
"shape",
"angular",
"render",
"x6",
"antv"
],
"scripts": {
"clean:turbo": "rss",
"clean:build": "rss",
"clean:coverage": "rss",
"clean": "rss",
"build:esm": "rss",
"build:cjs": "rss",
"build:umd": "rss",
"build:dev": "rss",
"build:watch": "rss",
"build:watch:esm": "rss",
"build:watch:cjs": "rss",
"build": "rss",
"prebuild": "rss",
"test": "rss",
"coveralls": "rss",
"pretest": "rss"
},
"peerDependencies": {
"@antv/x6": "^2.x",
"@angular/core": ">= 14"
},
"devDependencies": {
"@antv/x6": "^2.x"
},
"author": {
"name": "Eve-Sama",
"email": "948832626@qq.com"
},
"license": "MIT",
"homepage": "https://x6.antv.antgroup.com/tutorial/intermediate/angular",
"bugs": {
"url": "https://github.com/antvis/x6/issues"
},
"repository": {
"type": "git",
"url": "https://github.com/antvis/x6.git",
"directory": "packages/x6-angular-shape"
},
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
}
}

@ -0,0 +1,3 @@
export * from './node'
export * from './view'
export * from './registry'

@ -0,0 +1,102 @@
import { Node, Markup, ObjectExt } from '@antv/x6'
export class AngularShape<
Properties extends AngularShape.Properties = AngularShape.Properties,
> extends Node<Properties> {}
export namespace AngularShape {
export type Primer =
| 'rect'
| 'circle'
| 'path'
| 'ellipse'
| 'polygon'
| 'polyline'
export interface Properties extends Node.Properties {
primer?: Primer
}
}
export namespace AngularShape {
function getMarkup(primer?: Primer) {
const markup: Markup.JSONMarkup[] = []
const content = Markup.getForeignObjectMarkup()
if (primer) {
markup.push(
...[
{
tagName: primer,
selector: 'body',
},
content,
],
)
} else {
markup.push(content)
}
return markup
}
AngularShape.config<Properties>({
view: 'angular-shape-view',
markup: getMarkup(),
attrs: {
body: {
fill: 'none',
stroke: 'none',
refWidth: '100%',
refHeight: '100%',
},
fo: {
refWidth: '100%',
refHeight: '100%',
},
},
propHooks(metadata: Properties) {
if (metadata.markup == null) {
const primer = metadata.primer
if (primer) {
metadata.markup = getMarkup(primer)
let attrs = {}
switch (primer) {
case 'circle':
attrs = {
refCx: '50%',
refCy: '50%',
refR: '50%',
}
break
case 'ellipse':
attrs = {
refCx: '50%',
refCy: '50%',
refRx: '50%',
refRy: '50%',
}
break
default:
break
}
metadata.attrs = ObjectExt.merge(
{},
{
body: {
refWidth: null,
refHeight: null,
...attrs,
},
},
metadata.attrs || {},
)
}
}
return metadata
},
})
Node.registry.register('angular-shape', AngularShape, true)
}

@ -0,0 +1,32 @@
import { Injector, TemplateRef, Type } from '@angular/core'
import { Graph, Node } from '@antv/x6'
export type Content = TemplateRef<any> | Type<any>
export type AngularShapeConfig = Node.Properties & {
shape: string
injector: Injector
content: Content
}
export const registerInfo: Map<
string,
{
injector: Injector
content: Content
}
> = new Map()
export function register(config: AngularShapeConfig) {
const { shape, injector, content, ...others } = config
registerInfo.set(shape, { injector, content })
Graph.registerNode(
shape,
{
inherit: 'angular-shape',
...others,
},
true,
)
}

@ -0,0 +1,124 @@
import {
ComponentRef,
EmbeddedViewRef,
TemplateRef,
ViewContainerRef,
} from '@angular/core'
import { Dom, NodeView } from '@antv/x6'
import { AngularShape } from './node'
import { Content, registerInfo } from './registry'
export class AngularShapeView extends NodeView<AngularShape> {
getNodeContainer(): HTMLDivElement {
return this.selectors && (this.selectors.foContent as HTMLDivElement)
}
override confirmUpdate(flag: number): number {
const ret = super.confirmUpdate(flag)
return this.handleAction(ret, AngularShapeView.action, () =>
this.renderAngularContent(),
)
}
private getNgArguments(): Record<string, any> {
const input = (this.cell.data?.ngArguments as Record<string, any>) || {}
return input
}
/** 当执行 node.setData() 时需要对实例设置新的输入值 */
private setInstanceInput(
content: Content,
ref: EmbeddedViewRef<any> | ComponentRef<any>,
): void {
const ngArguments = this.getNgArguments()
if (content instanceof TemplateRef) {
const embeddedViewRef = ref as EmbeddedViewRef<any>
embeddedViewRef.context = { ngArguments }
} else {
const componentRef = ref as ComponentRef<any>
Object.keys(ngArguments).forEach((v) =>
componentRef.setInput(v, ngArguments[v]),
)
componentRef.changeDetectorRef.detectChanges()
}
}
protected renderAngularContent(): void {
this.unmountAngularContent()
const container = this.getNodeContainer()
if (container) {
const node = this.cell
const { injector, content } = registerInfo.get(node.shape)!
const viewContainerRef = injector.get(ViewContainerRef)
if (content instanceof TemplateRef) {
const ngArguments = this.getNgArguments()
const embeddedViewRef = viewContainerRef.createEmbeddedView(content, {
ngArguments,
})
embeddedViewRef.rootNodes.forEach((node) => container.appendChild(node))
embeddedViewRef.detectChanges()
node.on('change:data', () =>
this.setInstanceInput(content, embeddedViewRef),
)
} else {
const componentRef = viewContainerRef.createComponent(content)
const insertNode = (componentRef.hostView as EmbeddedViewRef<any>)
.rootNodes[0] as HTMLElement
container.appendChild(insertNode)
this.setInstanceInput(content, componentRef)
node.on('change:data', () =>
this.setInstanceInput(content, componentRef),
)
}
}
}
protected unmountAngularContent(): HTMLDivElement {
const container = this.getNodeContainer()
container.innerHTML = ''
return container
}
override onMouseDown(e: Dom.MouseDownEvent, x: number, y: number) {
const target = e.target as Element
const tagName = target.tagName.toLowerCase()
if (tagName === 'input') {
const type = target.getAttribute('type')
if (
type == null ||
[
'text',
'password',
'number',
'email',
'search',
'tel',
'url',
].includes(type)
) {
return
}
}
super.onMouseDown(e, x, y)
}
override unmount(): this {
this.unmountAngularContent()
super.unmount()
return this
}
}
export namespace AngularShapeView {
export const action = 'angular' as any
AngularShapeView.config({
bootstrap: [action],
actions: {
component: action,
},
})
NodeView.registry.register('angular-shape-view', AngularShapeView, true)
}

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

@ -1,3 +1,17 @@
## @antv/x6-common [2.0.10](https://github.com/antvis/x6/compare/@antv/x6-common@2.0.9...@antv/x6-common@2.0.10) (2023-02-24)
### Bug Fixes
* add textLength & lengthAdjust to CASE_SENSITIVE_ATTR ([#3281](https://github.com/antvis/x6/issues/3281)) ([76fb1ac](https://github.com/antvis/x6/commit/76fb1acf74b0f1c308f7c824d02c12244b7ac8f3))
## @antv/x6-common [2.0.8](https://github.com/antvis/x6/compare/@antv/x6-common@2.0.7...@antv/x6-common@2.0.8) (2023-02-18)
### Bug Fixes
* fix typo for dom event handlers ([#3255](https://github.com/antvis/x6/issues/3255)) ([9b4fa86](https://github.com/antvis/x6/commit/9b4fa86daa587fe8818f3615bc1e40738a0f2319))
## @antv/x6-common [2.0.6](https://github.com/antvis/x6/compare/@antv/x6-common@2.0.5...@antv/x6-common@2.0.6) (2023-01-31)

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

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

@ -6,6 +6,8 @@ export const CASE_SENSITIVE_ATTR = [
'attributeName',
'attributeType',
'repeatCount',
'textLength',
'lengthAdjust',
]
export type Attributes = { [key: string]: string | number | null | undefined }

@ -729,7 +729,7 @@ type TypeEventHandlersBase<TDelegateTarget, TData, TCurrentTarget, TTarget> = {
TCurrentTarget,
TTarget
>]?:
| TypeEventHandler<TDelegateTarget, TData, TCurrentTarget, TTarget, TType>
| TypeEventHandler<TDelegateTarget, TData, TCurrentTarget, TTarget, string>
| false
| Record<string, unknown>
}

@ -1,4 +1,4 @@
export { debounce } from 'lodash-es'
export { debounce, throttle } from 'lodash-es'
type Fn = (...args: any[]) => any

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,3 +1,10 @@
# @antv/x6-plugin-history [2.2.0](https://github.com/antvis/x6/compare/@antv/x6-plugin-history@2.1.3...@antv/x6-plugin-history@2.2.0) (2023-02-17)
### Features
* history add max stack size ([#3253](https://github.com/antvis/x6/issues/3253)) ([fba5310](https://github.com/antvis/x6/commit/fba531064ad8027c451a81b60d5efd7f7314a0fa))
## @antv/x6-plugin-history [2.1.3](https://github.com/antvis/x6/compare/@antv/x6-plugin-history@2.1.2...@antv/x6-plugin-history@2.1.3) (2022-11-25)

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-history",
"version": "2.1.3",
"version": "2.2.0",
"description": "history plugin for X6",
"main": "lib/index.js",
"module": "es/index.js",

@ -25,6 +25,7 @@ export class History
protected batchLevel = 0
protected lastBatchIndex = -1
protected freezed = false
protected stackSize = 0 // 0: not limit
protected readonly handlers: (<T extends History.ModelEvents>(
event: T,
@ -33,6 +34,8 @@ export class History
constructor(options: History.Options) {
super()
const { stackSize = 0 } = options
this.stackSize = stackSize
this.options = Util.getOptions(options)
this.validator = new History.Validator({
history: this,
@ -101,7 +104,7 @@ export class History
const cmd = this.redoStack.pop()
if (cmd) {
this.applyCommand(cmd, options)
this.undoStack.push(cmd)
this.undoStackPush(cmd)
this.notify('redo', cmd, options)
}
}
@ -424,7 +427,7 @@ export class History
const cmds = this.filterBatchCommand(this.batchCommands)
if (cmds.length > 0) {
this.redoStack = []
this.undoStack.push(cmds)
this.undoStackPush(cmds)
this.consolidateCommands()
this.notify('add', cmds, options)
}
@ -498,7 +501,7 @@ export class History
this.lastBatchIndex = Math.max(this.lastBatchIndex, 0)
this.emit('batch', { cmd, options })
} else {
this.undoStack.push(cmd)
this.undoStackPush(cmd)
this.consolidateCommands()
this.notify('add', cmd, options)
}
@ -560,6 +563,17 @@ export class History
this.undoStack.pop()
}
protected undoStackPush(cmd: History.Commands) {
if (this.stackSize === 0) {
this.undoStack.push(cmd)
return
}
if (this.undoStack.length >= this.stackSize) {
this.undoStack.shift()
}
this.undoStack.push(cmd)
}
@Basecoat.dispose()
dispose() {
this.validator.dispose()
@ -614,7 +628,9 @@ export namespace History {
cancelInvalid?: boolean
}
export interface Options extends Partial<CommonOptions> {}
export interface Options extends Partial<CommonOptions> {
stackSize?: number
}
interface Data {
id?: string

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,3 +1,10 @@
## @antv/x6-plugin-transform [2.1.6](https://github.com/antvis/x6/compare/@antv/x6-plugin-transform@2.1.5...@antv/x6-plugin-transform@2.1.6) (2023-02-24)
### Bug Fixes
* transform active-handle class should remove when active removed ([#3298](https://github.com/antvis/x6/issues/3298)) ([709a141](https://github.com/antvis/x6/commit/709a141e28e9f25d54ece0ade353bd343ac0e55f))
## @antv/x6-plugin-transform [2.1.5](https://github.com/antvis/x6/compare/@antv/x6-plugin-transform@2.1.4...@antv/x6-plugin-transform@2.1.5) (2022-12-24)

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
{
"name": "@antv/x6-plugin-transform",
"version": "2.1.5",
"version": "2.1.6",
"description": "transform plugin for X6",
"main": "lib/index.js",
"module": "es/index.js",

@ -493,7 +493,7 @@ export class TransformImpl extends View<TransformImpl.EventArgs> {
Dom.removeClass(this.container, `${this.containerClassName}-active`)
if (this.handle) {
Dom.removeClass(this.handle, `${this.containerClassName}-active`)
Dom.removeClass(this.handle, `${this.containerClassName}-active-handle`)
const pos = this.handle.getAttribute(
'data-position',

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2021-2022 Alipay.inc
Copyright (c) 2021-2023 Alipay.inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

@ -1,6 +1,6 @@
{
"name": "@antv/x6",
"version": "2.3.0",
"version": "2.5.1",
"description": "JavaScript diagramming library that uses SVG and HTML for rendering",
"main": "lib/index.js",
"module": "es/index.js",

@ -1,6 +1,7 @@
import { Dom } from '@antv/x6-common'
import { Model } from '../model'
import { CellView } from '../view'
import { Scheduler } from '../renderer/scheduler'
interface CommonEventArgs<E> {
e: E
@ -13,7 +14,8 @@ interface PositionEventArgs<E> extends CommonEventArgs<E> {
export interface EventArgs
extends Omit<Model.EventArgs, 'sorted' | 'updated' | 'reseted'>,
CellView.EventArgs {
CellView.EventArgs,
Scheduler.EventArgs {
'model:sorted'?: Model.EventArgs['sorted']
'model:updated': Model.EventArgs['updated']
'model:reseted': Model.EventArgs['reseted']

@ -3,7 +3,9 @@ import { Base } from './base'
export class VirtualRenderManager extends Base {
protected init() {
this.resetRenderArea = FunctionExt.debounce(this.resetRenderArea, 200)
this.resetRenderArea = FunctionExt.throttle(this.resetRenderArea, 200, {
leading: true,
})
this.resetRenderArea()
this.startListening()
}

@ -92,6 +92,7 @@ export class CellEditor extends ToolsView.ToolItem<
index: this.labelIndex,
})
}
editor.innerText = text || ''
// clear display value when edit status because char ghosting.
@ -105,7 +106,8 @@ export class CellEditor extends ToolsView.ToolItem<
const cell = this.cell
const value = this.editor.innerText.replace(/\n$/, '') || ''
// set value
this.setCellText(value)
// when value is null, we will remove label in edge
this.setCellText(value !== '' ? value : null)
// remove tool
cell.removeTool(cell.isEdge() ? 'edge-editor' : 'node-editor')
this.undelegateDocumentEvents()
@ -137,7 +139,7 @@ export class CellEditor extends ToolsView.ToolItem<
}
}
setCellText(value: string) {
setCellText(value: string | null) {
const setText = this.options.setText
if (typeof setText === 'function') {
FunctionExt.call(setText, this.cellView, {
@ -170,7 +172,7 @@ export namespace CellEditor {
this: CellView,
args: {
cell: Cell
value: string
value: string | null
index?: number
distance?: number
},
@ -204,7 +206,9 @@ export namespace CellEditor {
return cell.attr('text/text')
},
setText({ cell, value }) {
cell.attr('text/text', value)
if (value !== null) {
cell.attr('text/text', value)
}
},
})
@ -225,18 +229,20 @@ export namespace CellEditor {
setText({ cell, value, index, distance }) {
const edge = cell as Edge
if (index === -1) {
edge.appendLabel({
position: {
distance: distance!,
},
attrs: {
label: {
text: value,
},
},
})
} else {
if (value) {
edge.appendLabel({
position: {
distance: distance!,
},
attrs: {
label: {
text: value,
},
},
})
}
} else {
if (value !== null) {
edge.prop(`labels/${index}/attrs/label/text`, value)
} else if (typeof index === 'number') {
edge.removeLabelAt(index)

@ -313,6 +313,7 @@ export class Scheduler extends Disposable {
}
viewItem.state = Scheduler.ViewState.MOUNTED
this.graph.trigger('view:mounted', { view })
}
}
@ -334,6 +335,7 @@ export class Scheduler extends Disposable {
if (view) {
viewItem.view.remove()
delete this.willRemoveViews[cell.id]
this.graph.trigger('view:unmounted', { view })
}
}
@ -450,10 +452,16 @@ export class Scheduler extends Disposable {
for (let i = 0, n = edges.length; i < n; i += 1) {
const edge = edges[i]
const viewItem = this.views[edge.id]
if (!viewItem) {
continue
}
const edgeView = viewItem.view
if (!this.isViewMounted(edgeView)) {
continue
}
const flagLabels: FlagManager.Action[] = ['update']
if (edge.getTargetCell() === cell) {
flagLabels.push('target')
@ -472,10 +480,33 @@ export class Scheduler extends Disposable {
}
protected isInRenderArea(view: CellView) {
return (
!this.renderArea ||
this.renderArea.isIntersectWithRect(view.cell.getBBox())
)
if (!this.renderArea) {
return true
}
if (view.isNodeView()) {
const node = view.cell
return this.renderArea.isIntersectWithRect(node.getBBox())
}
if (view.isEdgeView()) {
const edge = view.cell
const sourceCell = edge.getSourceCell()
const targetCell = edge.getTargetCell()
if (sourceCell) {
const sourceViewItem = this.views[sourceCell.id]
if (sourceViewItem && !this.isViewMounted(sourceViewItem.view)) {
return false
}
}
if (targetCell) {
const targetViewItem = this.views[targetCell.id]
if (targetViewItem && !this.isViewMounted(targetViewItem.view)) {
return false
}
}
}
return true
}
protected getRenderPriority(view: CellView) {
@ -507,4 +538,9 @@ export namespace Scheduler {
options: any
state: ViewState
}
export interface EventArgs {
'view:mounted': { view: CellView }
'view:unmounted': { view: CellView }
}
}

62166
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

@ -1,5 +1,5 @@
import { defineConfig } from 'dumi'
import { repository, version } from './package.json'
import { repository } from './package.json'
export default defineConfig({
locales: [
@ -22,7 +22,7 @@ export default defineConfig({
showChartResize: true, // 是否在 demo 页展示图表视图切换
showAPIDoc: false, // 是否在 demo 页展示API文档
versions: {
[version]: 'https://x6.antv.antgroup.com',
'2.x': 'https://x6.antv.antgroup.com',
'1.x': 'https://x6.antv.vision',
},
navs: [
@ -288,8 +288,10 @@ export default defineConfig({
},
],
docsearchOptions: {
apiKey: 'fe8bee8366e56a9463229c3c81200866',
indexName: 'antv_x6',
appId: '7J0MWEOGMO',
apiKey: 'e0d8089bb224298dfd4415b3e98bb700',
indexName: 'x6_sites_2.0',
versionV3: true,
},
playground: {
extraLib: '',

@ -1,3 +1,11 @@
## @antv/x6-sites [1.5.5](https://github.com/antvis/x6/compare/@antv/x6-sites@1.5.4...@antv/x6-sites@1.5.5) (2023-02-27)
## @antv/x6-sites [1.5.4](https://github.com/antvis/x6/compare/@antv/x6-sites@1.5.3...@antv/x6-sites@1.5.4) (2023-02-24)
## @antv/x6-sites [1.5.3](https://github.com/antvis/x6/compare/@antv/x6-sites@1.5.2...@antv/x6-sites@1.5.3) (2023-02-23)
## @antv/x6-sites [1.5.2](https://github.com/antvis/x6/compare/@antv/x6-sites@1.5.1...@antv/x6-sites@1.5.2) (2023-02-18)
# @antv/x6-sites [1.5.0](https://github.com/antvis/x6/compare/@antv/x6-sites@1.4.1...@antv/x6-sites@1.5.0) (2023-02-06)

@ -241,8 +241,9 @@ getBBox(): Rectangle
返回边的包围盒。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该方法通过边的端点和路径点计算包围盒,并不是渲染到画布后的包围盒,涉及的计算只是一些算数运算。
:::
#### getPolyline()

@ -499,7 +499,7 @@ function parseStringLabel(label: string): Label {
```ts
Edge.config({
defaultlabel: {
defaultLabel: {
markup: [
{
tagName: "rect",

@ -716,7 +716,7 @@ toJSON(options?: ToJSONOptions): object
| 名称 | 类型 | 必选 | 默认值 | 描述 |
| ------------ | ---- | :--: | ------- | --------------------------------------------------------------------------------------------------------- |
| options.deep | diff | | `false` | 是否导出节点和边的差异数据(与节点和边的[默认配置](/zh/docs/tutorial/basic/cell#选项默认值)不同的部分)。 |
| options.deep | diff | | `false` | 是否导出节点和边的差异数据(与节点和边的[默认配置](/zh/docs/api/model/cell#选项默认值)不同的部分)。 |
### parseJSON(...)

@ -247,8 +247,9 @@ getBBox(options: { deep?: boolean }): Rectangle
获取节点的包围盒。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该方法通过节点的大小和位置计算包围盒,并不是渲染到画布后的包围盒,涉及的计算只是一些算数运算。
:::
<span class="tag-param">参数<span>

@ -78,8 +78,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的宽度是参照宽度百分之多少。例如 `refWidth: 0.75` 表示元素的宽度是参照宽度的 `75%`
- 当其值 `<0``>1` 时,表示元素的宽度在参照宽度的基础上减少或增加多少。例如 `refWidth: 20` 表示元素比相对元素宽 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持宽度 `width` 和高度 `height` 的元素,如 `<rect>` 元素。
:::
### refWidth2
@ -101,8 +102,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的高度是参照高度百分之多少。例如 `refHeight: 0.75` 表示元素的高度是参照高度的 `75%`
- 当其值 `<0``>1` 时,表示元素的高度在参照高度的基础上减少或增加多少。例如 `refHeight: 20` 表示元素比相对元素高 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持宽度 `width` 和高度 `height` 的元素,如 `<rect>` 元素。
:::
### refHeight2
@ -124,8 +126,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的 `cx` 是参照宽度百分之多少。例如 `refCx: 0.75` 表示元素中心 `x` 坐标位于参照宽度的 `75%` 处。
- 当其值 `<0``>1` 时,表示元素的 `cx` 是在参照宽度的基础上减少或增加多少。例如 `refCx: 20` 表示元素中心 `x` 坐标位于参照宽度加 `20px` 处。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `cx``cy` 属性的元素,如 `<ellipse>` 元素。
:::
### refCy
@ -134,8 +137,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的 `cy` 是参照高度百分之多少。例如 `refCy: 0.75` 表示元素中心 `y` 坐标位于参照高度的 `75%` 处。
- 当其值 `<0``>1` 时,表示元素的 `cy` 是在参照宽度的基础上减少或增加多少。例如 `refCy: 20` 表示元素中心 `y` 坐标位于参照高度加 `20px` 处。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `cx``cy` 属性的元素,如 `<ellipse>` 元素。
:::
### refRx
@ -144,8 +148,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的 `rx` 是参照宽度百分之多少。例如 `refRx: 0.75` 表示元素的 `rx` 是参照宽度的 `75%`
- 当其值 `<0``>1` 时,表示元素的 `rx` 是在参照宽度的基础上减少或增加多少。例如 `refRx: 20` 表示元素的 `rx` 是参照宽度加 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `rx``ry` 属性的元素,如 `<rect>` 元素。
:::
### refRy
@ -154,8 +159,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示元素的 `ry` 是参照高度百分之多少。例如 `refRy: 0.75` 表示元素的 `ry` 是参照高度的 `75%`
- 当其值 `<0``>1` 时,表示元素的 `ry` 是在参照宽度的基础上减少或增加多少。例如 `refRy: 20` 表示元素的 `ry` 是参照高度加 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `rx``ry` 属性的元素,如 `<rect>` 元素。
:::
### refRCircumscribed
@ -164,8 +170,9 @@ redirect_from:
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示 `r` 是参照长度百分之多少。例如 `refRCircumscribed: 0.75` 表示 `r` 是参照长度的 `75%`
- 当其值 `<0``>1` 时,表示 `r` 是在参照长度的基础上减少或增加多少。例如 `refRCircumscribed: 20` 表示 `r` 是参照长度加 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `r` 属性的元素,如 `<rect>` 元素。
:::
### refRInscribed
@ -176,8 +183,9 @@ _简称_**`refR`**
- 当其值在 `[0, 1]` 之间或为百分比(如 `75%`)时,表示 `r` 是参照长度百分之多少。例如 `refRInscribed: 0.75` 表示 `r` 是参照长度的 `75%`
- 当其值 `<0``>1` 时,表示 `r` 是在参照长度的基础上减少或增加多少。例如 `refRInscribed: 20` 表示 `r` 是参照长度加 `20px`
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该属性只适用于那些支持 `r` 属性的元素,如 `<rect>` 元素。
:::
### refDKeepOffset
@ -561,8 +569,9 @@ edge.attr("connection/sourceMarker", {
适用于所有 `<path>` 元素,在路径的终点添加一个 SVG 元素(如终点箭头),并自动旋转该元素,使其与根据路径方向保持一致。了解更多详情请参考[这篇教程](/zh/docs/tutorial/intermediate/marker)。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,该元素初始时就被旋转了 `180` 度,在此基础上再自动调整旋转角度,并与路径的方向保持一致。例如,对于一个水平的直线,我们为其起点指定了一个向左的箭头,我们也可以为其重点指定相同的箭头,这个箭头会自动指向右侧(自动旋转了 `180` 度)。
:::
### vertexMarker

@ -62,8 +62,9 @@ graph.addNode({
上面这些属性默认相对于节点的大小进行计算,另外我们可以通过 `ref` 属性来提供一个子元素选择器,这时所有的计算都相对于 `ref` 指代的元素,从而实现相对于子元素的大小和位置。
[[warning]]
:::warning{title=注意:}
| 需要注意的是,设置 `ref` 后,所有计算都依赖子元素在浏览器中的 bbox 测量,所以性能会比相对于节点的方式要慢。
:::
```ts
graph.addNode({

@ -21,7 +21,7 @@ redirect_from:
在项目实践中,经常会遇到下面两种场景:
- 画布容器还没有渲染完成(此时尺寸为 0就实例化画布对象导致布元素显示异常。
- 画布容器还没有渲染完成(此时尺寸为 0就实例化画布对象导致布元素显示异常。
- 页面的 `resize` 导致画布容器大小改变,导致画布元素显示异常。
我们可以使用 `autoResize` 配置来解决上述问题。

@ -0,0 +1,150 @@
---
title: Angular 节点
order: 4
redirect_from:
- /zh/docs
- /zh/docs/tutorial
- /zh/docs/tutorial/inermediate
---
:::info{title=在本章节中,你可以了解到:}
- 如何使用 Angular 来渲染节点内容
- 如何更新节点内容
- FAQ
:::
## 渲染节点
我们提供了一个独立的包 `@antv/x6-angular-shape` 以支持将 Angular 的组件/模板作为节点进行渲染。
### Component 渲染
```ts
@Component({
selector: 'app-node',
templateUrl: './node.component.html',
styleUrls: ['./node.component.scss'],
})
export class NodeComponent implements AfterViewInit, OnChanges {
@Input() value: string;
}
```
```ts
import { register } from "@antv/x6-angular-shape";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
ngAfterViewInit(): void {
register({
shape: 'custom-angular-component-node',
width: 120,
height: 20,
content: NodeComponent,
injector: this.injector,
});
this.graph.addNode({
shape: 'custom-angular-component-node',
x: 100,
y: 100,
data: {
// Input 的参数必须放在这里
ngArguments: {
value: '糟糕糟糕 Oh my god',
},
},
});
}
}
```
### TemplateRef 渲染
```html
<ng-template #template let-data="ngArguments">
<section class="template-container">
<span class="value">{{ data.value }}</span>
</section>
</ng-template>
```
```ts
import { register } from "@antv/x6-angular-shape";
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements AfterViewInit {
@ViewChild('template') template: TemplateRef<{}>;
ngAfterViewInit(): void {
register({
shape: 'custom-angular-template-node',
width: 120,
height: 20,
content: this.template,
injector: this.injector,
});
this.graph.addNode({
shape: 'custom-angular-template-node',
x: 100,
y: 100,
data: {
ngArguments: {
value: '魔法怎么失灵啦',
},
},
});
}
}
```
## 更新节点
无论是使用 Component 还是 TemplateRef, 都是一样的更新方式.
```ts
node.setData({
ngArguments: {
value: '晚风中闪过 几帧从前啊',
},
});
```
## 有 demo 吗?
有的, 因为X6渲染节点部分与框架是解耦的. 因此 `x6-angular-shape` 包并非是直接在源代码里改的, 而是在一个单独的Angular环境中开发的. 该 demo 还提供了多种节点类型的性能测试, 详情请参考[Eve-Sama/x6-angular-shape](https://github.com/Eve-Sama/x6-angular-shape)、[X6与G6的性能对比, 以及X6多节点类型下的FPS临界点讨论](https://github.com/antvis/X6/issues/3266)
## FAQ
### 为什么输入属性不能直接放在 data 中而需要放在 ngArguments 中? 且为什么不叫 ngInput?
因为并非所有 `node.data` 中的属性都是输入属性, 所以遍历 `data` 中的所有属性进行赋值是不合适的. 至于为什么叫 `ngArguments` 主要是有两点考虑.
- 1.x版本中已经这么用了, 沿用该API可以降低用户升级成本
- `Input` 的概念其实是来自 `Component`, 而 `TemplateRef` 中是 `context`, 在二者的基础上抽象一个 `Arguments` 的概念更通用些
### 2.x版本的 x6-angular-shape 相比较1.x版本有什么新特性吗?
实现思路其实和之前是差不多的. 但是确实有几个点值得一提.
#### demo更聚焦
1.x版本的 demo 除了渲染组件外, 还写了连线、清除等一系列案例. 看似扩展, 实则眼花缭乱. 作为 `x6-angular-shape`的 demo, 2.x版本更加聚焦, 更加聚焦于shape的使用与性能测试等, 与插件无关的内容请查看X6官网.
#### 功能更稳定
在1.x版本中, 虽然实现了功能, 但是使用场景的思考不够全面. 比如`ngArguments`的变化, 对 `TemplateRef`的场景并不生效. 虽然对`Component`生效但是无法触发`ngOnChanges`. 而在新版本中, 这些问题都将不复存在.
### 版本要求
你的Angular版本至少在14及以上才可以. 14以下需要用 hack 的方式实现一些特性, 比较麻烦. 暂时不提供, 如有需要可提issue, 我再专门介绍下如何实现.

@ -1,6 +1,6 @@
---
title: HTML 节点
order: 4
order: 7
redirect_from:
- /zh/docs
- /zh/docs/tutorial

@ -54,6 +54,7 @@ graph.use(
| 属性名 | 类型 | 默认值 | 必选 | 描述 |
| ---------------- | ------------------------------- | ------- | ---- | ---------------------------------------------------------------------------------------------------- |
| enabled | boolean | `false` | | 是否开启撤销重做功能 |
| stackSize | number | `0` | | `stackSize` 为 0 表示不限制历史记录栈的长度,如果设置为其他数字表示最多只会记录该数字长度的历史记录 |
| ignoreAdd | boolean | `false` | | `ignoreAdd` 如果为 `true`,添加添加元素不会被记录到历史记录 |
| ignoreRemove | boolean | `false` | | `ignoreRemove` 如果为 `true`,删除元素不会被记录到历史记录 |
| ignoreChange | boolean | `false` | | `ignoreChange` 如果为 `true`,元素属性变化是否被记录到历史记录 |

@ -97,7 +97,7 @@ export interface Group {
graphWidth?: number; // 模板画布宽度
graphHeight?: number; // 模板画布高度
graphPadding?: number; // 模板画布边距
graphOptions?: Graph.Options; // 模板画布线下
graphOptions?: Graph.Options; // 模板画布选项
layout?: (this: Stencil, model: Model, group?: Group | null) => any;
layoutOptions?: any; // 布局选项
}

@ -1,7 +1,7 @@
{
"private": true,
"name": "@antv/x6-sites",
"version": "2.3.0",
"version": "1.5.5",
"description": "X6 sites deployed on gh-pages",
"scripts": {
"dev": "dumi dev",
@ -31,9 +31,9 @@
"@antv/x6-plugin-transform": "^2.x",
"@antv/x6-react-components": "^2.x",
"@antv/x6-react-shape": "^2.x",
"antd": "^4.4.2",
"antd": "^5.0.0",
"dagre": "^0.8.5",
"dumi": "2.0.16",
"dumi": "^2.1.14",
"elkjs": "^0.8.2",
"highlight.js": "^10.1.2",
"react": "^18.0.0",

@ -1,29 +1,26 @@
.app {
.auto-resize-app {
display: flex;
width: 600px;
height: 400px;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.full {
width: 100%;
height: 100%;
}
:global {
.x6-split-box-horizontal > .x6-split-box-resizer,
.x6-split-box-vertical > .x6-split-box-resizer {
background: #e3e6e9;
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}
.full {
width: 100%;
height: 100%;
}
}
.x6-split-box-horizontal > .x6-split-box-resizer,
.x6-split-box-vertical > .x6-split-box-resizer {
background: #e3e6e9;
}

@ -3,7 +3,7 @@ import React from 'react'
import { Graph } from '@antv/x6'
import { SplitBox } from '@antv/x6-react-components'
import '@antv/x6-react-components/es/split-box/style/index.css'
import styles from './index.less'
import './index.less'
export default class Example extends React.Component {
private container1: HTMLDivElement
@ -50,16 +50,16 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className="auto-resize-app">
<SplitBox split="horizontal">
<div className={styles.full}>
<div className="full">
<div ref={this.refContainer1} />
</div>
<SplitBox split="vertical">
<div className={styles.full}>
<div className="full">
<div ref={this.refContainer2} />
</div>
<div className={styles.full}>
<div className="full">
<div ref={this.refContainer3} />
</div>
</SplitBox>

@ -1,15 +1,15 @@
.app {
.backgournd-grid-app {
display: flex;
width: 100%;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}

@ -1,6 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import styles from './index.less'
import './index.less'
const data = {
nodes: [
@ -93,8 +93,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="backgournd-grid-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -1,15 +1,15 @@
.app {
.panning-mousewheel-app {
display: flex;
width: 100%;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}

@ -1,6 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import styles from './index.less'
import './index.less'
const data = {
nodes: [
@ -98,8 +98,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="panning-mousewheel-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -1,21 +1,20 @@
.app {
display: flex;
flex-direction: column;
.transform-app {
width: 100%;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-btns {
bottom: 0;
padding: 0 8px;
}
.app-btns {
bottom: 0;
padding: 0 8px;
}
.app-content {
height: 240px;
margin-top: 16px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
.app-content {
width: 100%;
height: 240px;
margin-top: 16px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}

@ -1,8 +1,7 @@
import React from 'react'
import { Graph } from '@antv/x6'
import { Button } from 'antd'
import styles from './index.less'
import './index.less'
const data = {
nodes: [
@ -150,8 +149,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-btns']}>
<div className="transform-app">
<div className="app-btns">
<Button.Group>
{commands.map((item) => (
<Button onClick={() => this.transform(item.key)} key={item.key}>
@ -160,7 +159,7 @@ export default class Example extends React.Component {
))}
</Button.Group>
</div>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -1,14 +1,15 @@
.app {
.helloworld-app {
display: flex;
width: 100%;
padding: 0;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
}

@ -1,6 +1,6 @@
import React from 'react'
import { Graph } from '@antv/x6'
import styles from './index.less'
import './index.less'
const data = {
nodes: [
@ -81,8 +81,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="helloworld-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -1,31 +1,31 @@
.app {
.react-shape-app {
display: flex;
width: 100%;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.custom-react-node {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border: 1px solid #8f8f8f;
border-radius: 6px;
}
.custom-react-node {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border: 1px solid #8f8f8f;
border-radius: 6px;
}
.custom-react-node span {
display: inline-block;
width: 100%;
height: 100%;
.custom-react-node span {
display: inline-block;
width: 100%;
height: 100%;
}
}

@ -2,7 +2,7 @@ import React from 'react'
import { Graph, Node } from '@antv/x6'
import { register } from '@antv/x6-react-shape'
import { Dropdown } from 'antd'
import styles from './index.less'
import './index.less'
const CustomComponent = ({ node }: { node: Node }) => {
const label = node.prop('label')
@ -26,7 +26,7 @@ const CustomComponent = ({ node }: { node: Node }) => {
}}
trigger={['contextMenu']}
>
<div className={styles['custom-react-node']}>{label}</div>
<div className="custom-react-node">{label}</div>
</Dropdown>
)
}
@ -92,8 +92,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="react-shape-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -1,25 +1,25 @@
.app {
.use-plugin-app {
display: flex;
padding: 0;
padding: 16px 8px;
font-family: sans-serif;
}
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.custom-react-node {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border: 1px solid #8f8f8f;
border-radius: 6px;
padding: 0;
font-family: sans-serif;
.app-content {
flex: 1;
height: 280px;
margin-right: 8px;
margin-left: 8px;
border-radius: 5px;
box-shadow: 0 12px 5px -10px rgb(0 0 0 / 10%), 0 0 4px 0 rgb(0 0 0 / 10%);
}
.custom-react-node {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border: 1px solid #8f8f8f;
border-radius: 6px;
}
}

@ -3,8 +3,7 @@ import { Graph, Node } from '@antv/x6'
import { register } from '@antv/x6-react-shape'
import { Dropdown } from 'antd'
import { Snapline } from '@antv/x6-plugin-snapline'
import styles from './index.less'
import './index.less'
const CustomComponent = ({ node }: { node: Node }) => {
const label = node.prop('label')
@ -28,7 +27,7 @@ const CustomComponent = ({ node }: { node: Node }) => {
}}
trigger={['contextMenu']}
>
<div className={styles['custom-react-node']}>{label}</div>
<div className="custom-react-node">{label}</div>
</Dropdown>
)
}
@ -99,8 +98,8 @@ export default class Example extends React.Component {
render() {
return (
<div className={styles.app}>
<div className={styles['app-content']} ref={this.refContainer} />
<div className="use-plugin-app">
<div className="app-content" ref={this.refContainer} />
</div>
)
}

@ -14,7 +14,7 @@
"resolveJsonModule": true,
"experimentalDecorators": true,
"jsx": "react",
"target": "es5",
"target": "es6",
"lib": ["DOM", "ES2020"]
}
}