ようやく antv X6 2.0 バージョンのデモを抽出する時間が取れました。このプロジェクトでは常に vue3 を使用してこのプロセスを実行するだろうと思っていました。その結果、プロジェクト マネージャーは最近、antv X6 のメニュー機能は vue3 でのみ使用できると言いました。反応...しかしガチョウ... と書きました メニューモジュールで、それが使用できることがわかりました...
現在、私のプロジェクトの React バージョンにはさらに多くの機能があります (図に示すように、ノード追加時の自動レイアウト、右クリック メニューなど)。
この記事では、この小さなデモのいくつかの機能と実装を記録します~~~
(デモ ウェアハウスへのリンクが添付されています: https://github.com/Tipchak5/vue3_antv_X6_2.0.git )
-
テンプレートをインポートします(挿入するテンプレートをクリックするとキャンバス内のノードが直接形成され、左側のディレクトリツリーとテンプレートの関係が対応しており、ノードIDを使用してディレクトリツリーのラベルを表示します) )
コード:
1. まず、テンプレート データ tsakMasterplate をインポートします (後でバックエンド インターフェイスを調整し、インポートするテンプレートを選択する必要があります)
2. leadInMasterplate メソッドを直接調整すると、テンプレートをインポートする前にキャンバスがクリアされます。
const tsakMasterplate = ref(require('../../assets/masterplate.json')); // 引入模版数据
/** 导入模版 */
const leadInMasterplate = async () => {
// 清空所有单元格
const cells = graph.getCells(); // 获取所有单元格
graph.removeCells(cells); // 画布清空
// 模版渲染
await new Promise((resolve) => setTimeout(resolve, 100));
graph.fromJSON(tsakMasterplate.value).centerContent(); // 引入模版数据 这里tsakMasterplate是我自己写的一个死数据
// 对应的目录树
treeInfo.value = [
{
label: 'node2',
children: [
{
label: 'task2',
children: [
{
label: 'task3',
children: [],
},
{
label: 'task5',
children: [],
},
],
},
],
},
];
};
-
json をエクスポートします (実際、json をエクスポートすることは、テンプレートを作成し、後で呼び出すために使用するノード テンプレートを json 形式でバックエンドに渡すことと同じです)
コード:
// 导出json
function printNodeList() {
console.log(graph.toJSON(), '导出数组');
console.log(JSON.stringify(graph.toJSON({ diff: true })), 'JSON');
graph.clearCells();
}
-
新しいノード
-
<el-button type="primary" :icon="Plus" :title="task" @mousedown="startDrag(task, $event)" >新增{ { task }} </el-button> const task = ref('任务'); // 新增任务节点 const startDrag = (type) => { const tree = startDragToGraph(graph, type); // graph就是初始化的画布 因为我是将startDragToGraph方法抽出来放在公共js中直接调用的 所以传了graph 如果你在当前页面就不需要传了 id.value = tree.id; const label = { label: tree.id, children: [] }; treeInfo.value[0].children.push(label); // 更新树目录 }; // 这里startDragToGraph方法如下 let count = 0; const startDragToGraph = (graph, type) => { let node = null; node = graph.addNode({ shape: 'custom-rect', attrs: { label: { text: type, // 自动换行 textWrap: { width: '90%', height: '80%', ellipsis: true, breakWord: true, }, }, }, x: -50, y: -50, id: `task${++count}`, // 设置节点id 方便后期根据id抓节点 进行一些操作 }); ElMessage.success(`添加任务节点${node.id}成功!`); return node; };
-
ノード接続時の一部の操作
1. まず、そのノードにはポート と呼ばれる接続パイル(つまり、ノードの側面にある小さな点) があります。
2. いつポートを表示してノードを接続しますか? このケースでは、マウスがノード内に移動するとポートが表示されることがわかります。
非表示を削除する
コード
// 连接桩
const ports = {
groups: {
top: {
position: 'top',
attrs: {
circle: {
r: 2,
magnet: true,
stroke: 'black',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 2,
magnet: true,
stroke: 'black',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 2,
magnet: true,
stroke: 'black',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 2,
magnet: true,
stroke: 'black',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
],
};
ポートの表示・非表示
graph.on('node:mouseenter', () => {
const container = document.getElementById('graph-container');
const ports = container.querySelectorAll('.x6-port-body');
showPorts(ports, true);
});
graph.on('node:mouseleave', () => {
const container = document.getElementById('graph-container');
const ports = container.querySelectorAll('.x6-port-body');
showPorts(ports, false);
});
// 以上代码需要放在onMounted周期中 或者画布初始化的方法中也可以
// 函数showPorts我是单独抽成一个公共js的文件里面使用的 看个人实际情况
function showPorts(ports, show) {
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = show ? 'visible' : 'hidden';
}
}
3. 接続が成功したら、接続されたターゲットノードとソースノードに応じてツリーディレクトリのデータ構造をどのように表示するかを判断します(間違って、再帰が来ています...)
ターゲット ノードとソース ノードを取得した後、ツリー ディレクトリに移動してこれら 2 つの ID を見つけます。存在する場合は、ツリー ディレクトリ内のターゲット ノードをソース ノードの子にすると、次のディレクトリ ツリーに対応するディレクトリ ツリーを作成できます。ノード
そのうちの 1 つは非常に重要です。接続が成功すると、ディレクトリ ツリー内のターゲット ノードはソース ノードの子になります。ディレクトリ ツリー内の元のターゲット ノードを忘れずに削除してください (ちなみに、react のbookコンポーネントディレクトリ ツリー全体はサポートされていません。同じ 2 つのノードがあります。つまり、子ノードが 2 つのソース ノードを持つことはできません)
コード
// 节点连接成功时
graph.on('edge:connected', ({ isNew, edge }) => {
if (isNew) {
const sourceId = edge.getSourceCell().id;
const targetId = edge.getTargetCell().id;
// findNodeById 是我写的一个公共方法 在树目录里找和节点相同的id
const sourceNode = findNodeById(
treeInfo.value[0].children,
sourceId
); // 源节点id
const targetNode = findNodeById(
treeInfo.value[0].children,
targetId
); // 目标节点id
if (sourceNode && targetNode) {
sourceNode.children.push(targetNode);
// push之后删除原targetNode
for (let i = 0; i < treeInfo.value[0].children.length; i++) {
const node = treeInfo.value[0].children[i];
if (node.label === targetId) {
treeInfo.value[0].children.splice(i, 1);
}
}
}
}
});
// 树形目录中添加节点
export const findNodeById = (nodes, id) => {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
if (node.label === id) {
return node;
}
if (node.children) {
const result = findNodeById(node.children, id);
if (result) {
return result;
}
}
}
return null;
};
-
削除機能
ノードの削除はキーボードのボタンをバインドするか、公式のノードツールを使用することで操作できます(以下の図を削除したい場合は公式ドキュメントを参照できるので簡単です)
主にキーボードのボタン(「delete」、「backspace 」は削除可能)で操作しており、削除イベントをバインドするにはX6 に対応するプラグイン@antv/x6-plugin-keyboardをインストールする必要があります。ノード データに対応するディレクトリも削除する必要があります(削除の際、ノードに子ノードがある場合、後続の子ノードはすべて削除されます)
コード
/** 删除的一些操作 */
graph.bindKey(['delete', 'backspace'], () => {
const cells = graph.getSelectedCells();
const cellsId = cells[0].id; // 获取删除节点的id
if (cells.length) {
const allChildrenNode = [];
const nodes = cells.filter((cell) => cell.isNode());
nodes.forEach((node) => {
// 获取后继单元格
const successors = graph.getSuccessors(node, { depth: 3 });
allChildrenNode.push(...successors);
console.log(successors, 'successors');
});
// 获取后继节点id
let keyArr = [];
allChildrenNode.forEach((i) => {
keyArr.push(i.id);
});
// 删除当前节点
graph.removeCells(cells);
removeNodes(keyArr);
// 如果删除的节点和其他节点有共同子节点时 删除书目录中对应的数据
let newArr = [...keyArr, cellsId]; // 要删除的节点id
// 删除对应树目录数据
deleteNodeById(treeInfo.value[0].children, newArr);
console.log(newArr, 'newArr');
}
});
// 删除后继节点
function removeNodes(keys) {
if (keys && keys.length > 0) {
keys.forEach((key) => {
graph.removeNode(key, {
deep: true,
});
});
}
}
// 树形目录中删除对应节点
export const deleteNodeById = (treeArr, keyArr) => {
let deleted = false;
for (let i = treeArr.length - 1; i >= 0; i--) {
const node = treeArr[i];
if (keyArr.includes(node.label)) {
treeArr.splice(i, 1);
deleted = true;
} else if (node.children && node.children.length) {
if (deleteNodeById(node.children, keyArr)) {
deleted = true;
if (node.children.length === 0) {
node.children = [];
}
}
}
}
return deleted;
};
以上がこのデモのほぼすべての機能で、テキスト編集などもありますが、まだ試していないのでとりあえずは気にしません、まずはやってみましょう~
私はあなたとコミュニケーションを取り、一緒に進歩することを楽しみにしています。文章が悪いところがある場合は、ハイハンがご指導を歓迎します。最近、React バージョンのカスタムツールの登録で詰まっています。本当に困っています。」反応に慣れていない...頭が痛い...