基于@antv/x6的流程图
1.引言
基于 antv/x6 实现自定义流程图。
实现的功能有:背景网格编辑、链接桩隐藏与显示、适应画布、小地图、放大、缩小、层级改变、复制、剪切、粘贴、全选、框选、撤销、重做、重置、删除、清空、数据保存、另存为图片以及一些快捷键设置
antv/x6 官方文档:X6 图编辑引擎 | AntV
2.实际效果
gitee源码: 流程图-x6
3.下载与插件
3.1 下载x6
npm i @antv/x6@2.17.1
3.2 其他x6插件
"@antv/x6": "^2.17.1",
"@antv/x6-plugin-clipboard": "^2.0.0", // 如果使用剪切板功能,需要安装此包
"@antv/x6-plugin-dnd": "^2.0.0",// 如果使用 dnd 功能,需要安装此包
"@antv/x6-plugin-export": "^2.1.6",// 如果使用图片导出功能,需要安装此包
"@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-stencil": "^2.0.0",// 如果使用 stencil 功能,需要安装此包
"@antv/x6-plugin-transform": "^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 渲染功能,需要安装此包 "@vue/composition-api": "^1.7.2",// 如果使用 vue 渲染功能自定义节点时,在 vue2 下还需要安装此包,
注意:使用 @antv/x6-vue-shape
自定义节点时 在 vue2 下还需要安装 @vue/composition-api
,否则会报错
官方文档:使用 HTML/React/Vue/Angular 渲染 | X6
4.基础使用
4.1 插件引入
import { register, getTeleport } from "@antv/x6-vue-shape/lib";// register注册外部节点
import { Graph, Shape, Path, Edge, Platform, DataUri } 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 { Export } from "@antv/x6-plugin-export";
4.2 注册插件
handleUsePlugin() {
this.graph
.use(
new Transform({
resizing: true,
rotating: true,
})
)
.use(
//点选/框选,开启后可以通过点击或者套索框选节点
new Selection({
enabled: true, //启用
rubberband: false, //是否启用框选
showNodeSelectionBox: true, //是否显示节点的选择框
})
)
// .use(
// new Scroller({
// enabled: true,
// autoResize: true,
// pannable: true, //是否启用画布平移能力// modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
// eventTypes: ["leftMouseDown"],
// })
// )
.use(new Snapline())
.use(new Keyboard())
.use(new Clipboard())
.use(new History())
.use(new Export());
},
4.3 初始化渲染画布
initGraph() {
// 事件交互对象:cell 节点/边 -- node 节点 -- edge 边 -- blank 画布空白区域
// 创建 Graph 对象,并引入基本设置
const graph = new Graph({
container: document.getElementById("wrapper"),
...configSetting(Shape),
});
this.graph = graph;
// 画布键盘事件绑定
this.handleUsePlugin();
graphBindKey(this.graph);
// 画布事件 - 鼠标进入节点
this.graph.on("node:mouseenter", ({ node }) => {
this.changePortsShow(true, node);
});
//鼠标离开节点
this.graph.on("node:mouseleave", ({ node }) => {
if (this.isPortsShow) return; //如果链接桩常显状态开启,直接return
this.changePortsShow(false, node);
});
// 点击空白网格
this.graph.on("blank:click", () => {
// this.editGrid();
});
// 点击编辑节点/边
this.graph.on("cell:click", ({ cell }) => {
this.editForm(cell);
if (this.isPortsShow) return; //如果链接桩常显状态开启,直接return
this.changePortsShow(false, cell);
});
// 历史改变
this.graph.on("history:change", () => {
this.canRedo = this.graph.canRedo();
this.canUndo = this.graph.canUndo();
});
// 画布有变化
graph.on("cell:changed", () => {
this.isChangeValue();
});
// 删除
this.graph.bindKey(["delete", "backspace"], () => {
this.handlerDel();
});
// 赋值
// 返现方法
if (this.value && this.value.length) {
const resArr = this.value;
// 导出的时候删除了链接桩设置加回来
const portsGroups = configNodePorts().groups;
if (resArr.length) {
const jsonTemp = resArr.map((item) => {
if (item.ports) item.ports.groups = portsGroups;
return item;
});
graph.fromJSON(jsonTemp);
}
}
// 指定画布拖拽区
this.dnd = new Dnd({
target: this.graph,
scaled: false,
dndContainer: document.querySelector(".antv-menu"),
// 确保拖拽节点时和拖拽完成后节点id一致
getDragNode: (node) => node.clone({ keepId: true }),
getDropNode: (node) => {
// 拖拽结束后
let data = node.getData();
// this.menuItem = {
// ...data,
// id: node.id,
// isCreate: true,
// showDrawer: true,
// };
// console.log("this.menuItem :>> ", this.menuItem);
return node.clone({ keepId: true });
},
});
this.graph.zoomTo(1);
this.changeGrid();
this.graph.enablePanning();
},
4.4 给x6画布绑定事件(实现复制、粘贴、删除、撤销、回复等功能)
// 链接桩的显示与隐藏,主要是照顾菱形
changePortsShow(val, node) {
const container = document.getElementById("wrapper");
let ports = [];
if (node) {
ports = container.querySelectorAll(
`g[data-cell-id="${node.id}"] .x6-port-body`
);
} else {
ports = container.querySelectorAll(".x6-port-body");
}
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = val ? "visible" : "hidden";
}
},
// 适应画布
changeAdaptiveCanvas() {
// 缩放画布内容,使画布内容充满视口
this.graph.zoomToFit({ padding: 10, maxScale: 2 });
this.graph.centerContent(); // 将画布中元素居中展示
this.zoom = this.graph.zoom();
},
// 实际尺寸
changeActualSize() {
this.graph.zoomTo(1);
this.zoom = this.graph.zoom();
},
// 小地图的显示与隐藏
changeMinMapShow() {
// minMapShow -- true(显示)-- false(隐藏)
this.minMapShow = !this.minMapShow;
if (this.minMapShow) {
// 如果小地图的实例显示,重新注册
this.graph.use(
new MiniMap({
container: document.getElementById("minimap"),
width: 200,
height: 160,
scalable: false,
minScale: 0.1,
maxScale: 2,
padding: 10,
})
);
} else {
// 移除创建
this.graph.disposePlugins("minimap");
}
},
// 放大
EnlargeRoom() {
const zoom = this.graph.zoom().toFixed(1); //获取画布缩放比例
if (zoom < 2) {
this.graph.zoom(0.1);
this.zoom = this.graph.zoom().toFixed(1);
}
},
// 缩小
ShrinkRoom() {
const zoom = this.graph.zoom().toFixed(1); //获取画布缩放比例
if (zoom > 0.5) {
this.graph.zoom(-0.1);
this.zoom = this.graph.zoom().toFixed(1);
}
},
// 向下一层
toBackLevel() {
const cells = this.graph.getSelectedCells(); //获取选中的节点/边
if (cells.length) {
this.selectCell.toBack();
}
},
// 向上一层
toFrontLevel() {
const cells = this.graph.getSelectedCells(); //获取选中的节点/边
if (cells.length) {
this.selectCell.toFront();
}
},
// 复制
copyCells() {
const cells = this.graph.getSelectedCells(); //获取选中的节点/边
if (cells.length) {
this.graph.copy(cells); //复制节点/边
} else {
this.$message({ type: "info", message: "请先选中节点再复制" });
}
},
// 剪切
cutCells() {
const cells = this.graph.getSelectedCells(); //获取选中的节点/边
if (cells.length) {
this.graph.cut(cells); //剪切节点/边
} else {
this.$message({ type: "info", message: "请先选中节点再剪切" });
}
},
// 粘贴
selectCells() {
if (!this.graph.isClipboardEmpty()) {
//返回剪切板是否为空
const cells = this.graph.paste({ offset: 30, useLocalStorage: true }); //粘贴,返回粘贴到画布的节点/边。
this.graph.cleanSelection(); //清空选区。
this.graph.select(cells); //选中指定的节点/边
} else {
this.$message({ type: "info", message: "剪切板为空,不可粘贴!" });
}
},
// 全选
selectAllNodes() {
const nodes = this.graph.getNodes(); //返回画布中所有节点
if (nodes) {
this.graph.select(nodes); //选中指定的节点/边
}
},
// 撤销
handleUndo() {
if (this.graph.canUndo()) {
//是否可以撤销
this.graph.undo(); //撤销
}
},
// 重做
handleRedo() {
if (this.graph.canRedo()) {
//是否可以重做
this.graph.redo(); //重做
}
},
// 重置
handlerReset() {
this.isValueChange();
},
// 清空
handlerClear() {
this.graph.clearCells(); //清空画布
},
// 框选
handlerSelection() {
if (this.graph.isRubberbandEnabled()) {
this.graph.disableRubberband();
this.graph.enablePanning();
} else {
this.graph.enableRubberband();
this.graph.disablePanning();
}
},
// 网格参数设置
changeGridType(e) {
this.grid.type = e;
if (this.grid.type === "doubleMesh") {
this.grid.args = [
{
color: "#ccc", // 主网格线颜色
thickness: 1, // 主网格线宽度/网格点大小
},
{
color: "#999", // 次网格线颜色
thickness: 1, // 次网格线宽度
factor: 4, // 主次网格线间隔
},
];
} else {
this.grid.args = {
color: "#ccc", // 主网格线颜色
thickness: 1, // 主网格线宽度/网格点大小
};
}
this.changeGrid();
},
// 网格绘制
changeGrid() {
this.graph.drawGrid({
...this.grid,
});
},
4.5 编辑节点/边
//点击编辑节点/边
editForm(cell) {
console.log("obje点击编辑节点/边ct");
if (this.selectCell) this.selectCell.removeTools(); //删除所有工具
this.selectCell = cell; //当前选中或点击的节点/边
// 编辑node节点
if (
cell.isNode() &&
cell.data.type &&
cell.data.type.includes("default")
) {
this.editTitle = "编辑节点"; //节点/边参数编辑名称
const body =
cell.attrs.body ||
cell.attrs.rect ||
cell.attrs.polygon ||
cell.attrs.circle;
//节点参数
this.form = {
labelText: cell.attrs.label.text || "", //节点文本
fontSize: cell.attrs.label.fontSize || 14, //字体大小
fontFill: cell.attrs.label.fill || "", //字体颜色
fill: body.fill || "", //节点背景
stroke: body.stroke || "", //边框颜色
};
//侧边栏编辑抽屉显现
return (this.editDrawer = true);
}
// 编辑图片节点
if (cell.isNode() && cell.data.type && cell.data.type === "otherImage") {
this.editTitle = "编辑图片节点";
const attrs = cell.attrs || {
body: { fill: "" },
label: { text: "", fill: "" },
image: { xlinkHref: "", height: 80, width: 80 },
};
//节点参数
this.form = {
labelText: attrs.label.text, //节点文本
labelFill: attrs.label.fill, //字体颜色
fill: attrs.body.fill, //节点背景
height: (attrs.image && attrs.image.height) || 80, //图片尺寸
width: (attrs.image && attrs.image.width) || 80, //图片尺寸
//图片地址
xlinkHref:
(attrs.xlinkHref && attrs.image.xlinkHref) ||
"https://copyright.bdstatic.com/vcg/creative/cc9c744cf9f7c864889c563cbdeddce6.jpg@h_1280",
};
//侧边栏编辑抽屉显现
return (this.editDrawer = true);
}
// 编辑线
if (!cell.isNode() && cell.shape === "edge") {
this.editTitle = "编辑连线";
//边参数
this.form = {
label:
cell.labels && cell.labels[0]
? cell.labels[0].attrs.labelText.text
: "",
stroke: cell.attrs.line.stroke || "",
connector: "normal",
strokeWidth: cell.attrs.line.strokeWidth || "",
isArrows: cell.attrs.line.sourceMarker ? true : false, //sourceMarker - 起始箭头
isAnit: cell.attrs.line.strokeDasharray ? true : false, //流动线条
isTools: false, //调整线条
};
// 看是否有label
const edgeCellLabel =
(cell.labels && cell.labels[0] && cell.labels[0].attrs) || false;
if (this.form.label && edgeCellLabel) {
//标签内容参数
this.labelForm = {
fontColor: edgeCellLabel.labelText.fill || "#333", //字体颜色
fill: edgeCellLabel.labelBody.fill || "#fff", //背景颜色
stroke: edgeCellLabel.labelBody.stroke || "#555", //描边颜色
};
} else {
this.labelForm = { fontColor: "#333", fill: "#FFF", stroke: "#333" };
}
//侧边栏编辑抽屉显现
return (this.editDrawer = true);
}
},
// 隐藏侧边栏编辑抽屉
closeEditForm() {
this.editDrawer = false;
if (this.selectCell) this.selectCell.removeTools(); //删除所有工具
},
// 修改一般节点
changeNode(type, value) {
switch (type) {
case "labelText":
this.selectCell.attr("label/text", value);
break;
case "fontSize":
this.selectCell.attr("label/fontSize", value);
break;
case "fontFill":
this.selectCell.attr("label/fill", value);
break;
case "fill":
this.selectCell.attr("body/fill", value);
break;
case "stroke":
this.selectCell.attr("body/stroke", value);
break;
}
},
// 修改图片节点
changeImageNode(type, value) {
switch (type) {
case "labelText":
this.selectCell.attr("label/text", value);
break;
case "labelFill":
this.selectCell.attr("label/fill", value);
break;
case "fill":
this.selectCell.attr("body/fill", value);
break;
case "xlinkHref":
this.selectCell.attr("image/xlinkHref", value);
break;
case "height":
this.selectCell.attr("image/height", value);
break;
case "width":
this.selectCell.attr("image/width", value);
break;
}
},
// 修改边label属性
changeEdgeLabel(label, fontColor, fill, stroke) {
this.selectCell.setLabels([
configEdgeLabel(label, fontColor, fill, stroke),
]);
if (!label)
this.labelForm = { fontColor: "#333", fill: "#FFF", stroke: "#555" };
},
// 修改边的颜色
changeEdgeStroke(val) {
this.selectCell.attr("line/stroke", val);
},
// 边的样式
changeEdgeConnector(val) {
switch (val) {
case "normal":
this.selectCell.setConnector(val);
break;
case "smooth":
this.selectCell.setConnector(val);
break;
case "rounded":
this.selectCell.setConnector(val, { radius: 20 });
break;
case "jumpover":
this.selectCell.setConnector(val, { radius: 20 });
break;
}
},
// 边的宽度
changeEdgeStrokeWidth(val) {
if (this.form.isArrows) {
this.selectCell.attr({
line: {
strokeWidth: val,
sourceMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
targetMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
},
});
} else {
this.selectCell.attr({
line: {
strokeWidth: val,
targetMarker: {
width: 12 * (val / 2) || 12,
height: 8 * (val / 2) || 8,
},
},
});
}
},
// 边的箭头
changeEdgeArrows(val) {
if (val) {
this.selectCell.attr({
line: {
sourceMarker: {
name: "block",
width: 12 * (this.form.strokeWidth / 2) || 12,
height: 8 * (this.form.strokeWidth / 2) || 8,
},
targetMarker: {
name: "block",
width: 12 * (this.form.strokeWidth / 2) || 12,
height: 8 * (this.form.strokeWidth / 2) || 8,
},
},
});
} else {
this.selectCell.attr({
line: {
sourceMarker: "",
targetMarker: {
name: "block",
size: 10 * (this.form.strokeWidth / 2) || 10,
},
},
});
}
},
// 边的添加蚂蚁线
changeEdgeAnit(val) {
if (val) {
this.selectCell.attr({
line: {
strokeDasharray: 5,
style: {
animation: "ant-line 30s infinite linear",
},
},
});
} else {
this.selectCell.attr({
line: {
strokeDasharray: 0,
style: {
animation: "",
},
},
});
}
},
// 给线添加调节工具
changeEdgeTools(val) {
if (val) this.selectCell.addTools(["vertices", "segments"]);
else this.selectCell.removeTools();
},
// 删除节点
handlerDel() {
this.$confirm(
`此操作将永久删除此${this.editTitle === "编辑节点" || this.editTitle === "编辑图片节点"
? "节点"
: "连线"
}, 是否继续?`,
"提示",
{
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then(() => {
const cells = this.graph.getSelectedCells(); //获取选中的节点/边
if (cells.length) {
this.graph.removeCells(cells);
this.form = {};
this.editDrawer = false;
this.selectCell = "";
this.$message({ type: "success", message: "删除成功!" });
}
})
.catch(() => { });
},
4.6 导出、另存为图片
// 导出
handlerSend() {
// 我在这里删除了链接桩的设置,和工具(为了减少数据),反显的时候要把删除的链接桩加回来
const { cells: jsonArr } = this.graph.toJSON();
const tempGroupJson = jsonArr.map((item) => {
if (item.ports && item.ports.groups) delete item.ports.groups;
if (item.tools) delete item.tools;
return item;
});
if (this.selectCell) {
this.selectCell.removeTools();
this.selectCell = "";
}
this.$emit("finish", JSON.stringify(tempGroupJson));
},
//另存为图片
handlerSendPng() {
this.graph.toPNG(
(dataUri) => {
// 下载
DataUri.downloadDataUri(dataUri, "chart.png");
},
{
copyStyles: false,
padding: {
top: 20,
right: 30,
bottom: 40,
left: 50,
},
}
);
},
4.7 基本设置
import {
configSetting,
configNodeShape,
configNodePorts,
configEdgeLabel,
graphBindKey,
} from "./antvSetting";
// 基本设置
export const configSetting = (Shape) => {
return {
background: {
color: '#fff',
},
// scroller: true,
// grid: true, //网格,默认使用 10px 的网格,但不绘制网格背景
pannable: {
enabled: true,
modifiers: ["alt", "ctrl", "meta"], //设置修饰键后需要点击鼠标并按下修饰键才能触发画布拖拽
eventTypes: ['leftMouseDown']
},
autoResize: true,//是否监听容器大小改变,并自动更新画布大小。
// translating: { restrict: true },//限制节点移动
mousewheel: {//鼠标滚轮缩放
enabled: false,//是否开启滚轮缩放交互
zoomAtMousePosition: true,//是否将鼠标位置作为中心缩放
modifiers: 'ctrl',//修饰键,设置修饰键后需要按下修饰键并滚动鼠标滚轮时才触发画布缩放
minScale: 0.5,//最小的缩放级别
maxScale: 2,//最大的缩放级别
},
connecting: {//连线选项
router: {//路由
//name: 'orth', orth 正交路由,由水平或垂直的正交线段组成。
name: 'manhattan',// 智能正交路由,由水平或垂直的正交线段组成,并自动避开路径上的其他节点(障碍)。
args: {//路由参数
padding: 10,//设置锚点距离转角的最小距离
},
},
connector: {//连接器
name: 'normal',//圆角连接器时
args: {//连接器参数
radius: 10,//倒角半径
},
},
highlight: true,
anchor: 'center',//当连接到节点时,指定被连接的节点的锚点
connectionPoint: 'anchor',//指定连接点
allowLoop: false, // 是否允许创建循环连线,即边的起始节点和终止节点为同一节点,默认为 true
allowBlank: false,//是否允许连接到画布空白位置的点
snap: {//是否触发自动吸附,当 snap 设置为 true 时连线的过程中距离节点或者连接桩 50px 时会触发自动吸附
radius: 20,//自定义触发吸附的距离
},
highlight: true,
createEdge() {//连接的过程中创建新的边
return new Shape.Edge({
attrs: {//定制样式
line: {//定义了 'line'选择器
stroke: '#000',// 使用自定义边框色
strokeWidth: 2, //边宽度
targetMarker: {//终点箭头
name: 'block',//箭头名:实心箭头
width: 12,//箭头宽度
height: 8// 箭头高度
},
},
},
zIndex: 0,//层级
})
},
validateConnection({ targetMagnet }) {//在移动边的时候判断连接是否有效
return !!targetMagnet
},
},
onToolItemCreated({ tool }) {//当工具项渲染完成时触发的回调
const handle = tool
const options = handle.options
if (options && options.index % 2 === 1) {
tool.setAttrs({ fill: 'red' })
}
},
highlighting: {//高亮选项,指定触发某种交互时的高亮样式
// 当链接桩可以被链接时,在链接桩外围渲染一个 2px 宽的红色矩形框
magnetAvailable: {
name: 'stroke',
args: {
padding: 1,
attrs: {
'stroke-width': 1,
fill: '#fff',//填充的颜色
stroke: '#b1e1fa', //边的颜色
}
}
},
magnetAdsorbed: {//连线过程中,自动吸附到链接桩时被使用
name: 'stroke',
args: {//高亮样式
attrs: {//定制样式
padding: 4,
'stroke-width': 4,
fill: '#fff',//填充的颜色
stroke: '#5F95FF', //边的颜色
},
},
},
},
resizing: true,//缩放节点
rotating: true,//旋转节点
// selecting: {//点选/框选,开启后可以通过点击或者套索框选节点
// enabled: true,//启用
// rubberband: true,//是否启用框选
// showNodeSelectionBox: true,//是否显示节点的选择框
// },
snapline: true,//对齐线
keyboard: true,//键盘快捷键
clipboard: true,//剪切板
history: true,
}
}
// 节点预设类型 (0椭圆形: defaultOval, 1方形: defaultSquare, 2圆角矩形: defaultYSquare, 3菱形: defaultRhombus, 4平行四边形: defaultRhomboid, 5圆形: defaultCircle, 6图片: otherImage)
export const configNodeShape = (type) => {
const nodeShapeList = [{
label: '椭圆形',
/**
* 加入data里面的标识type是为了方便编辑的时候找到相对应的类型进行不同的编辑处理
* 另外获取初始对应的设置
*/
data: {
type: 'defaultOval'
},
shape: 'rect',
width: 100,
height: 50,
attrs: {
// 指定 rect 元素的样式
body: {
rx: 20,// x 半径
ry: 26,//y 半径
fill: '#fff',// 填充颜色
stroke: '#333'// 边框颜色
},
// 指定 text 元素的样式
label: {
text: '椭圆形', // 文本
fontSize: 16,// 文字大小
fill: '#333'//文字颜色
},
}
},
{
label: '方形',
data: {
type: 'defaultSquare',
},
shape: 'rect',
width: 100,
height: 50,
attrs: {
label: {
text: '方形',
fontSize: 16,
fill: '#333'
},
body: {
fill: '#fff',
stroke: '#333'
}
},
},
{
label: '圆角矩形',
data: {
type: 'defaultYSquare'
},
shape: 'rect',
width: 100,
height: 50,
attrs: {
body: {
rx: 6,
ry: 6,
fill: '#fff',
stroke: '#333'
},
label: {
text: '圆角矩形',
fontSize: 16,
fill: '#333'
}
},
},
{
label: '菱形',
data: {
type: 'defaultRhombus'
},
shape: 'polygon',
width: 120,
height: 50,
attrs: {
body: {
refPoints: '0,10 10,0 20,10 10,20',
fill: '#fff',
stroke: '#333'
},
label: {
text: '菱形',
fontSize: 16,
fill: '#333'
}
},
},
{
label: '平行四边形',
data: {
type: 'defaultRhomboid'
},
shape: 'polygon',
width: 120,
height: 50,
attrs: {
body: {
refPoints: '10,0 40,0 30,20 0,20',
fill: '#fff',
stroke: '#333'
},
label: {
text: '平行四边形',
fontSize: 16,
fill: '#333'
}
}
},
{
label: '圆形',
data: {
type: 'defaultCircle'
},
shape: 'circle',
width: 80,
height: 80,
attrs: {
label: {
text: '圆形',
fontSize: 16,
fill: '#333'
},
body: {
fill: '#fff',
stroke: '#333'
}
}
},
{
label: "图片",
data: {
type: 'otherImage'
},
shape: 'rect',
width: 80,
height: 80,
markup: [
{
tagName: 'rect',
selector: 'body',
},
{
tagName: 'image',
},
{
tagName: 'text',
selector: 'label',
},
],
attrs: {
body: {
fill: '#fff',
stroke: '#333'
},
image: {
width: 80,
height: 80,
refX: 0,
refY: 0,
xlinkHref: 'https://pic4.zhimg.com/80/v2-369ccdc73ae7e715bd1e4093072d3e3b_720w.jpg',
},
label: {
fontSize: 14,
fill: '#333',
text: '图片'
},
},
}
]
if (type) {
const obj = nodeShapeList.find(item => { return item.data.type === type })
return obj || nodeShapeList
}
return nodeShapeList
}
// 节点连接装设置
export const configNodePorts = () => {
return {
groups: {//链接桩组定义
top: {
position: 'top',//链接桩位置
attrs: {//属性和样式
circle: {//圆形
r: 4,//圆半径
magnet: true,//使链接桩在连线交互时可以被连接上
stroke: '#5F95FF',//自定义边框色
strokeWidth: 1,//边宽度
fill: '#fff',//填充的颜色
style: {
visibility: 'hidden',//是否可见
},
},
},
},
right: {
position: 'right',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
bottom: {
position: 'bottom',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
left: {
position: 'left',
attrs: {
circle: {
r: 4,
magnet: true,
stroke: '#5F95FF',
strokeWidth: 1,
fill: '#fff',
style: {
visibility: 'hidden',
},
},
},
},
},
items: [
{
group: 'top',
},
{
group: 'right',
},
{
group: 'bottom',
},
{
group: 'left',
},
]
}
}
// 连线 label 设置
export const configEdgeLabel = (labelText, fontColor, fill, stroke) => {
if (!labelText) return { attrs: { labelText: { text: '' }, labelBody: { fill: '', stroke: '' } } }
return {
markup: [//定制标签样式
{
tagName: 'rect',
selector: 'labelBody',
},
{
tagName: 'text',
selector: 'labelText',
},
],
attrs: {//标签样式
labelText: {
text: labelText || '',
fill: fontColor || '#333',
textAnchor: 'middle',
textVerticalAnchor: 'middle',
},
labelBody: {
ref: 'labelText',
refX: -8,
refY: -5,
refWidth: '100%',
refHeight: '100%',
refWidth2: 16,
refHeight2: 10,
stroke: stroke || '#555',
fill: fill || '#fff',
strokeWidth: 2,
rx: 5,
ry: 5,
},
}
}
}
// 键盘事件
export const graphBindKey = (graph) => {
// bindKey - 绑定键盘快捷键
// 复制
graph.bindKey(['meta+c', 'ctrl+c'], () => {
const cells = graph.getSelectedCells()//获取选中的节点/边
if (cells.length) {
graph.copy(cells)//复制节点/边
}
return false
})
// 剪切
graph.bindKey(['meta+x', 'ctrl+x'], () => {
const cells = graph.getSelectedCells()//获取选中的节点/边
if (cells.length) {
graph.cut(cells)//剪切节点/边
}
return false
})
// 粘贴
graph.bindKey(['meta+v', 'ctrl+v'], () => {
if (!graph.isClipboardEmpty()) {//返回剪切板是否为空
const cells = graph.paste({ offset: 32 })//粘贴,返回粘贴到画布的节点/边。
graph.cleanSelection()//清空选区。
graph.select(cells)//选中指定的节点/边
}
return false
})
//撤销
graph.bindKey(['meta+z', 'ctrl+z'], () => {
console.log('graph.canUndo() :>> ', graph.canUndo());
if (graph.canUndo()) {//是否可以撤销
graph.undo()//撤销
}
return false
})
// 重做
graph.bindKey(['meta+shift+z', 'ctrl+shift+z'], () => {
console.log('graph.canRedo() :>> ', graph.canRedo());
if (graph.canRedo()) {//是否可以重做
graph.redo()//重做
}
return false
})
// 全选
// graph.bindKey(['meta+a', 'ctrl+a'], () => {
// const nodes = graph.getNodes()//返回画布中所有节点
// if (nodes) {
// graph.select(nodes)//选中指定的节点/边
// }
// })
//删除 - 后续组件单独特殊处理
// graph.bindKey('backspace', () => {
// const cells = graph.getSelectedCells()
// if (cells.length) {
// graph.removeCells(cells)
// }
// })
// 缩放
// graph.bindKey(['ctrl+1', 'meta+1'], () => {
// const zoom = graph.zoom()//获取画布缩放比例
// if (zoom < 1.5) {
// graph.zoom(0.1)
// }
// })
// graph.bindKey(['ctrl+2', 'meta+2'], () => {
// const zoom = graph.zoom()//获取画布缩放比例
// if (zoom > 0.5) {
// graph.zoom(-0.1)
// }
// })
return graph
}