如何使用Antv X6使用拖拽布局?

拖拽效果图

拖拽后

布局预览

官方: X6 图编辑引擎 | AntV

安装依赖

# npm
npm install @antv/x6 --save
npm install @antv/x6-plugin-dnd --save
npm install @antv/x6-plugin-export --save

需要引入的代码

import { Graph, Shape } from '@antv/x6';
import { Dnd } from "@antv/x6-plugin-dnd";

页面布局实现 我们设计左右布局

<div class="pannels-drag-view"><div class="left-tree"><div style="width: 100%;padding: 0px 10px 10px;"><div style="border-bottom: 1px solid #FAFAFA;"></div><div class="tree-one"><a-space><AppstoreFilled height="20px"/><span>00000001</span></a-space></div><div style="padding-left: 10px;" class="move-view"@mousedown="startDrag($event, {key: value.key, title: value.title})">
1111111111111111</div><div></div></div><div class="right-view" :style="{height: graphHeight+'px', 'min-height': 500, maxHeight: 900}"><div ref="container" :style="{height: graphHeight+'px'}"></div></div>
</div>
<style lang="less">
.pannels-layout-view {background-color: #FFFFFF;width: 100%;height: 100%;display: flex;flex-direction: column;.left-tree {width: 200px;min-width: 200px;border: 1px solid #e5e5e5;margin-right: 20px;display: flex;display: -webkit-flex;flex-direction: column;.tree-one {padding: 10px 0px 0px;cursor: pointer;display: flex;flex-direction: row;justify-items: start;}.move-view {padding: 10px;cursor: move;}.move-view:hover {background-color: #F7F7F7;}.ant-tree-switcher {width: 10px;}.ant-tree {.ant-tree-node-content-wrapper  {.cursor_move {cursor: move;}}}}.right-view {display: flex;flex-direction: column;flex: 1;height: 100%;overflow: auto;border: 1px solid #e5e5e5;overflow: hidden;position: relative;.layout-setting {position: absolute;top: 1px;right: 1px;z-index: 10;padding: 6px 12px;background: hsla(0, 0%, 100%, .7);.ant-btn {border-width: 0;}.ant-btn-icon-only {padding: 1px 0;border-width: 0;}}}
}</style>

创建节点

// 创建节点
Graph.registerNode('custom-node',{inherit: 'rect',width: 50,height: 70,},true,
);

在onMounted 中设置画布,和初始化内容

// 初始化画布
graph = new Graph({container: container.value,autoResize: true,background: {color: '#F2F7FA',},interacting: ({cell}) => {if (cell.getData() == undefined || cell.getData().disableMove) {return { nodeMovable: false }}return true;},panning: true,mousewheel: true,embedding: {enabled: true,findParent({node}) {const bbox = node.getBBox();return this.getNodes().filter((nodeTemp) => {const data = nodeTemp.getData();if (data && data.parent) {const targetBox = nodeTemp.getBBox();const targetBBox = bbox.intersectsWithRect(targetBox);return targetBBox;}return false;})}}});

处理布局 ,在画布上绘制 19 * 20个虚线方框,作为父容器,如下图

代码示例:

onMounted(() => {let targetPointTemp = [];let nodesListTemp = []// 处理布局for (let i = 0; i < 20; i++) {for (let j = 0; j < 9; j++) {let x = i * 60;let y = j * 80;// 设置吸附点targetPointTemp.push({x: x,y: y})const rectNode = new Shape.Rect({id: `${i}-${j}`,shape: 'custom-node',x: x,y: y,width: 50,height: 70,zIndex: 9,// label: `${i}-${j}`,data: {parent: true,disableMove: true,cpx: i,cpy: j},draggable: false,attrs: {body: {fill: '#F1F4F6',stroke: "#333333",strokeWidth: 1,strokeDasharray: '4, 4'},title: {text: `${i}-${j}`,fill: '#333333',verticalAnchor: 'bottom',fontSize: 12,refX: 10,refY: 60,}},markup: [{tagName: 'rect',selector: 'body',},{tagName: 'text',selector: 'title',}],})nodesListTemp.push(rectNode);}}graph.zoom(-0.2);shapeNodesList.value = nodesListTemp;// 添加节点到画布graph.addNodes(nodesListTemp);
})

使用DND画布外向画布内拖拽,并吸附,效果如下:

实现:外部向画布拖拽

import { Dnd } from "@antv/x6-plugin-dnd";// 移动左侧树,配合DND 与Graph 拖拽与监听
const startDrag = (e, data) => {const {key, title } = data;const keys = key.split('-');const invSn = keys[0];const id = keys[1];const newNode = graph.createNode({id: title?title:'',shape: 'rect',width: 150,height: 30,draggable: true,data: {parent: false,disableMove: false,id: id,partSn: title,invSn: invSn,partSn: title,},attrs: {label: {text: title,fill: '#FFFFFF',verticalAnchor: 'middle',fontSize: 12,ellipsis: true,breakWord: true,textWrap: {width: -10,height: -10,ellipsis: true}},body: {stroke: "#333333",strokeWidth: 1,fill: '#999999'}},zIndex: 11});const dnd = new Dnd({target: graph,getDragNode: (node) => node.clone({keepId: true}),getDropNode: (node) => node.clone({keepId: true}),validateNode: () => {console.log("drag successed")},})dnd.start(newNode, e);currentParent.value = null;
}

画布中监听并处理拖拽事件并吸附

onMounted(() => {
// 添加节点监听graph.on('node:added', (env) => {const { cell, node } = env;console.log("node:added");const data = cell.data;// 获取父节点const parent = node.getParent();let position = cell.position();if (parent) {position = parent.getPosition();if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {graph.removeNode(cell);return false;}} else {if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {graph.removeNode(cell);return false;}}//  删除dom   removeDomResData(data, cell);node.setProp('size', { width: 50, height: 70 });if (parent) {// 判断子节点数量const childCount = parent.getChildCount();if (childCount > 1) {startDragOut(data, cell);return false}position = parent.getPosition();cell.position(position.x, position.y, cell);cell.setAttrs({body: {stroke: "#222222",strokeWidth: 1,fill: '#3E82FF'}})cell.setParent(parent);cell.insertTo(parent);} else {const cellParent = cell.getParent();cell.setAttrs({body: {stroke: "#222222",strokeWidth: 1,fill: '#3E82FF'}})if (cellParent) {cell.setParent(cellParent);cell.insertTo(cellParent);}}saveLayoutData(data, cell);});})

嵌入父节点的监听

// 嵌入父节点监听graph.on('node:embedded', ({ cell, node }) => {const parent = cell.getParent();const position = parent.getPosition();const data = cell.getData(); // 获取节点数据if (data.parent != undefined && data.parent) {parent.removeChild(node);return false;}if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {parent.removeChild(node);cell.position(startX.value, startY.value, cell);cell.setParent(currentParent.value);cell.insertTo(currentParent.value);return false;}const childCount = parent.getChildCount();if (childCount > 1) {parent.removeChild(node);if (currentParent.value) {cell.position(startX.value, startY.value, cell);cell.setParent(currentParent.value);cell.insertTo(currentParent.value);return false} else {const cellParent = cell.parent;if (cellParent) {const cellCount = cell.parent.getChildCount();if (cellCount > 1) {return false}const px = cellParent.getPosition().x;const py = cellParent.getPosition().y;cell.position(px, py, cell);cell.setParent(cellParent);cell.insertTo(cellParent);return false}// cell.setParent(null);graph.removeCell(cell);startDragOut(data, cell);return false;}}cell.position(position.x, position.y, cell);cell.setAttrs({body: {stroke: "#222222",strokeWidth: 1,fill: '#3E82FF'}})cell.setParent(parent);cell.insertTo(parent);saveLayoutData(data, cell);});

画布中 子节点的移动处理

onMounted(() => {// 鼠标按下事件graph.on('node:mousedown', (node)=>{console.log("node:mousedown")const { cell } = node;const parent = cell.getParent();if (parent) {currentParent.value = parent;} else {currentParent.value = null;}// 记录初始位置if (!cell.data.parent) {const position = cell.position();startX.value = position.x;startY.value = position.y;} else {startX.value = null;startY.value = null;}});
/// 鼠标按下后的离开事件graph.on("node:mouseup", (env) => {console.log("node:mouseup")const { cell, node } = env;const position = cell.position();if (position.x < 0 && position.y > 0 && position.y < maxY.value) {const data = cell.getData();startDragOut(data, cell);return false;}if (position.x > maxX.value || position.y > maxY.value || position.x < 0 || position.y < 0) {cell.position(startX.value, startY.value, cell);if (currentParent.value) {cell.setParent(currentParent.value);cell.insertTo(currentParent.value);}return false;}});// 监听节点事件函数graph.on('node:removed', (args) => {//   更新有效节点数据对象const { cell } = args;const data = cell.getData();removeLayoutData(data);});})

画布中布局变化记录事件

// 保存布局变化
const saveLayoutData = (data, cell) => {const { id, partSn, invSn} = data;let tempList = notSavedDataList.value;const position = cell.getPosition();const x = Math.ceil(position.x / 60);const y = Math.ceil(position.y / 80);const _index = _.findIndex(tempList, {id: id});if (_index >= 0) {const newData = {id: id,laidOutX: x,laidOutY: y,partSn: partSn,invSn: invSn}tempList[_index] = newData;} else {const newData = {id: id,laidOutX: x,laidOutY: y,partSn: partSn,invSn: invSn}tempList.push(newData);}notSavedDataList.value = tempList;
}

需要结合antv/X6的事件监听事件灵活应用

 

graph.on('cell:click', ({ e, x, y, cell, view }) => {})

graph.on('node:click', ({ e, x, y, node, view }) => {})

graph.on('edge:click', ({ e, x, y, edge, view }) => {})

graph.on('blank:click', ({ e, x, y }) => {})

graph.on('cell:mouseenter', ({ e, cell, view }) => {})

graph.on('node:mouseenter', ({ e, node, view }) => {})

graph.on('edge:mouseenter', ({ e, edge, view }) => {})

graph.on('graph:mouseenter', ({ e }) => {})

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/83988.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库健康监测器(BHM)实战:如何通过 HTML 报告识别潜在问题

在数据库运维中,健康监测是保障系统稳定性与性能的关键环节。通过 HTML 报告,开发者可以直观查看数据库的运行状态、资源使用情况与潜在风险。 本文将围绕 数据库健康监测器(Database Health Monitor, BHM) 的核心功能展开分析,结合 Prometheus + Grafana + MySQL Export…

PCB设计实践(二十四)PCB设计时如何避免EMI

PCB设计中避免电磁干扰&#xff08;EMI&#xff09;是一项涉及电路架构、布局布线、材料选择及制造工艺的系统工程。本文从设计原理到工程实践&#xff0c;系统阐述EMI产生机制及综合抑制策略&#xff0c;覆盖高频信号控制、接地优化、屏蔽技术等核心维度&#xff0c;为高密度、…

嵌入式硬件篇---陀螺仪|PID

文章目录 前言1. 硬件准备主控芯片陀螺仪模块电机驱动电源其他2. 硬件连接3. 软件实现步骤(1) MPU6050初始化与数据读取(2) 姿态解算(互补滤波或DMP)(3) PID控制器设计(4) 麦克纳姆轮协同控制4. 主程序逻辑5. 关键优化与调试技巧(1) 传感器校准(2) PID参数整定先调P再调D最后…

【Linux基础I/O】文件调用接口、文件描述符、重定向和缓冲区

【Linux基础I/O一】文件描述符和重定向 1.C语言的文件调用接口2.操作系统的文件调用接口2.1open接口2.2close接口2.3write接口2.4read接口 3.文件描述符fd的本质4.标准输入、输出、错误5.重定向5.1什么是重定向5.2输入重定向和输出重定向5.3系统调用的重定向dup2 6.缓冲区 1.C语…

鸿蒙HarmonyOS 【ArkTS组件】通用属性-背景设置

&#x1f4d1;往期推文全新看点&#xff08;附带最新鸿蒙全栈学习笔记&#xff09; 嵌入式开发适不适合做鸿蒙南向开发&#xff1f;看完这篇你就了解了~ 鸿蒙岗位需求突增&#xff01;移动端、PC端、IoT到底该怎么选&#xff1f; 分享一场鸿蒙开发面试经验记录&#xff08;三面…

【76. 最小覆盖子串】

Leetcode算法练习 笔记记录 76. 最小覆盖子串 76. 最小覆盖子串 滑动窗口的hard题目&#xff0c;思路先找到第一个覆盖的窗口&#xff0c;不断缩小左边界&#xff0c;找到更小的窗口并记录。 思路很简单&#xff0c;写起来就不是一会事了&#xff0c;看题解看了几个h&#xff0…

Spring事务简单操作

什么是事务&#xff1f; 事务是一组操作的集合&#xff0c;是一个不可分割的操作 事务会把所有的操作作为⼀个整体, ⼀起向数据库提交或者是撤销操作请求. 所以这组操作要么同时 成功, 要么同时失败. 事务的操作 分为三步&#xff1a; 1. 开启事start transaction/ begin …

Rust 学习笔记:关于错误处理的练习题

Rust 学习笔记&#xff1a;关于错误处理的练习题 Rust 学习笔记&#xff1a;关于错误处理的练习题想看到回溯&#xff0c;需要把哪个环境变量设置为 1&#xff1f;以下哪一项不是使用 panic 的好理由&#xff1f;以下哪一项最能描述为什么 File::open 返回的是 Result 而不是 O…

MCP 协议传输机制大变身:抛弃 SSE,投入 Streamable HTTP 的怀抱

在技术的江湖里&#xff0c;变革的浪潮总是一波接着一波。最近&#xff0c;模型上下文协议&#xff08;MCP&#xff09;的传输机制就搞出了大动静&#xff0c;决定和传统的服务器发送事件&#xff08;SSE&#xff09;说拜拜&#xff0c;转身拥抱 Streamable HTTP&#xff0c;这…

138. Copy List with Random Pointer

目录 题目描述 方法一、使用哈希表 方法二、不使用哈希表 题目描述 问题的关键是&#xff0c;random指针指向的是原链表的结点&#xff0c;这个原链表的结点对应哪一个新链表的结点呢&#xff1f;有两种办法。一是用哈希表。另一种是复制原链表的每一个结点&#xff0c;并将…

如何评估开源商城小程序源码的基础防护能力?

在电商行业快速发展的背景下&#xff0c;开源商城已经为更多企业或者开发者的首选方案&#xff0c;不过并不是所有的开源商城源码都能让人放心使用&#xff0c;今天就带大家一起了解下如何评估开源商城小程序源码的基础防护能力&#xff0c;帮助大家更好地筛选安全性高的商城源…

[Vue]跨组件传值

父子组件传值 详情可以看文章 跨组件传值 Vue 的核⼼是单向数据流。所以在父子组件间传值的时候&#xff0c;数据通常是通过属性从⽗组件向⼦组件&#xff0c;⽽⼦组件通过事件将数据传递回⽗组件。多层嵌套场景⼀般使⽤链式传递的⽅式实现provideinject的⽅式适⽤于需要跨层级…

悠易科技智能体矩阵撬动AI全域营销新时代

大数据产业创新服务媒体 ——聚焦数据 改变商业 在数字化浪潮与AI技术的双重驱动下&#xff0c;数据营销正经历前所未有的变革&#xff0c;从传统的全域智能营销&#xff0c;迈向更具颠覆性的AI全域营销时代。 麦肯锡的报告显示&#xff0c;采用AI驱动营销的企业&#xff0c;客…

Xilinx XCAU10P-2FFVB676I 赛灵思 Artix UltraScale+ FPGA

XCAU10P-2FFVB676I 是 AMD Xilinx 推出的 Artix UltraScale™ FPGA 器件&#xff0c;内部集成了约 96,250 逻辑单元&#xff0c;满足中等规模高性能应用的需求。该芯片采用 16 nm FinFET 制程工艺&#xff0c;核心电压典型值约 0.85 V&#xff0c;能够在较低功耗下提供高达 775…

Java SpringBoot 项目中 Redis 存储 Session 具体实现步骤

目录 一、添加依赖二、配置 Redis三、配置 RedisTemplate四、创建控制器演示 Session 使用五、启动应用并测试六、总结 Java 在 Spring Boot 项目中使用 Redis 来存储 Session&#xff0c;能够实现 Session 的共享和高可用&#xff0c;特别适用于分布式系统环境。以下是具体的实…

分布式电源的配电网无功优化

分布式电源(Distributed Generation, DG)的大规模接入配电网,改变了传统单向潮流模式,导致电压波动、功率因数降低、网损增加等问题,无功优化成为保障配电网安全、经济、高效运行的关键技术。 1. 核心目标 电压稳定性:抑制DG并网点(PCC)及敏感节点的电压越限(如超过5%…

JS手写代码篇---手写Promise

4、手写promise Promise 是一个内置对象&#xff0c;用于处理异步操作。Promise 对象表示一个尚未完成但预期将来会完成的操作。 Promise 的基本结构 一个 Promise 对象通常有以下状态&#xff1a; pending&#xff08;进行中&#xff09;&#xff1a;初始状态&#xff0c;…

我喜欢的vscode几个插件和主题

主题 Monokaione Monokai Python 语义高光支持 自定义颜色为 self 将 class , def 颜色更改为红色 为装饰器修复奇怪的颜色 适用于魔法功能的椂光 Python One Dark 这个主题只在python中效果最好。 我为我个人使用做了这个主题,但任何人都可以使用它。 插件 1.Pylance Pylanc…

【深度学习新浪潮】大模型时代,我们还需要学习传统机器学习么?

在大模型时代,AI 工程师仍需掌握传统机器学习知识,这不仅是技术互补的需求,更是应对复杂场景和职业发展的关键。以下从必要性和学习路径两方面展开分析: 一、传统机器学习在大模型时代的必要性 技术互补性 大模型(如GPT、BERT)擅长处理复杂语义和生成任务,但在数据量少…

年度工作计划总结述职报告PPT模版一组分享

工作计划总结述职报告PPT模版&#xff1a;工作计划述职报告PPT模版https://pan.quark.cn/s/fba40a5e87da 第一套PPT模版是医院年度工作计划的封面页&#xff0c;有蓝橙配色、医院标题、年度工作计划的大字、英文副标题、汇报人信息和右上角的医院logo区域&#xff0c;右侧还有医…