diff --git a/apps/basic/src/pages/dag/toolbar/index.tsx b/apps/basic/src/pages/dag/toolbar/index.tsx
index b6c9a62e..afc58058 100644
--- a/apps/basic/src/pages/dag/toolbar/index.tsx
+++ b/apps/basic/src/pages/dag/toolbar/index.tsx
@@ -1,9 +1,9 @@
-import { PlayCircleOutlined, CopyOutlined } from '@ant-design/icons';
-import type { Edge, NodeOptions, Node } from '@antv/xflow';
+import { CopyOutlined, PlayCircleOutlined } from '@ant-design/icons';
+import type { Edge, Node, NodeOptions } from '@antv/xflow';
import {
- useGraphInstance,
useClipboard,
useGraphEvent,
+ useGraphInstance,
useGraphStore,
useKeyboard,
} from '@antv/xflow';
@@ -88,7 +88,7 @@ const Toolbar = () => {
type="primary"
size="small"
style={{ fontSize: 12 }}
- onClick={handleExcute}
+ onClick={handleExecute}
>
全部执行
diff --git a/apps/basic/src/pages/diff/index.tsx b/apps/basic/src/pages/diff/index.tsx
index f7266bbe..8777aa9e 100644
--- a/apps/basic/src/pages/diff/index.tsx
+++ b/apps/basic/src/pages/diff/index.tsx
@@ -1,5 +1,5 @@
-/* eslint-disable */
import { DiffGraph } from '@antv/xflow-diff';
+import { useState } from 'react';
// 变更前数据
const originalData = {
@@ -318,9 +318,23 @@ const currentData = {
};
const Page = () => {
+ const [showDiffDetail, setShowDiffDetail] = useState(true);
+
+ const onClickHandle = () => {
+ setShowDiffDetail(!showDiffDetail);
+ };
return (
-
-
+
+
+
+
+
);
};
diff --git a/packages/diff/README.md b/packages/diff/README.md
index 3ee31410..b2da062e 100644
--- a/packages/diff/README.md
+++ b/packages/diff/README.md
@@ -1,3 +1,104 @@
[English (US)](README.md) | 简体中文
-# Diff
+# XFlow Diff
+
+XFlow Diff 是一个用于跟踪、比较和合并图形结构(如节点和边)差异的工具,适用于图形编辑器
+和可视化应用。
+
+## 目录
+
+- [特性](#特性)
+- [安装](#安装)
+- [使用示例](#使用示例)
+
+## 特性
+
+- 支持对比不同版本的图形状态。
+- 支持自定义比较函数和变更详情面板的展示。
+
+## 安装
+
+您可以通过 npm 安装 XFlow Diff:
+
+```shell
+# npm
+$ npm install @antv/xflow --save
+
+# yarn
+$ yarn add @antv/xflow
+
+# pnpm
+$ pnpm add @antv/xflow
+```
+
+## 使用示例
+
+请参照:apps/basic/src/pages/diff/index.tsx
+
+```tsx
+import React from 'react';
+import { DiffGraph } from '@antv/xflow-diff';
+
+const originalData = {
+ nodes: [
+ { id: 'node1', label: 'Node 1' },
+ { id: 'node2', label: 'Node 2' },
+ ],
+ edges: [{ source: 'node1', target: 'node2' }],
+};
+
+const currentData = {
+ nodes: [
+ { id: 'node1', label: 'Node 1' },
+ { id: 'node2', label: 'Node 2' },
+ { id: 'node3', label: 'Node 3' },
+ ],
+ edges: [
+ { source: 'node1', target: 'node2' },
+ { source: 'node2', target: 'node3' },
+ ],
+};
+
+const App = () => {
+ return (
+
+ );
+};
+```
+
+## API 详情
+
+```tsx
+export interface DiffGraphOptions {
+ /** 原始数据 */
+ originalData: GraphData;
+ /** 变更后数据 */
+ currentData: GraphData;
+ /** 新增颜色 */
+ addColor?: string;
+ /** 新增节点扩展属性 */
+ addExtAttr?: object;
+ /** 删除颜色 */
+ delColor?: string;
+ /** 删除节点扩展属性 */
+ delExtAttr?: object;
+ /** 变更颜色 */
+ changeColor?: string;
+ /** 变更节点扩展属性 */
+ changeExtAttr?: object;
+ /** 画布配置 */
+ graphOptions?: GraphOptions;
+ /** 展示diff详情 */
+ showDiffDetail?: boolean;
+ /** 自定义渲染diff详情 */
+ customRenderDiffDetail?: (detail: DiffInfo[]) => React.ReactNode;
+ /** 节点描述字段属性key */
+ nodeDescKey?: string;
+ /** 边描述字段属性key */
+ edgeDescKey?: string;
+}
+```
diff --git a/packages/diff/package.json b/packages/diff/package.json
index 872784df..8245e4fe 100644
--- a/packages/diff/package.json
+++ b/packages/diff/package.json
@@ -1,28 +1,36 @@
{
"name": "@antv/xflow-diff",
- "version": "1.0.0",
+ "version": "1.1.0",
+ "private": false,
"description": "",
+ "keywords": [
+ "xflow",
+ "x6",
+ "antv"
+ ],
+ "bugs": {
+ "url": "https://github.com/antvis/XFlow/issues"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/antvis/XFlow.git",
+ "directory": "packages/diff"
+ },
"main": "dist/index.cjs.js",
"module": "dist/index.esm.js",
"types": "dist/typing/index.d.ts",
- "private": false,
"files": [
"dist",
"src"
],
- "keywords": [
- "xflow",
- "x6",
- "antv"
- ],
"scripts": {
- "setup": "tsup src/index.ts",
"build": "tsup src/index.ts",
"dev": "tsup src/index.ts --watch",
- "lint:js": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"lint:css": "stylelint --allow-empty-input 'src/**/*.{css,less}'",
"lint:format": "prettier --check *.md *.json 'src/**/*.{js,jsx,ts,tsx,css,less,md,json}'",
+ "lint:js": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"lint:typing": "tsc --noEmit",
+ "setup": "tsup src/index.ts",
"test": "jest --coverage"
},
"dependencies": {
@@ -39,14 +47,6 @@
"react": ">=16.8.6 || >=17.0.0 || >=18.0.0",
"react-dom": ">=16.8.6 || >=17.0.0 || >= 18.0.0"
},
- "bugs": {
- "url": "https://github.com/antvis/XFlow/issues"
- },
- "repository": {
- "type": "git",
- "url": "https://github.com/antvis/XFlow.git",
- "directory": "packages/diff"
- },
"publishConfig": {
"access": "public"
}
diff --git a/packages/diff/src/components/DiffDetailPanel/index.less b/packages/diff/src/components/DiffDetailPanel/index.less
new file mode 100644
index 00000000..f07dbab3
--- /dev/null
+++ b/packages/diff/src/components/DiffDetailPanel/index.less
@@ -0,0 +1,17 @@
+.xflow-diff-panel {
+ display: flex;
+ overflow: auto;
+ height: 30%;
+ min-height: 10%;
+ width: 100%;
+
+ .info-tag {
+ font-size: small;
+ padding: 3px;
+ }
+
+ .table-header {
+ font-weight: bold;
+ font-size: 24px;
+ }
+}
diff --git a/packages/diff/src/components/DiffDetailPanel/index.tsx b/packages/diff/src/components/DiffDetailPanel/index.tsx
new file mode 100644
index 00000000..2fef9a3d
--- /dev/null
+++ b/packages/diff/src/components/DiffDetailPanel/index.tsx
@@ -0,0 +1,111 @@
+import type { EdgeOptions, NodeOptions } from '@antv/xflow';
+import { Table, Tag } from 'antd';
+import React from 'react';
+
+import type { DiffDetailPanelProps, DiffInfo } from '@/types';
+
+import './index.less';
+
+export const DiffDetailPanel: React.FC
= (props) => {
+ const {
+ showDiffDetail,
+ diffDetailInfo,
+ customRenderDiffDetail,
+ addColor,
+ delColor,
+ changeColor,
+ nodeDescKey = 'label',
+ edgeDescKey = 'id',
+ } = props;
+ if (!showDiffDetail || !diffDetailInfo?.length) {
+ return ;
+ }
+
+ /**
+ * diff详情描述词典
+ */
+ const DiffTypeDict: {
+ [keys in Exclude]: {
+ color: string;
+ text: string;
+ };
+ } = {
+ ADD: {
+ color: addColor,
+ text: '新增',
+ },
+ DEL: {
+ color: delColor,
+ text: '删除',
+ },
+ CHG: {
+ color: changeColor,
+ text: '变更',
+ },
+ };
+
+ const CellDict: {
+ [keys in DiffInfo['cellType']]: string;
+ } = {
+ Edge: '边',
+ Node: '节点',
+ };
+
+ const getDesc = (data: DiffInfo['currentData'], cellType: DiffInfo['cellType']) => {
+ if (cellType === 'Node') {
+ return (data as NodeOptions)[nodeDescKey];
+ } else {
+ return (data as EdgeOptions)[edgeDescKey];
+ }
+ };
+
+ const defaultDiffPanel = (
+
+ title={() => {'Diff变更详情列表'}}
+ pagination={false}
+ bodyStyle={{ textAlign: 'center' }}
+ bordered
+ columns={[
+ {
+ title: '变更详情',
+ dataIndex: 'currentData',
+ render: (currentData: DiffInfo['currentData'], record) => {
+ const oriPreStr = record.originalData
+ ? getDesc(record.originalData, record.cellType) + ' -> '
+ : '';
+ const curStr = getDesc(currentData, record.cellType);
+ return `${oriPreStr}${curStr}`;
+ },
+ },
+ {
+ title: '元素类型',
+ dataIndex: 'cellType',
+ render: (text: DiffInfo['cellType']) => {
+ return CellDict[text];
+ },
+ },
+ {
+ title: '变更类型',
+ dataIndex: 'diffType',
+ render: (text: Exclude) => {
+ const dicVal = DiffTypeDict[text];
+ return (
+
+ {dicVal.text}
+
+ );
+ },
+ },
+ ]}
+ dataSource={diffDetailInfo}
+ />
+ );
+
+ return (
+
+ {customRenderDiffDetail
+ ? customRenderDiffDetail(diffDetailInfo)
+ : defaultDiffPanel}
+
+ );
+};
diff --git a/packages/diff/src/components/DiffGraph/index.tsx b/packages/diff/src/components/DiffGraph/index.tsx
index a86ab3d7..8bd84a5d 100644
--- a/packages/diff/src/components/DiffGraph/index.tsx
+++ b/packages/diff/src/components/DiffGraph/index.tsx
@@ -3,9 +3,11 @@ import { XFlow, XFlowGraph } from '@antv/xflow';
import type { FC } from 'react';
import React, { useEffect, useState } from 'react';
-import type { DiffGraphOptions } from '@/types';
+import type { DiffGraphOptions, DiffInfo } from '@/types';
import { compare, syncGraph } from '@/util';
+import { DiffDetailPanel } from '../DiffDetailPanel';
+
import '../../styles/index.less';
import Tool from './tool';
@@ -20,6 +22,8 @@ const DiffGraph: FC = (props) => {
changeColor = '#ffc069', // 变更节点的颜色
changeExtAttr,
graphOptions,
+ showDiffDetail = false,
+ customRenderDiffDetail,
} = props;
const [originalDataWithDiffInfo, setOriginalDataWithDiffInfo] = useState<{
@@ -32,6 +36,7 @@ const DiffGraph: FC = (props) => {
}>({ nodes: [], edges: [] });
const [status, setStatus] = useState<'init' | 'computing' | 'done'>('init');
const [graphs, setGraphs] = useState[]>([]);
+ const [diffDetailInfo, setDiffDetailInfo] = useState();
useEffect(() => {
// 获取 diff 信息,注入 attr
@@ -39,6 +44,7 @@ const DiffGraph: FC = (props) => {
const {
originalDataWithDiffInfo: originalDataWithDiffInfoRe,
currentDataWithDiffInfo: currentDataWithDiffInfoRe,
+ diffDetailInfo: diffDetailInfoRe,
} = compare(
originalData,
currentData,
@@ -52,6 +58,7 @@ const DiffGraph: FC = (props) => {
setOriginalDataWithDiffInfo(originalDataWithDiffInfoRe);
setCurrentDataWithDiffInfo(currentDataWithDiffInfoRe);
+ setDiffDetailInfo(diffDetailInfoRe);
setStatus('done');
}, []); // eslint-disable-line
@@ -69,50 +76,61 @@ const DiffGraph: FC = (props) => {
};
return (
-
- {/* 左图 */}
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
- {/* @ts-ignore */}
-
- {status === 'done' && (
-
- )}
-
+ {/* diff详情展示 */}
+
+
+ {/* 左图 */}
+ {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
+ {/* @ts-ignore */}
+
+ {status === 'done' && (
+
+ )}
+
-
+ }}
+ {...graphOptions}
+ />
+
- {/* 右图 */}
- {/* eslint-disable-next-line @typescript-eslint/ban-ts-comment */}
- {/* @ts-ignore */}
-
- {status === 'done' && (
-
- )}
-
+ {status === 'done' && (
+
+ )}
+
-
+ }}
+ {...graphOptions}
+ />
+
+
);
};
diff --git a/packages/diff/src/components/DiffGraph/tool.tsx b/packages/diff/src/components/DiffGraph/tool.tsx
index 8958a08c..5d92b818 100644
--- a/packages/diff/src/components/DiffGraph/tool.tsx
+++ b/packages/diff/src/components/DiffGraph/tool.tsx
@@ -1,7 +1,7 @@
import type { EdgeOptions, NodeOptions } from '@antv/xflow';
import { useGraphInstance, useGraphStore } from '@antv/xflow';
-import type { FC } from 'react';
-import React, { useEffect } from 'react';
+import type React from 'react';
+import { useEffect } from 'react';
interface ToolOptions {
data: {
@@ -11,7 +11,7 @@ interface ToolOptions {
addGraph: (graph: ReturnType) => void;
}
-const Tool: FC = (props) => {
+const Tool: React.FC = (props) => {
const { data, addGraph } = props;
const initData = useGraphStore((state) => state.initData);
const graphIns = useGraphInstance();
diff --git a/packages/diff/src/styles/index.less b/packages/diff/src/styles/index.less
index ec7e20bb..1390f111 100644
--- a/packages/diff/src/styles/index.less
+++ b/packages/diff/src/styles/index.less
@@ -2,13 +2,11 @@
position: relative;
display: flex;
height: 100%;
+ flex-direction: column;
- &::after {
- position: absolute;
- left: 50%;
- width: 1px;
- height: 100%;
- background-color: #333;
- content: '';
+ .xflow-container {
+ flex: 1;
+ overflow: hidden;
+ display: flex;
}
}
diff --git a/packages/diff/src/types/index.ts b/packages/diff/src/types/index.ts
index d55c1c81..4adb4ec9 100644
--- a/packages/diff/src/types/index.ts
+++ b/packages/diff/src/types/index.ts
@@ -3,13 +3,30 @@ import type { EdgeOptions, GraphOptions, NodeOptions } from '@antv/xflow';
export interface DiffGraphOptions {
originalData: GraphData;
currentData: GraphData;
- addColor?: '';
+ addColor?: string;
addExtAttr?: object;
- delColor?: '';
+ delColor?: string;
delExtAttr?: object;
- changeColor?: '';
+ changeColor?: string;
changeExtAttr?: object;
graphOptions?: GraphOptions;
+ /** 展示diff详情 */
+ showDiffDetail?: boolean;
+ /** 自定义渲染diff详情 */
+ customRenderDiffDetail?: (detail: DiffInfo[]) => React.ReactNode;
+ /** 节点描述字段属性key */
+ nodeDescKey?: string;
+ /** 边描述字段属性key */
+ edgeDescKey?: string;
+}
+
+export interface DiffDetailPanelProps
+ extends Pick<
+ DiffGraphOptions,
+ 'showDiffDetail' | 'customRenderDiffDetail' | 'nodeDescKey' | 'edgeDescKey'
+ >,
+ Required> {
+ diffDetailInfo?: DiffInfo[];
}
export interface GraphData {
@@ -17,13 +34,37 @@ export interface GraphData {
edges: EdgeOptions[];
}
+export type DiffType = 'DEL' | 'ADD' | 'CHG' | 'NONE';
+
+type CellType = 'Node' | 'Edge';
+
export interface NodeOptionsWithDiffInfo extends NodeOptions {
- diffType?: 'DEL' | 'ADD' | 'CHG' | 'NONE';
+ diffType?: DiffType;
}
export interface EdgeOptionsWithDiffInfo extends EdgeOptions {
- diffType?: 'DEL' | 'ADD' | 'CHG' | 'NONE';
+ diffType?: DiffType;
}
+export type DiffInfo =
+ | {
+ diffType: DiffType;
+ originalData?: NodeOptions | EdgeOptions;
+ currentData: NodeOptions | EdgeOptions;
+ cellType: CellType;
+ }
+ | {
+ diffType: DiffType;
+ originalData?: NodeOptions;
+ currentData: NodeOptions;
+ cellType: 'Node';
+ }
+ | {
+ diffType: DiffType;
+ originalData?: EdgeOptions;
+ currentData: EdgeOptions;
+ cellType: 'Edge';
+ };
+
export interface GraphDataWithDiffInfo {
nodes: NodeOptionsWithDiffInfo[];
edges: EdgeOptionsWithDiffInfo[];
diff --git a/packages/diff/src/util/index.ts b/packages/diff/src/util/index.ts
index 6c2dba97..61143640 100644
--- a/packages/diff/src/util/index.ts
+++ b/packages/diff/src/util/index.ts
@@ -1,6 +1,6 @@
import type { useGraphInstance } from '@antv/xflow';
-import type { GraphData, GraphDataWithDiffInfo } from '..';
+import type { DiffInfo, GraphData, GraphDataWithDiffInfo } from '..';
export const compare: (
originalData: GraphData,
@@ -14,6 +14,7 @@ export const compare: (
) => {
originalDataWithDiffInfo: GraphDataWithDiffInfo;
currentDataWithDiffInfo: GraphDataWithDiffInfo;
+ diffDetailInfo: DiffInfo[];
} = (
originalData,
currentData,
@@ -33,12 +34,19 @@ export const compare: (
edges: [...currentData.edges],
};
+ const diffDetailInfo: DiffInfo[] = [];
+
// 寻找 currentData 中 originalData 没有的数据,即为新增的
const originalIds = originalData.nodes
.map((node) => node.id)
.concat(originalData.edges.map((edge) => edge.id));
+
for (let i = 0; i < currentData.nodes.length; i++) {
if (!originalIds.includes(currentData.nodes[i].id)) {
+ console.log(
+ currentDataWithDiffInfo.nodes[i],
+ currentDataWithDiffInfo.nodes[i].diffType,
+ );
currentDataWithDiffInfo.nodes[i].diffType = 'ADD';
currentDataWithDiffInfo.nodes[i].attrs = {
...currentDataWithDiffInfo.nodes[i].attrs,
@@ -48,8 +56,14 @@ export const compare: (
},
...addExtAttr,
};
+ diffDetailInfo.push({
+ diffType: 'ADD',
+ currentData: currentData.nodes[i],
+ cellType: 'Node',
+ });
}
}
+
for (let i = 0; i < currentData.edges.length; i++) {
if (!originalIds.includes(currentData.edges[i].id)) {
currentDataWithDiffInfo.edges[i].diffType = 'ADD';
@@ -61,13 +75,19 @@ export const compare: (
},
...addExtAttr,
};
+
+ diffDetailInfo.push({
+ diffType: 'ADD',
+ currentData: currentData.edges[i],
+ cellType: 'Edge',
+ });
}
}
- // 寻找 originalData 中 currentData 没有的数据,即为新增的
const currentIds = currentData.nodes
.map((node) => node.id)
.concat(currentData.edges.map((edge) => edge.id));
+
for (let i = 0; i < originalData.nodes.length; i++) {
if (!currentIds.includes(originalData.nodes[i].id)) {
originalDataWithDiffInfo.nodes[i].diffType = 'DEL';
@@ -79,6 +99,12 @@ export const compare: (
},
...delExtAttr,
};
+
+ diffDetailInfo.push({
+ diffType: 'DEL',
+ currentData: currentData.nodes[i],
+ cellType: 'Node',
+ });
}
}
for (let i = 0; i < originalData.edges.length; i++) {
@@ -92,6 +118,12 @@ export const compare: (
},
...delExtAttr,
};
+
+ diffDetailInfo.push({
+ diffType: 'DEL',
+ currentData: currentData.edges[i],
+ cellType: 'Edge',
+ });
}
}
@@ -121,6 +153,13 @@ export const compare: (
},
...changeExtAttr,
};
+
+ diffDetailInfo.push({
+ diffType: 'CHG',
+ originalData: originalData.nodes[i],
+ currentData: currentData.nodes[j],
+ cellType: 'Node',
+ });
}
}
}
@@ -149,11 +188,18 @@ export const compare: (
},
...changeExtAttr,
};
+
+ diffDetailInfo.push({
+ diffType: 'CHG',
+ originalData: originalData.edges[i],
+ currentData: currentData.edges[j],
+ cellType: 'Edge',
+ });
}
}
}
- return { originalDataWithDiffInfo, currentDataWithDiffInfo };
+ return { originalDataWithDiffInfo, currentDataWithDiffInfo, diffDetailInfo };
};
// 同步两图的缩放和移动