From 369f252f8436bb3aa19ff775e8f612652331d73f Mon Sep 17 00:00:00 2001 From: markleo Date: Mon, 31 Mar 2025 13:14:58 +0800 Subject: [PATCH 01/26] fix: (iadd demon prorogress) --- .../docs/manual/element/node/react-node.zh.md | 6 + .../element/custom-node/demo/meta.json | 8 ++ .../custom-node/demo/reactnode-idcard.jsx | 104 ++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx diff --git a/packages/site/docs/manual/element/node/react-node.zh.md b/packages/site/docs/manual/element/node/react-node.zh.md index f8e3918ab7b..46a3dc18d0f 100644 --- a/packages/site/docs/manual/element/node/react-node.zh.md +++ b/packages/site/docs/manual/element/node/react-node.zh.md @@ -2,3 +2,9 @@ title: 使用 React 定义节点 order: 5 --- + +## 在线测试 + + + + diff --git a/packages/site/examples/element/custom-node/demo/meta.json b/packages/site/examples/element/custom-node/demo/meta.json index d9ad9e02ce3..1fea117ce42 100644 --- a/packages/site/examples/element/custom-node/demo/meta.json +++ b/packages/site/examples/element/custom-node/demo/meta.json @@ -27,6 +27,14 @@ "en": "G2 activity Chart" }, "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GVyoQKk2WIIAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "reactnode-idcard.jsx", + "title": { + "zh": "React 节点 身份证", + "en": "React node IDCard" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GVyoQKk2WIIAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx new file mode 100644 index 00000000000..e8d944cbf16 --- /dev/null +++ b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx @@ -0,0 +1,104 @@ +// reactnode-idcard.js +import React from 'react'; +import { useEffect, useRef } from 'react'; +import { createRoot } from 'react-dom/client'; +//import ReactDOM from 'react-dom'; +import { Graph } from '@antv/g6'; +import { ExtensionCategory, register } from '@antv/g6'; +import { ReactNode } from '@antv/g6-extension-react'; +import { Card, Typography } from 'antd'; + +const { Title, Text } = Typography; + +// 定义自定义节点组件 +const IDCardNode = ({ id, data }) => { + const { name, idNumber, address } = data.data; + + console.log('IDCardNode props:', id, data); + + return ( + +
+ + {name} + + ID Number: + {idNumber} + Address: + {address} +
+
+ ); +}; + +// 注册自定义节点 +register(ExtensionCategory.NODE, 'id-card', ReactNode); + +// 定义 Graph 数据 +const data = { + nodes: [ + { + id: 'node1', + data: { + name: '张三', + idNumber: '11010519491231002X', + address: '北京市朝阳区', + }, + style: { x: 50, y: 50 }, + }, + { + id: 'node2', + data: { + name: '李四', + idNumber: '11010519500101001X', + address: '上海市浦东新区', + }, + style: { x: 500, y: 100 }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +export const ReactNodeDemo = () => { + const containerRef = useRef(); + + useEffect(() => { + // 创建 Graph 实例 + const graph = new Graph({ + container: containerRef.current, + width: 800, + height: 600, + data, + node: { + type: 'react', + style: { + size: [200, 80], + component: (data) => , + }, + }, + behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'], + }); + + // 渲染 Graph + graph.render(); + }, []); + + return
; +}; + + +// 渲染 React 组件到 DOM +const root = createRoot(document.getElementById('container')); +root.render(); From 7bcf2e155dddb2a107bcaf85309ff9873793e781 Mon Sep 17 00:00:00 2001 From: markleo Date: Mon, 31 Mar 2025 19:25:14 +0800 Subject: [PATCH 02/26] fix: docs/update-custom-react-node --- .../docs/manual/element/node/react-node.zh.md | 112 +++++++++++++++++- .../custom-node/demo/reactnode-idcard.jsx | 79 ++++++++---- 2 files changed, 164 insertions(+), 27 deletions(-) diff --git a/packages/site/docs/manual/element/node/react-node.zh.md b/packages/site/docs/manual/element/node/react-node.zh.md index 46a3dc18d0f..78842ecc5d8 100644 --- a/packages/site/docs/manual/element/node/react-node.zh.md +++ b/packages/site/docs/manual/element/node/react-node.zh.md @@ -3,8 +3,116 @@ title: 使用 React 定义节点 order: 5 --- +## 简介 + +在数据可视化领域,为高效率使用 AntV G6,节点定义可采用 React 组件的方式。AntV G6 功能强大但原生节点定义处理复杂交互和状态管理有挑战。 + +### ReactNode 和 GNode 方式自定义 G6 节点 + +#### ReactNode推荐 + +React Node 是指借助 React 框架来定义 G6 节点。 +这种方式能把 React 组件的优势发挥到极致。React 以组件化开发闻名,使用 React 定义节点可提升代码复用性,减少重复工作,提高开发效率。其强大的状态管理能力便于处理节点的各种交互状态,比如点击、悬停、拖拽等,能让节点交互体验更加流畅。并且 React 拥有庞大的生态系统,有丰富的工具和库可供使用,能轻松为节点添加复杂的样式和交互逻辑,满足多样化的业务需求。 + +#### GNode + +G Node 是基于 G 图形库来定义 G6 节点。 +G 是一个高性能的图形渲染引擎,在 G6 中使用 G 来定义节点,能实现高效的图形渲染。G 提供了丰富的图形绘制 API,可直接对节点的图形元素进行精细控制,例如绘制复杂的几何形状、设置样式等。这种方式更侧重于底层的图形操作,在需要对节点图形进行高度定制化时具有很大优势,能够满足对节点外观和性能有严格要求的场景。 + +### 使用React自定义节点优势 + +React 组件化、状态管理能力强,将其用于 AntV G6 节点定义,能结合二者优势。可进行组件复用,提升开发效率;轻松处理节点交互状态,优化用户体验。 + +- 提高开发效率:React 以组件化开发著称,这使得节点定义可以复用。对于相同类型的节点,只需创建一次组件,就能在不同地方重复使用,减少了重复代码的编写,极大地提高了开发效率。 +- 增强可维护性:组件化结构使代码逻辑更加清晰,每个组件都有明确的职责。当需要修改某个节点的样式或功能时,只需找到对应的组件进行修改,不会影响到其他部分的代码,降低了维护成本。 +- 便于状态管理:React 拥有强大的状态管理能力,能轻松处理节点的各种交互状态,如点击、拖拽、悬停等。通过状态的变化,实时更新节点的显示效果,为用户带来流畅的交互体验。 +- 丰富的生态系统:React 拥有庞大的生态系统,有大量可用的工具和库。可以将这些工具和库与 AntV G6 结合使用,为节点添加更多的功能,如动画效果、数据可视化等,满足复杂的业务需求。 +- 易于集成:React 作为前端开发的主流框架,与其他前端技术和工具的集成非常方便。可以将使用 React 定义的 AntV G6 节点轻松集成到现有的项目中,与其他组件协同工作,提高项目的整体开发效率。 + +## 使用步骤 + +### 安装 + +```bash +npm install @antv/g6-extension-react +``` + +### 自定义React节点 + +ReactNode方式 + +```jsx +import { ReactNode } from '@antv/g6-extension-react'; + +const MyReactNode = () => { + return
node
; +}; +``` + +GNode方式 + +```jsx +import { Group, Rect, Text } from '@antv/g6-extension-react'; + +const GNode = () => { + return + + + +}; +``` + +### 注册节点 + +```jsx +import { ExtensionCategory, register } from '@antv/g6'; +import { ReactNode } from '@antv/g6-extension-react'; + +register(ExtensionCategory.NODE, 'react', ReactNode); +``` + +### 使用节点 + +使用自定义的ReactNode节点: + +```jsx +const graph = new Graph({ + // ... other options + node: { + type: 'react', + style: { + component: () => , + }, + }, +}); +``` + +使用自定义的GNode节点: + +```jsx +const graph = new Graph({ + // ... other options + node: { + type: 'g', + style: { + component: () => , + }, + }, +}); +``` + ## 在线测试 - +
+ + +
- + diff --git a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx index e8d944cbf16..abff70f2a2c 100644 --- a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx +++ b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx @@ -1,39 +1,59 @@ -// reactnode-idcard.js -import React from 'react'; -import { useEffect, useRef } from 'react'; +// reactnode-idcard.jsx +import React, { useEffect, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; -//import ReactDOM from 'react-dom'; import { Graph } from '@antv/g6'; import { ExtensionCategory, register } from '@antv/g6'; import { ReactNode } from '@antv/g6-extension-react'; -import { Card, Typography } from 'antd'; +import { Card, Typography, Button } from 'antd'; const { Title, Text } = Typography; // 定义自定义节点组件 const IDCardNode = ({ id, data }) => { - const { name, idNumber, address } = data.data; + const { name, idNumber, address, expanded, graph } = data; + const [isExpanded, setIsExpanded] = useState(expanded || false); + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + graph.updateNodeData([{ + id, + data: { + ...data, + expanded: !isExpanded, + }, + }]); + }; console.log('IDCardNode props:', id, data); return ( + + {name} + + + + } style={{ width: 250, padding: 10, borderColor: '#ddd', }} > -
- - {name} - - ID Number: - {idNumber} - Address: - {address} -
+ {isExpanded ? ( +
+ ID Number: + {idNumber} + Address: + {address} +
+ ) : ( + IDCard Information + )}
); }; @@ -47,18 +67,20 @@ const data = { { id: 'node1', data: { - name: '张三', - idNumber: '11010519491231002X', - address: '北京市朝阳区', + name: 'Alice', + idNumber: 'IDUSAASD2131734', + address: '1234 Broadway, Apt 5B, New York, NY 10001', + expanded: false, // 初始状态为收缩 }, style: { x: 50, y: 50 }, }, { id: 'node2', data: { - name: '李四', - idNumber: '11010519500101001X', - address: '上海市浦东新区', + name: 'Bob', + idNumber: 'IDUSAASD1431920', + address: '3030 Chestnut St, Philadelphia, PA 19104', + expanded: false, // 初始状态为收缩 }, style: { x: 500, y: 100 }, }, @@ -73,6 +95,7 @@ const data = { export const ReactNodeDemo = () => { const containerRef = useRef(); + const graphRef = useRef(null); useEffect(() => { // 创建 Graph 实例 @@ -84,8 +107,8 @@ export const ReactNodeDemo = () => { node: { type: 'react', style: { - size: [200, 80], - component: (data) => , + size: [250, 120], // 调整大小以适应内容 + component: (data) => , }, }, behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'], @@ -93,12 +116,18 @@ export const ReactNodeDemo = () => { // 渲染 Graph graph.render(); + + // 保存 graph 实例 + graphRef.current = graph; + + return () => { + graph.destroy(); + }; }, []); return
; }; - // 渲染 React 组件到 DOM const root = createRoot(document.getElementById('container')); root.render(); From 34fcdfd233eb01a99888ccfd5291c31d76a6bfa8 Mon Sep 17 00:00:00 2001 From: jiangyu Date: Mon, 31 Mar 2025 20:01:00 +0800 Subject: [PATCH 03/26] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DFisheye=E5=9C=A8?= =?UTF-8?q?update=E6=97=B6=EF=BC=8C=E6=9C=AA=E6=9B=B4=E6=96=B0this.r/this.?= =?UTF-8?q?d=20(#6956)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 修复Fisheye在·update时,未更新this.r/this.d * fix: options.r/options.d非空判断 --------- Co-authored-by: liujiangyu --- packages/g6/src/plugins/fisheye/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/g6/src/plugins/fisheye/index.ts b/packages/g6/src/plugins/fisheye/index.ts index 0a563518bdd..5c81804b7b0 100644 --- a/packages/g6/src/plugins/fisheye/index.ts +++ b/packages/g6/src/plugins/fisheye/index.ts @@ -466,6 +466,8 @@ export class Fisheye extends BasePlugin { public update(options: Partial) { this.unbindEvents(); super.update(options); + this.r = options.r ?? this.r; + this.d = options.d ?? this.d; this.bindEvents(); } From bf0cc011f37e200e1c0fea8f29f8744c68112d10 Mon Sep 17 00:00:00 2001 From: ChiGao Date: Tue, 1 Apr 2025 10:00:18 +0800 Subject: [PATCH 04/26] docs: update antv-dagre layout document (#6958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #6904 Co-authored-by: 秦奇 --- .../layout/build-in/AntvDagreLayout.zh.md | 176 ++++++++++++------ 1 file changed, 115 insertions(+), 61 deletions(-) diff --git a/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md b/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md index b026475c398..e572f2e6868 100644 --- a/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md +++ b/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md @@ -2,64 +2,100 @@ title: AntvDagre 布局 --- -## 配置项 +## 概述 -### align +AntvDagre 在原先[dagre](https://github.com/dagrejs/dagre/wiki)布局的基础上增加了更多有用的设置项,比如`nodeOrder`、`edgeLabelSpace`等等。 `dagre`布局本身一种层次化布局,适用于有向无环图(DAG)的布局场景,能够自动处理节点之间的方向和间距,支持水平和垂直布局。参考更多 Dagre 布局[样例](https://g6.antv.antgroup.com/examples#layout-dagre)或[源码](https://github.com/dagrejs/dagre/blob/master/lib/layout.js)以及[官方文档](https://github.com/dagrejs/dagre/wiki)。 -> _DagreAlign_ **Default:** `'UL'` +Dagre布局 -节点对齐方式 U:upper(上);D:down(下);L:left(左);R:right(右) +## 配置方式 -- 'UL':对齐到左上角 -- 'UR':对齐到右上角 -- 'DL':对齐到左下角 -- 'DR':对齐到右下角 +```js +const graph = new Graph({ + layout: { + type: 'antv-dagre', + rankdir: 'TB', + align: 'UL', + nodesep: 50, + ranksep: 50, + controlPoints: false, + }, +}); +``` -### begin +## 配置项 -> _PointTuple_ **Default:** `undefined` +> 更多`dagre`原生配置项可参考[官方文档](https://github.com/dagrejs/dagre/wiki#configuring-the-layout),这里仅列出部分核心配置和新增的配置 + +Dagre 布局配置项图解 + +| 属性 | 描述 | 类型 | 默认值 | 必选 | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------- | ---- | --- | +| type | 布局类型 | `antv-dagre` | - | ✓ | +| rankdir | 布局方向,可选值 | `TB` \| `BT` \| `LR` \| `RL` | `TB` | | +| align | 节点对齐方式,可选值 | `UL` \| `UR` \| `DL` \| `DR` | `UL` | | +| nodesep | 节点间距(px)。在rankdir 为 `TB` 或 `BT` 时是节点的水平间距;在rankdir 为 `LR` 或 `RL` 时代表节点的竖直方向间距 | number | 50 | | +| nodesepFunc | 节点间距(px)的回调函数,通过该参数可以对不同节点设置不同的节点间距。在rankdir 为 `TB` 或 `BT` 时是节点的水平间距;在rankdir 为 `LR` 或 `RL` 时代表节点的竖直方向间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效 | (d?: Node) => number | | | +| ranksep | 层间距(px)。在rankdir 为 `TB` 或 `BT` 时是竖直方向相邻层间距;在rankdir 为 `LR` 或 `RL` 时代表水平方向相邻层间距 | number | 100 | | +| ranksepFunc | 层间距(px)的回调函数,通过该参数可以对不同层级设置不同的节点间距。在rankdir 为 `TB` 或 `BT` 时是节点的水平间距;在rankdir 为 `LR` 或 `RL` 时代表节点的竖直方向间距。优先级高于 ranksep,即若设置了 ranksepFunc,则 ranksep 不生效 | (d?: Node) => number | | | +| ranker | 为每个节点分配等级的算法,共支持三种算法,分别是:`longest-path` 最长路径算法、`tight-tree` 紧凑树算法、`network-simplex` 网络单形法 | `network-simplex` \| `tight-tree` \| `longest-path` | `network-simplex` | | +| nodeSize | 统一指定或为每个节点指定节点大小,用于防止节点重叠时的碰撞检测。如果仅返回单个number,则表示节点的宽度和高度相同;如果返回一个数组,则形如:`[width, height]` | Size | ((nodeData: Node) => Size) | | | +| controlPoints | 是否保留边的控制点,仅在边配置中使用了内置折线(type: 'polyline-edge') 时,或任何将自定义消费了 `style.controlPoints` 字段作为控制点位置的边时生效。本质上就是给边数据增加了 `style.controlPoints` | boolean | false | | +| begin | 布局的左上角对齐位置 | [number, number] \| [number, number, number] | | | +| sortByCombo | 同一层节点是否根据每个节点数据中的 parentId 进行排序,以防止 Combo 重叠置 | boolean | false | | +| edgeLabelSpace | 是否为边的label留位置 | boolean | true | | +| nodeOrder | 同层节点顺序的参考数组,存放节点 id 值,若未指定,则将按照 dagre 本身机制排列同层节点顺序 | string[] | | | +| radial | 是否基于 `dagre` 进行辐射布局 | boolean | false | | +| focusNode | 关注的节点,注意,仅在`radial` 为 true 时生效 | ID \| Node \| null | | | +| preset | 布局计算时参考的节点位置,一般用于切换数据时保证重新布局的连续性。在 G6 中,若是更新数据,则将自动使用已存在的布局结果数据作为输入 | OutNode[] | | | -布局的左上角对齐位置 +### align -### controlPoints +> _DagreAlign_ **Default:** `UL` -> _boolean_ **Default:** `false` +节点对齐方式 U:upper(上);D:down(下);L:left(左);R:right(右) -是否同时计算边上的的控制点位置 +- `UL`:对齐到左上角 +- `UR`:对齐到右上角 +- `DL`:对齐到左下角 +- `DR`:对齐到右下角 -仅在边配置中使用了内置折线(type: 'polyline-edge') 时,或任何将自定义消费了 data.controlPoints 字段作为控制点位置的边时生效。本质上就是给边数据增加了 data.controlPoints +### rankdir -### edgeLabelSpace +> _DagreRankdir_ **Default:** `TB` -> _boolean_ **Default:** `true` +布局的方向。T:top(上);B:bottom(下);L:left(左);R:right(右) -是否为边的label留位置 +- `TB`:从上至下布局 +- `BT`:从下至上布局 +- `LR`:从左至右布局 +- `RL`:从右至左布局 -这会影响是否在边中间添加dummy node +### ranker -### focusNode +> _`network-simplex` \| `tight-tree` \| `longest-path`_ -> _ID \| Node \| null_ +布局的模式 -关注的节点 +### ranksep -- ID: 节点 id -- Node: 节点实例 -- null: 取消关注 +> _number_ **Default:** 50 -radial 为 true 时生效 +层间距(px) -### nodeOrder +在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在 rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。ranksepFunc 拥有更高的优先级 + +### ranksepFunc -> _string[]_ **Default:** `false` +> _(d?: Node) => number_ -同层节点顺序的参考数组,存放节点 id 值 +层间距(px)的回调函数 -若未指定,则将按照 dagre 本身机制排列同层节点顺序 +在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在 rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效 ### nodesep -> _number_ **Default:** `50` +> _number_ **Default:** 50 节点间距(px) @@ -73,65 +109,83 @@ radial 为 true 时生效 在 rankdir 为 'TB' 或 'BT' 时是节点的水平间距;在 rankdir 为 'LR' 或 'RL' 时代表节点的竖直方向间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效 -### nodeSize +### begin -> _Size \| ((nodeData: Node) => Size)_ **Default:** `undefined` +> _[number, number] \| [number, number, number]_ **Default:** undefined -节点大小(直径)。 +布局的左上角对齐位置 -用于防止节点重叠时的碰撞检测 +### controlPoints -### preset +> _boolean_ **Default:** false -> _OutNode[]_ **Default:** `undefined` +是否保留边的控制点,仅在边配置中使用了内置折线(type: 'polyline-edge') 时,或任何将自定义消费了 `style.controlPoints` 字段作为控制点位置的边时生效。本质上就是给边数据增加了 `style.controlPoints` -布局计算时参考的节点位置 +### edgeLabelSpace -一般用于切换数据时保证重新布局的连续性。在 G6 中,若是更新数据,则将自动使用已存在的布局结果数据作为输入 +> _boolean_ **Default:** true -### radial +是否为边的label留位置 -> _boolean_ +这会影响是否在边中间添加dummy node -是否基于 dagre 进行辐射布局 +### focusNode -### rankdir +> _ID \| Node \| null_ -> _DagreRankdir_ **Default:** `'TB'` +关注的节点,注意,仅在`radial` 为 true 时生效 -布局的方向。T:top(上);B:bottom(下);L:left(左);R:right(右) +- ID: 节点 id +- Node: 节点实例 +- null: 取消关注 -- 'TB':从上至下布局 -- 'BT':从下至上布局 -- 'LR':从左至右布局 -- 'RL':从右至左布局 +### nodeOrder -### ranker +> _string[]_ **Default:** undefined -> _'network-simplex' \| 'tight-tree' \| 'longest-path'_ +同层节点顺序的参考数组,存放节点 id 值 -布局的模式 +若未指定,则将按照 dagre 本身机制排列同层节点顺序 -### ranksep +### nodeSize -> _number_ **Default:** `50` +> _Size \| ((nodeData: Node) => Size)_ **Default:** undefined -层间距(px) +统一指定或为每个节点指定节点大小。 -在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在 rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。ranksepFunc 拥有更高的优先级 +用于防止节点重叠时的碰撞检测 -### ranksepFunc +### preset -> _(d?: Node) => number_ +> _OutNode[]_ **Default:** undefined -层间距(px)的回调函数 +布局计算时参考的节点位置 -在 rankdir 为 'TB' 或 'BT' 时是竖直方向相邻层间距;在 rankdir 为 'LR' 或 'RL' 时代表水平方向相邻层间距。优先级高于 nodesep,即若设置了 nodesepFunc,则 nodesep 不生效 +一般用于切换数据时保证重新布局的连续性。在 G6 中,若是更新数据,则将自动使用已存在的布局结果数据作为输入 + +### radial + +> _boolean_ + +是否基于 dagre 进行辐射布局 ### sortByCombo -> _boolean_ **Default:** `false` +> _boolean_ **Default:** false 同一层节点是否根据每个节点数据中的 parentId 进行排序,以防止 Combo 重叠 建议在有 Combo 的情况下配置 + +## 布局适用场景 + +- **流程图**:适合展示流程图,节点之间的方向和间距会自动处理; +- **依赖关系图**:展示软件包或模块之间的依赖关系; +- **任务调度图**:展示任务之间的依赖关系和执行顺序。 + +## 相关文档 + +> 以下文档可以帮助你更好地理解Dagre 布局 + +- [图布局算法|详解 Dagre 布局](https://mp.weixin.qq.com/s/EdyTfFUH7fyMefNSBXI2nA) +- [深入解读Dagre布局算法](https://www.yuque.com/antv/g6-blog/xxp5nl) From 3e9c69ea260be3325da664d12d9079b07a9cdd86 Mon Sep 17 00:00:00 2001 From: jiangyu Date: Tue, 1 Apr 2025 15:14:29 +0800 Subject: [PATCH 05/26] =?UTF-8?q?docs:=20=E6=94=B9=E9=80=A0Fisheye?= =?UTF-8?q?=E6=8F=92=E4=BB=B6=E6=96=87=E6=A1=A3=20(#6960)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 添加 Snapline 插件文档和示例,更新中文和英文手册内容 * docs: 更新 Snapline 插件文档,优化使用场景描述和样式配置示例 * feat: 新增鱼眼放大镜插件文档,包含概述、使用场景、基本用法及配置项详细说明,更新中英文手册内容。 * docs: 更新鱼眼放大镜插件文档,增加样式属性和节点样式的详细说明,优化示例代码和中文翻译,删除API部分内容; --------- Co-authored-by: liujiangyu --- packages/site/common/api/plugins/fisheye.md | 251 ++++++++++++ .../docs/manual/plugin/build-in/Fisheye.en.md | 383 ++++++++++++------ .../docs/manual/plugin/build-in/Fisheye.zh.md | 358 +++++++++++----- 3 files changed, 757 insertions(+), 235 deletions(-) create mode 100644 packages/site/common/api/plugins/fisheye.md diff --git a/packages/site/common/api/plugins/fisheye.md b/packages/site/common/api/plugins/fisheye.md new file mode 100644 index 00000000000..e36227b2537 --- /dev/null +++ b/packages/site/common/api/plugins/fisheye.md @@ -0,0 +1,251 @@ +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // 上部节点 + { id: 'Myriel', style: { x: 197, y: 58 } }, + { id: 'Napoleon', style: { x: 147, y: 22 } }, + { id: 'Mlle.Baptistine', style: { x: 225, y: 141 } }, + { id: 'Mme.Magloire', style: { x: 255, y: 120 } }, + { id: 'CountessdeLo', style: { x: 151, y: -3 } }, + { id: 'Geborand', style: { x: 136, y: 41 } }, + { id: 'Champtercier', style: { x: 227, y: 8 } }, + { id: 'Cravatte', style: { x: 172, y: -10 } }, + { id: 'Count', style: { x: 172, y: 12 } }, + { id: 'OldMan', style: { x: 198, y: -6 } }, + // 中上部节点 + { id: 'Labarre', style: { x: 266, y: 203 } }, + { id: 'Marguerite', style: { x: 265, y: 171 } }, + { id: 'Mme.deR', style: { x: 299, y: 133 } }, + { id: 'Isabeau', style: { x: 282, y: 191 } }, + { id: 'Gervais', style: { x: 334, y: 148 } }, + { id: 'Simplice', style: { x: 286, y: 227 } }, + { id: 'Scaufflaire', style: { x: 250, y: 231 } }, + { id: 'Woman1', style: { x: 375, y: 202 } }, + { id: 'Judge', style: { x: 370, y: 139 } }, + { id: 'Champmathieu', style: { x: 404, y: 216 } }, + // 中部主要节点 + { id: 'Valjean', style: { x: 322, y: 221 } }, + { id: 'Fantine', style: { x: 337, y: 187 } }, + { id: 'Cosette', style: { x: 343, y: 248 } }, + { id: 'Javert', style: { x: 368, y: 263 } }, + { id: 'Thenardier', style: { x: 317, y: 300 } }, + { id: 'Mme.Thenardier', style: { x: 283, y: 267 } }, + { id: 'Eponine', style: { x: 268, y: 365 } }, + { id: 'Gavroche', style: { x: 393, y: 380 } }, + { id: 'Marius', style: { x: 336, y: 350 } }, + { id: 'Enjolras', style: { x: 376, y: 371 } }, + // 右侧和右上节点 + { id: 'Gribier', style: { x: 437, y: 160 } }, + { id: 'Jondrette', style: { x: 510, y: 327 } }, + { id: 'Mme.Burgon', style: { x: 466, y: 368 } }, + { id: 'Brevet', style: { x: 399, y: 183 } }, + { id: 'Chenildieu', style: { x: 425, y: 194 } }, + { id: 'Cochepaille', style: { x: 419, y: 148 } }, + { id: 'Child1', style: { x: 361, y: 387 } }, + { id: 'Child2', style: { x: 415, y: 432 } }, + { id: 'Brujon', style: { x: 330, y: 394 } }, + { id: 'Mme.Hucheloup', style: { x: 394, y: 450 } }, + // 中部其他节点 + { id: 'Favourite', style: { x: 284, y: 153 } }, + { id: 'Dahlia', style: { x: 303, y: 170 } }, + { id: 'Zephine', style: { x: 286, y: 94 } }, + { id: 'Tholomyes', style: { x: 359, y: 158 } }, + { id: 'Listolier', style: { x: 308, y: 80 } }, + { id: 'Fameuil', style: { x: 329, y: 89 } }, + { id: 'Blacheville', style: { x: 351, y: 95 } }, + { id: 'Perpetue', style: { x: 234, y: 195 } }, + { id: 'Woman2', style: { x: 304, y: 254 } }, + { id: 'MotherInnocent', style: { x: 350, y: 214 } }, + // 下部节点 + { id: 'Pontmercy', style: { x: 375, y: 307 } }, + { id: 'Boulatruelle', style: { x: 260, y: 279 } }, + { id: 'Anzelma', style: { x: 234, y: 303 } }, + { id: 'Gillenormand', style: { x: 338, y: 286 } }, + { id: 'Magnon', style: { x: 277, y: 317 } }, + { id: 'Mlle.Gillenormand', style: { x: 257, y: 306 } }, + { id: 'Mme.Pontmercy', style: { x: 307, y: 318 } }, + { id: 'Mlle.Vaubois', style: { x: 197, y: 325 } }, + { id: 'Lt.Gillenormand', style: { x: 294, y: 296 } }, + { id: 'Toussaint', style: { x: 306, y: 277 } }, + { id: 'Gueulemer', style: { x: 344, y: 323 } }, + { id: 'Babet', style: { x: 367, y: 319 } }, + { id: 'Claquesous', style: { x: 303, y: 347 } }, + { id: 'Montparnasse', style: { x: 322, y: 330 } }, + // 最下部节点 + { id: 'Combeferre', style: { x: 397, y: 416 } }, + { id: 'Prouvaire', style: { x: 309, y: 426 } }, + { id: 'Feuilly', style: { x: 314, y: 456 } }, + { id: 'Courfeyrac', style: { x: 332, y: 435 } }, + { id: 'Bahorel', style: { x: 343, y: 466 } }, + { id: 'Bossuet', style: { x: 305, y: 382 } }, + { id: 'Joly', style: { x: 371, y: 415 } }, + { id: 'Grantaire', style: { x: 370, y: 466 } }, + { id: 'MotherPlutarch', style: { x: 424, y: 461 } }, + ], + edges: [ + // 主要连接 + { id: 'e1', source: 'Valjean', target: 'Javert' }, + { id: 'e2', source: 'Valjean', target: 'Cosette' }, + { id: 'e3', source: 'Javert', target: 'Thenardier' }, + { id: 'e4', source: 'Cosette', target: 'Marius' }, + { id: 'e5', source: 'Eponine', target: 'Marius' }, + { id: 'e6', source: 'Enjolras', target: 'Marius' }, + { id: 'e7', source: 'Gavroche', target: 'Enjolras' }, + { id: 'e8', source: 'Valjean', target: 'Fantine' }, + { id: 'e9', source: 'Cosette', target: 'Thenardier' }, + { id: 'e10', source: 'Eponine', target: 'Thenardier' }, + // 上部连接 + { id: 'e11', source: 'Myriel', target: 'Napoleon' }, + { id: 'e12', source: 'Myriel', target: 'Mlle.Baptistine' }, + { id: 'e13', source: 'Mlle.Baptistine', target: 'Mme.Magloire' }, + { id: 'e14', source: 'CountessdeLo', target: 'Myriel' }, + { id: 'e15', source: 'Geborand', target: 'Myriel' }, + // 中部连接 + { id: 'e16', source: 'Favourite', target: 'Tholomyes' }, + { id: 'e17', source: 'Dahlia', target: 'Favourite' }, + { id: 'e18', source: 'Zephine', target: 'Favourite' }, + { id: 'e19', source: 'Tholomyes', target: 'Listolier' }, + { id: 'e20', source: 'Fameuil', target: 'Blacheville' }, + // 下部连接 + { id: 'e21', source: 'Combeferre', target: 'Enjolras' }, + { id: 'e22', source: 'Prouvaire', target: 'Combeferre' }, + { id: 'e23', source: 'Feuilly', target: 'Courfeyrac' }, + { id: 'e24', source: 'Bahorel', target: 'Bossuet' }, + { id: 'e25', source: 'Joly', target: 'Grantaire' }, + // 额外的中部连接 + { id: 'e26', source: 'Gueulemer', target: 'Thenardier' }, + { id: 'e27', source: 'Babet', target: 'Gueulemer' }, + { id: 'e28', source: 'Claquesous', target: 'Montparnasse' }, + { id: 'e29', source: 'Brujon', target: 'Babet' }, + { id: 'e30', source: 'Child1', target: 'Gavroche' }, + // 新增更多连接 + { id: 'e31', source: 'Valjean', target: 'Simplice' }, + { id: 'e32', source: 'Fantine', target: 'Simplice' }, + { id: 'e33', source: 'Javert', target: 'Simplice' }, + { id: 'e34', source: 'Marius', target: 'Gillenormand' }, + { id: 'e35', source: 'Cosette', target: 'Gillenormand' }, + { id: 'e36', source: 'Marius', target: 'Lt.Gillenormand' }, + { id: 'e37', source: 'Gillenormand', target: 'Lt.Gillenormand' }, + { id: 'e38', source: 'Cosette', target: 'Toussaint' }, + { id: 'e39', source: 'Javert', target: 'Toussaint' }, + { id: 'e40', source: 'Valjean', target: 'Toussaint' }, + // 随机添加更多连接 + ...Array.from({ length: 50 }, (_, i) => ({ + // 从40增加到50个随机连接 + id: `edge-${i + 41}`, + source: [ + 'Valjean', + 'Javert', + 'Cosette', + 'Marius', + 'Enjolras', + 'Fantine', + 'Thenardier', + 'Eponine', + 'Gavroche', + 'Gueulemer', + 'Babet', + 'Claquesous', + 'Favourite', + 'Tholomyes', + 'Simplice', + ][Math.floor(Math.random() * 15)], + target: [ + 'Favourite', + 'Dahlia', + 'Tholomyes', + 'Combeferre', + 'Prouvaire', + 'Feuilly', + 'Courfeyrac', + 'Bahorel', + 'Bossuet', + 'Montparnasse', + 'Brujon', + 'Child1', + 'Simplice', + 'Toussaint', + 'Gillenormand', + ][Math.floor(Math.random() * 15)], + })), + ], + }, + autoFit: 'view', + node: { + style: { + size: (datum) => datum.id.length * 2 + 10, + label: false, + labelText: (datum) => datum.id, + labelBackground: true, + icon: false, + iconFontFamily: 'iconfont', + iconText: '\ue6f6', + iconFill: '#fff', + }, + palette: { + type: 'group', + field: (datum) => datum.id, + color: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'], + }, + }, + edge: { + style: { + stroke: '#bfbfbf', + }, + }, + behaviors: ['drag-canvas'], + plugins: [ + { + type: 'fisheye', + key: 'fisheye', + r: 120, + d: 1.5, + nodeStyle: { + label: true, + icon: true, + }, + }, + ], + }, + { width: 600, height: 300 }, + (gui, graph) => { + const TRIGGER_OPTIONS = ['pointermove', 'drag', 'click']; + const SCALE_OPTIONS = ['wheel', 'drag', '-']; + + const options = { + type: 'fisheye', + trigger: 'pointermove', + r: 120, + d: 1.5, + maxR: 200, + minR: 50, + maxD: 5, + minD: 0.5, + scaleRBy: '-', + scaleDBy: '-', + showDPercent: true, + preventDefault: true, + }; + + const optionFolder = gui.addFolder('Fisheye Options'); + optionFolder.add(options, 'type').disable(true); + optionFolder.add(options, 'trigger', TRIGGER_OPTIONS); + optionFolder.add(options, 'r', 50, 200, 10); + optionFolder.add(options, 'd', 0.5, 5, 0.1); + optionFolder.add(options, 'scaleRBy', SCALE_OPTIONS); + optionFolder.add(options, 'scaleDBy', SCALE_OPTIONS); + optionFolder.add(options, 'showDPercent'); + optionFolder.add(options, 'preventDefault'); + + optionFolder.onChange(({ property, value }) => { + graph.updatePlugin({ + key: 'fisheye', + [property]: value === '-' ? undefined : value, + }); + graph.render(); + }); + }, +); +``` diff --git a/packages/site/docs/manual/plugin/build-in/Fisheye.en.md b/packages/site/docs/manual/plugin/build-in/Fisheye.en.md index a89e0524fe7..1a7430e11c9 100644 --- a/packages/site/docs/manual/plugin/build-in/Fisheye.en.md +++ b/packages/site/docs/manual/plugin/build-in/Fisheye.en.md @@ -2,158 +2,279 @@ title: Fisheye --- -Fisheye is designed for focus+context exploration, it keeps the context and the relationships between context and the focus while magnifying the focus area. +## Overview -## Options - -### Required type - -> _string_ - -Plugin type - -### d - -> _number_ **Default:** `1.5` - -Distortion factor - - - -### maxD - -> _number_ **Default:** `5` - -The maximum distortion factor that the fisheye lens can be adjusted, used with `scaleDBy` - -### maxR - -> _number_ **Default:** `画布宽高的最小值的一半` - -The maximum radius that the fisheye lens can be adjusted, used with `scaleRBy` - -### minD - -> _number_ **Default:** `0` - -The minimum distortion factor that the fisheye lens can be adjusted, used with `scaleDBy` - -### minR - -> _number_ **Default:** `0` - -The minimum radius that the fisheye lens can be adjusted, used with `scaleRBy` - -### nodeStyle - -> _NodeStyle_ _\| ((datum:_ [NodeData](/api/graph/option#nodedata)_) =>_ _NodeStyle\_\_)_ - -Node style in the fisheye lens - -### preventDefault - -> _boolean_ **Default:** `true` - -Whether to prevent the default event - -### r - -> _number_ **Default:** `120` - -The radius of the fisheye lens - - +The Fisheye plugin is designed for focus+context exploration scenarios. It can magnify the focus area while maintaining the context and the relationships between the context and the focus center, making it an important visualization exploration tool. -### scaleDBy +## Use Cases -> _'wheel' \| 'drag'_ +- Need to highlight certain areas during presentations +- Need to magnify local details while maintaining the overall view -The way to adjust the distortion factor of the fisheye lens +## Basic Usage -- `'wheel'`: adjust by wheel - -- `'drag'`: adjust by drag - -### scaleRBy - -> _'wheel' \| 'drag'_ - -The way to adjust the range radius of the fisheye lens - -- `'wheel'`: adjust by wheel - -- `'drag'`: adjust by drag - -If `trigger`, `scaleRBy`, and `scaleDBy` are set to `'drag'` at the same time, the priority order is `trigger` > `scaleRBy` > `scaleDBy`, and only the configuration item with the highest priority will be bound to the drag event. Similarly, if `scaleRBy` and `scaleDBy` are set to `'wheel'` at the same time, only `scaleRBy` will be bound to the wheel event - -### showDPercent - -> _boolean_ **Default:** `true` +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + trigger: 'drag', // Move fisheye by dragging + d: 1.5, // Set distortion factor + r: 120, // Set fisheye radius + showDPercent: true, // Show distortion percentage + }, + ], +}); +``` -Whether to display the value of the distortion factor in the fisheye lens +## Live Demo -### style + -> _CircleStyleProps_ +## Options -Fisheye Lens Style +| Property | Description | Type | Default | Required | +| -------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------- | +| type | Plugin type | string | `fisheye` | ✓ | +| trigger | The way to move the fisheye lens | `pointermove` \| `drag` \| `click` | `pointermove` | | +| r | The radius of the fisheye lens | number | 120 | | +| maxR | The maximum radius that the fisheye lens can be adjusted | number | half of the minimum canvas width/height | | +| minR | The minimum radius that the fisheye lens can be adjusted | number | 0 | | +| d | Distortion factor | number | 1.5 | | +| maxD | The maximum distortion factor that can be adjusted | number | 5 | | +| minD | The minimum distortion factor that can be adjusted | number | 0 | | +| scaleRBy | The way to adjust the range radius | `wheel` \| `drag` | - | | +| scaleDBy | The way to adjust the distortion factor | `wheel` \| `drag` | - | | +| showDPercent | Whether to display the distortion factor value | boolean | true | | +| style | Fisheye lens style | [CircleStyleProps](#circlestyleprops) | - | | +| nodeStyle | Node style in the fisheye lens | [NodeStyle](/en/manual/element/node/build-in/base-node#stylestyle-property-style) \| ((datum: [NodeData](/en/manual/data#node-data)) => [NodeStyle](/en/manual/element/node/build-in/base-node#stylestyle-property-style)) | `{ label: true }` | | +| preventDefault | Whether to prevent default events | boolean | true | | + +### CircleStyleProps + +Circle style properties, used to configure the appearance of the fisheye lens. + +| Property | Description | Type | Default | +| ------------- | --------------- | ----------------------------- | ------- | +| fill | Fill color | string \| Pattern \| null | - | +| stroke | Stroke color | string \| Pattern \| null | - | +| opacity | Overall opacity | number \| string | - | +| fillOpacity | Fill opacity | number \| string | - | +| strokeOpacity | Stroke opacity | number \| string | - | +| lineWidth | Line width | number \| string | - | +| lineCap | Line end style | `butt` \| `round` \| `square` | - | +| lineJoin | Line join style | `miter` \| `round` \| `bevel` | - | +| shadowColor | Shadow color | string | - | +| shadowBlur | Shadow blur | number | - | +| shadowOffsetX | Shadow X offset | number | - | +| shadowOffsetY | Shadow Y offset | number | - | ### trigger -> _'pointermove' \| 'drag' \| 'click'_ **Default:** `` - -The way to move the fisheye lens - -- `'pointermove'`: always follow the mouse movement - -- `'click'`: move when the mouse is clicked - -- `'drag'`: move by dragging - -## API - -### Fisheye.destroy() - -```typescript -destroy(): void; +The `trigger` property controls how the fisheye lens moves, supporting three configurations: + +- `'pointermove'`: The fisheye lens always follows mouse movement +- `'click'`: Move the fisheye lens to the clicked position +- `'drag'`: Move the fisheye lens by dragging + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + trigger: 'pointermove', // Follow mouse movement + // trigger: 'click', // Move on click + // trigger: 'drag', // Move by dragging + }, + ], +}); ``` -### Fisheye.update(options) - -```typescript -update(options: Partial): void; +### Zoom Control + +Use `scaleRBy` and `scaleDBy` to control how to adjust the radius and distortion factor of the fisheye lens: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + // Adjust radius by wheel + scaleRBy: 'wheel', + // Adjust distortion factor by dragging + scaleDBy: 'drag', + // Set range for radius and distortion factor + minR: 50, + maxR: 200, + minD: 1, + maxD: 3, + }, + ], +}); ``` -
View Parameters - - - -
- -Parameter - - +Note: When `trigger`, `scaleRBy`, and `scaleDBy` are all set to `'drag'`, the priority order is `trigger` > `scaleRBy` > `scaleDBy`, and only the highest priority configuration will be bound to the drag event. Similarly, if both `scaleRBy` and `scaleDBy` are set to `'wheel'`, only `scaleRBy` will be bound to the wheel event. -Type +## Code Examples - +### Basic Usage -Description +The simplest configuration: -
- -options - - - -Partial<[FisheyeOptions](#options)> - - +```js +const graph = new Graph({ + plugins: ['fisheye'], +}); +``` -
+### Custom Styles + +You can customize the appearance and behavior of the fisheye lens: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + r: 150, + d: 2, + style: { + fill: '#2f54eb', // Fill color of the fisheye area + fillOpacity: 0.2, // Fill opacity + stroke: '#1d39c4', // Border color of the fisheye + strokeOpacity: 0.8, // Border opacity + lineWidth: 1.5, // Border width + shadowColor: '#1d39c4', // Shadow color + shadowBlur: 10, // Shadow blur radius + shadowOffsetX: 0, // Shadow X offset + shadowOffsetY: 0, // Shadow Y offset + cursor: 'pointer', // Cursor style on hover + }, + nodeStyle: { + // Basic node styles + size: 40, // Node size + fill: '#d6e4ff', // Node fill color + stroke: '#2f54eb', // Node border color + lineWidth: 2, // Node border width + shadowColor: '#2f54eb', // Node shadow color + shadowBlur: 5, // Node shadow blur radius + cursor: 'pointer', // Cursor style on hover + + // Label styles + label: true, // Whether to show label + labelFontSize: 14, // Label font size + labelFontWeight: 'bold', // Label font weight + labelFill: '#1d39c4', // Label text color + labelBackground: true, // Whether to show label background + labelBackgroundFill: '#fff', // Label background fill color + labelBackgroundStroke: '#1d39c4', // Label background border color + labelBackgroundOpacity: 0.8, // Label background opacity + labelBackgroundPadding: [4, 8, 4, 8], // Label background padding [top,right,bottom,left] + + // Icon styles + icon: true, // Whether to show icon + iconFontFamily: 'iconfont', // Icon font family + iconText: '\ue6f6', // Icon Unicode + iconFill: '#1d39c4', // Icon color + iconSize: 16, // Icon size + iconFontWeight: 'normal', // Icon font weight + }, + }, + ], +}); +``` -**Returns**: +The effect is as follows: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + { id: 'node-1', style: { x: 150, y: 100 } }, + { id: 'node-2', style: { x: 250, y: 100 } }, + { id: 'node-3', style: { x: 200, y: 180 } }, + { id: 'node-4', style: { x: 120, y: 180 } }, + { id: 'node-5', style: { x: 280, y: 180 } }, + ], + edges: [ + { id: 'edge-1', source: 'node-1', target: 'node-2' }, + { id: 'edge-2', source: 'node-1', target: 'node-3' }, + { id: 'edge-3', source: 'node-2', target: 'node-3' }, + { id: 'edge-4', source: 'node-3', target: 'node-4' }, + { id: 'edge-5', source: 'node-3', target: 'node-5' }, + ], + }, + node: { + style: { + size: 30, + fill: '#e6f7ff', + stroke: '#1890ff', + lineWidth: 1, + label: false, + icon: false, + }, + }, + edge: { + style: { + stroke: '#91d5ff', + lineWidth: 1, + }, + }, + plugins: [ + { + type: 'fisheye', + key: 'fisheye', + r: 100, + d: 2, + style: { + fill: '#2f54eb', // Fill color of the fisheye area + fillOpacity: 0.2, // Fill opacity + stroke: '#1d39c4', // Border color of the fisheye + strokeOpacity: 0.8, // Border opacity + lineWidth: 1.5, // Border width + shadowColor: '#1d39c4', // Shadow color + shadowBlur: 10, // Shadow blur radius + shadowOffsetX: 0, // Shadow X offset + shadowOffsetY: 0, // Shadow Y offset + cursor: 'pointer', // Cursor style on hover + }, + nodeStyle: { + // Basic node styles + size: 40, // Node size + fill: '#d6e4ff', // Node fill color + stroke: '#2f54eb', // Node border color + lineWidth: 2, // Node border width + shadowColor: '#2f54eb', // Node shadow color + shadowBlur: 5, // Node shadow blur radius + cursor: 'pointer', // Cursor style on hover + + // Label styles + label: true, // Whether to show label + labelFontSize: 14, // Label font size + labelFontWeight: 'bold', // Label font weight + labelFill: '#1d39c4', // Label text color + labelBackground: true, // Whether to show label background + labelBackgroundFill: '#fff', // Label background fill color + labelBackgroundStroke: '#1d39c4', // Label background border color + labelBackgroundOpacity: 0.8, // Label background opacity + labelBackgroundPadding: [4, 8, 4, 8], // Label background padding [top,right,bottom,left] + + // Icon styles + icon: true, // Whether to show icon + iconFontFamily: 'iconfont', // Icon font family + iconText: '\ue6f6', // Icon Unicode + iconFill: '#1d39c4', // Icon color + iconSize: 16, // Icon size + iconFontWeight: 'normal', // Icon font weight + }, + }, + ], + }, + { width: 400, height: 300 }, +); +``` -- **Type:** void +## Examples -
+ diff --git a/packages/site/docs/manual/plugin/build-in/Fisheye.zh.md b/packages/site/docs/manual/plugin/build-in/Fisheye.zh.md index 7cfd97d08c8..0f4f1a0c735 100644 --- a/packages/site/docs/manual/plugin/build-in/Fisheye.zh.md +++ b/packages/site/docs/manual/plugin/build-in/Fisheye.zh.md @@ -2,129 +2,279 @@ title: Fisheye 鱼眼放大镜 --- -Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。 +## 概述 -**参考示例**: +鱼眼放大镜插件是为 focus+context 的探索场景设计的,它能够在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失,是一个重要的可视化探索工具。 -- [鱼眼放大镜](/examples/plugin/fisheye/#basic) -- [自定义鱼眼放大镜](/examples/plugin/fisheye/#custom) +## 使用场景 -## 配置项 - -### Required type - -> _`fisheye` \| string_ - -此插件已内置,你可以通过 `type: 'fisheye'` 来使用它。 - -### d - -> _number_ **Default:** `1.5` - -畸变因子 - - - -### maxD - -> _number_ **Default:** `5` - -鱼眼放大镜可调整的最大畸变因子,配合 `scaleDBy` 使用 - -### maxR - -> _number_ **Default:** `画布宽高的最小值的一半` - -鱼眼放大镜可调整的最大半径,配合 `scaleRBy` 使用 - -### minD - -> _number_ **Default:** `0` - -鱼眼放大镜可调整的最小畸变因子,配合 `scaleDBy` 使用 - -### minR - -> _number_ **Default:** `0` - -鱼眼放大镜可调整的最小半径,配合 `scaleRBy` 使用 - -### nodeStyle - -> _NodeStyle_ _\| ((datum:_ [NodeData](/manual/data#节点数据nodedata)_) =>_ _NodeStyle\_\_)_ - -在鱼眼放大镜中的节点样式 +- 在演示过程中需要突出展示某些区域内容 +- 需要局部放大查看细节时,同时又不想失去整体视图 -### preventDefault +## 基本用法 -> _boolean_ **Default:** `true` - -是否阻止默认事件 - -### r - -> _number_ **Default:** `120` - -鱼眼放大镜半径 - - - -### scaleDBy - -> _'wheel' \| 'drag'_ - -调整鱼眼放大镜畸变因子的方式 - -- `'wheel'`:滚轮调整 -- `'drag'`:拖拽调整 - -### scaleRBy - -> _'wheel' \| 'drag'_ - -调整鱼眼放大镜范围半径的方式 - -- `'wheel'`:滚轮调整 -- `'drag'`:拖拽调整 - -如果 `trigger`、`scaleRBy` 和 `scaleDBy` 同时设置为 `'drag'`,优先级顺序为 `trigger` > `scaleRBy` > `scaleDBy`,只会为优先级最高的配置项绑定拖拽事件。同理,如果 `scaleRBy` 和 `scaleDBy` 同时设置为 `'wheel'`,只会为 `scaleRBy` 绑定滚轮事件 - -### showDPercent - -> _boolean_ **Default:** `true` +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + trigger: 'drag', // 通过拖拽移动鱼眼 + d: 1.5, // 设置畸变因子 + r: 120, // 设置鱼眼半径 + showDPercent: true, // 显示畸变程度 + }, + ], +}); +``` -是否在鱼眼放大镜中显示畸变因子数值 +## 在线体验 -### style + -> _[CircleStyleProps](https://g.antv.antgroup.com/api/basic/circle)_ +## 配置项 -鱼眼放大镜样式 +| 属性 | 描述 | 类型 | 默认值 | 必选 | +| -------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | ---- | +| type | 插件类型 | string | `fisheye` | ✓ | +| trigger | 移动鱼眼放大镜的方式 | `pointermove` \| `drag` \| `click` | `pointermove` | | +| r | 鱼眼放大镜半径 | number | 120 | | +| maxR | 鱼眼放大镜可调整的最大半径 | number | 画布宽高的最小值的一半 | | +| minR | 鱼眼放大镜可调整的最小半径 | number | 0 | | +| d | 畸变因子 | number | 1.5 | | +| maxD | 鱼眼放大镜可调整的最大畸变因子 | number | 5 | | +| minD | 鱼眼放大镜可调整的最小畸变因子 | number | 0 | | +| scaleRBy | 调整鱼眼放大镜范围半径的方式 | `wheel` \| `drag` | - | | +| scaleDBy | 调整鱼眼放大镜畸变因子的方式 | `wheel` \| `drag` | - | | +| showDPercent | 是否在鱼眼放大镜中显示畸变因子数值 | boolean | true | | +| style | 鱼眼放大镜样式 | [CircleStyleProps](#circlestyleprops) | - | | +| nodeStyle | 在鱼眼放大镜中的节点样式 | [NodeStyle](/manual/element/node/build-in/base-node#style) \| ((datum: [NodeData](/manual/data#节点数据nodedata)) => [NodeStyle](/manual/element/node/build-in/base-node#style)) | `{ label: true }` | | +| preventDefault | 是否阻止默认事件 | boolean | true | | + +### CircleStyleProps + +圆形样式属性,用于配置鱼眼放大镜的外观。 + +| 属性 | 描述 | 类型 | 默认值 | +| ------------- | --------------- | ----------------------------- | ------ | +| fill | 填充颜色 | string \| Pattern \| null | - | +| stroke | 描边颜色 | string \| Pattern \| null | - | +| opacity | 整体透明度 | number \| string | - | +| fillOpacity | 填充透明度 | number \| string | - | +| strokeOpacity | 描边透明度 | number \| string | - | +| lineWidth | 线宽度 | number \| string | - | +| lineCap | 线段端点样式 | `butt` \| `round` \| `square` | - | +| lineJoin | 线段连接处样式 | `miter` \| `round` \| `bevel` | - | +| shadowColor | 阴影颜色 | string | - | +| shadowBlur | 阴影模糊程度 | number | - | +| shadowOffsetX | 阴影 X 方向偏移 | number | - | +| shadowOffsetY | 阴影 Y 方向偏移 | number | - | ### trigger -> _'pointermove' \| 'drag' \| 'click'_ **Default:** `` +`trigger` 属性用于控制鱼眼放大镜的移动方式,支持以下三种配置: + +- `'pointermove'`:鱼眼放大镜始终跟随鼠标移动 +- `'click'`:点击画布时移动鱼眼放大镜到点击位置 +- `'drag'`:通过拖拽方式移动鱼眼放大镜 + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + trigger: 'pointermove', // 跟随鼠标移动 + // trigger: 'click', // 点击移动 + // trigger: 'drag', // 拖拽移动 + }, + ], +}); +``` + +### 缩放控制 + +通过 `scaleRBy` 和 `scaleDBy` 可以分别控制鱼眼放大镜的半径和畸变因子的调整方式: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + // 通过滚轮调整半径 + scaleRBy: 'wheel', + // 通过拖拽调整畸变因子 + scaleDBy: 'drag', + // 设置半径和畸变因子的范围 + minR: 50, + maxR: 200, + minD: 1, + maxD: 3, + }, + ], +}); +``` -移动鱼眼放大镜的方式 +注意:当 `trigger`、`scaleRBy` 和 `scaleDBy` 同时设置为 `'drag'` 时,优先级顺序为 `trigger` > `scaleRBy` > `scaleDBy`,只会为优先级最高的配置项绑定拖拽事件。同理,如果 `scaleRBy` 和 `scaleDBy` 同时设置为 `'wheel'`,只会为 `scaleRBy` 绑定滚轮事件。 -- `'pointermove'`:始终跟随鼠标移动 -- `'click'`:鼠标点击时移动 -- `'drag'`:拖拽移动 +## 代码示例 -## API +### 基础用法 -### Fisheye.destroy() +最简单的配置方式: -```typescript -destroy(): void; +```js +const graph = new Graph({ + plugins: ['fisheye'], +}); ``` -### Fisheye.update(options) +### 自定义样式 + +可以自定义鱼眼放大镜的外观和行为: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'fisheye', + r: 150, + d: 2, + style: { + fill: '#2f54eb', // 鱼眼区域的填充颜色 + fillOpacity: 0.2, // 填充区域的透明度 + stroke: '#1d39c4', // 鱼眼边框的颜色 + strokeOpacity: 0.8, // 边框的透明度 + lineWidth: 1.5, // 边框的线宽 + shadowColor: '#1d39c4', // 阴影颜色 + shadowBlur: 10, // 阴影的模糊半径 + shadowOffsetX: 0, // 阴影的水平偏移 + shadowOffsetY: 0, // 阴影的垂直偏移 + cursor: 'pointer', // 鼠标悬停时的指针样式 + }, + nodeStyle: { + // 节点基础样式 + size: 40, // 节点大小 + fill: '#d6e4ff', // 节点填充颜色 + stroke: '#2f54eb', // 节点边框颜色 + lineWidth: 2, // 节点边框宽度 + shadowColor: '#2f54eb', // 节点阴影颜色 + shadowBlur: 5, // 节点阴影模糊半径 + cursor: 'pointer', // 鼠标悬停时的指针样式 + + // 标签样式 + label: true, // 是否显示标签 + labelFontSize: 14, // 标签字体大小 + labelFontWeight: 'bold', // 标签字体粗细 + labelFill: '#1d39c4', // 标签文字颜色 + labelBackground: true, // 是否显示标签背景 + labelBackgroundFill: '#fff', // 标签背景填充颜色 + labelBackgroundStroke: '#1d39c4', // 标签背景边框颜色 + labelBackgroundOpacity: 0.8, // 标签背景透明度 + labelBackgroundPadding: [4, 8, 4, 8], // 标签背景内边距 [上,右,下,左] + + // 图标样式 + icon: true, // 是否显示图标 + iconFontFamily: 'iconfont', // 图标字体 + iconText: '\ue6f6', // 图标的 Unicode 编码 + iconFill: '#1d39c4', // 图标颜色 + iconSize: 16, // 图标大小 + iconFontWeight: 'normal', // 图标字体粗细 + }, + }, + ], +}); +``` -```typescript -update(options: Partial): void; +效果如下: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + { id: 'node-1', style: { x: 150, y: 100 } }, + { id: 'node-2', style: { x: 250, y: 100 } }, + { id: 'node-3', style: { x: 200, y: 180 } }, + { id: 'node-4', style: { x: 120, y: 180 } }, + { id: 'node-5', style: { x: 280, y: 180 } }, + ], + edges: [ + { id: 'edge-1', source: 'node-1', target: 'node-2' }, + { id: 'edge-2', source: 'node-1', target: 'node-3' }, + { id: 'edge-3', source: 'node-2', target: 'node-3' }, + { id: 'edge-4', source: 'node-3', target: 'node-4' }, + { id: 'edge-5', source: 'node-3', target: 'node-5' }, + ], + }, + node: { + style: { + size: 30, + fill: '#e6f7ff', + stroke: '#1890ff', + lineWidth: 1, + label: false, + icon: false, + }, + }, + edge: { + style: { + stroke: '#91d5ff', + lineWidth: 1, + }, + }, + plugins: [ + { + type: 'fisheye', + key: 'fisheye', + r: 100, + d: 2, + style: { + fill: '#2f54eb', // 鱼眼区域的填充颜色 + fillOpacity: 0.2, // 填充区域的透明度 + stroke: '#1d39c4', // 鱼眼边框的颜色 + strokeOpacity: 0.8, // 边框的透明度 + lineWidth: 1.5, // 边框的线宽 + shadowColor: '#1d39c4', // 阴影颜色 + shadowBlur: 10, // 阴影的模糊半径 + shadowOffsetX: 0, // 阴影的水平偏移 + shadowOffsetY: 0, // 阴影的垂直偏移 + cursor: 'pointer', // 鼠标悬停时的指针样式 + }, + nodeStyle: { + // 节点基础样式 + size: 40, // 节点大小 + fill: '#d6e4ff', // 节点填充颜色 + stroke: '#2f54eb', // 节点边框颜色 + lineWidth: 2, // 节点边框宽度 + shadowColor: '#2f54eb', // 节点阴影颜色 + shadowBlur: 5, // 节点阴影模糊半径 + cursor: 'pointer', // 鼠标悬停时的指针样式 + + // 标签样式 + label: true, // 是否显示标签 + labelFontSize: 14, // 标签字体大小 + labelFontWeight: 'bold', // 标签字体粗细 + labelFill: '#1d39c4', // 标签文字颜色 + labelBackground: true, // 是否显示标签背景 + labelBackgroundFill: '#fff', // 标签背景填充颜色 + labelBackgroundStroke: '#1d39c4', // 标签背景边框颜色 + labelBackgroundOpacity: 0.8, // 标签背景透明度 + labelBackgroundPadding: [4, 8, 4, 8], // 标签背景内边距 [上,右,下,左] + + // 图标样式 + icon: true, // 是否显示图标 + iconFontFamily: 'iconfont', // 图标字体 + iconText: '\ue6f6', // 图标的 Unicode 编码 + iconFill: '#1d39c4', // 图标颜色 + iconSize: 16, // 图标大小 + iconFontWeight: 'normal', // 图标字体粗细 + }, + }, + ], + }, + { width: 400, height: 300 }, +); ``` -| 参数 | 类型 | 描述 | 默认值 | 必选 | -| ------- | ---------------------------------- | ------ | ------ | ---- | -| options | Partial<[FisheyeOptions](#配置项)> | 配置项 | - | ✓ | +## 实际案例 + + From a5ae240805336bbfb6eeb89af3f93c56c15d8121 Mon Sep 17 00:00:00 2001 From: jiangyu Date: Tue, 1 Apr 2025 21:19:09 +0800 Subject: [PATCH 06/26] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96Watermark?= =?UTF-8?q?=E6=B0=B4=E5=8D=B0=E7=9A=84=E6=96=87=E6=A1=A3=20(#6963)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 添加 Snapline 插件文档和示例,更新中文和英文手册内容 * docs: 更新 Snapline 插件文档,优化使用场景描述和样式配置示例 * feat: 新增鱼眼放大镜插件文档,包含概述、使用场景、基本用法及配置项详细说明,更新中英文手册内容。 * docs: 更新鱼眼放大镜插件文档,增加样式属性和节点样式的详细说明,优化示例代码和中文翻译,删除API部分内容; * docs: 更新水印插件文档,增加概述、使用场景、基本用法及配置项详细说明,优化示例代码和中英文翻译。 --------- Co-authored-by: liujiangyu --- .../manual/plugin/build-in/Watermark.en.md | 268 ++++++++--------- .../manual/plugin/build-in/Watermark.zh.md | 271 ++++++++---------- 2 files changed, 240 insertions(+), 299 deletions(-) diff --git a/packages/site/docs/manual/plugin/build-in/Watermark.en.md b/packages/site/docs/manual/plugin/build-in/Watermark.en.md index 6ac8d36bf2d..5308163331c 100644 --- a/packages/site/docs/manual/plugin/build-in/Watermark.en.md +++ b/packages/site/docs/manual/plugin/build-in/Watermark.en.md @@ -2,160 +2,134 @@ title: Watermark --- -Support using text and image as watermark, the principle is to add the `background-image` property to the div of the Graph container, and then you can control the position and style of the watermark through css. For text, it will be converted to an image using a hidden canvas +## Overview - - -## Options - -### Required type - -> _string_ - -Plugin type - -### backgroundAttachment - -> _string_ - -The background attachment of watermark - -### backgroundBlendMode - -> _string_ - -The background blend of watermark - -### backgroundClip - -> _string_ - -The background clip of watermark - -### backgroundColor - -> _string_ - -The background color of watermark - -### backgroundImage - -> _string_ - -The background image of watermark - -### backgroundOrigin - -> _string_ - -The background origin of watermark - -### backgroundPosition - -> _string_ - -The background position of watermark - -### backgroundPositionX - -> _string_ - -The background position-x of watermark - -### backgroundPositionY - -> _string_ - -The background position-y of watermark - -### backgroundRepeat - -> _string_ **Default:** `'repeat'` - -The background repeat of watermark +The Watermark plugin supports using text and images as watermarks. It works by adding a `background-image` property to the Graph container's div, allowing control of watermark position and style through CSS. For text watermarks, it converts text to images using a hidden canvas. -### backgroundSize +## Use Cases -> _string_ +The Watermark plugin is mainly used in the following scenarios: -The background size of watermark +- Add copyright or ownership identification to graphs +- Mark graph status during presentations or previews +- Add anti-leak markers to sensitive data -### height +## Basic Usage -> _number_ **Default:** `100` +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', // watermark text + opacity: 0.2, // opacity + rotate: Math.PI / 12, // rotation angle + }, + ], +}); +``` -The height of watermark(single) +## Live Demo -### imageURL - -> _string_ - -The image url, if it has a value, it will be used, otherwise it will use the text - -### opacity - -> _number_ **Default:** `0.2` - -The opacity of watermark - -### rotate - -> _number_ **Default:** `Math.PI / 12` - -The rotate angle of watermark - -### text - -> _string_ - -The text of watermark - -### textAlign - -> _'center' \| 'end' \| 'left' \| 'right' \| 'start'_ **Default:** `'center'` - -The text align of text watermark - -### textBaseline - -> _'alphabetic' \| 'bottom' \| 'hanging' \| 'ideographic' \| 'middle' \| 'top'_ **Default:** `'middle'` - -The text baseline of text watermark - -### textFill - -> _string_ **Default:** `'#000'` - -The color of text watermark - -### textFontFamily - -> _string_ - -The font of text watermark - -### textFontSize - -> _number_ **Default:** `16` - -The font size of text watermark - -### textFontVariant - -> _string_ - -The font variant of text watermark - -### textFontWeight - -> _string_ - -The font weight of text watermark - -### width - -> _number_ **Default:** `200` + -The width of watermark(single) +## Options -## API +| Property | Description | Type | Default | Required | +| -------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- | ------------ | -------- | +| type | Plugin type | string | `watermark` | ✓ | +| width | Width of single watermark | number | 200 | | +| height | Height of single watermark | number | 100 | | +| opacity | Opacity of watermark | number | 0.2 | | +| rotate | Rotation angle of watermark | number | Math.PI / 12 | | +| imageURL | Image URL for watermark, takes precedence over text | string | - | | +| text | Watermark text content | string | - | | +| textFill | Color of text watermark | string | `#000` | | +| textFontSize | Font size of text watermark | number | 16 | | +| textFontFamily | Font family of text watermark | string | - | | +| textFontWeight | Font weight of text watermark | string | - | | +| textFontVariant | Font variant of text watermark | string | - | | +| textAlign | Text alignment of watermark | `center` \| `end` \| `left` \| `right` \| `start` | `center` | | +| textBaseline | Text baseline of watermark | `alphabetic` \| `bottom` \| `hanging` \| `ideographic` \| `middle` \| `top` | `middle` | | +| backgroundRepeat | Repeat pattern of watermark | string | `repeat` | | +| backgroundAttachment | Background attachment behavior | string | - | | +| backgroundBlendMode | Background blend mode | string | - | | +| backgroundClip | Background clip | string | - | | +| backgroundColor | Background color | string | - | | +| backgroundImage | Background image | string | - | | +| backgroundOrigin | Background origin | string | - | | +| backgroundPosition | Background position | string | - | | +| backgroundPositionX | Horizontal background position | string | - | | +| backgroundPositionY | Vertical background position | string | - | | +| backgroundSize | Background size | string | - | | + +## Code Examples + +### Text Watermark + +Simplest text watermark configuration: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', + }, + ], +}); +``` + + + +### Image Watermark + +Using an image as watermark: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + imageURL: 'https://example.com/logo.png', + width: 100, + height: 50, + opacity: 0.1, + }, + ], +}); +``` + + + +### Custom Style + +Customize watermark style and position: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', + textFontSize: 20, // Set font size + textFontFamily: 'Arial', // Set font family + textFontWeight: 'bold', // Set font weight + textFill: '#1890ff', // Set text color + rotate: Math.PI / 6, // Set rotation angle + opacity: 0.15, // Set opacity + width: 180, // Set watermark width + height: 100, // Set watermark height + backgroundRepeat: 'space', // Set repeat pattern + backgroundPosition: 'center', // Set position + textAlign: 'center', // Set text alignment + textBaseline: 'middle', // Set baseline alignment + }, + ], +}); +``` + +## Examples + +- [Text Watermark](/en/examples/plugin/watermark/#text) +- [Image Watermark](/en/examples/plugin/watermark/#repeat) diff --git a/packages/site/docs/manual/plugin/build-in/Watermark.zh.md b/packages/site/docs/manual/plugin/build-in/Watermark.zh.md index 325074f1711..ead6e03d491 100644 --- a/packages/site/docs/manual/plugin/build-in/Watermark.zh.md +++ b/packages/site/docs/manual/plugin/build-in/Watermark.zh.md @@ -2,165 +2,132 @@ title: Watermark 水印 --- -支持使用文本和图片作为水印,实现原理是在 Graph 容器的 div 上加上 `background-image` 属性,然后就可以通过 css 来控制水印的位置和样式。对于文本,会使用隐藏 canvas 转成图片的方式来实现 +## 概述 - - -**参考示例**: - -- [文本水印](/examples/plugin/watermark/#text) -- [图片水印](/examples/plugin/watermark/#repeat) - -## 配置项 - -### Required type - -> _`watermark` \| string_ - -此插件已内置,你可以通过 `type: 'watermark'` 来使用它。 - -### backgroundAttachment - -> _string_ - -水印的背景定位行为 - -### backgroundBlendMode - -> _string_ - -水印的背景混合 - -### backgroundClip - -> _string_ - -水印的背景裁剪 - -### backgroundColor - -> _string_ - -水印的背景颜色 - -### backgroundImage - -> _string_ - -水印的背景图片 - -### backgroundOrigin - -> _string_ - -水印的背景原点 - -### backgroundPosition - -> _string_ - -水印的背景位置 - -### backgroundPositionX - -> _string_ - -水印的背景位置-x - -### backgroundPositionY - -> _string_ - -水印的背景位置-y - -### backgroundRepeat - -> _string_ **Default:** `'repeat'` +水印插件支持使用文本和图片作为水印,实现原理是在 Graph 容器的 div 上加上 `background-image` 属性,然后通过 CSS 来控制水印的位置和样式。对于文本水印,会使用隐藏 canvas 将文本转换为图片的方式来实现。 -水印的背景重复 +## 使用场景 -### backgroundSize +- 为图表添加版权或所有权标识 +- 在演示或预览时标记图表的状态 +- 为敏感数据添加防泄露标记 -> _string_ +## 基本用法 -水印的背景大小 +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', // 水印文本 + opacity: 0.2, // 透明度 + rotate: Math.PI / 12, // 旋转角度 + }, + ], +}); +``` -### height +## 在线体验 -> _number_ **Default:** `100` - -水印的高度(单个) - -### imageURL - -> _string_ - -图片地址,如果有值,则使用,否则使用文本 - -### opacity - -> _number_ **Default:** `0.2` - -水印的透明度 - -### rotate - -> _number_ **Default:** `Math.PI / 12` - -水印的旋转角度 - -### text - -> _string_ - -水印文本 - -### textAlign - -> _'center' \| 'end' \| 'left' \| 'right' \| 'start'_ **Default:** `'center'` - -文本水印的文本对齐方式 - -### textBaseline - -> _'alphabetic' \| 'bottom' \| 'hanging' \| 'ideographic' \| 'middle' \| 'top'_ **Default:** `'middle'` - -文本水印的文本对齐基线 - -### textFill - -> _string_ **Default:** `'#000'` - -文本水印的文字颜色 - -### textFontFamily - -> _string_ - -文本水印的文本字体 - -### textFontSize - -> _number_ **Default:** `16` - -文本水印的文本大小 - -### textFontVariant - -> _string_ - -文本水印的文本字体变体 - -### textFontWeight - -> _string_ - -文本水印的文本字体粗细 - -### width + -> _number_ **Default:** `200` +## 配置项 -水印的宽度(单个) +| 属性 | 描述 | 类型 | 默认值 | 必选 | +| -------------------- | ---------------------------------- | --------------------------------------------------------------------------- | ------------ | ---- | +| type | 插件类型 | string | `watermark` | ✓ | +| width | 单个水印的宽度 | number | 200 | | +| height | 单个水印的高度 | number | 100 | | +| opacity | 水印的透明度 | number | 0.2 | | +| rotate | 水印的旋转角度 | number | Math.PI / 12 | | +| imageURL | 图片水印的地址,优先级高于文本水印 | string | - | | +| text | 水印文本内容 | string | - | | +| textFill | 文本水印的颜色 | string | `#000` | | +| textFontSize | 文本水印的字体大小 | number | 16 | | +| textFontFamily | 文本水印的字体 | string | - | | +| textFontWeight | 文本水印的字体粗细 | string | - | | +| textFontVariant | 文本水印的字体变体 | string | - | | +| textAlign | 文本水印的对齐方式 | `center` \| `end` \| `left` \| `right` \| `start` | `center` | | +| textBaseline | 文本水印的基线对齐方式 | `alphabetic` \| `bottom` \| `hanging` \| `ideographic` \| `middle` \| `top` | `middle` | | +| backgroundRepeat | 水印的重复方式 | string | `repeat` | | +| backgroundAttachment | 水印的背景定位行为 | string | - | | +| backgroundBlendMode | 水印的背景混合模式 | string | - | | +| backgroundClip | 水印的背景裁剪 | string | - | | +| backgroundColor | 水印的背景颜色 | string | - | | +| backgroundImage | 水印的背景图片 | string | - | | +| backgroundOrigin | 水印的背景原点 | string | - | | +| backgroundPosition | 水印的背景位置 | string | - | | +| backgroundPositionX | 水印的背景水平位置 | string | - | | +| backgroundPositionY | 水印的背景垂直位置 | string | - | | +| backgroundSize | 水印的背景大小 | string | - | | + +## 代码示例 + +### 文本水印 + +最简单的文本水印配置: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', + }, + ], +}); +``` + + + +### 图片水印 + +使用图片作为水印: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + imageURL: 'https://example.com/logo.png', + width: 100, + height: 50, + opacity: 0.1, + }, + ], +}); +``` + + + +### 自定义样式 + +可以自定义水印的样式和位置: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'watermark', + text: 'G6 Graph', + textFontSize: 20, // 设置字体大小 + textFontFamily: 'Arial', // 设置字体 + textFontWeight: 'bold', // 设置字体粗细 + textFill: '#1890ff', // 设置文字颜色 + rotate: Math.PI / 6, // 设置旋转角度 + opacity: 0.15, // 设置透明度 + width: 180, // 设置水印宽度 + height: 100, // 设置水印高度 + backgroundRepeat: 'space', // 设置重复方式 + backgroundPosition: 'center', // 设置位置 + textAlign: 'center', // 设置文本对齐 + textBaseline: 'middle', // 设置基线对齐 + }, + ], +}); +``` + +## 实际案例 -## API +- [文本水印](/examples/plugin/watermark/#text) +- [图片水印](/examples/plugin/watermark/#repeat) From 8254624eec497b70f313e254e36056c8cd5baeb0 Mon Sep 17 00:00:00 2001 From: ChiGao Date: Tue, 1 Apr 2025 21:19:18 +0800 Subject: [PATCH 07/26] docs: update antv-dagre layout document (#6965) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix #6904 Co-authored-by: 秦奇 --- .../site/docs/manual/layout/build-in/AntvDagreLayout.zh.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md b/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md index e572f2e6868..bfe629ec068 100644 --- a/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md +++ b/packages/site/docs/manual/layout/build-in/AntvDagreLayout.zh.md @@ -29,8 +29,8 @@ const graph = new Graph({ Dagre 布局配置项图解 -| 属性 | 描述 | 类型 | 默认值 | 必选 | -| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -------------------------- | ---- | --- | +| 属性 | 描述 | 类型 | 默认值 | 必选 | +| -------------- | ---------- | ---------- | --------------- | ---- | | type | 布局类型 | `antv-dagre` | - | ✓ | | rankdir | 布局方向,可选值 | `TB` \| `BT` \| `LR` \| `RL` | `TB` | | | align | 节点对齐方式,可选值 | `UL` \| `UR` \| `DL` \| `DR` | `UL` | | From 888d6a79c74d640c273c26b29b419a175980cc02 Mon Sep 17 00:00:00 2001 From: avrinfly Date: Thu, 3 Apr 2025 10:02:30 +0800 Subject: [PATCH 08/26] =?UTF-8?q?docs(CreateEdge):=20=E6=94=B9=E9=80=A0?= =?UTF-8?q?=E6=96=87=E6=A1=A3=20[=E4=BA=A4=E4=BA=92=20-=20=E5=86=85?= =?UTF-8?q?=E7=BD=AE=E4=BA=A4=E4=BA=92=20-=20CreateEdge=20=E5=88=9B?= =?UTF-8?q?=E5=BB=BA=E8=BE=B9]=20(#6962)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(CreateEdge): 改造文档 [交互 - 内置交互 - CreateEdge 创建边] * docs: 创建边中文文档更新 * docs: 内置交互-createEdge创建边文档删除销毁交互实例部分 --- .../manual/behavior/build-in/CreateEdge.zh.md | 118 ++++++++++++++---- 1 file changed, 92 insertions(+), 26 deletions(-) diff --git a/packages/site/docs/manual/behavior/build-in/CreateEdge.zh.md b/packages/site/docs/manual/behavior/build-in/CreateEdge.zh.md index 84310b486f7..816ad69b103 100644 --- a/packages/site/docs/manual/behavior/build-in/CreateEdge.zh.md +++ b/packages/site/docs/manual/behavior/build-in/CreateEdge.zh.md @@ -2,52 +2,118 @@ title: CreateEdge 创建边 --- -通过拖拽或点击节点创建边,支持自定义边样式。 +## 概述 - - -## 配置项 +CreateEdge 是 G6 中用于实现画布中交互式创建边(Edge)的内置交互。用户触发交互(点击或拖拽)后,边会随鼠标移动,连接到目标节点即完成创建,若取消则自动移除。 -### Required type +此外,该交互支持自定义边的样式,如颜色、线条样式、箭头等,以适应不同的可视化需求。 -> _`create-edge` \| string_ +该交互支持连接的元素为 `node` 和 `combo`。 -此插件已内置,你可以通过 `type: 'create-edge'` 来使用它。 +## 使用场景 -### enable +这一交互主要用于: -> _boolean \| ((event:_ [Event](/api/event#事件对象属性)_) => boolean)_ **Default:** `true` +- 需要交互式创建节点间连接关系的可视化场景,如流程图、知识图谱等 -是否启用创建边的功能 +## 在线体验 -### onCreate + -> _(edge: [EdgeData](/manual/data#边数据edgedata)) =>_ [EdgeData](/manual/data#边数据edgedata)) +## 基本用法 + +在图配置中添加这一交互 + +```javascript +// 使用默认配置 +const graph = new Graph({ + // 其他配置... + behaviors: ['create-edge'], // 直接添加,使用默认配置 +}); + +// 或使用自定义配置 +const graph = new Graph({ + // 其他配置 + behaviors: [ + { + type: 'create-edge', + trigger: 'click', // 交互配置,通过点击创建边 + style: {} // 边自定义样式 + } + ] +}); -创建边回调,返回边数据 +``` -### onFinish +## 配置项 -> _(edge: [EdgeData](/manual/data#边数据edgedata)) => void_ +| 配置项 | 说明 | 类型 | 默认值 | 必选 | +| -------------- | -------------------------------------------------------- | ------------ | ------------ | ---- | +| type | 交互类型名称 | string | `create-edge` | √ | +| trigger | 触发新建边的方式,支持点击(click)或拖拽(drag) | string | `drag` | | +| enable | 是否启用该交互 | boolean \| ((event:_ [Event](/api/event#事件对象属性)_) => boolean) | true | | +| onCreate | 创建边回调函数,返回边数据 | (edge: [EdgeData](/manual/data#边数据edgedata)) => [EdgeData](/manual/data#边数据edgedata)) | - | | +| onFinish | 成功创建边回调函数 | (edge: [EdgeData](/manual/data#边数据edgedata)) => void | - | | +| style | 新建边的样式[配置项](/manual/element/edge/build-in/base-edge#style) | edgeStyle: object | - | | -成功创建边回调 ### style -> _EdgeStyle_ +`style` 该交互创建出的边的配置项,可以配置边的样式,类型参考 [边的style配置](/manual/element/edge/build-in/base-edge#style) -新建边的样式配置 - -### trigger +```javascript +{ + style: { + stroke: 'red', + lineWidth: 2 + } +} +``` -> _'click' \| 'drag'_ **Default:** `'drag'` +## 代码示例 -交互配置 点击 或 拖拽 +### 基础创建边功能 -## API +```javascript +const graph = new Graph({ + container: 'container', + width: 800, + height: 600, + behaviors: ['create-edge'], +}) +``` -### CreateEdge.destroy() +### 自定义创建边功能 + +```javascript +const graph = new Graph({ + // 其他配置, + behaviors: [ + { + type: 'create-edge', + style: { + stroke: red, + lineWidth: 3 + } + } + ] +}) +``` -```typescript -destroy(): void; +### 使用点击创建边 + +```javascript +const graph = new Graph({ + // 其他配置 + behaviors: [ + { + type: 'create-edge', + trigger: 'click' + } + ] +}) ``` + +## 实际案例 + + From a38f1405376b08f823c7890085cf946dcfde408c Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Thu, 3 Apr 2025 11:22:39 +0800 Subject: [PATCH 09/26] chore(site): optimize ssr in production env (#6970) --- packages/site/.dumirc.ts | 8 +------- packages/site/.gitignore | 3 +++ packages/site/mako.config.json | 7 ------- packages/site/package.json | 6 +++--- 4 files changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/site/.dumirc.ts b/packages/site/.dumirc.ts index 9de0143b2fe..be3ff4e6be8 100644 --- a/packages/site/.dumirc.ts +++ b/packages/site/.dumirc.ts @@ -3,13 +3,7 @@ import { version } from '../g6/package.json'; import { homepage, repository } from './package.json'; export default defineConfig({ - ssr: - process.env.NODE_ENV === 'production' - ? { - builder: 'mako', - } - : false, - mako: {}, + ...(process.env.NODE_ENV === 'production' ? { ssr: { builder: 'webpack', mako: false } } : { ssr: false, mako: {} }), locales: [ { id: 'zh', name: '中文' }, { id: 'en', name: 'English' }, diff --git a/packages/site/.gitignore b/packages/site/.gitignore index 777b0145cc4..e7f0743e030 100644 --- a/packages/site/.gitignore +++ b/packages/site/.gitignore @@ -7,3 +7,6 @@ docs/api/.gitignore /server + +# Dead links report +dead-links-report.log diff --git a/packages/site/mako.config.json b/packages/site/mako.config.json index a8c034356c1..13e9af1e11f 100644 --- a/packages/site/mako.config.json +++ b/packages/site/mako.config.json @@ -2,12 +2,5 @@ "optimization": { "skipModules": false, "concatenateModules": false - }, - "hmr": false, - "devtool": false, - "dynamicImportToRequire": true, - "moduleIdStrategy": "hashed", - "experimental": { - "magicComment": true } } diff --git a/packages/site/package.json b/packages/site/package.json index 0cb4d01c97a..51d7dbae481 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -40,7 +40,7 @@ "dependencies": { "@ant-design/icons": "^5.6.1", "@antv/algorithm": "^0.1.26", - "@antv/dumi-theme-antv": "^0.7.2", + "@antv/dumi-theme-antv": "^0.7.3", "@antv/g": "^6.1.21", "@antv/g-svg": "^2.0.34", "@antv/g-webgl": "^2.0.44", @@ -56,8 +56,8 @@ "dumi": "^2.4.17", "insert-css": "^2.0.0", "lodash": "^4.17.21", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "stats.js": "^0.17.0", "styled-components": "^6.1.15" }, From 099752c5b970322944a5cd6e30af035701a0479e Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Thu, 3 Apr 2025 16:38:27 +0800 Subject: [PATCH 10/26] docs: update theme overview and custom theme docs (#6939) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 更新主题总览和自定义主题文档 * docs: update toc --- packages/site/.dumirc.ts | 22 +- packages/site/docs/manual/data.zh.md | 2 +- .../site/docs/manual/theme/custom-theme.zh.md | 172 +++-- .../site/docs/manual/theme/overview.zh.md | 615 ++++++++++++++++++ 4 files changed, 730 insertions(+), 81 deletions(-) diff --git a/packages/site/.dumirc.ts b/packages/site/.dumirc.ts index be3ff4e6be8..3742fa08c8f 100644 --- a/packages/site/.dumirc.ts +++ b/packages/site/.dumirc.ts @@ -127,7 +127,7 @@ export default defineConfig({ { slug: 'manual/graph', title: { - zh: '图 - Graph', + zh: '图 Graph', en: 'Graph', }, order: 3, @@ -135,7 +135,7 @@ export default defineConfig({ { slug: 'manual/element', title: { - zh: '元素 - Element', + zh: '元素 Element', en: 'Element', }, order: 5, @@ -143,7 +143,7 @@ export default defineConfig({ { slug: 'manual/element/node', title: { - zh: '节点', + zh: '节点 Node', en: 'Node', }, order: 3, @@ -159,7 +159,7 @@ export default defineConfig({ { slug: 'manual/element/edge', title: { - zh: '边', + zh: '边 Edge', en: 'Edge', }, order: 4, @@ -175,7 +175,7 @@ export default defineConfig({ { slug: 'manual/element/combo', title: { - zh: '组合', + zh: '组合 Combo', en: 'Combo', }, order: 5, @@ -199,7 +199,7 @@ export default defineConfig({ { slug: 'manual/layout', title: { - zh: '布局 - Layout', + zh: '布局 Layout', en: 'Layout', }, order: 5, @@ -215,7 +215,7 @@ export default defineConfig({ { slug: 'manual/behavior', title: { - zh: '交互 - Behavior', + zh: '交互 Behavior', en: 'Behavior', }, order: 6, @@ -231,7 +231,7 @@ export default defineConfig({ { slug: 'manual/plugin', title: { - zh: '插件 - Plugin', + zh: '插件 Plugin', en: 'Plugin', }, order: 7, @@ -247,7 +247,7 @@ export default defineConfig({ { slug: 'manual/transform', title: { - zh: '数据处理 - Transform', + zh: '数据处理 Transform', en: 'Transform', }, order: 8, @@ -263,7 +263,7 @@ export default defineConfig({ { slug: 'manual/theme', title: { - zh: '主题 - Theme', + zh: '主题 Theme', en: 'Theme', }, order: 9, @@ -271,7 +271,7 @@ export default defineConfig({ { slug: 'manual/animation', title: { - zh: '动画 - Animation', + zh: '动画 Animation', en: 'Animation', }, order: 10, diff --git a/packages/site/docs/manual/data.zh.md b/packages/site/docs/manual/data.zh.md index 8e2aae52f69..9999ea1e857 100644 --- a/packages/site/docs/manual/data.zh.md +++ b/packages/site/docs/manual/data.zh.md @@ -1,5 +1,5 @@ --- -title: 数据 - Data +title: 数据 Data order: 4 --- diff --git a/packages/site/docs/manual/theme/custom-theme.zh.md b/packages/site/docs/manual/theme/custom-theme.zh.md index 526c66d2cf0..50f1b5b488b 100644 --- a/packages/site/docs/manual/theme/custom-theme.zh.md +++ b/packages/site/docs/manual/theme/custom-theme.zh.md @@ -3,95 +3,129 @@ title: 自定义主题 order: 2 --- -## 概述 +除了使用内置主题外,G6 还支持创建自定义主题来满足特定的视觉需求。本文将介绍如何创建和使用自定义主题。 -G6 中的主题是 Graph Options 的子集,它包含了关于画布和元素样式的配置。主题可以帮助你快速地切换不同的图样式。 +## 创建自定义主题 -## 定制主题 +一个自定义主题需要遵循主题的基本结构,包含画布背景色和元素样式配置: -对于元素样式来说,主题中的配置都是静态的,不支持使用回调函数动态计算样式,同时 `type` 也不支持在主题中配置。一个主题包含以下配置: +```javascript +const customTheme = { + // 1. 画布背景色 + background: '#f0f0f0', -- `background` 画布背景颜色 -- `node` 节点样式 -- `edge` 边样式 -- `combo` 组合样式 - -下面是一个简单的主题配置示例: - -```typescript -const theme = { - background: '#fff', + // 2. 节点配置 node: { + // 调色板配置 + palette: { + type: 'group', + color: ['#1783FF', '#00C9C9' /* 自定义颜色... */], + }, + // 基础样式 style: { - fill: '#e1f3fe', - lineWidth: 0, + fill: '#fff', + stroke: '#d9d9d9', + lineWidth: 1, + // ... 其他节点样式 }, - selected: { - style: { - fill: '#3b71d6', - lineWidth: 1, + // 状态样式 + state: { + selected: { + fill: '#e8f3ff', + stroke: '#1783FF', }, + // ... 其他状态样式 }, }, + + // 3. 边配置 edge: { - // ... - }, - combo: { - // ... + style: { + stroke: '#d9d9d9', + lineWidth: 1, + // ... 其他边样式 + }, + state: { + // ... 状态样式 + }, }, -}; -``` - -❌ 错误示例 -```typescript -const theme = { - node: { - // ❌ 主题不支持配置元素类型 - type: 'rect', + // 4. Combo 配置 + combo: { style: { - // ❌ 主题不支持回调函数 - fill: (d) => d.style.color, + fill: '#f7f7f7', + stroke: '#d9d9d9', + // ... 其他 Combo 样式 + }, + state: { + // ... 状态样式 }, }, }; ``` -:::warning{title=注意} -对于元素状态样式来说,请确保状态样式中的每一个属性在默认样式(style)中都有对应的默认样式,否则可能会导致无法清除状态样式。 -::: - -## 注册主题 - -通过 G6 提供的 register 方法注册即可,下面是一个示例: - -```typescript +## 使用限制 + +在创建自定义主题时,需要注意以下限制: + +1. **仅支持静态值** + + ```javascript + // ❌ 错误示例:不支持回调函数 + const theme = { + node: { + style: { + fill: (d) => d.style.color, + }, + }, + }; + ``` + +2. **不支持配置元素类型** + + ```javascript + // ❌ 错误示例:不支持在主题中配置元素类型 + const theme = { + node: { + type: 'rect', + style: { + fill: '#fff', + }, + }, + }; + ``` + +3. **状态样式需要对应默认样式** + ```javascript + // ✅ 正确示例:状态样式的属性在默认样式中都有定义 + const theme = { + node: { + style: { + fill: '#fff', + stroke: '#000', + }, + state: { + selected: { + fill: '#e8f3ff', + stroke: '#1783FF', + }, + }, + }, + }; + ``` + +## 应用自定义主题 + +先注册主题,然后通过名称引用: + +```javascript +// 1. 注册主题 import { register, ExtensionCategory } from '@antv/g6'; +register(ExtensionCategory.THEME, 'custom-theme', customTheme); -register(ExtensionCategory.THEME, 'custom-theme', theme); -``` - -## 配置主题 - -要启用并配置主题,需要在实例化 `Graph` 时传入 `theme` 配置项: - -```typescript -{ +// 2. 使用主题 +const graph = new Graph({ theme: 'custom-theme', -} -``` - -### 切换主题 - -在 `Graph` 实例化后可以通过 [setTheme](/api/theme#graphsetthemetheme) 方法切换主题: - -```typescript -graph.setTheme('dark'); -``` - -此外你还可以通过 [getTheme](/api/theme#graphgettheme) 方法获取当前主题: - -```typescript -graph.getTheme(); -// => 'dark' + // ... 其他配置 +}); ``` diff --git a/packages/site/docs/manual/theme/overview.zh.md b/packages/site/docs/manual/theme/overview.zh.md index 67e50ae1ec6..91101610628 100644 --- a/packages/site/docs/manual/theme/overview.zh.md +++ b/packages/site/docs/manual/theme/overview.zh.md @@ -4,3 +4,618 @@ order: 1 --- ## 概述 + +G6 中的主题是 Graph Options 的子集,它包含了关于画布和元素样式的配置。多主题可以帮助你快速地切换不同的图样式。 + + + +## 主题结构 + +一个主题由以下四个部分组成: + +1. **画布背景色 (background)** + + - 控制整个画布的背景颜色 + +2. **节点配置 (node)** + + - 基础样式:填充色、描边、标签等静态视觉属性 + - [调色板](/manual/theme/palette):用于节点分组的颜色配置 + - 状态样式:不同状态下的样式配置(选中、激活、禁用等) + - 动画配置:节点的动画效果配置 + +3. **边配置 (edge)** + + - 基础样式:线条样式、箭头、标签等静态视觉属性 + - [调色板](/manual/theme/palette):用于边分组的颜色配置 + - 状态样式:不同状态下的样式配置 + - 动画配置:边的动画效果配置 + +4. **Combo 配置 (combo)** + - 基础样式:填充、描边、折叠按钮等静态视觉属性 + - 状态样式:不同状态下的样式配置 + - 动画配置:Combo 的动画效果配置 + +> 注意:主题中的样式配置仅支持静态值,不支持回调函数形式的动态配置。如需动态样式,请使用图的配置项。 + +## 内置主题 + +G6 默认提供两种内置主题: + +### 亮色主题(默认) + +亮色主题 + +
查看亮色主题完整配置项 + +```js +const lightTheme = { + background: '#ffffff', + node: { + palette: { + type: 'group', + color: [ + '#1783FF', + '#00C9C9', + '#F08F56', + '#D580FF', + '#7863FF', + '#DB9D0D', + '#60C42D', + '#FF80CA', + '#2491B3', + '#17C76F', + ], + }, + style: { + donutOpacity: 1, + badgeBackgroundOpacity: 1, + badgeFill: '#fff', + badgeFontSize: 8, + badgePadding: [0, 4], + badgePalette: ['#7E92B5', '#F4664A', '#FFBE3A'], + fill: '#1783ff', + fillOpacity: 1, + halo: false, + iconFill: '#fff', + iconOpacity: 1, + labelBackground: false, + labelBackgroundFill: '#ffffff', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelFill: '#000000', + labelFillOpacity: 0.85, + labelLineHeight: 16, + labelPadding: [0, 2], + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + labelOffsetY: 2, + lineWidth: 0, + portFill: '#1783ff', + portLineWidth: 1, + portStroke: '#000000', + portStrokeOpacity: 0.65, + size: 32, + stroke: '#000000', + strokeOpacity: 1, + zIndex: 2, + }, + state: { + selected: { + halo: true, + haloLineWidth: 24, + haloStrokeOpacity: 0.25, + labelFontSize: 12, + labelFontWeight: 'bold', + lineWidth: 4, + stroke: '#000000', + }, + active: { + halo: true, + haloLineWidth: 12, + haloStrokeOpacity: 0.15, + }, + highlight: { + labelFontWeight: 'bold', + lineWidth: 4, + stroke: '#000000', + strokeOpacity: 0.85, + }, + inactive: { + badgeBackgroundOpacity: 0.25, + donutOpacity: 0.25, + fillOpacity: 0.25, + iconOpacity: 0.85, + labelFill: '#000000', + labelFillOpacity: 0.25, + strokeOpacity: 0.25, + }, + disabled: { + badgeBackgroundOpacity: 0.25, + donutOpacity: 0.06, + fill: '#1B324F', + fillOpacity: 0.06, + iconFill: '#1B324F', + iconOpacity: 0.25, + labelFill: '#000000', + labelFillOpacity: 0.25, + strokeOpacity: 0.06, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + show: 'fade', + hide: 'fade', + expand: 'node-expand', + collapse: 'node-collapse', + update: [{ fields: ['x', 'y', 'fill', 'stroke'] }], + translate: [{ fields: ['x', 'y'] }], + }, + }, + edge: { + palette: { + type: 'group', + color: [ + '#99ADD1', + '#1783FF', + '#00C9C9', + '#F08F56', + '#D580FF', + '#7863FF', + '#DB9D0D', + '#60C42D', + '#FF80CA', + '#2491B3', + '#17C76F', + ], + }, + style: { + badgeBackgroundFill: '#99ADD1', + badgeFill: '#fff', + badgeFontSize: 8, + badgeOffsetX: 10, + fillOpacity: 1, + halo: false, + haloLineWidth: 12, + haloStrokeOpacity: 1, + increasedLineWidthForHitTesting: 2, + labelBackground: false, + labelBackgroundFill: '#ffffff', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelBackgroundPadding: [4, 4, 4, 4], + labelFill: '#000000', + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + labelPlacement: 'center', + labelTextBaseline: 'middle', + lineWidth: 1, + stroke: '#99ADD1', + strokeOpacity: 1, + zIndex: 1, + }, + state: { + selected: { + halo: true, + haloStrokeOpacity: 0.25, + labelFontSize: 14, + labelFontWeight: 'bold', + lineWidth: 3, + }, + active: { + halo: true, + haloStrokeOpacity: 0.15, + }, + highlight: { + labelFontWeight: 'bold', + lineWidth: 3, + }, + inactive: { + stroke: '#1B324F', + fillOpacity: 0.08, + labelOpacity: 0.25, + strokeOpacity: 0.08, + badgeBackgroundOpacity: 0.25, + }, + disabled: { + stroke: '#d9d9d9', + fillOpacity: 0.45, + strokeOpacity: 0.45, + labelOpacity: 0.25, + badgeBackgroundOpacity: 0.45, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + expand: 'path-in', + collapse: 'path-out', + show: 'fade', + hide: 'fade', + update: [{ fields: ['sourceNode', 'targetNode'] }, { fields: ['stroke'], shape: 'key' }], + translate: [{ fields: ['sourceNode', 'targetNode'] }], + }, + }, + combo: { + style: { + collapsedMarkerFill: '#ffffff', + collapsedMarkerFontSize: 12, + collapsedMarkerFillOpacity: 1, + collapsedSize: 32, + collapsedFillOpacity: 1, + fill: '#99ADD1', + halo: false, + haloLineWidth: 12, + haloStroke: '#99ADD1', + haloStrokeOpacity: 0.25, + labelBackground: false, + labelBackgroundFill: '#ffffff', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelBackgroundPadding: [2, 4, 2, 4], + labelFill: '#000000', + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + lineDash: 0, + lineWidth: 1, + fillOpacity: 0.04, + strokeOpacity: 1, + padding: 10, + stroke: '#99ADD1', + }, + state: { + selected: { + halo: true, + labelFontSize: 14, + labelFontWeight: 700, + lineWidth: 4, + }, + active: { + halo: true, + }, + highlight: { + labelFontWeight: 700, + lineWidth: 4, + }, + inactive: { + fillOpacity: 0.65, + labelOpacity: 0.25, + strokeOpacity: 0.65, + }, + disabled: { + fill: '#d9d9d9', + fillOpacity: 0.25, + labelOpacity: 0.25, + stroke: '#d9d9d9', + strokeOpacity: 0.25, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + show: 'fade', + hide: 'fade', + expand: 'combo-expand', + collapse: 'combo-collapse', + update: [{ fields: ['x', 'y'] }, { fields: ['fill', 'stroke', 'lineWidth'], shape: 'key' }], + translate: [{ fields: ['x', 'y'] }], + }, + }, +}; +``` + +
+ +### 暗色主题 + +暗色主题 + +
查看暗色主题完整配置项 + +```js +const darkTheme = { + background: '#000000', + node: { + palette: { + type: 'group', + color: [ + '#1783FF', + '#00C9C9', + '#F08F56', + '#D580FF', + '#7863FF', + '#DB9D0D', + '#60C42D', + '#FF80CA', + '#2491B3', + '#17C76F', + ], + }, + style: { + donutOpacity: 1, + badgeBackgroundOpacity: 1, + badgeFill: '#fff', + badgeFontSize: 8, + badgePadding: [0, 4], + badgePalette: ['#7E92B5', '#F4664A', '#FFBE3A'], + fill: '#1783ff', + fillOpacity: 1, + halo: false, + iconFill: '#fff', + iconOpacity: 1, + labelBackground: false, + labelBackgroundFill: '#000000', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelFill: '#ffffff', + labelFillOpacity: 0.85, + labelLineHeight: 16, + labelPadding: [0, 2], + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + labelOffsetY: 2, + lineWidth: 0, + portFill: '#1783ff', + portLineWidth: 1, + portStroke: '#d0e4ff', + portStrokeOpacity: 0.65, + size: 32, + stroke: '#d0e4ff', + strokeOpacity: 1, + zIndex: 2, + }, + state: { + selected: { + halo: true, + haloLineWidth: 24, + haloStrokeOpacity: 0.45, + labelFontSize: 12, + labelFontWeight: 'bold', + lineWidth: 4, + stroke: '#d0e4ff', + }, + active: { + halo: true, + haloLineWidth: 12, + haloStrokeOpacity: 0.25, + }, + highlight: { + labelFontWeight: 'bold', + lineWidth: 4, + stroke: '#d0e4ff', + strokeOpacity: 0.85, + }, + inactive: { + badgeBackgroundOpacity: 0.45, + donutOpacity: 0.45, + fillOpacity: 0.45, + iconOpacity: 0.45, + labelFill: '#ffffff', + labelFillOpacity: 0.45, + strokeOpacity: 0.45, + }, + disabled: { + badgeBackgroundOpacity: 0.25, + donutOpacity: 0.25, + fill: '#D0E4FF', + fillOpacity: 0.25, + iconFill: '#D0E4FF', + iconOpacity: 0.25, + labelFill: '#ffffff', + labelFillOpacity: 0.25, + strokeOpacity: 0.25, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + show: 'fade', + hide: 'fade', + expand: 'node-expand', + collapse: 'node-collapse', + update: [{ fields: ['x', 'y', 'fill', 'stroke'] }], + translate: [{ fields: ['x', 'y'] }], + }, + }, + edge: { + palette: { + type: 'group', + color: [ + '#637088', + '#0F55A6', + '#008383', + '#9C5D38', + '#8B53A6', + '#4E40A6', + '#8F6608', + '#3E801D', + '#A65383', + '#175E75', + '#0F8248', + ], + }, + style: { + badgeBackgroundFill: '#637088', + badgeFill: '#fff', + badgeFontSize: 8, + badgeOffsetX: 10, + fillOpacity: 1, + halo: false, + haloLineWidth: 12, + haloStrokeOpacity: 1, + increasedLineWidthForHitTesting: 2, + labelBackground: false, + labelBackgroundFill: '#000000', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelBackgroundPadding: [4, 4, 4, 4], + labelFill: '#ffffff', + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + labelPlacement: 'center', + labelTextBaseline: 'middle', + lineWidth: 1, + stroke: '#637088', + strokeOpacity: 1, + zIndex: 1, + }, + state: { + selected: { + halo: true, + haloStrokeOpacity: 0.25, + labelFontSize: 14, + labelFontWeight: 'bold', + lineWidth: 3, + }, + active: { + halo: true, + haloStrokeOpacity: 0.15, + }, + highlight: { + labelFontWeight: 'bold', + lineWidth: 3, + }, + inactive: { + stroke: '#D0E4FF', + fillOpacity: 0.08, + labelOpacity: 0.25, + strokeOpacity: 0.08, + badgeBackgroundOpacity: 0.25, + }, + disabled: { + stroke: '#637088', + fillOpacity: 0.45, + strokeOpacity: 0.45, + labelOpacity: 0.25, + badgeBackgroundOpacity: 0.45, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + expand: 'path-in', + collapse: 'path-out', + show: 'fade', + hide: 'fade', + update: [{ fields: ['sourceNode', 'targetNode'] }, { fields: ['stroke'], shape: 'key' }], + translate: [{ fields: ['sourceNode', 'targetNode'] }], + }, + }, + combo: { + style: { + collapsedMarkerFill: '#000000', + collapsedMarkerFontSize: 12, + collapsedMarkerFillOpacity: 1, + collapsedSize: 32, + collapsedFillOpacity: 1, + fill: '#fdfdfd', + halo: false, + haloLineWidth: 12, + haloStroke: '#99add1', + haloStrokeOpacity: 0.25, + labelBackground: false, + labelBackgroundFill: '#000000', + labelBackgroundLineWidth: 0, + labelBackgroundOpacity: 0.75, + labelBackgroundPadding: [2, 4, 2, 4], + labelFill: '#ffffff', + labelFontSize: 12, + labelFontWeight: 400, + labelOpacity: 1, + lineDash: 0, + lineWidth: 1, + fillOpacity: 0.04, + strokeOpacity: 1, + padding: 10, + stroke: '#99add1', + }, + state: { + selected: { + halo: true, + labelFontSize: 14, + labelFontWeight: 700, + lineWidth: 4, + }, + active: { + halo: true, + }, + highlight: { + labelFontWeight: 700, + lineWidth: 4, + }, + inactive: { + fillOpacity: 0.65, + labelOpacity: 0.25, + strokeOpacity: 0.65, + }, + disabled: { + fill: '#d0e4ff', + fillOpacity: 0.25, + labelOpacity: 0.25, + stroke: '#969696', + strokeOpacity: 0.25, + }, + }, + animation: { + enter: 'fade', + exit: 'fade', + show: 'fade', + hide: 'fade', + expand: 'combo-expand', + collapse: 'combo-collapse', + update: [{ fields: ['x', 'y'] }, { fields: ['fill', 'stroke', 'lineWidth'], shape: 'key' }], + translate: [{ fields: ['x', 'y'] }], + }, + }, +}; +``` + +
+ +## 使用主题 + +### 配置主题 + +在创建图时通过 `theme` 选项指定要使用的主题: + +```javascript +const graph = new Graph({ + theme: 'light', // 或 'dark' + // ... 其他配置 +}); +``` + +### 切换主题 + +创建图后,可以通过 `setTheme` 方法动态切换主题: + +```javascript +// 切换到暗色主题 +graph.setTheme('dark'); + +// 获取当前主题 +const currentTheme = graph.getTheme(); // 'dark' +``` + +## 样式优先级 + +在 G6 中,元素的最终样式由多个层级的样式合并而成,按优先级从低到高排序: + +**⭐️ 主题默认样式** < 调色板样式 < 数据样式 < 图的默认样式 < **⭐️ 主题状态样式** < 图的状态样式 + +详细说明: + +1. **主题默认样式**:主题系统提供的基础样式 +2. **调色板样式**:基于主题调色板配置的自动着色样式 +3. **数据样式**:在数据中定义的样式 +4. **图的默认样式**:通过图的配置项设置的样式 +5. **主题状态样式**:主题中定义的状态样式 +6. **图的状态样式**:通过图的配置项设置的状态样式 + +更多关于自定义主题的内容,请参考[自定义主题](/manual/theme/custom-theme)。 From 39f034a016dcfd286b39b6309e5d96163b19d1ba Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Thu, 3 Apr 2025 18:22:10 +0800 Subject: [PATCH 11/26] chore: pin dumi version to 2.4.17 (#6973) --- packages/site/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/site/package.json b/packages/site/package.json index 51d7dbae481..0e034c3bd2f 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -53,7 +53,7 @@ "@antv/layout-wasm": "^1.4.2", "@antv/util": "^3.3.10", "antd": "^5.24.3", - "dumi": "^2.4.17", + "dumi": "2.4.17", "insert-css": "^2.0.0", "lodash": "^4.17.21", "react": "^19.1.0", From f372054e50d3915440bec1cdc6752f00c9473a7c Mon Sep 17 00:00:00 2001 From: markleo Date: Mon, 31 Mar 2025 13:14:58 +0800 Subject: [PATCH 12/26] fix: (iadd demon prorogress) --- .../docs/manual/element/node/react-node.zh.md | 6 + .../element/custom-node/demo/meta.json | 8 ++ .../custom-node/demo/reactnode-idcard.jsx | 104 ++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 100644 packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx diff --git a/packages/site/docs/manual/element/node/react-node.zh.md b/packages/site/docs/manual/element/node/react-node.zh.md index f8e3918ab7b..46a3dc18d0f 100644 --- a/packages/site/docs/manual/element/node/react-node.zh.md +++ b/packages/site/docs/manual/element/node/react-node.zh.md @@ -2,3 +2,9 @@ title: 使用 React 定义节点 order: 5 --- + +## 在线测试 + + + + diff --git a/packages/site/examples/element/custom-node/demo/meta.json b/packages/site/examples/element/custom-node/demo/meta.json index d9ad9e02ce3..1fea117ce42 100644 --- a/packages/site/examples/element/custom-node/demo/meta.json +++ b/packages/site/examples/element/custom-node/demo/meta.json @@ -27,6 +27,14 @@ "en": "G2 activity Chart" }, "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GVyoQKk2WIIAAAAAAAAAAAAADmJ7AQ/original" + }, + { + "filename": "reactnode-idcard.jsx", + "title": { + "zh": "React 节点 身份证", + "en": "React node IDCard" + }, + "screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*GVyoQKk2WIIAAAAAAAAAAAAADmJ7AQ/original" } ] } diff --git a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx new file mode 100644 index 00000000000..e8d944cbf16 --- /dev/null +++ b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx @@ -0,0 +1,104 @@ +// reactnode-idcard.js +import React from 'react'; +import { useEffect, useRef } from 'react'; +import { createRoot } from 'react-dom/client'; +//import ReactDOM from 'react-dom'; +import { Graph } from '@antv/g6'; +import { ExtensionCategory, register } from '@antv/g6'; +import { ReactNode } from '@antv/g6-extension-react'; +import { Card, Typography } from 'antd'; + +const { Title, Text } = Typography; + +// 定义自定义节点组件 +const IDCardNode = ({ id, data }) => { + const { name, idNumber, address } = data.data; + + console.log('IDCardNode props:', id, data); + + return ( + +
+ + {name} + + ID Number: + {idNumber} + Address: + {address} +
+
+ ); +}; + +// 注册自定义节点 +register(ExtensionCategory.NODE, 'id-card', ReactNode); + +// 定义 Graph 数据 +const data = { + nodes: [ + { + id: 'node1', + data: { + name: '张三', + idNumber: '11010519491231002X', + address: '北京市朝阳区', + }, + style: { x: 50, y: 50 }, + }, + { + id: 'node2', + data: { + name: '李四', + idNumber: '11010519500101001X', + address: '上海市浦东新区', + }, + style: { x: 500, y: 100 }, + }, + ], + edges: [ + { + source: 'node1', + target: 'node2', + }, + ], +}; + +export const ReactNodeDemo = () => { + const containerRef = useRef(); + + useEffect(() => { + // 创建 Graph 实例 + const graph = new Graph({ + container: containerRef.current, + width: 800, + height: 600, + data, + node: { + type: 'react', + style: { + size: [200, 80], + component: (data) => , + }, + }, + behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'], + }); + + // 渲染 Graph + graph.render(); + }, []); + + return
; +}; + + +// 渲染 React 组件到 DOM +const root = createRoot(document.getElementById('container')); +root.render(); From 770244c5a177f08c9486261b2f92e19a7e6db6bd Mon Sep 17 00:00:00 2001 From: markleo Date: Mon, 31 Mar 2025 19:25:14 +0800 Subject: [PATCH 13/26] fix: docs/update-custom-react-node --- .../docs/manual/element/node/react-node.zh.md | 112 +++++++++++++++++- .../custom-node/demo/reactnode-idcard.jsx | 79 ++++++++---- 2 files changed, 164 insertions(+), 27 deletions(-) diff --git a/packages/site/docs/manual/element/node/react-node.zh.md b/packages/site/docs/manual/element/node/react-node.zh.md index 46a3dc18d0f..78842ecc5d8 100644 --- a/packages/site/docs/manual/element/node/react-node.zh.md +++ b/packages/site/docs/manual/element/node/react-node.zh.md @@ -3,8 +3,116 @@ title: 使用 React 定义节点 order: 5 --- +## 简介 + +在数据可视化领域,为高效率使用 AntV G6,节点定义可采用 React 组件的方式。AntV G6 功能强大但原生节点定义处理复杂交互和状态管理有挑战。 + +### ReactNode 和 GNode 方式自定义 G6 节点 + +#### ReactNode推荐 + +React Node 是指借助 React 框架来定义 G6 节点。 +这种方式能把 React 组件的优势发挥到极致。React 以组件化开发闻名,使用 React 定义节点可提升代码复用性,减少重复工作,提高开发效率。其强大的状态管理能力便于处理节点的各种交互状态,比如点击、悬停、拖拽等,能让节点交互体验更加流畅。并且 React 拥有庞大的生态系统,有丰富的工具和库可供使用,能轻松为节点添加复杂的样式和交互逻辑,满足多样化的业务需求。 + +#### GNode + +G Node 是基于 G 图形库来定义 G6 节点。 +G 是一个高性能的图形渲染引擎,在 G6 中使用 G 来定义节点,能实现高效的图形渲染。G 提供了丰富的图形绘制 API,可直接对节点的图形元素进行精细控制,例如绘制复杂的几何形状、设置样式等。这种方式更侧重于底层的图形操作,在需要对节点图形进行高度定制化时具有很大优势,能够满足对节点外观和性能有严格要求的场景。 + +### 使用React自定义节点优势 + +React 组件化、状态管理能力强,将其用于 AntV G6 节点定义,能结合二者优势。可进行组件复用,提升开发效率;轻松处理节点交互状态,优化用户体验。 + +- 提高开发效率:React 以组件化开发著称,这使得节点定义可以复用。对于相同类型的节点,只需创建一次组件,就能在不同地方重复使用,减少了重复代码的编写,极大地提高了开发效率。 +- 增强可维护性:组件化结构使代码逻辑更加清晰,每个组件都有明确的职责。当需要修改某个节点的样式或功能时,只需找到对应的组件进行修改,不会影响到其他部分的代码,降低了维护成本。 +- 便于状态管理:React 拥有强大的状态管理能力,能轻松处理节点的各种交互状态,如点击、拖拽、悬停等。通过状态的变化,实时更新节点的显示效果,为用户带来流畅的交互体验。 +- 丰富的生态系统:React 拥有庞大的生态系统,有大量可用的工具和库。可以将这些工具和库与 AntV G6 结合使用,为节点添加更多的功能,如动画效果、数据可视化等,满足复杂的业务需求。 +- 易于集成:React 作为前端开发的主流框架,与其他前端技术和工具的集成非常方便。可以将使用 React 定义的 AntV G6 节点轻松集成到现有的项目中,与其他组件协同工作,提高项目的整体开发效率。 + +## 使用步骤 + +### 安装 + +```bash +npm install @antv/g6-extension-react +``` + +### 自定义React节点 + +ReactNode方式 + +```jsx +import { ReactNode } from '@antv/g6-extension-react'; + +const MyReactNode = () => { + return
node
; +}; +``` + +GNode方式 + +```jsx +import { Group, Rect, Text } from '@antv/g6-extension-react'; + +const GNode = () => { + return + + + +}; +``` + +### 注册节点 + +```jsx +import { ExtensionCategory, register } from '@antv/g6'; +import { ReactNode } from '@antv/g6-extension-react'; + +register(ExtensionCategory.NODE, 'react', ReactNode); +``` + +### 使用节点 + +使用自定义的ReactNode节点: + +```jsx +const graph = new Graph({ + // ... other options + node: { + type: 'react', + style: { + component: () => , + }, + }, +}); +``` + +使用自定义的GNode节点: + +```jsx +const graph = new Graph({ + // ... other options + node: { + type: 'g', + style: { + component: () => , + }, + }, +}); +``` + ## 在线测试 - +
+ + +
- + diff --git a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx index e8d944cbf16..abff70f2a2c 100644 --- a/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx +++ b/packages/site/examples/element/custom-node/demo/reactnode-idcard.jsx @@ -1,39 +1,59 @@ -// reactnode-idcard.js -import React from 'react'; -import { useEffect, useRef } from 'react'; +// reactnode-idcard.jsx +import React, { useEffect, useRef, useState } from 'react'; import { createRoot } from 'react-dom/client'; -//import ReactDOM from 'react-dom'; import { Graph } from '@antv/g6'; import { ExtensionCategory, register } from '@antv/g6'; import { ReactNode } from '@antv/g6-extension-react'; -import { Card, Typography } from 'antd'; +import { Card, Typography, Button } from 'antd'; const { Title, Text } = Typography; // 定义自定义节点组件 const IDCardNode = ({ id, data }) => { - const { name, idNumber, address } = data.data; + const { name, idNumber, address, expanded, graph } = data; + const [isExpanded, setIsExpanded] = useState(expanded || false); + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + graph.updateNodeData([{ + id, + data: { + ...data, + expanded: !isExpanded, + }, + }]); + }; console.log('IDCardNode props:', id, data); return ( + + {name} + + + + } style={{ width: 250, padding: 10, borderColor: '#ddd', }} > -
- - {name} - - ID Number: - {idNumber} - Address: - {address} -
+ {isExpanded ? ( +
+ ID Number: + {idNumber} + Address: + {address} +
+ ) : ( + IDCard Information + )}
); }; @@ -47,18 +67,20 @@ const data = { { id: 'node1', data: { - name: '张三', - idNumber: '11010519491231002X', - address: '北京市朝阳区', + name: 'Alice', + idNumber: 'IDUSAASD2131734', + address: '1234 Broadway, Apt 5B, New York, NY 10001', + expanded: false, // 初始状态为收缩 }, style: { x: 50, y: 50 }, }, { id: 'node2', data: { - name: '李四', - idNumber: '11010519500101001X', - address: '上海市浦东新区', + name: 'Bob', + idNumber: 'IDUSAASD1431920', + address: '3030 Chestnut St, Philadelphia, PA 19104', + expanded: false, // 初始状态为收缩 }, style: { x: 500, y: 100 }, }, @@ -73,6 +95,7 @@ const data = { export const ReactNodeDemo = () => { const containerRef = useRef(); + const graphRef = useRef(null); useEffect(() => { // 创建 Graph 实例 @@ -84,8 +107,8 @@ export const ReactNodeDemo = () => { node: { type: 'react', style: { - size: [200, 80], - component: (data) => , + size: [250, 120], // 调整大小以适应内容 + component: (data) => , }, }, behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'], @@ -93,12 +116,18 @@ export const ReactNodeDemo = () => { // 渲染 Graph graph.render(); + + // 保存 graph 实例 + graphRef.current = graph; + + return () => { + graph.destroy(); + }; }, []); return
; }; - // 渲染 React 组件到 DOM const root = createRoot(document.getElementById('container')); root.render(); From 5dd7d57bfaff24f0fbc6c842e263b444eada8f1c Mon Sep 17 00:00:00 2001 From: markleo Date: Thu, 3 Apr 2025 19:09:34 +0800 Subject: [PATCH 14/26] fix: docs/update-custom-node-react --- .../docs/manual/element/node/react-node.zh.md | 157 ++++++++--- .../custom-node/demo/reactnode-idcard.jsx | 255 +++++++++++++++++- 2 files changed, 360 insertions(+), 52 deletions(-) diff --git a/packages/site/docs/manual/element/node/react-node.zh.md b/packages/site/docs/manual/element/node/react-node.zh.md index 78842ecc5d8..a97493c0d52 100644 --- a/packages/site/docs/manual/element/node/react-node.zh.md +++ b/packages/site/docs/manual/element/node/react-node.zh.md @@ -5,62 +5,65 @@ order: 5 ## 简介 -在数据可视化领域,为高效率使用 AntV G6,节点定义可采用 React 组件的方式。AntV G6 功能强大但原生节点定义处理复杂交互和状态管理有挑战。 +在数据可视化领域,为高效率使用 AntV G6,节点定义可采用 React 组件的方式。 +为了让用户能更加方便地进行react自定义节点,我们提供了g6-extension-react库。 -### ReactNode 和 GNode 方式自定义 G6 节点 +### g6-extension-react -#### ReactNode推荐 +g6-extension-react 是 AntV G6 图可视化库的一个扩展,它将 React 框架与 G6 进行了集成,使得开发者能够使用 React 组件来定义和渲染 G6 中的节点、边等图元素。通过这种方式,开发者可以充分利用 React 的组件化开发模式、状态管理能力以及丰富的生态系统来构建复杂且交互性强的图可视化应用。 -React Node 是指借助 React 框架来定义 G6 节点。 -这种方式能把 React 组件的优势发挥到极致。React 以组件化开发闻名,使用 React 定义节点可提升代码复用性,减少重复工作,提高开发效率。其强大的状态管理能力便于处理节点的各种交互状态,比如点击、悬停、拖拽等,能让节点交互体验更加流畅。并且 React 拥有庞大的生态系统,有丰富的工具和库可供使用,能轻松为节点添加复杂的样式和交互逻辑,满足多样化的业务需求。 +#### 优点 -#### GNode +- 组件化开发:React 以组件化开发著称,使用 g6-extension-react 可以将图中的节点和边封装成独立的 React 组件。这有助于提高代码的复用性,减少重复开发工作。例如,在一个复杂的图可视化应用中,可能有多种类型的节点,但部分节点的基本样式和交互逻辑是相同的,此时可以将这些共性封装成一个 React 组件,在不同地方复用。 +- 状态管理:React 提供了强大的状态管理机制,如 useState、useReducer 等钩子函数,以及 Redux、MobX 等外部状态管理库。在 g6-extension-react 中,可以利用这些机制轻松管理图元素的状态,实现动态更新。比如,当用户点击某个节点时,可以通过修改节点组件的状态来改变其样式,如颜色、大小等。 +- 丰富的生态系统:React 拥有庞大的生态系统,有众多的开源组件和工具可供使用。在 g6-extension-react 中,可以引入这些组件和工具来增强图元素的功能。例如,可以使用 react-icons 库为节点添加图标,使用 react-transition-group 库实现节点的动画效果。 +- 易于集成:由于 g6-extension-react 基于 React 开发,它可以很方便地集成到现有的 React 项目中。开发者可以利用现有的 React 开发流程和工具链,快速搭建图可视化应用。 -G Node 是基于 G 图形库来定义 G6 节点。 -G 是一个高性能的图形渲染引擎,在 G6 中使用 G 来定义节点,能实现高效的图形渲染。G 提供了丰富的图形绘制 API,可直接对节点的图形元素进行精细控制,例如绘制复杂的几何形状、设置样式等。这种方式更侧重于底层的图形操作,在需要对节点图形进行高度定制化时具有很大优势,能够满足对节点外观和性能有严格要求的场景。 +## 使用步骤 -### 使用React自定义节点优势 +### 准备工作 -React 组件化、状态管理能力强,将其用于 AntV G6 节点定义,能结合二者优势。可进行组件复用,提升开发效率;轻松处理节点交互状态,优化用户体验。 +1、在使用 g6-extension-react 之前,请确保已经安装并创建React项目 +2、react版本要求:>=16.8.0 +3、如果使用tTypeScrit,需要tsconfig.json文件支持jsx语法 -- 提高开发效率:React 以组件化开发著称,这使得节点定义可以复用。对于相同类型的节点,只需创建一次组件,就能在不同地方重复使用,减少了重复代码的编写,极大地提高了开发效率。 -- 增强可维护性:组件化结构使代码逻辑更加清晰,每个组件都有明确的职责。当需要修改某个节点的样式或功能时,只需找到对应的组件进行修改,不会影响到其他部分的代码,降低了维护成本。 -- 便于状态管理:React 拥有强大的状态管理能力,能轻松处理节点的各种交互状态,如点击、拖拽、悬停等。通过状态的变化,实时更新节点的显示效果,为用户带来流畅的交互体验。 -- 丰富的生态系统:React 拥有庞大的生态系统,有大量可用的工具和库。可以将这些工具和库与 AntV G6 结合使用,为节点添加更多的功能,如动画效果、数据可视化等,满足复杂的业务需求。 -- 易于集成:React 作为前端开发的主流框架,与其他前端技术和工具的集成非常方便。可以将使用 React 定义的 AntV G6 节点轻松集成到现有的项目中,与其他组件协同工作,提高项目的整体开发效率。 +```json +{ + "compilerOptions": { + "jsx": "react-jsx" + } +} +``` -## 使用步骤 +### 安装依赖 -### 安装 +npm 方式安装: ```bash npm install @antv/g6-extension-react ``` -### 自定义React节点 +yard 方式安装: -ReactNode方式 +```bash +yarn add @antv/g6-extension-react +``` -```jsx -import { ReactNode } from '@antv/g6-extension-react'; +pnpm方式安装: -const MyReactNode = () => { - return
node
; -}; +```bash +pnpm add @antv/g6-extension-react ``` -GNode方式 +### 自定义React节点 -```jsx -import { Group, Rect, Text } from '@antv/g6-extension-react'; +````jsx +import { ReactNode } from '@antv/g6-extension-react'; -const GNode = () => { - return - - - +const MyReactNode = () => { + return
node
; }; -``` + ### 注册节点 @@ -69,7 +72,7 @@ import { ExtensionCategory, register } from '@antv/g6'; import { ReactNode } from '@antv/g6-extension-react'; register(ExtensionCategory.NODE, 'react', ReactNode); -``` +```` ### 使用节点 @@ -87,18 +90,94 @@ const graph = new Graph({ }); ``` -使用自定义的GNode节点: +## 状态和交互事件 + +### 状态 + +我们可以通过data传入状态,以便节点展示不同的样式。 + +#### 示例: + +通过data添加selected参数,实现节点选中和取消选中的样式变化。 + +graph实例所需data中传递: + +```json +const data = { + nodes: [ + { + id: 'node1', + data: { + ... // other data + selected: true, // selcted status + }, + }, + ... + ] +} +``` + +自定义节点内展示: + +```jsx +const MyReactNode = ({ data }) => { + return ( + + ... +} +``` + +### 交互事件 + +我们可以传递回调函数,在节点上与图实例进行交互。 + +#### 示例: + +通过data传递回调事件,实现通过自定义节点操作图数据。 + +注册节点: +通过在props定义接受回调函数 + +```json +const IDCardNode = ({ id, data, onSelectChange }) => { + ... + const handleSelect = () => { + onSelectChange() + } + + return ( + ... + + + + + {name} @@ -39,18 +103,26 @@ const IDCardNode = ({ id, data }) => { } style={{ - width: 250, + width: 500, padding: 10, - borderColor: '#ddd', + borderColor: selected ? 'orange' : '#ddd', // 根据选中状态设置边框颜色 + cursor: 'pointer', // 添加鼠标指针样式 }} + onClick={() => { + if (!selected) { + handleSelect(1); // 默认选择本节点 + } + }} // 点击节点时切换选中状态 > {isExpanded ? ( -
- ID Number: - {idNumber} - Address: - {address} -
+ + {idNumber} + {address} + ) : ( IDCard Information )} @@ -71,6 +143,8 @@ const data = { idNumber: 'IDUSAASD2131734', address: '1234 Broadway, Apt 5B, New York, NY 10001', expanded: false, // 初始状态为收缩 + selected: true, // 初始状态为未选中 + selectedOption: 1, // 初始选择本节点 }, style: { x: 50, y: 50 }, }, @@ -81,8 +155,98 @@ const data = { idNumber: 'IDUSAASD1431920', address: '3030 Chestnut St, Philadelphia, PA 19104', expanded: false, // 初始状态为收缩 + selected: false, // 初始状态为未选中 + selectedOption: 0, // 初始不选择 + }, + style: { x: 700, y: 100 }, + }, + { + id: 'node3', + data: { + name: 'Charlie', + idNumber: 'IDUSAASD1431921', + address: '4040 Elm St, Chicago, IL 60611', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node4', + data: { + name: 'David', + idNumber: 'IDUSAASD1431922', + address: '5050 Oak St, Houston, TX 77002', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node5', + data: { + name: 'Eve', + idNumber: 'IDUSAASD1431923', + address: '6060 Pine St, Phoenix, AZ 85001', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node6', + data: { + name: 'Frank', + idNumber: 'IDUSAASD1431924', + address: '7070 Maple St, San Antonio, TX 78201', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node7', + data: { + name: 'Grace', + idNumber: 'IDUSAASD1431925', + address: '8080 Cedar St, San Diego, CA 92101', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node8', + data: { + name: 'Hannah', + idNumber: 'IDUSAASD1431926', + address: '9090 Walnut St, Dallas, TX 75201', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node9', + data: { + name: 'Ian', + idNumber: 'IDUSAASD1431927', + address: '1010 Birch St, San Jose, CA 95101', + expanded: false, + selected: false, + selectedOption: 0, + }, + }, + { + id: 'node10', + data: { + name: 'Judy', + idNumber: 'IDUSAASD1431928', + address: '1111 Spruce St, Austin, TX 73301', + expanded: false, + selected: false, + selectedOption: 0, }, - style: { x: 500, y: 100 }, }, ], edges: [ @@ -90,6 +254,42 @@ const data = { source: 'node1', target: 'node2', }, + { + source: 'node2', + target: 'node3', + }, + { + source: 'node3', + target: 'node4', + }, + { + source: 'node4', + target: 'node5', + }, + { + source: 'node5', + target: 'node6', + }, + { + source: 'node6', + target: 'node7', + }, + { + source: 'node7', + target: 'node8', + }, + { + source: 'node8', + target: 'node9', + }, + { + source: 'node9', + target: 'node10', + }, + { + source: 'node10', + target: 'node1', + }, ], }; @@ -97,6 +297,20 @@ export const ReactNodeDemo = () => { const containerRef = useRef(); const graphRef = useRef(null); + const handleSelectChange = (id, selected, option) => { + console.log(`Node ${id} selected: ${selected}, Option: ${option}`); + // 更新节点的 selected 属性和 selectedOption + graphRef.current.updateNodeData([{ + id, + data: { + selected, + selectedOption: option, + }, + }]); + + console.log('Selected nodes:', graphRef.current.getNodeData()); + }; + useEffect(() => { // 创建 Graph 实例 const graph = new Graph({ @@ -108,14 +322,29 @@ export const ReactNodeDemo = () => { type: 'react', style: { size: [250, 120], // 调整大小以适应内容 - component: (data) => , + component: (data) => , }, }, behaviors: ['drag-element', 'zoom-canvas', 'drag-canvas'], + plugins: [{ + type: 'minimap', + style: { + size: [200, 200], + position: 'bottom-right', + }, + }], + layout: { + type: 'grid', + align: 'left', + nodesep: 50, + nodeSize: [500, 220], + ranksep: 50, + }, }); // 渲染 Graph graph.render(); + graph.fitView(); // 保存 graph 实例 graphRef.current = graph; From 8d529c8edb585a85dcc64657c4f4caef1c97c12f Mon Sep 17 00:00:00 2001 From: jiangyu Date: Wed, 9 Apr 2025 13:10:10 +0800 Subject: [PATCH 15/26] =?UTF-8?q?fix:=20EdgeFilterLens=E5=9C=A8=E8=A7=A6?= =?UTF-8?q?=E5=8F=91update=E6=97=B6=EF=BC=8Coptions.r=E6=9C=AA=E8=B5=8B?= =?UTF-8?q?=E5=80=BC=E7=BB=99this.r=EF=BC=8C=E5=AF=BC=E8=87=B4=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=9C=AA=E7=94=9F=E6=95=88=EF=BC=9Bfix:=20style=20opt?= =?UTF-8?q?ion=E7=9A=84ts=E7=B1=BB=E5=9E=8B=E5=BA=94=E8=AF=A5=E4=BD=BF?= =?UTF-8?q?=E7=94=A8CircleStyleProps=20(#6976)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 当触发update时,options.r未赋值给this.r,导致配置未生效 fix: style option的ts类型应该使用CircleStyleProps * chore: defaultLensStyle改为使用CircleStyleProps,与fisheye plugin保持一致 * chore: 更新edge-filter-lens插件,CircleStyleProps的导入方式增加type前缀 --------- Co-authored-by: liujiangyu --- packages/g6/src/plugins/edge-filter-lens/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/g6/src/plugins/edge-filter-lens/index.ts b/packages/g6/src/plugins/edge-filter-lens/index.ts index 9ef13a467b6..42c618b5b52 100644 --- a/packages/g6/src/plugins/edge-filter-lens/index.ts +++ b/packages/g6/src/plugins/edge-filter-lens/index.ts @@ -1,6 +1,5 @@ -import type { BaseStyleProps } from '@antv/g'; import { CommonEvent } from '../../constants'; -import { Circle } from '../../elements'; +import { Circle, type CircleStyleProps } from '../../elements'; import type { RuntimeContext } from '../../runtime/types'; import type { EdgeData, GraphData, NodeData } from '../../spec'; import type { EdgeStyle } from '../../spec/element/edge'; @@ -100,7 +99,7 @@ export interface EdgeFilterLensOptions extends BasePluginOptions { * * The style of the lens */ - style?: BaseStyleProps; + style?: Partial; /** * 在透镜中节点的样式 * @@ -122,7 +121,7 @@ export interface EdgeFilterLensOptions extends BasePluginOptions { preventDefault?: boolean; } -const defaultLensStyle: BaseStyleProps = { +const defaultLensStyle: Exclude = { fill: '#fff', fillOpacity: 1, lineWidth: 1, @@ -388,6 +387,7 @@ export class EdgeFilterLens extends BasePlugin { public update(options: Partial) { this.unbindEvents(); super.update(options); + this.r = options.r ?? this.r; this.bindEvents(); } From 31ea481a7ce983b6fa652ef000f0e3129cf796f8 Mon Sep 17 00:00:00 2001 From: Aaron Date: Wed, 9 Apr 2025 14:06:13 +0800 Subject: [PATCH 16/26] fix: fix g6-ssr (#6984) --- packages/g6-ssr/package.json | 2 +- packages/g6-ssr/src/index.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/g6-ssr/package.json b/packages/g6-ssr/package.json index 34883a4ee5d..c84fc19b745 100644 --- a/packages/g6-ssr/package.json +++ b/packages/g6-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-ssr", - "version": "0.0.14", + "version": "0.0.15", "description": "Support SSR for G6", "keywords": [ "antv", diff --git a/packages/g6-ssr/src/index.ts b/packages/g6-ssr/src/index.ts index ef2e84085c8..69de87e9d81 100644 --- a/packages/g6-ssr/src/index.ts +++ b/packages/g6-ssr/src/index.ts @@ -1,4 +1,7 @@ +import * as G6 from '@antv/g6'; + export { getExtension, getExtensions, register } from '@antv/g6'; export { createCanvas } from './canvas'; export { createGraph } from './graph'; export type { Graph, MetaData, Options } from './types'; +export { G6 }; From a1cf3e968a8d266a3a2374ad80fd04aa46c85961 Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Wed, 9 Apr 2025 14:06:27 +0800 Subject: [PATCH 17/26] feat(g6-extension-react): react 19 compatibility support (#6972) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(g6-extension-react): react 19 compatibility support * fix: test failed * chore(g6-extension-react): 更新 React 和相关类型定义至 19 版本,调整 createRoot 初始化逻辑以支持 React 18+ 的动态导入 * chore(g6-extension-react): 启用动态导入以支持更灵活的模块加载 * fix: 更新 react-dom 引用以支持 React 18+ 的新客户端 API --- .../g6-extension-react/__tests__/graph.tsx | 4 +-- .../unit/attribute-changed-callback.spec.tsx | 3 ++- packages/g6-extension-react/package.json | 8 +++--- packages/g6-extension-react/rollup.config.mjs | 1 + .../src/react-node/render.ts | 27 ++++++++++++++----- packages/site/.dumi/global.ts | 2 +- packages/site/package.json | 2 +- 7 files changed, 31 insertions(+), 16 deletions(-) diff --git a/packages/g6-extension-react/__tests__/graph.tsx b/packages/g6-extension-react/__tests__/graph.tsx index 8a3ab471a37..2a833ffd9eb 100644 --- a/packages/g6-extension-react/__tests__/graph.tsx +++ b/packages/g6-extension-react/__tests__/graph.tsx @@ -10,7 +10,7 @@ export interface GraphProps { export const Graph = (props: GraphProps) => { const { options, onRender, onDestroy } = props; - const graphRef = useRef(); + const graphRef = useRef(null); const containerRef = useRef(null); useEffect(() => { @@ -22,7 +22,7 @@ export const Graph = (props: GraphProps) => { if (graph) { graph.destroy(); onDestroy?.(); - graphRef.current = undefined; + graphRef.current = null; } }; }, []); diff --git a/packages/g6-extension-react/__tests__/unit/attribute-changed-callback.spec.tsx b/packages/g6-extension-react/__tests__/unit/attribute-changed-callback.spec.tsx index cc411a1a4d0..3fbcfef458e 100644 --- a/packages/g6-extension-react/__tests__/unit/attribute-changed-callback.spec.tsx +++ b/packages/g6-extension-react/__tests__/unit/attribute-changed-callback.spec.tsx @@ -16,7 +16,8 @@ it('attribute changed callback', () => { }, }); - const spy = jest.spyOn(node, 'attributeChangedCallback'); + const spy = jest.fn(); + node.attributeChangedCallback = spy; const component = () =>
test1
; diff --git a/packages/g6-extension-react/package.json b/packages/g6-extension-react/package.json index db4340e5f32..1f7b3db02dd 100644 --- a/packages/g6-extension-react/package.json +++ b/packages/g6-extension-react/package.json @@ -41,11 +41,11 @@ "devDependencies": { "@ant-design/icons": "^5.5.2", "@antv/g6": "workspace:*", - "@types/react": "^18.3.17", - "@types/react-dom": "^18.3.5", + "@types/react": "^19.1.0", + "@types/react-dom": "^19.1.0", "antd": "^5.22.5", - "react": "^18.3.1", - "react-dom": "^18.3.1", + "react": "^19.1.0", + "react-dom": "^19.1.0", "react-router-dom": "^6.28.0", "styled-components": "^6.1.13" }, diff --git a/packages/g6-extension-react/rollup.config.mjs b/packages/g6-extension-react/rollup.config.mjs index 5ce30c21508..806938b2d44 100644 --- a/packages/g6-extension-react/rollup.config.mjs +++ b/packages/g6-extension-react/rollup.config.mjs @@ -15,6 +15,7 @@ export default [ name: 'G6ExtensionReact', format: 'umd', sourcemap: false, + inlineDynamicImports: true, }, plugins: [ nodePolyfills(), diff --git a/packages/g6-extension-react/src/react-node/render.ts b/packages/g6-extension-react/src/react-node/render.ts index 63ad2c2814b..514e82ddfd2 100644 --- a/packages/g6-extension-react/src/react-node/render.ts +++ b/packages/g6-extension-react/src/react-node/render.ts @@ -14,16 +14,27 @@ const fullClone = { type CreateRoot = (container: ContainerType) => Root; -const { version, render: reactRender, unmountComponentAtNode } = fullClone; +const { version, render: reactRender, unmountComponentAtNode } = fullClone as any; let createRoot: CreateRoot | undefined; -try { + +async function initCreateRoot() { + if (createRoot) return; const mainVersion = Number((version || '').split('.')[0]); - if (mainVersion >= 18 && fullClone.createRoot) { - ({ createRoot } = fullClone); + + // React 18+ 使用 createRoot + if (mainVersion >= 18) { + try { + /* @vite-ignore */ + const client = await import('react-dom/client'); + if (client.createRoot) { + createRoot = client.createRoot; + } + } catch (error) { + // 如果动态导入失败,回退到旧版本渲染 + // Silent error + } } -} catch (e) { - // Do nothing; } /** @@ -85,7 +96,8 @@ function legacyRender(node: React.ReactElement, container: ContainerType) { * @param node - React 节点 | React node * @param container - 容器 | Container */ -export function render(node: React.ReactElement, container: ContainerType) { +export async function render(node: React.ReactElement, container: ContainerType) { + await initCreateRoot(); if (createRoot) modernRender(node, container); else legacyRender(node, container); } @@ -123,6 +135,7 @@ function legacyUnmount(container: ContainerType) { * @returns Promise | Promise */ export async function unmount(container: ContainerType) { + await initCreateRoot(); if (createRoot) { // Delay to unmount to avoid React 18 sync warning return modernUnmount(container); diff --git a/packages/site/.dumi/global.ts b/packages/site/.dumi/global.ts index 76cfb3166e2..a498bca2620 100644 --- a/packages/site/.dumi/global.ts +++ b/packages/site/.dumi/global.ts @@ -25,7 +25,7 @@ if (typeof window !== 'undefined' && window) { window.react = require('react'); window.React = window.react; - window.client = require('react-dom'); + window.client = require('react-dom/client'); window.styledComponents = require('styled-components'); window.addPanel = async (renderPanel: (gui) => void) => { diff --git a/packages/site/package.json b/packages/site/package.json index 0e034c3bd2f..351466d16c2 100644 --- a/packages/site/package.json +++ b/packages/site/package.json @@ -70,7 +70,7 @@ "@rushstack/node-core-library": "^4.3.0", "@types/fs-extra": "^11.0.4", "@types/lodash": "^4.17.16", - "@types/react": "^18.3.18", + "@types/react": "^19.1.0", "@types/resolve": "^1.20.6", "fs-extra": "^11.3.0", "gh-pages": "^6.3.0", From 10ee238636e7804771b6343013051051dd974043 Mon Sep 17 00:00:00 2001 From: jiangyu Date: Wed, 9 Apr 2025 14:35:47 +0800 Subject: [PATCH 18/26] =?UTF-8?q?docs:=20=E4=BC=98=E5=8C=96EdgeFilterLens?= =?UTF-8?q?=E8=BE=B9=E8=BF=87=E6=BB=A4=E9=95=9C=E6=96=87=E6=A1=A3=EF=BC=8C?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=AD=E8=8B=B1=E6=96=87=E6=89=8B=E5=86=8C?= =?UTF-8?q?=E5=86=85=E5=AE=B9=20(#6975)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs: 添加边过滤镜插件文档,包含概述、使用场景、基本用法及配置项详细说明,更新中英文手册内容 * docs: 更新EdgeFilterLens文档,增加完整样式属性的参考链接,更新中英文手册内容 --------- Co-authored-by: liujiangyu --- .../common/api/plugins/edge-filter-lens.md | 188 ++++++++ .../plugin/build-in/EdgeFilterLens.en.md | 431 +++++++++++++----- .../plugin/build-in/EdgeFilterLens.zh.md | 405 ++++++++++++---- 3 files changed, 823 insertions(+), 201 deletions(-) create mode 100644 packages/site/common/api/plugins/edge-filter-lens.md diff --git a/packages/site/common/api/plugins/edge-filter-lens.md b/packages/site/common/api/plugins/edge-filter-lens.md new file mode 100644 index 00000000000..3c967d2fced --- /dev/null +++ b/packages/site/common/api/plugins/edge-filter-lens.md @@ -0,0 +1,188 @@ +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // 上部疏散区域 + { id: 'Myriel', style: { x: 207, y: 78, label: 'Myriel' } }, + { id: 'Napoleon', style: { x: 127, y: 62, label: 'Napoleon' } }, + { id: 'CountessdeLo', style: { x: 171, y: 47, label: 'CountessdeLo' } }, + { id: 'Geborand', style: { x: 106, y: 81, label: 'Geborand' } }, + { id: 'Champtercier', style: { x: 247, y: 58, label: 'Champtercier' } }, + { id: 'Cravatte', style: { x: 152, y: 50, label: 'Cravatte' } }, + + // 中上部区域 + { id: 'Mlle.Baptistine', style: { x: 205, y: 141, label: 'Mlle.Baptistine' } }, + { id: 'Mme.Magloire', style: { x: 275, y: 120, label: 'Mme.Magloire' } }, + { id: 'Labarre', style: { x: 246, y: 183, label: 'Labarre' } }, + { id: 'Valjean', style: { x: 342, y: 221, label: 'Valjean' } }, + { id: 'Marguerite', style: { x: 285, y: 171, label: 'Marguerite' } }, + + // 中部密集区域 + { id: 'Tholomyes', style: { x: 379, y: 158, label: 'Tholomyes' } }, + { id: 'Listolier', style: { x: 288, y: 80, label: 'Listolier' } }, + { id: 'Fameuil', style: { x: 349, y: 89, label: 'Fameuil' } }, + { id: 'Blacheville', style: { x: 381, y: 95, label: 'Blacheville' } }, + { id: 'Favourite', style: { x: 264, y: 153, label: 'Favourite' } }, + { id: 'Dahlia', style: { x: 323, y: 170, label: 'Dahlia' } }, + { id: 'Zephine', style: { x: 306, y: 114, label: 'Zephine' } }, + { id: 'Fantine', style: { x: 357, y: 187, label: 'Fantine' } }, + + // 右侧区域 + { id: 'Bamatabois', style: { x: 411, y: 156, label: 'Bamatabois' } }, + { id: 'Perpetue', style: { x: 454, y: 195, label: 'Perpetue' } }, + { id: 'Simplice', style: { x: 406, y: 227, label: 'Simplice' } }, + + // 下部区域 + { id: 'Cosette', style: { x: 343, y: 248, label: 'Cosette' } }, + { id: 'Javert', style: { x: 388, y: 263, label: 'Javert' } }, + { id: 'Fauchelevent', style: { x: 397, y: 276, label: 'Fauchelevent' } }, + { id: 'Thenardier', style: { x: 317, y: 300, label: 'Thenardier' } }, + { id: 'Eponine', style: { x: 268, y: 365, label: 'Eponine' } }, + { id: 'Anzelma', style: { x: 234, y: 303, label: 'Anzelma' } }, + { id: 'Woman2', style: { x: 304, y: 254, label: 'Woman2' } }, + + // 最右侧独立节点 + { id: 'Gribier', style: { x: 457, y: 160, label: 'Gribier' } }, + { id: 'Jondrette', style: { x: 510, y: 327, label: 'Jondrette' } }, + ], + edges: [ + // 上部连接 + { id: 'e1', source: 'Myriel', target: 'CountessdeLo' }, + { id: 'e2', source: 'Napoleon', target: 'Myriel' }, + { id: 'e3', source: 'Geborand', target: 'Napoleon' }, + { id: 'e4', source: 'Champtercier', target: 'Myriel' }, + { id: 'e5', source: 'Cravatte', target: 'CountessdeLo' }, + + // 中上部连接 + { id: 'e6', source: 'Mlle.Baptistine', target: 'Mme.Magloire' }, + { id: 'e7', source: 'Labarre', target: 'Valjean' }, + { id: 'e8', source: 'Valjean', target: 'Marguerite' }, + { id: 'e9', source: 'Marguerite', target: 'Mme.Magloire' }, + + // 中部密集连接 + { id: 'e10', source: 'Tholomyes', target: 'Listolier' }, + { id: 'e11', source: 'Listolier', target: 'Fameuil' }, + { id: 'e12', source: 'Fameuil', target: 'Blacheville' }, + { id: 'e13', source: 'Blacheville', target: 'Favourite' }, + { id: 'e14', source: 'Favourite', target: 'Dahlia' }, + { id: 'e15', source: 'Dahlia', target: 'Zephine' }, + { id: 'e16', source: 'Zephine', target: 'Fantine' }, + { id: 'e17', source: 'Tholomyes', target: 'Fantine' }, + { id: 'e18', source: 'Valjean', target: 'Fantine' }, + + // 右侧连接 + { id: 'e19', source: 'Bamatabois', target: 'Perpetue' }, + { id: 'e20', source: 'Perpetue', target: 'Simplice' }, + { id: 'e21', source: 'Bamatabois', target: 'Gribier' }, + + // 下部连接 + { id: 'e22', source: 'Valjean', target: 'Cosette' }, + { id: 'e23', source: 'Cosette', target: 'Javert' }, + { id: 'e24', source: 'Javert', target: 'Fauchelevent' }, + { id: 'e25', source: 'Fauchelevent', target: 'Thenardier' }, + { id: 'e26', source: 'Thenardier', target: 'Eponine' }, + { id: 'e27', source: 'Eponine', target: 'Anzelma' }, + { id: 'e28', source: 'Woman2', target: 'Cosette' }, + + // 跨区域连接 + { id: 'e29', source: 'Fantine', target: 'Bamatabois' }, + { id: 'e30', source: 'Javert', target: 'Bamatabois' }, + { id: 'e31', source: 'Simplice', target: 'Jondrette' }, + { id: 'e32', source: 'Thenardier', target: 'Jondrette' }, + { id: 'e33', source: 'Favourite', target: 'Valjean' }, + { id: 'e34', source: 'Tholomyes', target: 'Cosette' }, + ], + }, + node: { + style: { + label: true, + size: 16, + }, + palette: { + field: (datum) => Math.floor(datum.style?.y / 60), + }, + }, + edge: { + style: { + label: true, + labelText: (d) => d.data.value?.toString(), + stroke: '#ccc', + endArrow: true, + endArrowType: 'triangle', + }, + }, + plugins: [ + { + type: 'edge-filter-lens', + key: 'edge-filter-lens', + r: 80, + trigger: 'pointermove', + }, + ], + }, + { width: 600, height: 400 }, + (gui, graph) => { + const TRIGGER_TYPES = ['pointermove', 'click', 'drag']; + const NODE_TYPES = ['both', 'source', 'target', 'either']; + + const options = { + type: 'edge-filter-lens', + r: 80, // 透镜半径 + trigger: 'pointermove', // 触发方式 + nodeType: 'both', // 边显示条件 + minR: 50, // 最小半径 + maxR: 150, // 最大半径 + scaleRBy: 'wheel', // 缩放方式 + style: { + fill: '#f0f5ff', + fillOpacity: 0.4, + stroke: '#1d39c4', + strokeOpacity: 0.8, + lineWidth: 1.5, + }, + nodeStyle: { + size: 35, + fill: '#d6e4ff', + stroke: '#2f54eb', + lineWidth: 2, + labelFontSize: 14, + labelFontWeight: 'bold', + labelFill: '#1d39c4', + }, + edgeStyle: { + stroke: '#1d39c4', + lineWidth: 2, + strokeOpacity: 0.8, + }, + }; + + const optionFolder = gui.addFolder('Edge Filter Lens Options'); + optionFolder.add(options, 'type').disable(true); + optionFolder.add(options, 'r', 50, 150, 5); + optionFolder.add(options, 'trigger', TRIGGER_TYPES); + optionFolder.add(options, 'nodeType', NODE_TYPES); + optionFolder.add(options, 'minR', 20, 100, 5); + optionFolder.add(options, 'maxR', 100, 200, 5); + + optionFolder.onChange(({ property, value }) => { + if (property.includes('.')) { + const [group, prop] = property.split('.'); + graph.updatePlugin({ + key: 'edge-filter-lens', + [group]: { + ...options[group], + [prop]: value, + }, + }); + } else { + graph.updatePlugin({ + key: 'edge-filter-lens', + [property]: value, + }); + } + graph.render(); + }); + }, +); +``` diff --git a/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.en.md b/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.en.md index c9d98ac3393..eb7c68f7748 100644 --- a/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.en.md +++ b/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.en.md @@ -2,142 +2,347 @@ title: EdgeFilterLens --- -EdgeFilterLens can keep the focused edges within the lens range, while other edges will not be displayed within that range. +## Overview -## Options - -### Required type - -> _string_ - -Plugin type - -### edgeStyle - -> _EdgeStyle_ _\| ((datum:_ [EdgeData](/api/graph/option#edgedata)_) =>_ _EdgeStyle\_\_)_ - -The style of the edges displayed in the lens - -### filter - -> _(id:_ _string\_\_, elementType:_ _'node' \| 'edge' \| 'combo'\_\_) => boolean_ - -Filter elements that are never displayed in the lens - -### maxR - -> _number_ **Default:** `canvas 宽高最小值的一半` - -The maximum radius of the lens. Only valid when `scaleRBy` is `wheel` - -### minR - -> _number_ **Default:** `0` - -The minimum radius of the lens. Only valid when `scaleRBy` is `wheel` - -### nodeStyle - -> _NodeStyle_ _\| ((datum:_ [NodeData](/api/graph/option#nodedata)_) =>_ _NodeStyle\_\_)_ - -The style of the nodes displayed in the lens - -### nodeType - -> _'both' \| 'source' \| 'target' \| 'either'_ **Default:** `'both'` - -The condition for displaying the edge - -- `'both'`: The edge is displayed only when both the source node and the target node are in the lens - -- `'source'`: The edge is displayed only when the source node is in the lens - -- `'target'`: The edge is displayed only when the target node is in the lens - -- `'either'`: The edge is displayed when either the source node or the target node is in the lens +EdgeFilterLens can keep the focused edges within the lens range, while other edges will not be displayed within that range. This is an important visualization exploration tool that helps users focus on edge relationships in specific areas. -### preventDefault +## Use Cases -> _boolean_ **Default:** `true` +- Need to focus on edge relationships in local areas +- Highlight connections between specific nodes in complex networks -Whether to prevent the default event +## Basic Usage -### r - -> _number_ **Default:** `60` - -The radius of the lens - -### scaleRBy - -> _'wheel'_ **Default:** `` - -The way to scale the radius of the lens +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + trigger: 'pointermove', // Follow mouse movement + r: 60, // Set lens radius + nodeType: 'both', // Edge display condition + }, + ], +}); +``` -- `'wheel'`: scale the radius of the lens by the wheel +## Live Demo -### style + -> _BaseStyleProps_ +## Options -The style of the lens +| Name | Description | Type | Default | Required | +| -------------- | ----------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------- | +| type | Plugin type | string | `edge-filter-lens` | ✓ | +| trigger | The way to move the lens | `pointermove` \| `click` \| `drag` | `pointermove` | | +| r | The radius of the lens | number | 60 | | +| maxR | The maximum radius of the lens | number | Half of the minimum canvas width/height | | +| minR | The minimum radius of the lens | number | 0 | | +| scaleRBy | The way to scale the radius | `wheel` | - | | +| nodeType | The condition for displaying edges | `both` \| `source` \| `target` \| `either` | `both` | | +| filter | Filter elements never shown in lens | (id: string, elementType: 'node' \| 'edge' \| 'combo') => boolean | () => true | | +| style | The style of the lens | [CircleStyleProps](#circlestyleprops) | `{ fill: '#fff', fillOpacity: 1, lineWidth: 1, stroke: '#000', strokeOpacity: 0.8, zIndex: -Infinity }` | | +| nodeStyle | The style of nodes in lens | [NodeStyle](/en/manual/element/node/build-in/base-node#stylestyle-property-style) \| ((datum: [NodeData](/en/manual/data#node-data)) => [NodeStyle](/en/manual/element/node/build-in/base-node#stylestyle-property-style)) | `{ label: false }` | | +| edgeStyle | The style of edges in lens | [EdgeStyle](/en/manual/element/edge/build-in/base-edge#edge-style-style) \| ((datum: [EdgeData](/en/manual/data#edge-data)) => [EdgeStyle](/en/manual/element/edge/build-in/base-edge#edge-style-style)) | `{ label: true }` | | +| preventDefault | Whether to prevent default events | boolean | true | | + +### CircleStyleProps + +Style properties for the circular lens. + +| Name | Description | Type | Default | +| ------------- | -------------- | ------ | --------- | +| fill | Fill color | string | `#fff` | +| fillOpacity | Fill opacity | number | 1 | +| stroke | Stroke color | string | `#000` | +| strokeOpacity | Stroke opacity | number | 0.8 | +| lineWidth | Line width | number | 1 | +| zIndex | Layer level | number | -Infinity | + +For complete style properties, refer to [Element - Node - Built-in Node - Common Style Properties - style](/en/manual/element/node/build-in/base-node#styletype-property-type) ### trigger -> _'pointermove' \| 'click' \| 'drag'_ **Default:** `'pointermove'` - -The way to move the lens - -- `'pointermove'`: always follow the mouse movement - -- `'click'`: move the lens when the mouse clicks - -- `'drag'`: drag the lens - -## API - -### EdgeFilterLens.destroy() - -```typescript -destroy(): void; +The `trigger` property controls how the lens moves, supporting three configurations: + +- `pointermove`: Lens always follows mouse movement +- `click`: Move lens to clicked position +- `drag`: Move lens by dragging + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + trigger: 'pointermove', // Follow mouse movement + // trigger: 'click', // Click to move + // trigger: 'drag', // Drag to move + }, + ], +}); ``` -### EdgeFilterLens.update(options) +### nodeType -```typescript -update(options: Partial): void; +The `nodeType` property controls edge display conditions: + +- `both`: Edge displays only when both source and target nodes are in lens +- `source`: Edge displays only when source node is in lens +- `target`: Edge displays only when target node is in lens +- `either`: Edge displays when either source or target node is in lens + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + nodeType: 'both', // Show edge when both nodes are in lens + // nodeType: 'source', // Show edge when source node is in lens + // nodeType: 'target', // Show edge when target node is in lens + // nodeType: 'either', // Show edge when either node is in lens + }, + ], +}); ``` -
View Parameters - - - -
- -Parameter - - - -Type - - - -Description +### Zoom Control + +Use `scaleRBy` to control lens radius adjustment: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + // Adjust radius by wheel + scaleRBy: 'wheel', + // Set radius range + minR: 50, + maxR: 200, + }, + ], +}); +``` -
+## Examples -options +### Basic Usage - +Simplest configuration: -Partial<[EdgeFilterLensOptions](#options)> +```js +const graph = new Graph({ + plugins: ['edge-filter-lens'], +}); +``` - +Effect: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // Upper scattered area + { id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } }, + { id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } }, + { id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } }, + { id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } }, + + // Middle area + { id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } }, + { id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } }, + { id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } }, + { id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } }, + { id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } }, + { id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } }, + + // Lower area + { id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } }, + { id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } }, + { id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } }, + { id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } }, + ], + edges: [ + // Upper connections + { id: 'edge1', source: 'node1', target: 'node2' }, + { id: 'edge2', source: 'node2', target: 'node3' }, + { id: 'edge3', source: 'node3', target: 'node4' }, + + // Middle connections + { id: 'edge4', source: 'node5', target: 'node6' }, + { id: 'edge5', source: 'node6', target: 'node7' }, + { id: 'edge6', source: 'node7', target: 'node8' }, + { id: 'edge7', source: 'node8', target: 'node9' }, + { id: 'edge8', source: 'node9', target: 'node10' }, + + // Lower connections + { id: 'edge9', source: 'node11', target: 'node12' }, + { id: 'edge10', source: 'node12', target: 'node13' }, + { id: 'edge11', source: 'node13', target: 'node14' }, + + // Cross-region connections + { id: 'edge12', source: 'node4', target: 'node8' }, + { id: 'edge13', source: 'node7', target: 'node11' }, + { id: 'edge14', source: 'node10', target: 'node13' }, + ], + }, + node: { + style: { + size: 20, + label: true, + }, + }, + edge: { + style: { + stroke: '#91d5ff', + lineWidth: 1, + }, + }, + plugins: ['edge-filter-lens'], + }, + { width: 400, height: 300 }, +); +``` -
+### Custom Styles + +You can customize the appearance and behavior of the lens: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + r: 80, + style: { + fill: '#f0f5ff', // Lens area fill color + fillOpacity: 0.6, // Fill area opacity + stroke: '#7e3feb', // Lens border color (purple) + strokeOpacity: 0.8, // Border opacity + lineWidth: 1.5, // Border line width + }, + nodeStyle: { + size: 24, // Enlarged node + fill: '#7e3feb', // Purple fill + stroke: '#5719c9', // Deep purple stroke + lineWidth: 1, // Thin border + label: true, // Show label + labelFill: '#ffffff', // White text + labelFontSize: 14, // Enlarged text + labelFontWeight: 'bold', // Bold text + }, + edgeStyle: { + stroke: '#8b9baf', // Gray edge + lineWidth: 2, // Thicker line + label: true, // Show label + labelFill: '#5719c9', // Deep purple text + opacity: 0.8, // Proper opacity + }, + }, + ], +}); +``` -**Returns**: +Effect: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // Upper scattered area + { id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } }, + { id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } }, + { id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } }, + { id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } }, + + // Middle area + { id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } }, + { id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } }, + { id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } }, + { id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } }, + { id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } }, + { id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } }, + + // Lower area + { id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } }, + { id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } }, + { id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } }, + { id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } }, + ], + edges: [ + // Upper connections + { id: 'edge1', source: 'node1', target: 'node2' }, + { id: 'edge2', source: 'node2', target: 'node3' }, + { id: 'edge3', source: 'node3', target: 'node4' }, + + // Middle connections + { id: 'edge4', source: 'node5', target: 'node6' }, + { id: 'edge5', source: 'node6', target: 'node7' }, + { id: 'edge6', source: 'node7', target: 'node8' }, + { id: 'edge7', source: 'node8', target: 'node9' }, + { id: 'edge8', source: 'node9', target: 'node10' }, + + // Lower connections + { id: 'edge9', source: 'node11', target: 'node12' }, + { id: 'edge10', source: 'node12', target: 'node13' }, + { id: 'edge11', source: 'node13', target: 'node14' }, + + // Cross-region connections + { id: 'edge12', source: 'node4', target: 'node8' }, + { id: 'edge13', source: 'node7', target: 'node11' }, + { id: 'edge14', source: 'node10', target: 'node13' }, + ], + }, + node: { + style: { + size: 20, + label: true, + }, + }, + edge: { + style: { + stroke: '#91d5ff', + lineWidth: 1, + }, + }, + plugins: [ + { + type: 'edge-filter-lens', + r: 80, + style: { + fill: '#f0f5ff', + fillOpacity: 0.6, + stroke: '#7e3feb', + strokeOpacity: 0.8, + lineWidth: 1.5, + }, + nodeStyle: { + size: 24, + fill: '#7e3feb', + stroke: '#5719c9', + lineWidth: 1, + label: true, + labelFill: '#ffffff', + labelFontSize: 14, + labelFontWeight: 'bold', + }, + edgeStyle: { + stroke: '#8b9baf', + lineWidth: 2, + label: true, + labelFill: '#5719c9', + opacity: 0.8, + }, + }, + ], + }, + { width: 400, height: 300 }, +); +``` -- **Type:** void +## Live Examples -
+- [Edge Filter Lens](/en/examples/plugin/edge-filter-lens/#basic) diff --git a/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.zh.md b/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.zh.md index 47cb044e88b..1da7ef8fb3c 100644 --- a/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.zh.md +++ b/packages/site/docs/manual/plugin/build-in/EdgeFilterLens.zh.md @@ -2,116 +2,345 @@ title: EdgeFilterLens 边过滤镜 --- -边过滤镜可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。 +## 概述 -**参考示例**: +边过滤镜插件可以将关注的边保留在过滤镜范围内,其他边将在该范围内不显示。这是一个重要的可视化探索工具,可以帮助用户聚焦于特定区域的边关系。 -- [边过滤镜](/examples/plugin/edge-filter-lens/#basic) - -## 配置项 - -### Required type - -> _`edge-filter-lens` \| string_ - -此插件已内置,你可以通过 `type: 'edge-filter-lens'` 来使用它。 - -### edgeStyle - -> _EdgeStyle \| (datum: [EdgeData](/manual/data#边数据edgedata)) => EdgeStyle_ - -在透镜中边的样式 - -### filter - -> _(id: string, elementType: 'node' \| 'edge' \| 'combo') => boolean_ +## 使用场景 -过滤出始终不在透镜中显示的元素 +- 需要聚焦查看局部区域的边关系 +- 在复杂网络中突出显示特定节点之间的连接 -### maxR +## 基本用法 -> _number_ **Default:** `canvas 宽高最小值的一半` - -透镜的最大半径。只有在 `scaleRBy` 为 `wheel` 时生效 +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + trigger: 'pointermove', // 跟随鼠标移动 + r: 60, // 设置透镜半径 + nodeType: 'both', // 边的显示条件 + }, + ], +}); +``` -### minR +## 在线体验 -> _number_ **Default:** `0` + -透镜的最小半径。只有在 `scaleRBy` 为 `wheel` 时生效 +## 配置项 -### nodeStyle +| 属性 | 描述 | 类型 | 默认值 | 必选 | +| -------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | ---- | +| type | 插件类型 | string | `edge-filter-lens` | ✓ | +| trigger | 移动透镜的方式 | `pointermove` \| `click` \| `drag` | `pointermove` | | +| r | 透镜的半径 | number | 60 | | +| maxR | 透镜的最大半径 | number | 画布宽高最小值的一半 | | +| minR | 透镜的最小半径 | number | 0 | | +| scaleRBy | 缩放透镜半径的方式 | `wheel` | - | | +| nodeType | 边显示的条件 | `both` \| `source` \| `target` \| `either` | `both` | | +| filter | 过滤出始终不在透镜中显示的元素 | (id: string, elementType: 'node' \| 'edge' \| 'combo') => boolean | () => true | | +| style | 透镜的样式 | [CircleStyleProps](#circlestyleprops) | `{fill: '#fff', fillOpacity: 1, lineWidth: 1, stroke: '#000', strokeOpacity: 0.8, zIndex: -Infinity }` | | +| nodeStyle | 在透镜中节点的样式 | [NodeStyle](/manual/element/node/build-in/base-node#style) \| ((datum: [NodeData](/manual/data#节点数据nodedata)) => [NodeStyle](/manual/element/node/build-in/base-node#style)) | `{ label: false }` | | +| edgeStyle | 在透镜中边的样式 | [EdgeStyle](/manual/element/edge/build-in/base-edge#style) \| ((datum: [EdgeData](/manual/data#边数据edgedata)) => [EdgeStyle](/manual/element/edge/build-in/base-edge#style)) | `{ label: true }` | | +| preventDefault | 是否阻止默认事件 | boolean | true | | + +### CircleStyleProps + +圆形透镜的样式属性。 + +| 属性 | 描述 | 类型 | 默认值 | +| ------------- | --------------- | ----------------------------- | ------ | +| fill | 填充颜色 | string \| Pattern \| null | - | +| stroke | 描边颜色 | string \| Pattern \| null | - | +| opacity | 整体透明度 | number \| string | - | +| fillOpacity | 填充透明度 | number \| string | - | +| strokeOpacity | 描边透明度 | number \| string | - | +| lineWidth | 线宽度 | number \| string | - | +| lineCap | 线段端点样式 | `butt` \| `round` \| `square` | - | +| lineJoin | 线段连接处样式 | `miter` \| `round` \| `bevel` | - | +| shadowColor | 阴影颜色 | string | - | +| shadowBlur | 阴影模糊程度 | number | - | +| shadowOffsetX | 阴影 X 方向偏移 | number | - | +| shadowOffsetY | 阴影 Y 方向偏移 | number | - | + +完整样式属性参考 [元素 -节点 - 内置节点 - 通用样式属性 - style](/manual/element/node/build-in/base-node#style) -> _NodeStyle_ _\| ((datum:_ [NodeData](/manual/data#节点数据nodedata)_) =>_ _NodeStyle\_\_)_ +### trigger -在透镜中节点的样式 +`trigger` 属性用于控制透镜的移动方式,支持以下三种配置: + +- `pointermove`:透镜始终跟随鼠标移动 +- `click`:点击画布时移动透镜到点击位置 +- `drag`:通过拖拽方式移动透镜 + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + trigger: 'pointermove', // 跟随鼠标移动 + // trigger: 'click', // 点击移动 + // trigger: 'drag', // 拖拽移动 + }, + ], +}); +``` ### nodeType -> _'both' \| 'source' \| 'target' \| 'either'_ **Default:** `'both'` - -边显示的条件 - -- `'both'`:只有起始节点和目标节点都在透镜中时,边才会显示 - -- `'source'`:只有起始节点在透镜中时,边才会显示 - -- `'target'`:只有目标节点在透镜中时,边才会显示 - -- `'either'`:只要起始节点或目标节点有一个在透镜中时,边就会显示 - -### preventDefault - -> _boolean_ **Default:** `true` - -是否阻止默认事件 - -### r - -> _number_ **Default:** `60` - -透镜的半径 - -### scaleRBy - -> _'wheel'_ **Default:** `` - -缩放透镜半径的方式 - -- `'wheel'`:通过滚轮缩放透镜的半径 - -### style - -> _BaseStyleProps_ - -透镜的样式 - -### trigger - -> _'pointermove' \| 'click' \| 'drag'_ **Default:** `'pointermove'` - -移动透镜的方式 +`nodeType` 属性用于控制边的显示条件: + +- `both`:只有起始节点和目标节点都在透镜中时,边才会显示 +- `source`:只有起始节点在透镜中时,边才会显示 +- `target`:只有目标节点在透镜中时,边才会显示 +- `either`:只要起始节点或目标节点有一个在透镜中时,边就会显示 + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + nodeType: 'both', // 起始和目标节点都在透镜中时显示边 + // nodeType: 'source', // 起始节点在透镜中时显示边 + // nodeType: 'target', // 目标节点在透镜中时显示边 + // nodeType: 'either', // 起始或目标节点在透镜中时显示边 + }, + ], +}); +``` -- `'pointermove'`:始终跟随鼠标移动 +### 缩放控制 + +通过 `scaleRBy` 可以控制透镜半径的调整方式: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + // 通过滚轮调整半径 + scaleRBy: 'wheel', + // 设置半径范围 + minR: 50, + maxR: 200, + }, + ], +}); +``` -- `'click'`:鼠标点击时透镜移动 +## 代码示例 -- `'drag'`:拖拽透镜 +### 基础用法 -## API +最简单的配置方式: -### EdgeFilterLens.destroy() +```js +const graph = new Graph({ + plugins: ['edge-filter-lens'], +}); +``` -```typescript -destroy(): void; +效果如下: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // 上部疏散区域 + { id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } }, + { id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } }, + { id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } }, + { id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } }, + + // 中部区域 + { id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } }, + { id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } }, + { id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } }, + { id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } }, + { id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } }, + { id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } }, + + // 下部区域 + { id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } }, + { id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } }, + { id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } }, + { id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } }, + ], + edges: [ + // 上部连接 + { id: 'edge1', source: 'node1', target: 'node2' }, + { id: 'edge2', source: 'node2', target: 'node3' }, + { id: 'edge3', source: 'node3', target: 'node4' }, + + // 中部连接 + { id: 'edge4', source: 'node5', target: 'node6' }, + { id: 'edge5', source: 'node6', target: 'node7' }, + { id: 'edge6', source: 'node7', target: 'node8' }, + { id: 'edge7', source: 'node8', target: 'node9' }, + { id: 'edge8', source: 'node9', target: 'node10' }, + + // 下部连接 + { id: 'edge9', source: 'node11', target: 'node12' }, + { id: 'edge10', source: 'node12', target: 'node13' }, + { id: 'edge11', source: 'node13', target: 'node14' }, + + // 跨区域连接 + { id: 'edge12', source: 'node4', target: 'node8' }, + { id: 'edge13', source: 'node7', target: 'node11' }, + { id: 'edge14', source: 'node10', target: 'node13' }, + ], + }, + node: { + style: { + size: 20, + }, + }, + plugins: ['edge-filter-lens'], + }, + { width: 400, height: 300 }, +); ``` -### EdgeFilterLens.update(options) +### 自定义样式 + +可以自定义透镜的外观和行为: + +```js +const graph = new Graph({ + plugins: [ + { + type: 'edge-filter-lens', + r: 80, + style: { + fill: '#f0f5ff', // 透镜区域的填充颜色 + fillOpacity: 0.6, // 填充区域的透明度 + stroke: '#7e3feb', // 透镜边框改为紫色 + strokeOpacity: 0.8, // 边框的透明度 + lineWidth: 1.5, // 边框的线宽 + }, + nodeStyle: { + size: 24, // 放大节点 + fill: '#7e3feb', // 紫色填充 + stroke: '#5719c9', // 深紫色描边 + lineWidth: 1, // 细边框 + label: true, // 显示标签 + labelFill: '#ffffff', // 白色文字 + labelFontSize: 14, // 放大文字 + labelFontWeight: 'bold', // 文字加粗 + }, + edgeStyle: { + stroke: '#8b9baf', // 灰色边 + lineWidth: 2, // 加粗边线 + label: true, // 显示标签 + labelFill: '#5719c9', // 深紫色文字 + opacity: 0.8, // 适当的透明度 + }, + }, + ], +}); +``` -```typescript -update(options: Partial): void; +效果如下: + +```js | ob { pin: false } +createGraph( + { + data: { + nodes: [ + // 上部疏散区域 + { id: 'node1', style: { x: 150, y: 60, label: 'Node 1' } }, + { id: 'node2', style: { x: 100, y: 40, label: 'Node 2' } }, + { id: 'node3', style: { x: 200, y: 35, label: 'Node 3' } }, + { id: 'node4', style: { x: 150, y: 30, label: 'Node 4' } }, + + // 中部区域 + { id: 'node5', style: { x: 220, y: 140, label: 'Node 5' } }, + { id: 'node6', style: { x: 280, y: 160, label: 'Node 6' } }, + { id: 'node7', style: { x: 220, y: 120, label: 'Node 7' } }, + { id: 'node8', style: { x: 260, y: 100, label: 'Node 8' } }, + { id: 'node9', style: { x: 240, y: 130, label: 'Node 9' } }, + { id: 'node10', style: { x: 300, y: 110, label: 'Node 10' } }, + + // 下部区域 + { id: 'node11', style: { x: 240, y: 200, label: 'Node 11' } }, + { id: 'node12', style: { x: 280, y: 220, label: 'Node 12' } }, + { id: 'node13', style: { x: 300, y: 190, label: 'Node 13' } }, + { id: 'node14', style: { x: 320, y: 210, label: 'Node 14' } }, + ], + edges: [ + // 上部连接 + { id: 'edge1', source: 'node1', target: 'node2' }, + { id: 'edge2', source: 'node2', target: 'node3' }, + { id: 'edge3', source: 'node3', target: 'node4' }, + + // 中部连接 + { id: 'edge4', source: 'node5', target: 'node6' }, + { id: 'edge5', source: 'node6', target: 'node7' }, + { id: 'edge6', source: 'node7', target: 'node8' }, + { id: 'edge7', source: 'node8', target: 'node9' }, + { id: 'edge8', source: 'node9', target: 'node10' }, + + // 下部连接 + { id: 'edge9', source: 'node11', target: 'node12' }, + { id: 'edge10', source: 'node12', target: 'node13' }, + { id: 'edge11', source: 'node13', target: 'node14' }, + + // 跨区域连接 + { id: 'edge12', source: 'node4', target: 'node8' }, + { id: 'edge13', source: 'node7', target: 'node11' }, + { id: 'edge14', source: 'node10', target: 'node13' }, + ], + }, + node: { + style: { + size: 20, + }, + }, + edge: { + style: { + stroke: '#91d5ff', + lineWidth: 1, + }, + }, + plugins: [ + { + type: 'edge-filter-lens', + r: 80, + style: { + fill: '#f0f5ff', // 透镜区域的填充颜色 + fillOpacity: 0.6, // 填充区域的透明度 + stroke: '#7e3feb', // 透镜边框改为紫色 + strokeOpacity: 0.8, // 边框的透明度 + lineWidth: 1.5, // 边框的线宽 + }, + nodeStyle: { + size: 24, // 放大节点 + fill: '#7e3feb', // 紫色填充 + stroke: '#5719c9', // 深紫色描边 + lineWidth: 1, // 细边框 + label: true, // 显示标签 + labelFill: '#ffffff', // 白色文字 + labelFontSize: 14, // 放大文字 + labelFontWeight: 'bold', // 文字加粗 + }, + edgeStyle: { + stroke: '#8b9baf', // 灰色边 + lineWidth: 2, // 加粗边线 + label: true, // 显示标签 + labelFill: '#5719c9', // 深紫色文字 + opacity: 0.8, // 适当的透明度 + }, + }, + ], + }, + { width: 400, height: 300 }, +); ``` -| 参数 | 类型 | 描述 | 默认值 | 必选 | -| ------- | ----------------------------------------- | ------ | ------ | ---- | -| options | Partial<[EdgeFilterLensOptions](#配置项)> | 配置项 | - | ✓ | +## 实际案例 + +- [边过滤镜](/examples/plugin/edge-filter-lens/#basic) From b9b962697f4f465ea6a4344b99d973c5bcf91543 Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:00:21 +0800 Subject: [PATCH 19/26] chore: update version (#6985) --- packages/g6-extension-3d/package.json | 2 +- packages/g6-extension-react/package.json | 2 +- packages/g6-ssr/package.json | 2 +- packages/g6/package.json | 2 +- packages/g6/src/version.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/g6-extension-3d/package.json b/packages/g6-extension-3d/package.json index f23c1a3fde8..21de5be45f5 100644 --- a/packages/g6-extension-3d/package.json +++ b/packages/g6-extension-3d/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-extension-3d", - "version": "0.1.18", + "version": "0.1.19", "description": "3D extension for G6", "keywords": [ "antv", diff --git a/packages/g6-extension-react/package.json b/packages/g6-extension-react/package.json index 1f7b3db02dd..e3d5294a82e 100644 --- a/packages/g6-extension-react/package.json +++ b/packages/g6-extension-react/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-extension-react", - "version": "0.2.0", + "version": "0.2.1", "description": "Using React Component to Define Your G6 Graph Node", "keywords": [ "antv", diff --git a/packages/g6-ssr/package.json b/packages/g6-ssr/package.json index c84fc19b745..7005d1cdb49 100644 --- a/packages/g6-ssr/package.json +++ b/packages/g6-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6-ssr", - "version": "0.0.15", + "version": "0.0.16", "description": "Support SSR for G6", "keywords": [ "antv", diff --git a/packages/g6/package.json b/packages/g6/package.json index 125424b14fa..f1fe210be74 100644 --- a/packages/g6/package.json +++ b/packages/g6/package.json @@ -1,6 +1,6 @@ { "name": "@antv/g6", - "version": "5.0.44", + "version": "5.0.45", "description": "A Graph Visualization Framework in JavaScript", "keywords": [ "antv", diff --git a/packages/g6/src/version.ts b/packages/g6/src/version.ts index 46a36426b07..a6d9b39fb5f 100644 --- a/packages/g6/src/version.ts +++ b/packages/g6/src/version.ts @@ -1 +1 @@ -export const version = '5.0.44'; +export const version = '5.0.45'; From 368316c674d5bc96fdaa839f86f5bfa1d348ea10 Mon Sep 17 00:00:00 2001 From: Yuxin <55794321+yvonneyx@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:58:54 +0800 Subject: [PATCH 20/26] docs: update demo links from Codesandbox to Stackblitz (#7000) --- packages/site/common/angular-snippet.md | 8 ++------ packages/site/common/react-snippet.md | 5 +---- packages/site/common/vue-snippet.md | 7 ++----- .../docs/manual/getting-started/integration/angular.en.md | 2 +- .../docs/manual/getting-started/integration/angular.zh.md | 2 +- .../docs/manual/getting-started/integration/react.en.md | 2 +- .../docs/manual/getting-started/integration/react.zh.md | 2 +- .../docs/manual/getting-started/integration/vue.en.md | 2 +- .../docs/manual/getting-started/integration/vue.zh.md | 2 +- 9 files changed, 11 insertions(+), 21 deletions(-) diff --git a/packages/site/common/angular-snippet.md b/packages/site/common/angular-snippet.md index 82c71a33ea8..23e8487c537 100644 --- a/packages/site/common/angular-snippet.md +++ b/packages/site/common/angular-snippet.md @@ -1,9 +1,5 @@ - + **app.component.html** diff --git a/packages/site/common/react-snippet.md b/packages/site/common/react-snippet.md index 103d43d574f..76eb67d5c5f 100644 --- a/packages/site/common/react-snippet.md +++ b/packages/site/common/react-snippet.md @@ -1,8 +1,5 @@ - ```jsx diff --git a/packages/site/common/vue-snippet.md b/packages/site/common/vue-snippet.md index 0bad4bed206..b29ccfeac25 100644 --- a/packages/site/common/vue-snippet.md +++ b/packages/site/common/vue-snippet.md @@ -1,9 +1,6 @@ - + title="G6 Vue"> ```html