1、 下载x6:
npm i @antv/x6@2.17.1
同步下载所需要用到的x6插件:
"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包
"@antv/x6-plugin-history": "^2.0.0", // 如果使用撤销重做功能,需要安装此包
"@antv/x6-plugin-keyboard": "^2.0.0", // 如果使用快捷键功能,需要安装此包
"@antv/x6-plugin-minimap": "^2.0.0", // 如果使用小地图功能,需要安装此包
"@antv/x6-plugin-scroller": "^2.0.0", // 如果使用滚动画布功能,需要安装此包
"@antv/x6-plugin-selection": "^2.0.0", // 如果使用框选功能,需要安装此包
"@antv/x6-plugin-snapline": "^2.0.0", // 如果使用对齐线功能,需要安装此包
"@antv/x6-plugin-dnd": "^2.0.0", // 如果使用 dnd 功能,需要安装此包
"@antv/x6-plugin-stencil": "^2.0.0", // 如果使用 stencil 功能,需要安装此包
"@antv/x6-plugin-transform": "^2.0.0", // 如果使用图形变换功能,需要安装此包
"@antv/x6-plugin-export": "^2.0.0", // 如果使用图片导出功能,需要安装此包
"@antv/x6-react-components": "^2.0.0", // 如果使用配套 UI 组件,需要安装此包
"@antv/x6-react-shape": "^2.0.0", // 如果使用 react 渲染功能,需要安装此包
"@antv/x6-vue-shape": "^2.0.0" // 如果使用 vue 渲染功能,需要安装此包
2、页面中引入x6
<template><div class="entry-container" ref="screenRef"><div class="mapBox"><div id="mapBody"></div></div><div id="minimap"></div><TeleportContainer /> // 很重要,必须将TeleportContainer标签放置在html结构中</div></div>
</template><script setup>
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
</script>
3、注册自定义节点:
// 引入自定义节点
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";// 注册外部节点
register({shape: "ArtNode",inherit: "vue-shape",width: 300,height: 72,component: ArtNode,ports: { ...ports },
});// 注册群组节点
register({shape: "GroupNode",inherit: "vue-shape",width: 325,height: 210,component: GroupNode,ports: { ...ports },
});const ports = {groups: {in: {position: "left",attrs: {circle: {r: 5,magnet: true,stroke: "#ccc",strokeWidth: 2,fill: "#fff",},},},out: {position: "right",attrs: {circle: {r: 5,magnet: true,stroke: "#ccc",strokeWidth: 2,fill: "#fff",},},},},items: [{id: "port1",group: "in",},{id: "port2",group: "out",},],
};
4、注册x6插件
// 注册插件
const handleUsePlugin = () => {graph.use(new Transform({resizing: true,rotating: true,})).use(new Selection({rubberband: true,showNodeSelectionBox: true,})).use(new Scroller({enabled: true,autoResize: true,pannable: true, //是否启用画布平移能力modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽})).use(new MiniMap({container: document.getElementById("minimap"),width: 200,height: 160,scalable: false,minScale: 0.1,maxScale: 2,padding: 10,})).use(new Snapline()).use(new Keyboard()).use(new Clipboard()).use(new History());
};
5、注册边&连接器
Edge.config({markup: [{tagName: "path",selector: "wrap",attrs: {fill: "none",cursor: "pointer",stroke: "transparent",strokeLinecap: "round",},},{tagName: "path",selector: "line",attrs: {fill: "none",pointerEvents: "none",},},],connector: { name: "curveConnector" },attrs: {wrap: {connection: true,strokeWidth: 10,strokeLinejoin: "round",},line: {connection: true,stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "classic",size: 6,},},},
});// 注册连接器
Graph.registerConnector("curveConnector",(sourcePoint, targetPoint) => {const hgap = Math.abs(targetPoint.x - sourcePoint.x);const path = new Path();path.appendSegment(Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y));path.appendSegment(Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y));// 水平三阶贝塞尔曲线path.appendSegment(Path.createSegment("C",sourcePoint.x < targetPoint.x? sourcePoint.x + hgap / 2: sourcePoint.x - hgap / 2,sourcePoint.y,sourcePoint.x < targetPoint.x? targetPoint.x - hgap / 2: targetPoint.x + hgap / 2,targetPoint.y,targetPoint.x - 6,targetPoint.y));path.appendSegment(Path.createSegment("L", targetPoint.x + 2, targetPoint.y));return path.serialize();},true
);Graph.registerEdge("data-processing-curve", Edge, true);
6、初始化x6画布
// 创建画布
const handleInit = () => {// 创建画布graph = new Graph({container: document.getElementById("mapBody"),grid: true,width: 1500,height: 1200,autoResize: true,// 设置画布背景颜色background: {color: "#f0f3f5",},// 画布缩放 按住ctrlmousewheel: {enabled: true,global: true,modifiers: ["ctrl", "meta"],},// 设置连接线connecting: {snap: true, //开启和关闭连线过程中自动吸附anchor: "center",connectionPoint: "boundary",allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 trueallowBlank: false, // 是否允许连接到画布空白位置的点highlight: true,sourceAnchor: {name: "left",args: {dx: Platform.IS_SAFARI ? 4 : 8,},},targetAnchor: {name: "right",args: {dx: Platform.IS_SAFARI ? 4 : -8,},},//创建边createEdge() {return graph.createEdge({shape: "data-processing-curve",attrs: {line: {stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "block",width: 12,height: 8,},},},zIndex: -1,});},validateConnection({ targetMagnet }) {return !!targetMagnet;},},highlighting: {magnetAdsorbed: {name: "stroke",args: {attrs: {fill: "#006eff",stroke: "#006eff",},},},},});graph.centerContent();handleUsePlugin();
7、给x6画布绑定事件(实现复制、粘贴、删除、撤销、回复等功能)
// 画布方法&快捷键
const handleGraphMethods = () => {// 撤销/复原graph.bindKey(["meta+z", "ctrl+z"], () => {if (graph.canUndo()) {graph.undo();}return false;});graph.bindKey(["meta+y", "ctrl+y"], () => {if (graph.canRedo()) {graph.redo();}return false;});//复制graph.bindKey(["meta+c", "ctrl+c"], () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);}return false;});// 粘贴graph.bindKey(["meta+v", "ctrl+v"], () => {if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" && (node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}return false;});// 删除graph.bindKey("backspace", () => {const cells = graph.getSelectedCells();if (cells.length) {graph.removeCells(cells);}});// 右键菜单graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {handleContextmenu(e);});// 历史改变graph.on("history:change", () => {canRedo.value = graph.canRedo();canUndo.value = graph.canUndo();});// 选中节点graph.on("cell:selected", ({ cell, index, options }) => {selectCell.value = cell;});// 取消选中节点graph.on("cell:unselected", ({ cell, index, options }) => {selectCell.value = null;});// 双击节点graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {if (graph.isNode(cell)) {drawerData.value = JSON.parse(JSON.stringify(cell.getData()));drawerData.value.showDrawer = true;}});
};
8、拖拽生成节点
// 指定画布拖拽区dnd = new Dnd({target: graph,scaled: false,dndContainer: document.querySelector(".treeContent"),// 确保拖拽节点时和拖拽完成后节点id一致getDragNode: (node) => node.clone({ keepId: true }),getDropNode: (node) => {// 拖拽结束后let data = node.getData();drawerData.value = {...data,id: node.id,isCreate: true,showDrawer: true,};return node.clone({ keepId: true });},});handleGraphMethods();
};// 拖拽生成节点
const handleCreateNode = (type, e) => {// graph.cleanSelection();const node =type === "group"? graph.createNode({shape: "GroupNode",data: {type: "group",group: [{ type: "inside" }, { type: "outside" }],},}): graph.createNode({shape: "ArtNode",data: {type,},});dnd.start(node, e);
};
9、保存/设置节点数据
// 保存
const handleToJson = () => {const json = graph.toJSON();json.knwlgMapId = route.query.pkId;submitNode(json).then(() => {proxy.$modal.msgSuccess("保存成功!");});
};// 获取地图数据
const getMapDetail = () => {loading.value = true;getNodeDetail({ knwlgMapId: route.query.pkId }).then(({ data: cells }) => {graph.fromJSON({ cells });loading.value = false;}).catch(() => {loading.value = false;});
};
完整代码:
<template><div class="entry-container" ref="screenRef" v-loading="loading"><div class="treeContent" style="width: 240px"><div class="flex mb20"><div class="mr5">插入节点</div><el-tooltip placement="bottom"><template #content>* 创建节点方式:<br />按住内部节点/外部节点/文章集合后往右侧区域拖拽<br />* 节点/边操作快捷键:<br />删除:点击选中节点/边后按Backspace键<br />撤销:操作后Ctrl+z<br />恢复:操作后Ctrl+y<br />复制:选中节点/边后Ctrl+c Ctrl+v<br />多选:直接框选或按住Ctrl单点<br />* 画布操作快捷键:<br />按住Ctrl+鼠标左键可拖拽画布<br />按住Ctrl+鼠标滚轮可缩放画布<br /></template><el-icon class="mr5" :size="14" color="#006eff"><QuestionFilled/></el-icon></el-tooltip></div><div class="sigleGroup"><divclass="shapeItem flex"v-for="(item, index) in shapeList":key="index"@mousedown="handleCreateNode(item.type, $event)"><img class="mr10" :src="item.imgSrc" /><span>{{ item.name }}</span></div></div></div><div class="mapContent"><div class="mapHeader flex"><div class="flex"><div v-if="!mapInfo.showIpt">{{ mapInfo.knwlgMapName }}</div><el-inputv-elseref="mapNameRef"maxlength="20"autofocusv-model.trim="mapInfo.knwlgMapName"placeholder="输入地图名称"show-word-limitclearablestyle="width: 200px"@blur="handleBlur"></el-input><el-buttonv-if="!mapInfo.showIpt"linktype="primary"icon="Edit"@click="handleShowIpt"></el-button></div><div class="flex"><el-tooltip content="撤销"><el-button:disabled="!canUndo"linktype="primary"icon="RefreshLeft"@click="handleUndo"></el-button></el-tooltip><el-tooltip content="恢复"><el-button:disabled="!canRedo"linktype="primary"icon="RefreshRight"@click="handleRedo"></el-button></el-tooltip><el-tooltip content="删除选中的线或节点"><el-button:disabled="!selectCell"linktype="primary"icon="Delete"@click="handleDelete()"></el-button></el-tooltip><el-tooltip content="复制选中的线或节点"><el-button:disabled="!selectCell"linktype="primary"icon="CopyDocument"@click="handleCopy"></el-button></el-tooltip><el-tooltip content="全屏"><el-buttonlinktype="primary"icon="FullScreen"@click="toggle"></el-button></el-tooltip><el-divider direction="vertical" /><el-button type="primary" @click="handleToJson">保存</el-button><el-button @click="handleClosePage">关闭</el-button></div></div><div class="mapBox"><div id="mapBody"></div></div><div id="minimap"></div><TeleportContainer /></div><mapDrawerv-model="drawerData"@handleUpdateData="handleUpdateData"@handleDelCell="handleDelete"/></div>
</template><script setup name="Ekms/map/grapth">
import { getNodeDetail, submitNode, editName } from "@/api/ekms/map";
import { register, getTeleport } from "@antv/x6-vue-shape/lib";
import { Graph, Path, Edge, Platform } from "@antv/x6";
import { Transform } from "@antv/x6-plugin-transform";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Scroller } from "@antv/x6-plugin-scroller";
import { Dnd } from "@antv/x6-plugin-dnd";
import { MiniMap } from "@antv/x6-plugin-minimap";
import { useFullscreen } from "@vueuse/core";
import ContextMenu from "@imengyu/vue3-context-menu";
import useTagsViewStore from "@/store/modules/tagsView";
import { ports, shapeList } from "./components/constans.js";
import ArtNode from "./components/artNode.vue";
import GroupNode from "./components/groupNode.vue";
import mapDrawer from "./components/mapDrawer.vue";const { proxy } = getCurrentInstance();
const screenRef = ref(null);
const { toggle } = useFullscreen(screenRef);
const route = useRoute();
const router = useRouter();
const mapInfo = ref({originName: route.query.knwlgMapName,knwlgMapName: route.query.knwlgMapName,
});let graph = null;
let dnd = null;
const TeleportContainer = getTeleport();
const canRedo = ref(false);
const canUndo = ref(false);
const loading = ref(false);
// const routeId = ref(route.query.pkId);
const selectCell = ref(null);
const drawerData = ref({showDrawer: false,title: "",group: [{ type: "inside" }, { type: "outside" }],
});onMounted(() => {if (!route.query.pkId) return;handleInit();// window.addEventListener("resize", resizeFn);
});// const resizeFn = () => {
// const width = document.querySelector(".mapContent").offsetWidth;
// const height = document.querySelector(".mapContent").offsetHeight;
// graph.resize(width, height);
// };// onActivated(() => {
// if (routeId.value != route.query.pkId) {
// routeId.value = route.query.pkId;
// graph && graph.dispose();
// handleInit();
// } else {
// resizeFn();
// graph.centerContent();
// }
// });const handleShowIpt = () => {mapInfo.value.showIpt = true;nextTick(() => {proxy.$refs.mapNameRef.input.focus();});
};const handleBlur = async () => {mapInfo.value.showIpt = false;!mapInfo.value.knwlgMapName?.length &&(mapInfo.value.knwlgMapName = route.query.knwlgMapName);await editName({pkId: route.query.pkId,knwlgMapName: mapInfo.value.knwlgMapName,});
};// 获取地图数据
const getMapDetail = () => {loading.value = true;getNodeDetail({ knwlgMapId: route.query.pkId }).then(({ data: cells }) => {graph.fromJSON({ cells });loading.value = false;}).catch(() => {loading.value = false;});
};// 注册外部节点
register({shape: "ArtNode",inherit: "vue-shape",width: 300,height: 72,component: ArtNode,ports: { ...ports },
});// 注册群组节点
register({shape: "GroupNode",inherit: "vue-shape",width: 325,height: 210,component: GroupNode,ports: { ...ports },
});Edge.config({markup: [{tagName: "path",selector: "wrap",attrs: {fill: "none",cursor: "pointer",stroke: "transparent",strokeLinecap: "round",},},{tagName: "path",selector: "line",attrs: {fill: "none",pointerEvents: "none",},},],connector: { name: "curveConnector" },attrs: {wrap: {connection: true,strokeWidth: 10,strokeLinejoin: "round",},line: {connection: true,stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "classic",size: 6,},},},
});// 注册连接器
Graph.registerConnector("curveConnector",(sourcePoint, targetPoint) => {const hgap = Math.abs(targetPoint.x - sourcePoint.x);const path = new Path();path.appendSegment(Path.createSegment("M", sourcePoint.x - 4, sourcePoint.y));path.appendSegment(Path.createSegment("L", sourcePoint.x + 12, sourcePoint.y));// 水平三阶贝塞尔曲线path.appendSegment(Path.createSegment("C",sourcePoint.x < targetPoint.x? sourcePoint.x + hgap / 2: sourcePoint.x - hgap / 2,sourcePoint.y,sourcePoint.x < targetPoint.x? targetPoint.x - hgap / 2: targetPoint.x + hgap / 2,targetPoint.y,targetPoint.x - 6,targetPoint.y));path.appendSegment(Path.createSegment("L", targetPoint.x + 2, targetPoint.y));return path.serialize();},true
);Graph.registerEdge("data-processing-curve", Edge, true);// 创建画布
const handleInit = () => {// 创建画布graph = new Graph({container: document.getElementById("mapBody"),grid: true,width: 1500,height: 1200,autoResize: true,// 设置画布背景颜色background: {color: "#f0f3f5",},// 画布缩放 按住ctrlmousewheel: {enabled: true,global: true,modifiers: ["ctrl", "meta"],},// 设置连接线connecting: {snap: true, //开启和关闭连线过程中自动吸附anchor: "center",connectionPoint: "boundary",allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 trueallowBlank: false, // 是否允许连接到画布空白位置的点highlight: true,sourceAnchor: {name: "left",args: {dx: Platform.IS_SAFARI ? 4 : 8,},},targetAnchor: {name: "right",args: {dx: Platform.IS_SAFARI ? 4 : -8,},},//创建边createEdge() {return graph.createEdge({shape: "data-processing-curve",attrs: {line: {stroke: "#A2B1C3",strokeWidth: 1,targetMarker: {name: "block",width: 12,height: 8,},},},zIndex: -1,});},validateConnection({ targetMagnet }) {return !!targetMagnet;},},highlighting: {magnetAdsorbed: {name: "stroke",args: {attrs: {fill: "#006eff",stroke: "#006eff",},},},},});// resizeFn();graph.centerContent();getMapDetail();handleUsePlugin();// 指定画布拖拽区dnd = new Dnd({target: graph,scaled: false,dndContainer: document.querySelector(".treeContent"),// 确保拖拽节点时和拖拽完成后节点id一致getDragNode: (node) => node.clone({ keepId: true }),getDropNode: (node) => {// 拖拽结束后let data = node.getData();drawerData.value = {...data,id: node.id,isCreate: true,showDrawer: true,};return node.clone({ keepId: true });},});handleGraphMethods();
};// 画布方法&快捷键
const handleGraphMethods = () => {// 撤销/复原graph.bindKey(["meta+z", "ctrl+z"], () => {if (graph.canUndo()) {graph.undo();}return false;});graph.bindKey(["meta+y", "ctrl+y"], () => {if (graph.canRedo()) {graph.redo();}return false;});//复制graph.bindKey(["meta+c", "ctrl+c"], () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);}return false;});// 粘贴graph.bindKey(["meta+v", "ctrl+v"], () => {if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" && (node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}return false;});// 删除graph.bindKey("backspace", () => {handleDelete();});// 右键菜单graph.on("node:contextmenu", ({ e, x, y, cell, view }) => {handleContextmenu(e);});// 历史改变graph.on("history:change", () => {canRedo.value = graph.canRedo();canUndo.value = graph.canUndo();});// 选中节点graph.on("cell:selected", ({ cell, index, options }) => {selectCell.value = cell;});// 取消选中节点graph.on("cell:unselected", ({ cell, index, options }) => {selectCell.value = null;});// 双击节点graph.on("cell:dblclick", ({ e, x, y, cell, view }) => {if (graph.isNode(cell)) {drawerData.value = JSON.parse(JSON.stringify(cell.getData()));drawerData.value.showDrawer = true;}});
};// 注册插件
const handleUsePlugin = () => {graph.use(new Transform({resizing: true,rotating: true,})).use(new Selection({rubberband: true,showNodeSelectionBox: true,})).use(new Scroller({enabled: true,autoResize: true,pannable: true, //是否启用画布平移能力modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽})).use(new MiniMap({container: document.getElementById("minimap"),width: 200,height: 160,scalable: false,minScale: 0.1,maxScale: 2,padding: 10,})).use(new Snapline()).use(new Keyboard()).use(new Clipboard()).use(new History());
};// 拖拽生成节点
const handleCreateNode = (type, e) => {// graph.cleanSelection();const node =type === "group"? graph.createNode({shape: "GroupNode",data: {type: "group",group: [{ type: "inside" }, { type: "outside" }],},}): graph.createNode({shape: "ArtNode",data: {type,},});dnd.start(node, e);
};// 修改节点内容
const handleUpdateData = (id, value) => {let cell = graph.getCellById(id || selectCell.value.id);cell.updateData(value);if (drawerData.value.type === "group") {cell.resize(325, 50 + drawerData.value.group.length * 82);}drawerData.value.showDrawer = false;
};// 撤销
const handleUndo = () => {graph.undo();
};// 恢复
const handleRedo = () => {graph.redo();
};// 复制
const handleCopy = () => {const cells = graph.getSelectedCells();if (cells.length) {graph.copy(cells);if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });graph.cleanSelection();graph.select(cells);}}
};// 右键菜单
const handleContextmenu = (e) => {const cells = graph.getSelectedCells();ContextMenu.showContextMenu({x: e.pageX,y: e.pageY,items: [{label: "复制(Ctrl+c&Ctrl+z)",disabled: cells.length === 0 ? true : false,onClick: () => {if (cells.length) {graph.copy(cells);if (!graph.isClipboardEmpty()) {const cells = graph.paste({ offset: 32 });// 解决复制的节点修改失效问题cells.forEach((item) => {let node = item.store.data;node.shape !== "data-processing-curve" &&(node.data.id = node.id);});graph.cleanSelection();graph.select(cells);}}},},{label: "删除(Backspace)",disabled: cells.length === 0 ? true : false,onClick: () => {if (cells.length) {graph.removeCells(cells);}},},],});
};// 删除节点/边
const handleDelete = (id) => {// 判断是新增删除还是修改删除const cells = id ? [graph.getCellById(id)] : graph.getSelectedCells();if (cells.length) {graph.removeCells(cells);}
};// 保存
const handleToJson = () => {const json = graph.toJSON();json.knwlgMapId = route.query.pkId;submitNode(json).then(() => {proxy.$modal.msgSuccess("保存成功!");});
};// 关闭页面
const handleClosePage = () => {let tag = useTagsViewStore().visitedViews.find((item) => item.path === "/ekms/map/grapth");proxy.$tab.closePage(tag).then(() => {router.push("/ekms/map/mapLibrary");});
};onBeforeUnmount(() => {graph && graph.dispose();
});
</script><style lang="scss" scoped>
.entry-container {display: flex;position: relative;background: #f0f3f5;height: calc(100vh - 70px);:deep(.treeContent) {padding: 20px;min-width: 240px;.shapeItem {padding: 0 10px;margin-bottom: 10px;height: 44px;line-height: 20px;font-size: 14px;background: #f6f8fd;border-radius: 4px;user-select: none;cursor: move;&:hover {border: 1px solid rgba(0, 112, 255, 1);}img {width: 28px;height: 28px;}}}:deep(.mapContent) {flex: 1;position: relative;.mapHeader {padding: 0 20px;width: 100%;height: 40px;line-height: 40px;background: #fff;justify-content: space-between;box-shadow: 0px 1px 0px 0px rgba(215, 220, 230, 1);}.mapBox {width: calc(100vw - 245px);height: calc(100vh - 110px);}#minimap {position: absolute;right: 0;bottom: 0;}}
}
:deep(#mapBody) {.x6-widget-selection-box {border-color: rgba(0, 110, 255, 1);}.x6-widget-transform {display: none !important;}.x6-edge-selected path:nth-child(2) {stroke: #006eff;stroke-dasharray: 8px, 2px;}
}
</style>