├── src
├── views
│ ├── flowChart
│ │ ├── Editor
│ │ │ ├── utils
│ │ │ │ ├── eventBus.js
│ │ │ │ ├── customStack.js
│ │ │ │ └── commonCaculate.js
│ │ │ ├── behavior
│ │ │ │ ├── index.js
│ │ │ │ ├── add-edge.js
│ │ │ │ └── edit-control.js
│ │ │ ├── custom
│ │ │ │ ├── edge
│ │ │ │ │ ├── customLinkEdge.js
│ │ │ │ │ ├── polylineFinding.js
│ │ │ │ │ └── customPolyEdge.js
│ │ │ │ ├── index.js
│ │ │ │ └── node
│ │ │ │ │ ├── customEndNode.js
│ │ │ │ │ ├── customStartNode.js
│ │ │ │ │ ├── customBranchNode.js
│ │ │ │ │ ├── customParallelNode.js
│ │ │ │ │ └── customToolNode.js
│ │ │ ├── config
│ │ │ │ └── commonConfig.js
│ │ │ └── containers
│ │ │ │ ├── PanelLeft.vue
│ │ │ │ ├── ContextMenu.vue
│ │ │ │ └── ToolBar.vue
│ │ ├── assets
│ │ │ └── images
│ │ │ │ ├── end.png
│ │ │ │ ├── branch.png
│ │ │ │ ├── start.png
│ │ │ │ ├── tool.png
│ │ │ │ └── parallel.png
│ │ └── DemoFroTest.vue
│ ├── treetopo
│ │ ├── index.vue
│ │ └── component
│ │ │ └── subtopo.vue
│ └── topo
│ │ ├── component
│ │ └── subTopo.vue
│ │ └── index.vue
├── assets
│ ├── 1.png
│ ├── 3.png
│ ├── 10.png
│ ├── 15.png
│ ├── logo.png
│ ├── defend.png
│ ├── database.png
│ ├── service.png
│ └── wancheng.png
├── main.js
├── router
│ └── router.js
├── App.vue
└── jsondata
│ ├── subdata.js
│ ├── data.js
│ ├── treetopo.js
│ └── togo.js
├── babel.config.js
├── public
├── favicon.ico
└── index.html
├── vue.config.js
├── .gitignore
├── .github
└── workflows
│ └── cl.yml
└── package.json
/src/views/flowChart/Editor/utils/eventBus.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | export default new Vue();
--------------------------------------------------------------------------------
/src/assets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/1.png
--------------------------------------------------------------------------------
/src/assets/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/3.png
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/assets/10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/10.png
--------------------------------------------------------------------------------
/src/assets/15.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/15.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/defend.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/defend.png
--------------------------------------------------------------------------------
/src/assets/database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/database.png
--------------------------------------------------------------------------------
/src/assets/service.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/service.png
--------------------------------------------------------------------------------
/src/assets/wancheng.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/assets/wancheng.png
--------------------------------------------------------------------------------
/src/views/flowChart/assets/images/end.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/views/flowChart/assets/images/end.png
--------------------------------------------------------------------------------
/src/views/flowChart/assets/images/branch.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/views/flowChart/assets/images/branch.png
--------------------------------------------------------------------------------
/src/views/flowChart/assets/images/start.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/views/flowChart/assets/images/start.png
--------------------------------------------------------------------------------
/src/views/flowChart/assets/images/tool.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/views/flowChart/assets/images/tool.png
--------------------------------------------------------------------------------
/src/views/flowChart/assets/images/parallel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Jason-chen-coder/D3-EasyFlowRender/HEAD/src/views/flowChart/assets/images/parallel.png
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | publicPath: process.env.NODE_ENV === 'production'
3 | ? '/D3-EasyFlowRender/'
4 | : '/',
5 | outputDir: 'dist',
6 | lintOnSave: true,
7 | };
8 |
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 | # local env files
6 | .env.local
7 | .env.*.local
8 |
9 | # Log files
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 | pnpm-debug.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 | import router from './router/router'
4 | // 导入element插件
5 | import ElementUI from 'element-ui';
6 | import 'element-ui/lib/theme-chalk/index.css';
7 | Vue.config.productionTip = false
8 | Vue.use(ElementUI);
9 |
10 |
11 | new Vue({
12 | router,
13 | render: h => h(App),
14 | }).$mount('#app')
15 |
--------------------------------------------------------------------------------
/src/router/router.js:
--------------------------------------------------------------------------------
1 | import Vue from "vue"
2 | import VueRouter from "vue-router"
3 | import topoindex from "../views/topo/index.vue"
4 | import treetopo from "../views/treetopo/index.vue"
5 | Vue.use(VueRouter)
6 | export default new VueRouter({
7 | routes: [
8 | {
9 | path: '/topoindex',
10 | name: "topoindex",
11 | component: topoindex
12 | },
13 | {
14 | path: '/treetopo',
15 | name: "treetopo",
16 | component: treetopo
17 | },
18 | ]
19 | })
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/behavior/index.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 |
3 | import addLine from './add-edge';
4 | import editControl from './edit-control';
5 |
6 | const behavors = {
7 |
8 | 'edit-control': editControl,
9 | 'add-edge': addLine,
10 | };
11 |
12 | export function initBehavors() {
13 | for (let key in behavors) {
14 | G6.registerBehavior(key, behavors[key])
15 | }
16 | }
17 | export function clearTempState(graph) {
18 | for (let key in behavors) {
19 | behavors[key].clearTempState(graph);
20 | }
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/utils/customStack.js:
--------------------------------------------------------------------------------
1 | const items = new WeakMap();
2 |
3 | export default class Stack {
4 | constructor () {
5 | items.set(this, []);
6 | }
7 |
8 | push(element) {
9 | let s = items.get(this);
10 | s.push(element);
11 | }
12 |
13 | pop() {
14 | let s = items.get(this);
15 | return s.pop();
16 | }
17 |
18 | peek() {
19 | let s = items.get(this);
20 | return s[s.length - 1];
21 | }
22 |
23 | isEmpty() {
24 | return items.get(this).length === 0;
25 | }
26 |
27 | size() {
28 | return items.get(this).length;
29 | }
30 |
31 | clear() {
32 | items.set(this, []);
33 | }
34 |
35 | print() {
36 | console.log(items.get(this).toString());
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/edge/customLinkEdge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 修改自 https://github.com/caoyu48/vue-g6-editor
3 | * 用于连线过程中的虚线
4 | */
5 |
6 | import G6 from "@antv/g6/build/g6";
7 |
8 | const customLinkEdge = {
9 | init(editor) {
10 | G6.registerEdge('customLinkEdge', {
11 | draw(cfg, group) {
12 | let path = []
13 | path = [
14 | ['M', cfg.source.x, cfg.source.y],
15 | ['L', cfg.target.x, cfg.target.y]
16 | ]
17 | const keyShape = group.addShape('path', {
18 | attrs: {
19 | path: path,
20 | stroke: '#1890FF',
21 | strokeOpacity: 0.9,
22 | lineDash: [5, 5]
23 | }
24 | });
25 | return keyShape
26 | },
27 | });
28 | }
29 | }
30 |
31 | export default customLinkEdge
32 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 注册自定义节点和边
3 | */
4 |
5 | import customStartNode from './node/customStartNode'
6 | import customEndNode from './node/customEndNode'
7 |
8 | import customParallelNode from './node/customParallelNode'//并行网关
9 | import customBranchlNode from './node/customBranchNode'//分支网关mpo
10 | import customToolNode from './node/customToolNode'//工具
11 |
12 | import customPolyEdge from './edge/customPolyEdge'
13 | import customLinkEdge from './edge/customLinkEdge'
14 |
15 | const obj = {
16 |
17 | customStartNode,
18 | customEndNode,
19 | customParallelNode,
20 | customBranchlNode,
21 | customToolNode,
22 | customPolyEdge,
23 | customLinkEdge,
24 | }
25 |
26 | export default function (editor) {
27 | Object.values(obj).map(item => {
28 | item.init(editor);
29 | })
30 | }
31 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/config/commonConfig.js:
--------------------------------------------------------------------------------
1 | export default {
2 | node :{
3 | anchorPoints:[[0.5, 0],[0.5, 1],[0, 0.5],[1, 0.5]],//上,下,左,右
4 | anchorR:5,
5 | defaultDeleteR:8,
6 |
7 | anchorColor_blue:'#3688ed',//锚点颜色
8 | anchorColor_grey:'#e1e4e8',//锚点颜色
9 | anchorBiggerOpacity:'0.3',//锚点外面大的圈圈的透明度
10 |
11 | strokeColor_grey:'#b5b9c0',//边的灰色
12 | strokeColor_blue:'#64a1ec',//边的蓝色
13 |
14 | centerColor_blue:'#53699d',//内部的颜色
15 | centerColor_white:'#fff',//内部的颜色
16 |
17 | lineWidth_thin:1,
18 | lineWidth_bolder:2,
19 |
20 | deleteByIcon:false,
21 |
22 | },
23 | edge:{
24 | strokeColor_grey:'#b8c3ce',//线的颜色
25 | strokeColor_blue:'#64a1ec',//线的颜色
26 |
27 | strokeWidth_thin: 2,
28 | strokeWidth_bolder: 2,
29 |
30 | deleteByIcon:false
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
28 |
29 |
47 |
--------------------------------------------------------------------------------
/.github/workflows/cl.yml:
--------------------------------------------------------------------------------
1 | # This is a basic workflow to help you get started with Actions
2 |
3 | name: CI
4 |
5 | # Controls when the workflow will run
6 | on:
7 | # Triggers the workflow on push or pull request events but only for the V1.0-beat branch
8 | push:
9 | branches: [ master ]
10 | pull_request:
11 | branches: [ master ]
12 |
13 | # Allows you to run this workflow manually from the Actions tab
14 | workflow_dispatch:
15 |
16 | # jobs 表示要执行的一项或者多项任务
17 | jobs:
18 | # 任务名,可自定义
19 | build-and-deploy:
20 | # runs-on字段指定运行所需要的虚拟机环境。它是必填字段。目前可用的虚拟机如下。
21 | runs-on: ubuntu-latest
22 | # steps表示执行步骤
23 | steps:
24 | # 检出代码,这里用了 actions/checkout@master 库来完成
25 | - name: Checkout
26 | uses: actions/checkout@master
27 | # 这里展示了如何执行多条命令
28 | - name: Install and Build
29 | run: |
30 | yarn
31 | yarn build
32 | # 这里引用了别人写好的发布库,具体参数信息可以查阅上面的链接
33 | - name: Deploy to GitHub Pages
34 | uses: JamesIves/github-pages-deploy-action@4.0.0
35 | with:
36 | branch: gh-pages
37 | folder: dist
38 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "D3-EasyFlowRender",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "serve": "vue-cli-service serve",
7 | "build": "vue-cli-service build",
8 | "lint": "vue-cli-service lint"
9 | },
10 | "dependencies": {
11 | "core-js": "^3.6.5",
12 | "d3": "^5.16.0",
13 | "dagre-d3": "^0.6.4",
14 | "element-ui": "^2.13.2",
15 | "less-loader": "^6.1.0",
16 | "vue": "^2.6.11",
17 | "vue-router": "^3.3.2"
18 | },
19 | "devDependencies": {
20 | "@vue/cli-plugin-babel": "~4.4.0",
21 | "@vue/cli-plugin-eslint": "~4.4.0",
22 | "@vue/cli-service": "~4.4.0",
23 | "babel-eslint": "^10.1.0",
24 | "eslint": "^6.7.2",
25 | "eslint-plugin-vue": "^6.2.2",
26 | "jquery": "^3.6.0",
27 | "vue-template-compiler": "^2.6.11"
28 | },
29 | "eslintConfig": {
30 | "root": true,
31 | "env": {
32 | "node": true
33 | },
34 | "extends": [
35 | "plugin:vue/essential",
36 | "eslint:recommended"
37 | ],
38 | "parserOptions": {
39 | "parser": "babel-eslint"
40 | },
41 | "rules": {}
42 | },
43 | "browserslist": [
44 | "> 1%",
45 | "last 2 versions",
46 | "not dead"
47 | ]
48 | }
49 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/containers/PanelLeft.vue:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by OXOYO on 2019/7/2.
3 | *
4 | * PanelLeft 左侧面板
5 | */
6 |
7 |
8 |
9 |
10 |
11 |
![]()
16 |
17 |
18 |
19 |
20 |
21 |
59 |
--------------------------------------------------------------------------------
/src/jsondata/subdata.js:
--------------------------------------------------------------------------------
1 | let __options = {
2 | data: [
3 | {
4 | type: 'database',
5 | name: 'web-server',
6 | time: 30,
7 | rpm: 40,
8 | epm: 50,
9 | active: 3,
10 | total: 5,
11 | code: 'java',
12 | health: 1,
13 | lineProtocol: 'http',
14 | lineTime: 12,
15 | lineRpm: 34
16 | },
17 | {
18 | type: 'database',
19 | name: 'Mysql',
20 | time: 30,
21 | rpm: 40,
22 | epm: 50,
23 | active: 3,
24 | total: 5,
25 | code: 'java',
26 | health: 2,
27 | lineProtocol: 'http',
28 | lineTime: 12,
29 | lineRpm: 34
30 | },
31 | {
32 | type: 'app',
33 | name: 'Redis',
34 | time: 30,
35 | rpm: 40,
36 | epm: 50,
37 | active: 3,
38 | total: 5,
39 | code: 'java',
40 | health: 3,
41 | lineProtocol: 'http',
42 | lineTime: 12,
43 | lineRpm: 34
44 | },
45 | {
46 | type: 'earth',
47 | name: 'ES',
48 | time: 30,
49 | rpm: 40,
50 | epm: 50,
51 | active: 3,
52 | total: 5,
53 | code: 'java',
54 | health: 1,
55 | lineProtocol: 'http',
56 | lineTime: 12,
57 | lineRpm: 34,
58 | value: 100
59 | },
60 | {
61 | type: 'docker',
62 | name: 'ES',
63 | time: 30,
64 | rpm: 40,
65 | epm: 50,
66 | active: 3,
67 | total: 5,
68 | code: 'java',
69 | health: 1,
70 | lineProtocol: 'http',
71 | lineTime: 12,
72 | lineRpm: 34,
73 | value: 100
74 | },
75 | {
76 | type: 'cloud',
77 | name: 'cloud',
78 | time: 30,
79 | rpm: 40,
80 | epm: 50,
81 | active: 3,
82 | total: 5,
83 | code: 'java',
84 | health: 1,
85 | lineProtocol: 'http',
86 | lineTime: 12,
87 | lineRpm: 34,
88 | value: 100
89 | }
90 | ]
91 | }
92 | export default __options
--------------------------------------------------------------------------------
/src/jsondata/data.js:
--------------------------------------------------------------------------------
1 | let options = {
2 | data: [
3 | {
4 | type: 'app', // 节点类型
5 | name: 'monitor-app', // 节点名称
6 | active: 9, // 已完成数
7 | total: 10, // 总数
8 | health: 1, // 监控健康程度
9 | underText: 'underText', // 节点之间连接线下方的文字
10 | upwardText: "upwardText", // 节点之间连接线上方的文字
11 | isConfig: "true" // 节点配置状态
12 | },
13 | {
14 | type: 'app',
15 | name: 'monitor-app2',
16 | active: 1,
17 | total: 10,
18 | health: 3,
19 | underText: 'underText',
20 | upwardText: "upwardText",
21 | isConfig: "true"
22 | },
23 | {
24 | type: 'database',
25 | name: 'Mysql',
26 | active: 3,
27 | total: 5,
28 | health: 2,
29 | underText: 'underText',
30 | upwardText: "upwardText",
31 | isConfig: "false"
32 | },
33 | {
34 | type: 'cloud',
35 | name: 'Redis',
36 | active: 3,
37 | total: 5,
38 | health: 3,
39 | underText: 'underText',
40 | upwardText: "upwardText",
41 | isConfig: "false"
42 | },
43 | {
44 | type: 'earth',
45 | name: 'ES',
46 | active: 3,
47 | total: 5,
48 | health: 1,
49 | underText: 'underText',
50 | upwardText: "upwardText",
51 | value: 100,
52 | isConfig: "false"
53 | },
54 | {
55 | type: 'earth',
56 | name: 'Redis',
57 | active: 3,
58 | total: 5,
59 | health: 3,
60 | underText: 'underText',
61 | upwardText: "upwardText",
62 | isConfig: "true"
63 | },
64 | {
65 | type: 'myapp',
66 | name: 'AKA',
67 | active: 3,
68 | total: 5,
69 | code: 'Python',
70 | health: 1,
71 | underText: 'underText',
72 | upwardText: "upwardText",
73 | value: 10,
74 | isConfig: "true"
75 | }
76 | ],
77 | edges: [
78 | {
79 | source: 0, // 连接线起点(数值对应data数组中的元素)
80 | target: 3, // 连接线终点(数值对应data数组中的元素)
81 | },
82 | {
83 | source: 1,
84 | target: 2,
85 | },
86 | {
87 | source: 1,
88 | target: 6,
89 | },
90 | {
91 | source: 1,
92 | target: 3,
93 | },
94 | {
95 | source: 0,
96 | target: 1,
97 | },
98 | {
99 | source: 4,
100 | target: 5,
101 | },
102 | {
103 | source: 1,
104 | target: 2,
105 | },
106 | {
107 | source: 5,
108 | target: 2,
109 | }
110 | ]
111 | }
112 | export default options
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/containers/ContextMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
63 |
64 |
89 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/utils/commonCaculate.js:
--------------------------------------------------------------------------------
1 | import commonConfig from "../config/commonConfig";
2 |
3 | const nodeTitleLengthLimit = 8;
4 |
5 | export const getShowNodeTitle = function (title){
6 | let result = title;
7 | if(title==null){
8 | result = '';
9 | }
10 | if(result.length>nodeTitleLengthLimit){
11 | result = result.substring(0,nodeTitleLengthLimit-1)+'...';
12 | }
13 | return result;
14 | }
15 |
16 | export const getNodeToolTip = function (title){
17 | if(title && title.length>nodeTitleLengthLimit){
18 | return title;
19 | }
20 | return null;
21 | }
22 |
23 | /*
24 | 计算连接点相对节点的偏移量
25 | 预置的连线 :pointOffset为null,nodeAnchorIndex不应为空,为空就默认选一个点
26 | 拖动生成的连线:pointOffset不为null,nodeAnchorIndex为空
27 | */
28 | export const getPointOffset = function (pointOffset, node, nodeAnchorIndex){
29 | let nodeModel = node.getModel();
30 | if(!pointOffset){
31 |
32 | let nodeAnchorPoint = commonConfig.node.anchorPoints[0];
33 | if(nodeAnchorIndex!=null){
34 | nodeAnchorPoint = commonConfig.node.anchorPoints[nodeAnchorIndex];
35 | }
36 | //计算出锚点的具体坐标
37 | let nodeWidth = nodeModel.size[0];
38 | let nodeHeight = nodeModel.size[1];
39 | if (nodeModel.size.length == 1) {
40 | nodeHeight = nodeWidth;
41 | }
42 | pointOffset = {x:nodeWidth * nodeAnchorPoint[0] - nodeWidth / 2, y:nodeHeight * nodeAnchorPoint[1] - nodeHeight / 2};
43 | }
44 | return pointOffset;
45 | }
46 |
47 | export const getEdgeStartOrEndPoint = function (pointOffset, node, anchorIndex){
48 | let offset = getPointOffset(pointOffset, node, anchorIndex);
49 | return {
50 | x: Math.round(node.getModel().x + offset.x),
51 | y: Math.round(node.getModel().y + offset.y)
52 | }
53 | }
54 |
55 | //深拷贝
56 | export const deepCopy = function (obj){
57 | let tmp = JSON.stringify(obj);
58 | return JSON.parse(tmp);
59 |
60 | // var copy = Object.create(Object.getPrototypeOf(obj));
61 | // var propNames = Object.getOwnPropertyNames(obj);
62 | // propNames.forEach(function(name) {
63 | // var desc = Object.getOwnPropertyDescriptor(obj, name);
64 | // Object.defineProperty(copy, name, desc);
65 | // });
66 | // return copy;
67 | }
68 |
69 | /**
70 | * [generateUUID 返回一串序列码]
71 | * @return {String} [uuid]
72 | */
73 | export const generateUUID = function () {
74 | var d = new Date().getTime();
75 | var uuid = 'xxxxxxxxxxxx4xxxyxxxxxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
76 | var r = (d + Math.random() * 16) % 16 | 0;
77 | d = Math.floor(d / 16);
78 | return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
79 | });
80 | return uuid;
81 | }
82 |
83 |
84 | /**
85 | * 判断两个数组是否相同
86 | * @return boolean
87 | */
88 | export const isArrayEqual = function (arr1 = [],arr2 = []){
89 | if(arr1 instanceof Array && arr2 instanceof Array && arr1.length === arr2.length){
90 | return JSON.stringify(arr1.sort()) == JSON.stringify(arr2.sort())
91 | }else{
92 | return false;
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/jsondata/treetopo.js:
--------------------------------------------------------------------------------
1 | let list = {
2 | nodeInfos: [
3 | {
4 | id: "node1",
5 | label: "组件1",
6 | type: "service",
7 | isFinished: false
8 | }, {
9 | id: "node2",
10 | label: "组件2",
11 | type: "defend",
12 | isFinished: true
13 | }, {
14 | id: "node3",
15 | label: "组件3",
16 | type: "defend",
17 | isFinished: false
18 | }, {
19 | id: "node4",
20 | label: "组件4",
21 | type: "service",
22 | isFinished: true
23 | }, {
24 | id: "node5",
25 | label: "组件5",
26 | type: "service",
27 | isFinished: false
28 | }, {
29 | id: "node6",
30 | label: "组件6",
31 | type: "defend",
32 | isFinished: true
33 | }, {
34 | id: "node7",
35 | label: "组件7",
36 | type: "database",
37 | isFinished: false
38 | }, {
39 | id: "node8",
40 | label: "组件8",
41 | type: "database",
42 | isFinished: false
43 | }, {
44 | id: "node9",
45 | label: "组件9",
46 | type: "service",
47 | isFinished: false
48 | }, {
49 | id: "node10",
50 | label: "组件10",
51 | type: "service",
52 | isFinished: true
53 | }, {
54 | id: "node11",
55 | label: "组件11",
56 | type: "defend",
57 | isFinished: false
58 | }, {
59 | id: "node12",
60 | label: "组件12",
61 | type: "service",
62 | isFinished: false
63 | }, {
64 | id: "node13",
65 | label: "组件13",
66 | type: "service",
67 | isFinished: true
68 | }, {
69 | id: "node14",
70 | label: "组件14",
71 | type: "service",
72 | isFinished: false
73 | },
74 | ],
75 | edges: [
76 | {
77 | source: "node1",
78 | target: "node2",
79 | },
80 | {
81 | source: "node1",
82 | target: "node5",
83 | },
84 | {
85 | source: "node1",
86 | target: "node6",
87 | },
88 | {
89 | source: "node2",
90 | target: "node2",
91 | },
92 | {
93 | source: "node2",
94 | target: "node3",
95 | },
96 | {
97 | source: "node2",
98 | target: "node4",
99 | },
100 | {
101 | source: "node2",
102 | target: "node4",
103 | },
104 | {
105 | source: "node1",
106 | target: "node4",
107 | },
108 | {
109 | source: "node7",
110 | target: "node1",
111 | },
112 | {
113 | source: "node8",
114 | target: "node8",
115 | },
116 | {
117 | source: "node1",
118 | target: "node9",
119 | },
120 | {
121 | source: "node4",
122 | target: "node11",
123 | },
124 | {
125 | source: "node1",
126 | target: "node10",
127 | },
128 | {
129 | source: "node1",
130 | target: "node12",
131 | },
132 | {
133 | source: "node10",
134 | target: "node13",
135 | },
136 | {
137 | source: "node4",
138 | target: "node14",
139 | },
140 | {
141 | source: "node8",
142 | target: "node10",
143 | }, {
144 | source: "node14",
145 | target: "node7",
146 | }
147 | ],
148 | dataFlow: {
149 | 0: '配置完成',
150 | 1: '待配置',
151 | 2: '配置完成',
152 | 3: '待配置',
153 | 4: '待配置',
154 | 5: '待配置',
155 | 6: '配置完成',
156 | 7: '配置完成',
157 | 8: '待配置',
158 | 9: '待配置',
159 | 10: '待配置',
160 | 11: '待配置',
161 | 12: '配置完成',
162 | 13: '待配置',
163 | 14: '配置完成',
164 | 15: '待配置',
165 | 16: '配置完成',
166 | 17: '待配置',
167 | }
168 | }
169 | export default list
--------------------------------------------------------------------------------
/src/views/treetopo/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
16 |
17 |
23 |
24 |
27 |
28 |
29 |
30 |
141 |
142 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/node/customEndNode.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 | import commonConfig from "../../config/commonConfig"
3 | const config = {
4 | defaultSize:[80],
5 | title:'结束',
6 | ...commonConfig.node,
7 | deleteEnabled:false
8 | };
9 |
10 | const customEndNode = {
11 | init(editor) {
12 | G6.registerNode("customEndNode", {
13 | draw(cfg, group) {
14 | let currentMode = editor.getCurrentMode();
15 |
16 | let r = parseInt(config.defaultSize[0]/2);
17 | cfg.size = [2*r];
18 |
19 |
20 | const shape = group.addShape("circle", {//这个是主图形
21 | attrs: {
22 | id: cfg.id,
23 | x: 0,
24 | y: 0,
25 | r: r,
26 | isMain:true,
27 | stroke: config.strokeColor_grey,
28 | fill: '#fff',//此处必须有fill 不然不能触发事件
29 | lineDash: [4, 4],//虚线
30 | lineWidth: config.lineWidth_thin,
31 | }
32 | });
33 | group.addShape("circle", {
34 | attrs: {
35 | x: 0,
36 | y: 0,
37 | r: parseInt(r * 0.8),
38 | fill: config.centerColor_blue,
39 | }
40 | });
41 | group.addShape("text", {
42 | attrs: {
43 | x: 0,
44 | y: 0,
45 | textAlign: "center",
46 | textBaseline: "middle",
47 | text: config.title,
48 | fill: config.centerColor_white,
49 | fontSize: 18,
50 | fontWeight:'bolder'//最粗的字体
51 | }
52 | });
53 | group.addShape("circle", {//加一个透明的图形解决点击有的地方不好拖动的问题
54 | attrs: {
55 | x: 0,
56 | y: 0,
57 | r: r,
58 | fill: '#fff',//此处必须有fill 不然不能触发事件
59 | opacity: 0,
60 | cursor:'move',
61 | }
62 | });
63 | if(config.deleteByIcon && config.deleteEnabled && currentMode === 'edit') {
64 | const offset_ = parseInt(0.8 * r);
65 | group.addShape("circle", {//删除的按钮
66 | attrs: {
67 | x: offset_,
68 | y: -offset_,
69 | r: config.defaultDeleteR,
70 | isDeleteButton: true,
71 | enabled: false,
72 | stroke: '#fff',
73 | lineWidth: 2,
74 | fill: '#FF111A',
75 | cursor: 'pointer',
76 | opacity: 0,
77 | }
78 | });
79 | group.addShape("text", {//删除的按钮的叉叉
80 | attrs: {
81 | x: offset_,
82 | y: -offset_ + 0.95,
83 | textAlign: "center",
84 | textBaseline: "middle",
85 | text: '×',
86 | fontSize: 18,
87 | cursor: 'pointer',
88 | isDeleteButton: true,
89 | enabled: false,
90 | fill: '#fff',
91 | opacity: 0,
92 | fontWeight: 'bolder'//最粗的字体
93 | }
94 | });
95 | }
96 | if(currentMode === 'edit') {
97 | for (let i = 0; i < config.anchorPoints.length; i++) {//画上下左右的4个点
98 | group.addShape("circle", {
99 | attrs: {
100 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
101 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
102 | r: 0,
103 | isAnchorPointBigger:true,
104 | anchorPointIndex:i,
105 | opacity: config.anchorBiggerOpacity,
106 | fill: config.anchorColor_blue,
107 | cursor:'crosshair',
108 | }
109 | });
110 | group.addShape("circle", {
111 | attrs: {
112 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
113 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
114 | r: 0,
115 | isAnchorPoint:true,
116 | anchorPointIndex:i,
117 | opacity: 1,
118 | stroke: config.anchorColor_blue,
119 | fill: config.anchorColor_grey,
120 | cursor:'crosshair',
121 | }
122 | });
123 | }
124 | }
125 | return shape;
126 | },
127 | //设置状态
128 | setState(name, value, item) {
129 | const group = item.getContainer();
130 | const shape = group.findAll(point => {
131 | return point._attrs.isMain;
132 | })[0];
133 | const points = group.findAll(point => {
134 | return point._attrs.isAnchorPoint;
135 | });
136 | const pointBiggers = group.findAll(point => {
137 | return point._attrs.isAnchorPointBigger;
138 | });
139 | const deleteButtons = group.findAll(point => {
140 | return point._attrs.isDeleteButton;
141 | });
142 | const selectStyles = () => {
143 | shape.attr("stroke", config.strokeColor_blue);
144 | shape.attr("lineWidth", config.lineWidth_bolder);
145 |
146 | deleteButtons.forEach(button => {
147 | button.attr('opacity', 1);
148 | button.attr('enabled',true);
149 | });
150 | };
151 | const defaultStyles = () => {
152 | shape.attr("stroke", config.strokeColor_grey);
153 | shape.attr("lineWidth", config.lineWidth_thin);
154 | points.forEach(point => {
155 | point.attr('r', 0);
156 | });
157 | pointBiggers.forEach(point => {
158 | point.attr('r', 0);
159 | });
160 | deleteButtons.forEach(button => {
161 | button.attr('opacity', 0);
162 | button.attr('enabled',false);
163 | });
164 | };
165 | let hover_addedge = () => {
166 | points.forEach(point => {
167 | point.attr('r', config.anchorR);
168 | point.attr('fill', config.anchorColor_grey);
169 | });
170 | pointBiggers.forEach(point => {
171 | point.attr('r', config.anchorR*3);
172 | });
173 | };
174 | switch (name) {
175 | case "selected":
176 | case "hover":
177 | if (value) {
178 | selectStyles()
179 | } else {
180 | defaultStyles()
181 | }
182 | break;
183 | case "hover_addedge":
184 | if (value) {
185 | hover_addedge()
186 | } else {
187 | defaultStyles()
188 | }
189 | break;
190 | }
191 | }
192 | });
193 | }
194 | }
195 | export default customEndNode
196 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/node/customStartNode.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 | import commonConfig from "../../config/commonConfig"
3 | const config = {
4 | defaultSize:[80],
5 | title:'开始',
6 | ...commonConfig.node,
7 | deleteEnabled:false,
8 | outEdgeNum:1,
9 | };
10 |
11 | const customStartNode = {
12 | init(editor) {
13 | G6.registerNode("customStartNode", {
14 | draw(cfg, group) {
15 | let currentMode = editor.getCurrentMode();
16 | let r = parseInt(config.defaultSize[0]/2);
17 | cfg.size = [2*r];
18 |
19 |
20 | const shape = group.addShape("circle", {//这个是主图形
21 | attrs: {
22 | id: cfg.id,
23 | x: 0,
24 | y: 0,
25 | r: r,
26 | isMain:true,
27 | stroke: config.strokeColor_grey,
28 | fill: '#fff',//此处必须有fill 不然不能触发事件
29 | lineDash: [4, 4],//虚线
30 | lineWidth: config.lineWidth_thin,
31 | }
32 | });
33 | group.addShape("circle", {
34 | attrs: {
35 | x: 0,
36 | y: 0,
37 | r: parseInt(r * 0.8),
38 | fill: config.centerColor_blue,
39 | }
40 | });
41 | group.addShape("text", {
42 | attrs: {
43 | x: 0,
44 | y: 0,
45 | textAlign: "center",
46 | textBaseline: "middle",
47 | text: config.title,
48 | fill: config.centerColor_white,
49 | fontSize: 18,
50 | fontWeight:'bolder'//最粗的字体
51 | }
52 | });
53 | group.addShape("circle", {//加一个透明的图形解决点击有的地方不好拖动的问题
54 | attrs: {
55 | x: 0,
56 | y: 0,
57 | r: r,
58 | fill: '#fff',//此处必须有fill 不然不能触发事件
59 | opacity: 0,
60 | cursor:'move',
61 | }
62 | });
63 | if(config.deleteByIcon && config.deleteEnabled && currentMode == 'edit') {
64 | const offset_ = parseInt(0.8 * r);
65 | group.addShape("circle", {//删除的按钮
66 | attrs: {
67 | x: offset_,
68 | y: -offset_,
69 | r: config.defaultDeleteR,
70 | isDeleteButton: true,
71 | enabled: false,
72 | stroke: '#fff',
73 | lineWidth: 2,
74 | fill: '#FF111A',
75 | cursor: 'pointer',
76 | opacity: 0,
77 | }
78 | });
79 | group.addShape("text", {//删除的按钮的叉叉
80 | attrs: {
81 | x: offset_,
82 | y: -offset_ + 0.95,
83 | textAlign: "center",
84 | textBaseline: "middle",
85 | text: '×',
86 | fontSize: 18,
87 | cursor: 'pointer',
88 | isDeleteButton: true,
89 | enabled: false,
90 | fill: '#fff',
91 | opacity: 0,
92 | fontWeight: 'bolder'//最粗的字体
93 | }
94 | });
95 | }
96 | if(currentMode === 'edit') {
97 | for (let i = 0; i < config.anchorPoints.length; i++) {//画上下左右的4个点
98 |
99 | group.addShape("circle", {
100 | attrs: {
101 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
102 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
103 | r: 0,
104 | isAnchorPointBigger:true,
105 | anchorPointIndex:i,
106 | opacity: config.anchorBiggerOpacity,
107 | fill: config.anchorColor_blue,
108 | cursor:'crosshair',
109 | }
110 | });
111 |
112 | group.addShape("circle", {
113 | attrs: {
114 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
115 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
116 | r: 0,
117 | isAnchorPoint:true,
118 | anchorPointIndex:i,
119 | opacity: 1,
120 | stroke: config.anchorColor_blue,
121 | fill: config.anchorColor_grey,
122 | cursor:'crosshair',
123 | }
124 | });
125 | }
126 | }
127 | return shape;
128 | },
129 | //设置状态
130 | setState(name, value, item) {
131 | let showOutAnchor = false;
132 | if((name === 'selected' || name === 'hover' || name === 'hover_addedge') && item._cfg.edges){
133 | let existOutEdges = 0;
134 | item._cfg.edges.forEach(edge => {
135 | if (edge._cfg.sourceNode._cfg.id == item._cfg.id) {
136 | existOutEdges++;
137 | }
138 | });
139 | if(existOutEdges {
145 | return point._attrs.isMain;
146 | })[0];
147 | const points = group.findAll(point => {
148 | return point._attrs.isAnchorPoint;
149 | });
150 | const pointBiggers = group.findAll(point => {
151 | return point._attrs.isAnchorPointBigger;
152 | });
153 | const deleteButtons = group.findAll(point => {
154 | return point._attrs.isDeleteButton;
155 | });
156 |
157 |
158 | const selectStyles = () => {
159 | shape.attr("stroke", config.strokeColor_blue);
160 | shape.attr("lineWidth", config.lineWidth_bolder);
161 | if(showOutAnchor){
162 | points.forEach(point => {
163 | point.attr('r', config.anchorR);
164 | point.attr('fill', config.anchorColor_grey);
165 | });
166 | }else{
167 | points.forEach(point => {
168 | point.attr('r', 0);
169 | });
170 | }
171 | deleteButtons.forEach(button => {
172 | button.attr('opacity', 1);
173 | button.attr('enabled',true);
174 | });
175 | };
176 | const defaultStyles = () => {
177 | shape.attr("stroke", config.strokeColor_grey);
178 | shape.attr("lineWidth", config.lineWidth_thin);
179 |
180 | points.forEach(point => {
181 | point.attr('r', 0);
182 | });
183 | pointBiggers.forEach(point => {
184 | point.attr('r', 0);
185 | });
186 | deleteButtons.forEach(button => {
187 | button.attr('r', 0);
188 | button.attr('enabled',false);
189 | });
190 | };
191 | const hover_changeedgesource = () => {
192 | //只是改个锚点,肯定满足出入的规则
193 | points.forEach(point => {
194 | point.attr('r', config.anchorR);
195 | point.attr('fill', config.anchorColor_grey);
196 | });
197 | pointBiggers.forEach(point => {
198 | point.attr('r', config.anchorR*3);
199 | });
200 | };
201 | switch (name) {
202 | case "selected":
203 | case "hover":
204 | if (value) {
205 | selectStyles()
206 | } else {
207 | defaultStyles()
208 | }
209 | break;
210 | case "hover_addedge":
211 | break;
212 | case "hover_changeedgesource":
213 | if (value) {
214 | hover_changeedgesource()
215 | } else {
216 | defaultStyles()
217 | }
218 | break;
219 | }
220 | }
221 | });
222 | }
223 | }
224 |
225 | export default customStartNode
226 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/behavior/add-edge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 修改自 https://github.com/caoyu48/vue-g6-editor
3 | */
4 | import eventBus from "../utils/eventBus";
5 | import {getEdgeStartOrEndPoint} from "../utils/commonCaculate";
6 |
7 |
8 | let startItem = null;
9 | let startPointOffset = null;
10 | let sourceAnchorIndex = null;
11 | let edgeAdding = null;
12 |
13 | let changeEdgeId = null;
14 | let changeSource = null;
15 |
16 | export default {
17 | getEvents() {
18 | return {
19 | mousemove: 'onMousemove',
20 | mouseup: 'onMouseup',
21 | 'node:mouseover': 'onNodeHover',
22 | 'node:mouseout': 'onNodeOut',
23 | 'editor:addEdge': 'startAddEdge',
24 | 'editor:moveEdge': 'startMoveEdge',
25 | };
26 | },
27 | onNodeOut(event) {
28 | let _t = this;
29 | _t.log('add-edge onNodeOut');
30 | if(startItem && event.item._cfg.id !== startItem._cfg.id && !changeEdgeId){
31 | _t.graph.setItemState(event.item, 'hover_addedge', false);//因为直接设置true的话,原来就是true,就不会执行
32 | _t.graph.setItemState(event.item, 'hover_addedge', true);
33 | }
34 | },
35 | onMouseup(event) {
36 | let _t = this;
37 | _t.log('add-edge onMouseup');
38 |
39 | const item = event.item;
40 | if (startItem
41 | && item
42 | && item.getType() === 'node'
43 | && item._cfg.id !== startItem._cfg.id
44 | && edgeAdding
45 | &&(event.target._attrs.isAnchorPoint || event.target._attrs.isAnchorPointBigger)) {
46 | let targetItem = item;
47 | let endPointOffset = {x:parseInt(event.target._attrs.x), y:parseInt(event.target._attrs.y)};
48 | _t.graph.removeItem(edgeAdding);
49 | _t.graph.setMode('edit');//这里先改模式,否则可能会出现画线的时候,mode还是addEdge
50 | if(!changeEdgeId){
51 | const model = {
52 | source: startItem._cfg.id,//必传
53 | target: targetItem._cfg.id,//必传
54 | startPointOffset: startPointOffset,
55 | sourceAnchorIndex: sourceAnchorIndex,
56 | endPointOffset: endPointOffset,
57 | targetAnchorIndex:event.target._attrs.anchorPointIndex,
58 | };
59 | eventBus.$emit('add_edge', model);
60 | }else{
61 | const model = {
62 | id: changeEdgeId
63 | };
64 | if (changeSource) {
65 | model.sourceAnchorIndex = event.target._attrs.anchorPointIndex;
66 | } else {
67 | model.targetAnchorIndex = event.target._attrs.anchorPointIndex;
68 | }
69 | eventBus.$emit('change_edge', model);
70 | }
71 | }else{
72 | if(changeEdgeId){
73 | eventBus.$emit('delete_item', {ids:[changeEdgeId],type:'edge'});
74 | }
75 | }
76 | if (edgeAdding){
77 | _t.graph.removeItem(edgeAdding);
78 | }
79 | _t.graph.paint();
80 |
81 | startPointOffset = null;
82 | sourceAnchorIndex = null;
83 | startItem = null;
84 | edgeAdding = null;
85 | changeEdgeId = null;
86 | changeSource = null;
87 | _t.doClearAllStates();
88 | _t.graph.setMode('edit');
89 | },
90 | onMousemove(event) {
91 | let _t = this;
92 | _t.log('add-edge onMousemove');
93 | if (startItem) {
94 | const point = {x: event.x, y: event.y};
95 | if (edgeAdding) {
96 | // 增加边的过程中,移动时边跟着移动
97 | _t.graph.updateItem(edgeAdding, {
98 | target: point
99 | });
100 | }
101 | }
102 | },
103 | startAddEdge(event){
104 | let _t = this;
105 | _t.log('add-edge startAddEdge');
106 | const item = event.item;
107 | if(!item){
108 | _t.clearAddEdge();
109 | return;
110 | }
111 | let model = item.getModel();
112 | startPointOffset = {x:parseInt(event.target._attrs.x), y:parseInt(event.target._attrs.y)};
113 |
114 | sourceAnchorIndex = event.target._attrs.anchorPointIndex;
115 | let startPoint = {x: startPointOffset.x+model.x, y: startPointOffset.y+model.y};
116 | startItem = item;
117 | edgeAdding = _t.graph.addItem('edge', {
118 | id:'tempLinkLine',
119 | source: startPoint,
120 | target: startPoint,
121 | type:'linkLine',
122 | shape: 'customLinkEdge'
123 | });
124 |
125 | //把其他的节点都设置为hover_addedge true
126 | _t.graph.getNodes().forEach(node => {
127 | if(node._cfg.id !== startItem._cfg.id){
128 | _t.graph.setItemState(node, 'hover_addedge', true);
129 | }
130 | });
131 | },
132 |
133 | //线只能改锚点,不能换节点(因为有的线上还有设置参数,不好处理)
134 | startMoveEdge(event){
135 | let _t = this;
136 | _t.log('add-edge startMoveEdge');
137 | let targetPoint = {x: event.x, y: event.y};
138 | let startPoint = null;
139 | let targetNode = null;
140 | if (event.target._attrs.sourceMoveBtn) {
141 | startPoint = getEdgeStartOrEndPoint(null, event.item._cfg.target, event.item._cfg.targetAnchorIndex);
142 | sourceAnchorIndex = event.item._cfg.model.targetAnchorIndex;
143 | startItem = event.item._cfg.target;
144 | targetNode = event.item._cfg.source;
145 | changeSource = true;
146 | }else{
147 | startPoint = getEdgeStartOrEndPoint(null, event.item._cfg.source, event.item._cfg.sourceAnchorIndex);
148 | sourceAnchorIndex = event.item._cfg.model.sourceAnchorIndex;
149 | startItem = event.item._cfg.source;
150 | targetNode = event.item._cfg.target;
151 | changeSource = false;
152 | }
153 |
154 | let state = 'hover_addedge';
155 | if (event.target._attrs.sourceMoveBtn) {
156 | state = 'hover_changeedgesource';//因为只是改个锚点,所以肯定满足节点的出入线的规则
157 | }
158 | changeEdgeId = event.item._cfg.id;
159 | _t.graph.removeItem(event.item);//这里先不急着让Index里面删掉这根线,等改完(即鼠标mouseup的时候,该删就删,该改就改)
160 | edgeAdding = _t.graph.addItem('edge', {
161 | id:'tempLinkLine',
162 | source: startPoint,
163 | target: targetPoint,
164 | type:'linkLine',
165 | shape: 'customLinkEdge'
166 | });
167 |
168 | //把节点设置为hover_changeEdgeSource true
169 | _t.graph.setItemState(targetNode, state, true);
170 |
171 | },
172 |
173 | clearAddEdge(){
174 | let _t = this;
175 | _t.graph.setMode('edit');
176 | },
177 | // 清除所有状态
178 | doClearAllStates() {
179 | let _t = this;
180 | if (!_t.graph) {
181 | return
182 | }
183 | // 批量操作时关闭自动重绘,以提升性能
184 | _t.graph.setAutoPaint(false);
185 | _t.graph.getNodes().forEach(function (node) {
186 | _t.graph.clearItemStates(node, ['hover', 'hover_addedge', 'hover_changeedgesource', 'selected']);
187 | });
188 | _t.graph.getEdges().forEach(function (edge) {
189 | _t.graph.clearItemStates(edge, ['hover', 'hover_addedge', 'hover_changeedgesource', 'selected']);
190 | });
191 | _t.graph.paint();
192 | _t.graph.setAutoPaint(true);
193 | },
194 | onNodeHover(event) {
195 | let _t = this;
196 | _t.log('add-edge onNodeHover');
197 | if (startItem && event.item._cfg.id !== startItem._cfg.id) {
198 | if(!changeEdgeId){
199 | _t.graph.setItemState(event.item, 'hover_addedge', true);
200 | }
201 | if (event.target._attrs.isAnchorPoint || event.target._attrs.isAnchorPointBigger) {//鼠标的目标是锚点
202 | event.item.getContainer().findAll(point => {
203 | if(point._attrs.isAnchorPoint) {
204 | if (event.target._attrs.anchorPointIndex === point._attrs.anchorPointIndex) {
205 | point.attr("fill", "#3688ed");
206 | } else {
207 | point.attr("fill", "#e1e4e8");
208 | }
209 | }
210 | });
211 | }
212 | }
213 | },
214 | clearTempState(graph){
215 | if (edgeAdding){
216 | graph.removeItem(edgeAdding);
217 | }
218 | startItem = null;
219 | startPointOffset = null;
220 | sourceAnchorIndex = null;
221 | edgeAdding = null;
222 |
223 | },
224 | log(message) {
225 | if (false) {
226 | console.log(message);
227 | }
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/containers/ToolBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
14 |
20 |
21 |
23 |
30 |
31 |
36 |
41 |
46 |
51 |
52 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
199 |
200 |
201 |
238 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/edge/polylineFinding.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by OXOYO on 2019/8/7.
3 | *
4 | * 折线寻径
5 | *
6 | * 文档:https://www.yuque.com/antv/blog/eyi70n
7 | * 鸣谢:@guozhaolong https://github.com/guozhaolong/wfd
8 | * 参考:https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js
9 | */
10 |
11 | // 折线寻径
12 | export const polylineFinding = function (sNode, tNode, sPort, tPort, offset = 10) {
13 | const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort)
14 | const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort)
15 | // 获取节点带 offset 的区域(扩展区域)
16 | const sBBox = getExpandedBBox(sourceBBox, offset)
17 | const tBBox = getExpandedBBox(targetBBox, offset)
18 | // 获取扩展区域上的起始和终止连接点
19 | const sPoint = getExpandedPort(sBBox, sPort)
20 | const tPoint = getExpandedPort(tBBox, tPort)
21 | // 获取合法折点集
22 | let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint)
23 | // 过滤合法点集,预处理、剪枝等
24 | filterConnectablePoints(points, sBBox)
25 | // 过滤合法点集,预处理、剪枝等
26 | filterConnectablePoints(points, tBBox)
27 | // 用 A-Star 算法寻径
28 | let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox)
29 | return polylinePoints
30 | }
31 |
32 | const getPointBBox = function (t) {
33 | return {
34 | centerX: t.x,
35 | centerY: t.y,
36 | minX: t.x,
37 | minY: t.y,
38 | maxX: t.x,
39 | maxY: t.y,
40 | height: 0,
41 | width: 0
42 | }
43 | }
44 |
45 | // 获取扩展区域
46 | const getExpandedBBox = function (bbox, offset) {
47 | if (bbox.width === 0 && bbox.height === 0) {
48 | return bbox
49 | }
50 | return {
51 | centerX: bbox.centerX,
52 | centerY: bbox.centerY,
53 | minX: bbox.minX - offset,
54 | minY: bbox.minY - offset,
55 | maxX: bbox.maxX + offset,
56 | maxY: bbox.maxY + offset,
57 | height: bbox.height + 2 * offset,
58 | width: bbox.width + 2 * offset
59 | }
60 | }
61 |
62 | // 获取扩展区域上的连接点
63 | const getExpandedPort = function (bbox, point) {
64 | // 判断连接点在上下左右哪个区域,相应地给 x 或 y 加上或者减去 offset
65 | if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) {
66 | return {
67 | x: point.x > bbox.centerX ? bbox.maxX : bbox.minX,
68 | y: point.y
69 | }
70 | }
71 | return {
72 | x: point.x,
73 | y: point.y > bbox.centerY ? bbox.maxY : bbox.minY
74 | }
75 | }
76 |
77 | // 获取合法折点集合
78 | const getConnectablePoints = function (sBBox, tBBox, sPoint, tPoint) {
79 | let lineBBox = getBBoxFromVertexes(sPoint, tPoint)
80 | let outerBBox = combineBBoxes(sBBox, tBBox)
81 | let sLineBBox = combineBBoxes(sBBox, lineBBox)
82 | let tLineBBox = combineBBoxes(tBBox, lineBBox)
83 | let points = [
84 | ...vertexOfBBox(sLineBBox),
85 | ...vertexOfBBox(tLineBBox),
86 | ...vertexOfBBox(outerBBox)
87 | ]
88 | const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY }
89 | let bboxes = [ outerBBox, sLineBBox, tLineBBox, lineBBox ]
90 | bboxes.forEach(bbox => {
91 | // 包含 bbox 延长线和线段的相交线
92 | points = [
93 | ...points,
94 | ...crossPointsByLineAndBBox(bbox, centerPoint)
95 | ]
96 | })
97 | points.push({ x: sPoint.x, y: tPoint.y })
98 | points.push({ x: tPoint.x, y: sPoint.y })
99 | return points
100 | }
101 |
102 | const getBBoxFromVertexes = function (sPoint, tPoint) {
103 | const minX = Math.min(sPoint.x, tPoint.x)
104 | const maxX = Math.max(sPoint.x, tPoint.x)
105 | const minY = Math.min(sPoint.y, tPoint.y)
106 | const maxY = Math.max(sPoint.y, tPoint.y)
107 |
108 | return {
109 | centerX: (minX + maxX) / 2,
110 | centerY: (minY + maxY) / 2,
111 | maxX: maxX,
112 | maxY: maxY,
113 | minX: minX,
114 | minY: minY,
115 | height: maxY - minY,
116 | width: maxX - minX
117 | }
118 | }
119 |
120 | const combineBBoxes = function (sBBox, tBBox) {
121 | const minX = Math.min(sBBox.minX, tBBox.minX)
122 | const minY = Math.min(sBBox.minY, tBBox.minY)
123 | const maxX = Math.max(sBBox.maxX, tBBox.maxX)
124 | const maxY = Math.max(sBBox.maxY, tBBox.maxY)
125 |
126 | return {
127 | centerX: (minX + maxX) / 2,
128 | centerY: (minY + maxY) / 2,
129 | minX: minX,
130 | minY: minY,
131 | maxX: maxX,
132 | maxY: maxY,
133 | height: maxY - minY,
134 | width: maxX - minX
135 | }
136 | }
137 |
138 | const vertexOfBBox = function (bbox) {
139 | return [
140 | { x: bbox.minX, y: bbox.minY },
141 | { x: bbox.maxX, y: bbox.minY },
142 | { x: bbox.maxX, y: bbox.maxY },
143 | { x: bbox.minX, y: bbox.maxY }
144 | ]
145 | }
146 |
147 | const crossPointsByLineAndBBox = function (bbox, centerPoint) {
148 | let crossPoints = []
149 | if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) {
150 | crossPoints = [
151 | ...crossPoints,
152 | { x: centerPoint.x, y: bbox.minY },
153 | { x: centerPoint.x, y: bbox.maxY }
154 | ]
155 | }
156 | if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) {
157 | crossPoints = [
158 | ...crossPoints,
159 | { x: bbox.minX, y: centerPoint.y },
160 | { x: bbox.maxX, y: centerPoint.y }
161 | ]
162 | }
163 |
164 | return crossPoints
165 | }
166 |
167 | // 过滤连接点
168 | const filterConnectablePoints = function (points, bbox) {
169 | return points.filter(point => {
170 | return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY
171 | })
172 | }
173 |
174 | const crossBBox = function (bboxes, p1, p2) {
175 | for (let i = 0; i < bboxes.length; i++) {
176 | const bbox = bboxes[i]
177 | if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) {
178 | if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) {
179 | return true
180 | }
181 | } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) {
182 | if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) {
183 | return true
184 | }
185 | }
186 | }
187 | return false
188 | }
189 |
190 | const getCost = function (p1, p2) {
191 | return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
192 | }
193 |
194 | // aStar 寻径
195 | const AStar = function (points, sPoint, tPoint, sBBox, tBBox) {
196 | const openList = [sPoint]
197 | const closeList = []
198 | points.forEach(item => {
199 | item.id = item.x + '-' + item.y
200 | })
201 | let tmpArr = []
202 | points.forEach(item => {
203 | if (!tmpArr.includes(target => target.id === item.id)) {
204 | tmpArr.push(item)
205 | }
206 | })
207 | points = [
208 | ...tmpArr,
209 | tPoint
210 | ]
211 | let endPoint
212 | while (openList.length > 0) {
213 | let minCostPoint
214 | openList.forEach((p, i) => {
215 | if (!p.parent) {
216 | p.f = 0
217 | }
218 | if (!minCostPoint) {
219 | minCostPoint = p
220 | }
221 | if (p.f < minCostPoint.f) {
222 | minCostPoint = p
223 | }
224 | })
225 | if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) {
226 | endPoint = minCostPoint
227 | break
228 | }
229 | openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1)
230 | closeList.push(minCostPoint)
231 | const neighbor = points.filter(p => {
232 | return (p.x === minCostPoint.x || p.y === minCostPoint.y) &&
233 | !(p.x === minCostPoint.x && p.y === minCostPoint.y) &&
234 | !crossBBox([sBBox, tBBox], minCostPoint, p)
235 | }
236 | )
237 | neighbor.forEach(p => {
238 | const inOpen = openList.find(o => o.x === p.x && o.y === p.y)
239 | const currentG = getCost(p, minCostPoint)
240 | if (!closeList.find(o => o.x === p.x && o.y === p.y)) {
241 | if (inOpen) {
242 | if (p.g > currentG) {
243 | p.parent = minCostPoint
244 | p.g = currentG
245 | p.f = p.g + p.h
246 | }
247 | } else {
248 | p.parent = minCostPoint
249 | p.g = currentG
250 | let h = getCost(p, tPoint)
251 | if (crossBBox([tBBox], p, tPoint)) {
252 | // 如果穿过bbox则增加该点的预估代价为bbox周长的一半
253 | h += (tBBox.width / 2 + tBBox.height / 2)
254 | }
255 | p.h = h
256 | p.f = p.g + p.h
257 | openList.push(p)
258 | }
259 | }
260 | })
261 | }
262 | if (endPoint) {
263 | const result = []
264 | result.push({
265 | x: endPoint.x,
266 | y: endPoint.y
267 | })
268 | while (endPoint.parent) {
269 | endPoint = endPoint.parent
270 | result.push({
271 | x: endPoint.x,
272 | y: endPoint.y
273 | })
274 | }
275 | return result.reverse()
276 | }
277 | return []
278 | }
279 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/node/customBranchNode.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 | import commonConfig from "../../config/commonConfig"
3 | const config = {
4 | defaultSize:[80],
5 | title:'x',
6 | ...commonConfig.node,
7 | inEdgeNum:1,
8 | outEdgeNum:10,
9 | };
10 |
11 |
12 | const customBranchNode = {
13 | init(editor) {
14 | G6.registerNode("customBranchNode", {
15 | draw(cfg, group) {
16 | let currentMode = editor.getCurrentMode();
17 | let r = parseInt(config.defaultSize[0]/2);
18 |
19 | cfg.size = [2*r];
20 |
21 |
22 | const shape = group.addShape("circle", {//这个是主图形
23 | attrs: {
24 | id: cfg.id,
25 | x: 0,
26 | y: 0,
27 | r: r,
28 | isMain:true,
29 | stroke: config.strokeColor_grey,
30 | fill: '#fff',//此处必须有fill 不然不能触发事件
31 | lineDash: [4, 4],//虚线
32 | cursor:'move',
33 | lineWidth: config.lineWidth_thin,
34 | }
35 | });
36 |
37 |
38 | let diamondR = parseInt(r * 0.6);
39 | group.addShape("polygon", {//多边形做成菱形
40 | attrs: {
41 | x: 0,
42 | y: 0,
43 | width: 2*diamondR,
44 | height: 2*diamondR,
45 | points:[[0,-diamondR],[-diamondR,0],[0,diamondR],[diamondR,0]],
46 | stroke: config.centerColor_blue,
47 | lineWidth:2,
48 | }
49 | });
50 | group.addShape("text", {
51 | attrs: {
52 | isText:true,
53 | x: 0,
54 | y: 0,
55 | textAlign: "center",
56 | textBaseline: "middle",
57 | text: config.title,
58 | fill: config.centerColor_blue,
59 | fontSize: 30,
60 | cursor:'move',
61 | fontWeight:'bolder'//最粗的字体
62 | }
63 | });
64 | group.addShape("circle", {//加一个透明的图形解决点击有的地方不好拖动的问题
65 | attrs: {
66 | x: 0,
67 | y: 0,
68 | r: r,
69 | fill: '#fff',//此处必须有fill 不然不能触发事件
70 | opacity: 0,
71 | cursor:'move',
72 | }
73 | });
74 | if(config.deleteByIcon && currentMode === 'edit') {
75 | const offset_ = parseInt(0.8 * r);
76 | group.addShape("circle", {//删除的按钮
77 | attrs: {
78 | x: offset_,
79 | y: -offset_,
80 | r: config.defaultDeleteR,
81 | isDeleteButton: true,
82 | enabled: false,
83 | stroke: '#fff',
84 | lineWidth: 2,
85 | fill: '#FF111A',
86 | cursor: 'pointer',
87 | opacity: 0,
88 | }
89 | });
90 | group.addShape("text", {//删除的按钮的叉叉
91 | attrs: {
92 | x: offset_,
93 | y: -offset_ + 0.95,
94 | textAlign: "center",
95 | textBaseline: "middle",
96 | text: '×',
97 | fontSize: 18,
98 | cursor: 'pointer',
99 | isDeleteButton: true,
100 | enabled: false,
101 | fill: '#fff',
102 | opacity: 0,
103 | fontWeight: 'bolder'//最粗的字体
104 | }
105 | });
106 | }
107 | if(currentMode === 'edit') {
108 | for (let i = 0; i < config.anchorPoints.length; i++) {//画上下左右的4个点
109 | group.addShape("circle", {
110 | attrs: {
111 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
112 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
113 | r: 0,
114 | isAnchorPointBigger:true,
115 | anchorPointIndex:i,
116 | opacity: config.anchorBiggerOpacity,
117 | fill: config.anchorColor_blue,
118 | cursor:'crosshair',
119 | }
120 | });
121 | group.addShape("circle", {
122 | attrs: {
123 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
124 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
125 | r: 0,
126 | isAnchorPoint:true,
127 | anchorPointIndex:i,
128 | opacity: 1,
129 | stroke: config.anchorColor_blue,
130 | fill: config.anchorColor_grey,
131 | cursor:'crosshair',
132 | }
133 | });
134 | }
135 | }
136 | return shape;
137 | },
138 | //设置状态
139 | setState(name, value, item) {
140 | let showOutAnchor = false;
141 | let showInAnchor = false;
142 | if((name === 'selected' || name === 'hover' || name === 'hover_addedge') && item._cfg.edges){
143 | let existOutEdges = 0;
144 | let existInEdges = 0;
145 | item._cfg.edges.forEach(edge => {
146 | if (edge._cfg.sourceNode._cfg.id == item._cfg.id) {
147 | existOutEdges++;
148 | }
149 | if(edge._cfg.targetNode._cfg.id == item._cfg.id){
150 | existInEdges++;
151 | }
152 | });
153 | if(existOutEdges {
162 | return point._attrs.isMain;
163 | })[0];
164 | const points = group.findAll(point => {
165 | return point._attrs.isAnchorPoint;
166 | });
167 | const pointBiggers = group.findAll(point => {
168 | return point._attrs.isAnchorPointBigger;
169 | });
170 | const deleteButtons = group.findAll(point => {
171 | return point._attrs.isDeleteButton;
172 | });
173 |
174 |
175 | const selectStyles = () => {
176 | shape.attr("stroke", config.strokeColor_blue);
177 | shape.attr("lineWidth", config.lineWidth_bolder);
178 |
179 | if(showOutAnchor){
180 | points.forEach(point => {
181 | point.attr('r', config.anchorR);
182 | point.attr('fill', config.anchorColor_grey);
183 | });
184 | }else{
185 | points.forEach(point => {
186 | point.attr('r', 0);
187 | });
188 | }
189 | deleteButtons.forEach(button => {
190 | button.attr('opacity', 1);
191 | button.attr('enabled',true);
192 | });
193 | };
194 | const defaultStyles = () => {
195 | shape.attr("stroke", config.strokeColor_grey);
196 | shape.attr("lineWidth", config.lineWidth_thin);
197 |
198 | points.forEach(point => {
199 | point.attr('r', 0);
200 | });
201 | pointBiggers.forEach(point => {
202 | point.attr('r', 0);
203 | });
204 | deleteButtons.forEach(button => {
205 | button.attr('opacity', 0);
206 | button.attr('enabled',false);
207 | });
208 | };
209 | let hover_addedge = () => {
210 | if(showInAnchor){
211 | points.forEach(point => {
212 | point.attr('r', config.anchorR);
213 | point.attr('fill', config.anchorColor_grey);
214 | });
215 | pointBiggers.forEach(point => {
216 | point.attr('r', config.anchorR*3);
217 | });
218 | }else{
219 | points.forEach(point => {
220 | point.attr('r', 0);
221 | });
222 | pointBiggers.forEach(point => {
223 | point.attr('r', 0);
224 | });
225 | }
226 | };
227 | const hover_changeedgesource = () => {
228 | //只是改个锚点,肯定满足出入的规则
229 | points.forEach(point => {
230 | point.attr('r', config.anchorR);
231 | point.attr('fill', config.anchorColor_grey);
232 | });
233 | pointBiggers.forEach(point => {
234 | point.attr('r', config.anchorR*3);
235 | });
236 | };
237 | switch (name) {
238 | case "selected":
239 | case "hover":
240 | if (value) {
241 | selectStyles()
242 | } else {
243 | defaultStyles()
244 | }
245 | break;
246 | case "hover_addedge":
247 | if (value) {
248 | hover_addedge()
249 | } else {
250 | defaultStyles()
251 | }
252 | break;
253 | case "hover_changeedgesource":
254 | if (value) {
255 | hover_changeedgesource()
256 | } else {
257 | defaultStyles()
258 | }
259 | break;
260 | }
261 | }
262 | });
263 | }
264 | }
265 |
266 | export default customBranchNode
267 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/node/customParallelNode.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 | import commonConfig from "../../config/commonConfig"
3 | const config = {
4 | defaultSize:[80],
5 | title:'=',
6 | ...commonConfig.node,
7 | inEdgeNum:10,
8 | outEdgeNum:1,
9 | };
10 |
11 | const customParallelNode = {
12 | init(editor) {
13 | G6.registerNode("customParallelNode", {
14 | draw(cfg, group) {
15 | let currentMode = editor.getCurrentMode();
16 |
17 | let r = parseInt(config.defaultSize[0]/2);
18 | cfg.size = [2*r];
19 |
20 |
21 | const shape = group.addShape("circle", {//这个是主图形
22 | attrs: {
23 | id: cfg.id,
24 | x: 0,
25 | y: 0,
26 | r: r,
27 | isMain:true,
28 | stroke: config.strokeColor_grey,
29 | fill: '#fff',//此处必须有fill 不然不能触发事件
30 | lineDash: [4, 4],//虚线
31 | cursor:'move',
32 | lineWidth: config.lineWidth_thin,
33 | }
34 | });
35 |
36 | let diamondR = parseInt(r * 0.6);
37 | group.addShape("polygon", {//多边形做成菱形
38 | attrs: {
39 | x: 0,
40 | y: 0,
41 | width: 2*diamondR,
42 | height: 2*diamondR,
43 | points:[[0,-diamondR],[-diamondR,0],[0,diamondR],[diamondR,0]],
44 | stroke: config.centerColor_blue,
45 | lineWidth:2,
46 | }
47 | });
48 | group.addShape("text", {
49 | attrs: {
50 | isText:true,
51 | x: 0,
52 | y: 0,
53 | textAlign: "center",
54 | textBaseline: "middle",
55 | text: config.title,
56 | fill: config.centerColor_blue,
57 | fontSize: 30,
58 | cursor:'move',
59 | fontWeight:'bolder'//最粗的字体
60 | }
61 | });
62 | group.addShape("circle", {//加一个透明的图形解决点击有的地方不好拖动的问题
63 | attrs: {
64 | x: 0,
65 | y: 0,
66 | r: r,
67 | fill: '#fff',//此处必须有fill 不然不能触发事件
68 | opacity: 0,
69 | cursor:'move',
70 | }
71 | });
72 | if(config.deleteByIcon && currentMode === 'edit') {
73 | const offset_ = parseInt(0.8 * r);
74 | group.addShape("circle", {//删除的按钮
75 | attrs: {
76 | x: offset_,
77 | y: -offset_,
78 | r: config.defaultDeleteR,
79 | isDeleteButton: true,
80 | enabled: false,
81 | stroke: '#fff',
82 | lineWidth: 2,
83 | fill: '#FF111A',
84 | cursor: 'pointer',
85 | opacity: 0,
86 | }
87 | });
88 | group.addShape("text", {//删除的按钮的叉叉
89 | attrs: {
90 | x: offset_,
91 | y: -offset_ + 0.95,
92 | textAlign: "center",
93 | textBaseline: "middle",
94 | text: '×',
95 | fontSize: 18,
96 | cursor: 'pointer',
97 | isDeleteButton: true,
98 | enabled: false,
99 | fill: '#fff',
100 | opacity: 0,
101 | fontWeight: 'bolder'//最粗的字体
102 | }
103 | });
104 | if(currentMode === 'edit') {
105 | for (let i = 0; i < config.anchorPoints.length; i++) {//画上下左右的4个点
106 | group.addShape("circle", {
107 | attrs: {
108 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
109 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
110 | r: 0,
111 | isAnchorPointBigger:true,
112 | anchorPointIndex:i,
113 | opacity: config.anchorBiggerOpacity,
114 | fill: config.anchorColor_blue,
115 | cursor:'crosshair',
116 | }
117 | });
118 | group.addShape("circle", {
119 | attrs: {
120 | x: parseInt(config.anchorPoints[i][0] * 2 * r - r),
121 | y: parseInt(config.anchorPoints[i][1] * 2 * r - r),
122 | r: 0,
123 | isAnchorPoint:true,
124 | anchorPointIndex:i,
125 | opacity: 1,
126 | stroke: config.anchorColor_blue,
127 | fill: config.anchorColor_grey,
128 | cursor:'crosshair',
129 | }
130 | });
131 | }
132 | }
133 | }
134 | return shape;
135 | },
136 | //设置状态
137 | setState(name, value, item) {
138 | let showOutAnchor = false;
139 | let showInAnchor = false;
140 | if((name === 'selected' || name === 'hover' || name === 'hover_addedge') && item._cfg.edges){
141 | let existOutEdges = 0;
142 | let existInEdges = 0;
143 | item._cfg.edges.forEach(edge => {
144 | if (edge._cfg.sourceNode._cfg.id == item._cfg.id) {
145 | existOutEdges++;
146 | }
147 | if(edge._cfg.targetNode._cfg.id == item._cfg.id){
148 | existInEdges++;
149 | }
150 | });
151 | if(existOutEdges {
160 | return point._attrs.isMain;
161 | })[0];
162 | const points = group.findAll(point => {
163 | return point._attrs.isAnchorPoint;
164 | });
165 | const pointBiggers = group.findAll(point => {
166 | return point._attrs.isAnchorPointBigger;
167 | });
168 | const deleteButtons = group.findAll(point => {
169 | return point._attrs.isDeleteButton;
170 | });
171 |
172 |
173 | const selectStyles = () => {
174 | shape.attr("stroke", config.strokeColor_blue);
175 | shape.attr("lineWidth", config.lineWidth_bolder);
176 |
177 | if(showOutAnchor){
178 | points.forEach(point => {
179 | point.attr('r', config.anchorR);
180 | point.attr('fill', config.anchorColor_grey);
181 | });
182 | }else{
183 | points.forEach(point => {
184 | point.attr('r', 0);
185 | });
186 | }
187 | deleteButtons.forEach(button => {
188 | button.attr('opacity', 1);
189 | button.attr('enabled',true);
190 | });
191 | };
192 | const defaultStyles = () => {
193 | shape.attr("stroke", config.strokeColor_grey);
194 | shape.attr("lineWidth", config.lineWidth_thin);
195 |
196 | points.forEach(point => {
197 | point.attr('r', 0);
198 | });
199 | pointBiggers.forEach(point => {
200 | point.attr('r', 0);
201 | });
202 | deleteButtons.forEach(button => {
203 | button.attr('opacity', 0);
204 | button.attr('enabled',false);
205 | });
206 | };
207 | let hover_addedge = () => {
208 | if(showInAnchor){
209 | points.forEach(point => {
210 | point.attr('r', config.anchorR);
211 | point.attr('fill', config.anchorColor_grey);
212 | });
213 | pointBiggers.forEach(point => {
214 | point.attr('r', config.anchorR*3);
215 | });
216 | }else{
217 | points.forEach(point => {
218 | point.attr('r', 0);
219 | });
220 | pointBiggers.forEach(point => {
221 | point.attr('r', 0);
222 | });
223 | }
224 | };
225 | const hover_changeedgesource = () => {
226 | //只是改个锚点,肯定满足出入的规则
227 | points.forEach(point => {
228 | point.attr('r', config.anchorR);
229 | point.attr('fill', config.anchorColor_grey);
230 | });
231 | pointBiggers.forEach(point => {
232 | point.attr('r', config.anchorR*3);
233 | });
234 | };
235 | switch (name) {
236 | case "selected":
237 | case "hover":
238 | if (value) {
239 | selectStyles()
240 | } else {
241 | defaultStyles()
242 | }
243 | break;
244 | case "hover_addedge":
245 | if (value) {
246 | hover_addedge()
247 | } else {
248 | defaultStyles()
249 | }
250 | break;
251 | case "hover_changeedgesource":
252 | if (value) {
253 | hover_changeedgesource()
254 | } else {
255 | defaultStyles()
256 | }
257 | break;
258 | }
259 | }
260 | });
261 |
262 | }
263 | }
264 |
265 | export default customParallelNode
266 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/node/customToolNode.js:
--------------------------------------------------------------------------------
1 | import G6 from "@antv/g6/build/g6";
2 | import commonConfig from "../../config/commonConfig"
3 | import {getShowNodeTitle} from "../../utils/commonCaculate"
4 |
5 | const config = {
6 | defaultSize:[130,70],
7 | title:'工具',
8 | ...commonConfig.node,
9 | inEdgeNum:10,
10 | outEdgeNum:1,
11 | unConfiguredColor:'#ABB8CC',
12 | };
13 |
14 | const customToolNode = {
15 | init(editor) {
16 | G6.registerNode("customToolNode", {
17 | draw(cfg, group) {
18 | let currentMode = editor.getCurrentMode();
19 | let width = config.defaultSize[0];
20 | let height = config.defaultSize[1];
21 | let title = getShowNodeTitle(cfg.title);
22 |
23 | cfg.size = [width,height];
24 |
25 | let centerColor = config.centerColor_blue;
26 | if(cfg.unConfigured){
27 | centerColor = config.unConfiguredColor;
28 | }
29 |
30 | const shape = group.addShape("rect", {//这个是主图形
31 | attrs: {
32 | id: cfg.id,
33 | x: -parseInt(width/2),
34 | y: -parseInt(height/2),
35 | width: width,
36 | height: height,
37 | isMain:true,
38 | stroke: config.strokeColor_grey,
39 | fill: config.centerColor_white,//此处必须有fill 不然不能触发事件
40 | lineWidth: config.lineWidth_thin,
41 |
42 | }
43 | });
44 |
45 | group.addShape("rect", {
46 | attrs: {
47 | x: -parseInt(width/2),
48 | y: parseInt(height*0.2),
49 | isCenterShape:true,
50 | width: width,
51 | height: parseInt(height*0.3),
52 | fill: centerColor,//此处必须有fill 不然不能触发事件
53 | }
54 | });
55 |
56 | group.addShape("text", {
57 | attrs: {
58 | x: 0,
59 | y: -parseInt(height*0.1),
60 | textAlign: "center",
61 | textBaseline: "middle",
62 | text: title,
63 | fill: config.centerColor_blue,
64 | fontSize: 16,
65 | // fontWeight:'bolder'//最粗的字体
66 | }
67 | });
68 | group.addShape("rect", {//加一个透明的图形解决点击有的地方不好拖动的问题
69 | attrs: {
70 | x: -parseInt(width/2),
71 | y: -parseInt(height/2),
72 | width: width,
73 | height: height,
74 |
75 | fill: '#fff',//此处必须有fill 不然不能触发事件
76 | cursor:'move',
77 | opacity: 0,
78 | }
79 | });
80 | if(config.deleteByIcon && currentMode === 'edit') {
81 | group.addShape("circle", {//删除的按钮
82 | attrs: {
83 | x: parseInt(width / 2),
84 | y: -parseInt(height / 2),
85 | r: parseInt(config.defaultDeleteR),
86 | isDeleteButton: true,
87 | enabled: false,
88 | stroke: '#fff',
89 | lineWidth: 2,
90 | fill: '#FF111A',
91 | cursor: 'pointer',
92 | opacity: 0,
93 | }
94 | });
95 | group.addShape("text", {//删除的按钮的叉叉
96 | attrs: {
97 | x: parseInt(width / 2),
98 | y: -parseInt(height / 2) + 0.95,
99 | textAlign: "center",
100 | textBaseline: "middle",
101 | text: '×',
102 | fontSize: 18,
103 | cursor: 'pointer',
104 | isDeleteButton: true,
105 | enabled: false,
106 | fill: '#fff',
107 | opacity: 0,
108 | fontWeight: 'bolder'//最粗的字体
109 | }
110 | });
111 | }
112 |
113 |
114 | if(currentMode === 'edit') {
115 | for (let i = 0; i < config.anchorPoints.length; i++) {//画上下左右的4个点
116 | group.addShape("circle", {
117 | attrs: {
118 | x: parseInt(config.anchorPoints[i][0] * width - 0.5 * width),
119 | y: parseInt(config.anchorPoints[i][1] * height - 0.5 * height),
120 | r: 0,
121 | isAnchorPointBigger:true,
122 | anchorPointIndex:i,
123 | opacity: config.anchorBiggerOpacity,
124 | fill: config.anchorColor_blue,
125 | cursor:'crosshair',
126 | }
127 | });
128 | group.addShape("circle", {
129 | attrs: {
130 | x: parseInt(config.anchorPoints[i][0] * width - 0.5 * width),
131 | y: parseInt(config.anchorPoints[i][1] * height - 0.5 * height),
132 | r: 0,
133 | isAnchorPoint:true,
134 | anchorPointIndex:i,
135 | opacity: 1,
136 | stroke: config.anchorColor_blue,
137 | fill: config.anchorColor_grey,
138 | cursor:'crosshair',
139 | }
140 | });
141 |
142 | }
143 | }
144 | return shape;
145 | },
146 |
147 | //设置状态
148 | setState(name, value, item) {
149 | let showOutAnchor = false;
150 | let showInAnchor = false;
151 | if((name === 'selected' || name === 'hover' || name === 'hover_addedge') && item._cfg.edges){
152 | let existOutEdges = 0;
153 | let existInEdges = 0;
154 | item._cfg.edges.forEach(edge => {
155 | if (edge._cfg.sourceNode._cfg.id === item._cfg.id) {
156 | existOutEdges++;
157 | }
158 | if(edge._cfg.targetNode._cfg.id === item._cfg.id){
159 | existInEdges++;
160 | }
161 | });
162 | if(existOutEdges {
171 | return point._attrs.isMain;
172 | })[0];
173 | const centerShapes = group.findAll(center => {
174 | return center._attrs.isCenterShape;
175 | });
176 | const points = group.findAll(point => {
177 | return point._attrs.isAnchorPoint;
178 | });
179 | const pointBiggers = group.findAll(point => {
180 | return point._attrs.isAnchorPointBigger;
181 | });
182 | const deleteButtons = group.findAll(point => {
183 | return point._attrs.isDeleteButton;
184 | });
185 |
186 |
187 | const selectStyles = () => {
188 | shape.attr("stroke", config.strokeColor_blue);
189 | shape.attr("lineWidth", config.lineWidth_bolder);
190 |
191 | if(showOutAnchor){
192 | points.forEach(point => {
193 | point.attr('r', config.anchorR);
194 | point.attr('fill', config.anchorColor_grey);
195 | });
196 | }else{
197 | points.forEach(point => {
198 | point.attr('r', 0);
199 | });
200 | }
201 | deleteButtons.forEach(button => {
202 | button.attr('opacity', 1);
203 | button.attr('enabled',true);
204 | });
205 | };
206 | let hover_addedge = () => {
207 | if(showInAnchor){
208 | points.forEach(point => {
209 | point.attr('r', config.anchorR);
210 | point.attr('fill', config.anchorColor_grey);
211 | });
212 | pointBiggers.forEach(point => {
213 | point.attr('r', config.anchorR*3);
214 | });
215 | }else{
216 | points.forEach(point => {
217 | point.attr('r', 0);
218 | });
219 | pointBiggers.forEach(point => {
220 | point.attr('r', 0);
221 | });
222 | }
223 | };
224 | const hover_changeedgesource = () => {
225 | //只是改个锚点,肯定满足出入的规则
226 | points.forEach(point => {
227 | point.attr('r', config.anchorR);
228 | point.attr('fill', config.anchorColor_grey);
229 | });
230 | pointBiggers.forEach(point => {
231 | point.attr('r', config.anchorR*3);
232 | });
233 | };
234 | const defaultStyles = () => {
235 | shape.attr("stroke", config.strokeColor_grey);
236 | shape.attr("lineWidth", config.lineWidth_thin);
237 | points.forEach(point => {
238 | point.attr('r', 0);
239 | });
240 | pointBiggers.forEach(point => {
241 | point.attr('r', 0);
242 | });
243 |
244 |
245 | deleteButtons.forEach(button => {
246 | button.attr('opacity', 0);
247 | button.attr('enabled',false);
248 | });
249 |
250 | };
251 |
252 | switch (name) {
253 | case "selected":
254 | case "hover":
255 | if (value) {
256 | selectStyles()
257 | } else {
258 | defaultStyles()
259 | }
260 | break;
261 | case "hover_addedge":
262 | if (value) {
263 | hover_addedge()
264 | } else {
265 | defaultStyles()
266 | }
267 | break;
268 | case "hover_changeedgesource":
269 | if (value) {
270 | hover_changeedgesource()
271 | } else {
272 | defaultStyles()
273 | }
274 | break;
275 |
276 | }
277 | }
278 | });
279 | }
280 | }
281 |
282 | export default customToolNode
283 |
--------------------------------------------------------------------------------
/src/views/flowChart/Editor/custom/edge/customPolyEdge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 修改自 https://github.com/OXOYO/X-Flowchart-Vue
3 | */
4 |
5 | import G6 from "@antv/g6/build/g6";
6 | import { polylineFinding } from './polylineFinding'
7 | import { customPolylineFinding } from './customPolylineFinding'
8 | import { getPointOffset, getEdgeStartOrEndPoint } from '../../utils/commonCaculate'
9 | import commonConfig from "../../config/commonConfig"
10 |
11 | const config = {
12 | ...commonConfig.edge,
13 | };
14 |
15 | const selectStyle = {
16 | strokeColor:'#64a1ec',//线的颜色
17 | strokeWidth: 2,
18 | };
19 |
20 |
21 | const customPolyEdge = {
22 | init(editor) {
23 | G6.registerEdge('customPolyEdge', {
24 | draw (cfg, group) {
25 | let currentMode = editor.getCurrentMode();
26 | let source = cfg.source;
27 | let target = cfg.target;
28 | let title = cfg.title;
29 | if(!source._cfg && source.x == null){//source传的是id
30 | source = editor.findById(cfg.source);
31 | }
32 | if(!target._cfg && target.x == null){
33 | target = editor.findById(cfg.target);
34 | }
35 | if(cfg.sourceAnchorIndex == null){
36 | cfg.sourceAnchorIndex = 1;
37 | }
38 | if(cfg.targetAnchorIndex == null){
39 | cfg.targetAnchorIndex = 0;
40 | }
41 |
42 | const startPoint = getEdgeStartOrEndPoint(cfg.startPointOffset, source, cfg.sourceAnchorIndex);
43 | const endPoint = getEdgeStartOrEndPoint(cfg.endPointOffset, target, cfg.targetAnchorIndex);
44 |
45 | cfg.source = source;//预置的连线没有这个参数,但是这个参数是必须的
46 | cfg.target = target;
47 |
48 | if((title == null || title === '') && source._cfg.model.type === 'exclusiveGateway'){
49 | title = '默认';
50 | }
51 |
52 | // const controlPoints = this.getControlPoints(cfg.controlPoints,source, target, startPoint, endPoint);
53 | // let points = [startPoint];
54 | // if (controlPoints) {
55 | // points.push(...controlPoints)
56 | // }
57 | // points.push(endPoint);
58 | let points = customPolylineFinding(startPoint, endPoint, cfg.sourceAnchorIndex, cfg.targetAnchorIndex);
59 |
60 | let path = this.getPath(points);
61 | const keyShape = group.addShape('path', {
62 | className: 'edge-shape',
63 | attrs: {
64 | path: path,
65 | isEdge: true,
66 | stroke: config.strokeColor_grey,
67 | lineAppendWidth: 10,//边响应鼠标事件时的检测宽度,
68 | lineWidth: config.strokeWidth_thin,//边的宽度
69 | endArrow: {
70 | path: 'M 8,0 L -6,-6 L -6,6 Z',
71 | d: 8,
72 | },
73 | }
74 | });
75 |
76 |
77 |
78 |
79 | let first = 0;
80 | let second = 1;
81 | //取points的最中间的2个点的下标
82 | if (points.length > 3) {
83 | first = parseInt(points.length / 2) -1;
84 | second = first + 1;
85 | }
86 |
87 | let middlePoint = this.getMiddlePoint(points);//因为获取到的points有问题,所以根据距离来算不行
88 | const iconX = middlePoint.x;
89 | const iconY = middlePoint.y;
90 |
91 | if(title != null && title !==''){
92 | group.addShape("text", {//线上的文字
93 | attrs: {
94 | x: iconX,
95 | y: iconY-5,
96 | textAlign: "center",
97 | textBaseline: "middle",
98 | text: title,
99 | fontSize: 18,
100 | fill: '#000000',
101 | }
102 | });
103 | }
104 | if (currentMode === 'edit') {
105 |
106 | //增加透明的可以用来拖动线的按钮
107 | let sourceParam= this.getMoveBtnSizeAndOffset(cfg.sourceAnchorIndex);
108 | let targetParam = this.getMoveBtnSizeAndOffset(cfg.targetAnchorIndex);
109 | group.addShape("rect", {//线两头的可拖动的透明按钮
110 | attrs: {
111 | x: points[0].x+sourceParam.offsetX,
112 | y: points[0].y+sourceParam.offsetY,
113 | fill: '#ff1a21',
114 | width: sourceParam.width,
115 | height: sourceParam.height,
116 | sourceMoveBtn:true,
117 | opacity: 0,
118 | cursor:'move',
119 | }
120 | });
121 | group.addShape("rect", {//线两头的可拖动的透明按钮
122 | attrs: {
123 | x: points[points.length-1].x+targetParam.offsetX,
124 | y: points[points.length-1].y+targetParam.offsetY,
125 | fill: '#18ff40',
126 | width: targetParam.width,
127 | height: targetParam.height,
128 | targetMoveBtn:true,
129 | opacity: 0,
130 | cursor:'move',
131 | }
132 | });
133 |
134 | if(config.deleteByIcon){
135 | group.addShape("circle", {//删除的按钮
136 | attrs: {
137 | x: iconX,
138 | y: iconY,
139 | r: 8,
140 | isDeleteButton: true,
141 | enabled: false,
142 | stroke: '#fff',
143 | lineWidth: 2,
144 | fill: '#FF111A',
145 | cursor: 'pointer',
146 | opacity: 0,
147 | }
148 | });
149 |
150 | group.addShape("text", {//删除的按钮的叉叉
151 | attrs: {
152 | x: iconX,
153 | y: iconY + 0.95,
154 | textAlign: "center",
155 | textBaseline: "middle",
156 | text: '×',
157 | fontSize: 18,
158 | cursor: 'pointer',
159 | isDeleteButton: true,
160 | enabled: false,
161 | fill: '#fff',
162 | opacity: 0,
163 | fontWeight: 'bolder'//最粗的字体
164 | }
165 | });
166 | }
167 | }
168 |
169 | return keyShape
170 | },
171 |
172 | getMoveBtnSizeAndOffset(anchorIndex){
173 | let width = 10;
174 | let height = 20;
175 | let offsetX = 0;
176 | let offsetY = 0;
177 | switch(anchorIndex){
178 | case 0:
179 | width = 10;
180 | height = 20;
181 | offsetX = -5;
182 | offsetY = -20;
183 | break;
184 | case 1:
185 | width = 10;
186 | height = 20;
187 | offsetX = -5;
188 | offsetY = 0;
189 | break;
190 | case 2:
191 | width = 20;
192 | height = 10;
193 | offsetX = -20;
194 | offsetY = -5;
195 | break;
196 | case 3:
197 | width = 20;
198 | height = 10;
199 | offsetX = 0;
200 | offsetY = -5;
201 | break;
202 | default:
203 | width = 20;
204 | height = 10;
205 | offsetX = 0;
206 | offsetY = -5;
207 | break
208 | }
209 | return {width:width,height:height,offsetX:offsetX,offsetY:offsetY}
210 | },
211 |
212 | getPath (points) {
213 | const path = []
214 | for (let i = 0; i < points.length; i++) {
215 | const point = points[i]
216 | if (i === 0) {
217 | path.push([ 'M', point.x, point.y ])
218 | } else if (i === points.length - 1) {
219 | path.push([ 'L', point.x, point.y ])
220 | } else {
221 | const prevPoint = points[i - 1]
222 | let nextPoint = points[i + 1]
223 | let cornerLen = 5
224 | if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) {
225 | if (prevPoint.x === point.x) {
226 | path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen])
227 | } else if (prevPoint.y === point.y) {
228 | path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
229 | }
230 | }
231 | const yLen = Math.abs(point.y - nextPoint.y)
232 | const xLen = Math.abs(point.x - nextPoint.x)
233 | if (yLen > 0 && yLen < cornerLen) {
234 | cornerLen = yLen
235 | } else if (xLen > 0 && xLen < cornerLen) {
236 | cornerLen = xLen
237 | }
238 | if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) {
239 | path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen])
240 | } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) {
241 | path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
242 | }
243 | }
244 | }
245 | return path
246 | },
247 | getControlPoints (controlPoints, source, target, startPoint, endPoint) {
248 | if (!source) {
249 | return controlPoints;
250 | }
251 | return polylineFinding(source, target, startPoint, endPoint, 30);
252 | },
253 |
254 | getMiddlePoint(points){
255 | let distance = 0;
256 | let length = points.length;
257 | let array = [];//相邻两个点之间的距离
258 | for(let i = 0; i < length-1; i++) {
259 | let temp = 0;
260 | if(Math.abs(points[i].x - points[i+1].x) < 1){
261 | temp = Math.abs(points[i].y - points[i+1].y)
262 | }else{
263 | temp = Math.abs(points[i].x - points[i+1].x)
264 | }
265 | array.push(temp);
266 | distance += temp;
267 | }
268 | distance = parseInt(distance/2);
269 | let point = {x:points[0].x,y:points[0].y};
270 | for(let j = 0; j < length-1; j++){
271 | if(distance - array[j] < 0){//说明中间点在第i和第i+1个点中间
272 | if(Math.abs(points[j].x - points[j+1].x) < 1){//x坐标相同
273 | if(points[j].y>points[j+1].y){
274 | point.y = points[j].y-distance;
275 | }else{
276 | point.y = points[j].y+distance;
277 | }
278 | point.x = points[j].x;
279 | }else{
280 | if(points[j].x>points[j+1].x){
281 | point.x = points[j].x-distance;
282 | }else{
283 | point.x = points[j].x+distance;
284 | }
285 | point.y = points[j].y;
286 | }
287 | break;
288 | }else{
289 | distance -= array[j]
290 | }
291 | }
292 | return point;
293 | },
294 | setState(name, value, item) {
295 | const group = item.getContainer();
296 | const shape = group.findAll(point => {
297 | return point._attrs.isEdge;
298 | })[0];
299 | const deleteButtons = group.findAll(point => {
300 | return point._attrs.isDeleteButton;
301 | });
302 | const selectStyles = () => {
303 | shape.attr("stroke", config.strokeColor_blue);
304 | shape.attr("lineWidth", config.strokeWidth_bolder);
305 |
306 | deleteButtons.forEach(button => {
307 | button.attr('opacity', 1);
308 | button.attr('enabled',true);
309 | });
310 | };
311 | const unSelectStyles = () => {
312 | shape.attr("stroke", config.strokeColor_grey);
313 | shape.attr("lineWidth", config.strokeWidth_thin);
314 | deleteButtons.forEach(button => {
315 | button.attr('opacity', 0);
316 | button.attr('enabled',false);
317 | });
318 | };
319 |
320 | switch (name) {
321 | case "selected":
322 | case "hover":
323 | if (value) {
324 | selectStyles()
325 | } else {
326 | unSelectStyles()
327 | }
328 | break;
329 | }
330 | }
331 | });
332 | }
333 | }
334 |
335 | export default customPolyEdge
336 |
--------------------------------------------------------------------------------
/src/views/treetopo/component/subtopo.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
261 |
--------------------------------------------------------------------------------
/src/jsondata/togo.js:
--------------------------------------------------------------------------------
1 | import d3 from 'd3'
2 | export class Togo {
3 | /**/
4 | constructor(svg, option) {
5 | this.data = option.data;
6 | this.edges = option.edges;
7 | this.svg = d3.select(svg);
8 |
9 | }
10 |
11 | //初始化节点位置
12 | initPosition () {
13 | let width = this.svg.attr('width');
14 | let points = this.getVertices(this.data.length);
15 | this.data.forEach((item, i) => {
16 | // 加上width/2是为了让所有节点的位置在中间
17 | item.x = points[i].x + width / 2;
18 | item.y = points[i].y;
19 | })
20 | }
21 |
22 | //根据节点的个数,生成矩形阵列中所有节点的定位坐标[{x:..,y:...},...]
23 | getVertices (n) {
24 | if (typeof n !== 'number') return;
25 | var i = 0;
26 | var j = 0;
27 | var k = 0
28 | var points = [];
29 | while (k < n) {
30 | points.push({
31 | x: 100 + 200 * i,
32 | y: 100 + 200 * j,
33 | });
34 | if (i < 2) {
35 | i++;
36 | } else {
37 | i = 0
38 | j++
39 | }
40 | k++
41 | }
42 | return points;
43 | }
44 |
45 | // // 计算两点的中心点
46 | // getCenter (x1, y1, x2, y2) {
47 | // return [(x1 + x2) / 2, (y1 + y2) / 2]
48 | // }
49 |
50 | // // 计算两点的距离
51 | // getDistance (x1, y1, x2, y2) {
52 | // return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
53 | // }
54 |
55 | // // 计算两点角度
56 | // getAngle (x1, y1, x2, y2) {
57 | // var x = Math.abs(x1 - x2);
58 | // var y = Math.abs(y1 - y2);
59 | // var z = Math.sqrt(x * x + y * y);
60 | // return Math.round((Math.asin(y / z) / Math.PI * 180));
61 | // }
62 |
63 | // 初始化缩放器
64 | initZoom () {
65 | let self = this;
66 | let zoom = d3.zoom()
67 | .scaleExtent([0.7, 3])
68 | .on('zoom', function () {
69 | self.onZoom(this)
70 | });
71 | this.svg.call(zoom)
72 | }
73 |
74 | // 初始化图标库
75 | initDefineSymbol () {
76 | // defs用于预定义一个元素使其能够在SVG图像中重复使用,我们defs标签中的g元素必须在元素上设置一个ID,通过ID来引用它。
77 | let defs = this.container.append('svg:defs');
78 | // 向defs中添加箭头元素
79 | defs
80 | .selectAll('marker')
81 | .data(this.edges)
82 | .enter()
83 | .append('svg:marker')
84 | .attr('id', (link, i) => 'marker-' + i)
85 | .attr('markerUnits', 'userSpaceOnUse')
86 | .attr('viewBox', '0 -5 10 10')
87 | .attr('refX', symbolSize / 2 + padding)
88 | .attr('refY', 0)
89 | .attr('markerWidth', 14)
90 | .attr('markerHeight', 14)
91 | .attr('orient', 'auto')
92 | .attr('stroke-width', 2)
93 | .append('svg:path')
94 | .attr('d', 'M2,0 L0,-3 L9,0 L0,3 M2,0 L0,-3')
95 | .attr('class', 'arrow')
96 |
97 | // console.log(marker)
98 | // 向defs中添加数据库
99 | defs.append('g')
100 | .attr('id', 'database')
101 | .attr('transform', 'scale(0.042)').append('path')
102 | .attr('d', 'M512 800c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V640c0 88.37-200.58 160-448 160z M512 608c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V448c0 88.37-200.58 160-448 160z M512 416c-247.42 0-448-71.63-448-160v160c0 88.37 200.58 160 448 160s448-71.63 448-160V256c0 88.37-200.58 160-448 160z M64 224a448 160 0 1 0 896 0 448 160 0 1 0-896 0Z')
103 | .attr('style', "fill:#297aff")
104 |
105 | // 向defs中添加云
106 | defs.append('g')
107 | .attr('id', 'cloud')
108 | .attr('transform', 'scale(0.042)')
109 | .append('path')
110 | .attr('d', 'M709.3 285.8C668.3 202.7 583 145.4 484 145.4c-132.6 0-241 102.8-250.4 233-97.5 27.8-168.5 113-168.5 213.8 0 118.9 98.8 216.6 223.4 223.4h418.9c138.7 0 251.3-118.8 251.3-265.3 0-141.2-110.3-256.2-249.4-264.5z')
111 |
112 | // 向defs中添加地球
113 | let earth = defs.append('g')
114 | .attr('id', "earth")
115 | .attr('transform', 'scale(0.042)');
116 |
117 | earth.append("path")
118 | .attr("d", 'm 973.70505,457.95556 c -9.82626,-86.10909 -43.70101,-167.56364 -97.74545,-235.57172 -12.54142,6.07677 -24.69495,12.15353 -36.7192,17.71313 3.23233,14.48081 6.46465,29.34949 9.30909,45.12323 10.73132,58.69899 18.61819,131.7495 17.71314,218.76364 36.71919,-13.83434 72.53333,-28.70303 107.44242,-46.02828 z M 224.32323,247.59596 c -2.84444,8.40404 -6.07677,17.19596 -8.79192,26.50505 -17.71313,59.08687 -30.25454,134.46465 -26.50505,227.55556 39.04647,15.38585 84.29899,30.25454 134.98182,41.8909 57.66465,13.05859 126.57778,22.75556 204.8,22.36768 V 310.9495 h -8.40404 c -101.49495,0 -202.47273,-21.46263 -296.08081,-63.35354 z M 550.14141,48.355556 V 289.87475 c 80.03233,-3.23233 168.98586,-20.94546 264.40405,-61.41414 -7.88687,-31.67677 -17.71314,-62.3192 -28.83233,-92.57374 C 716.41212,85.20404 635.34546,54.949495 550.14141,48.355556 Z M 52.622222,432.87273 c 21.850505,13.96363 61.414138,37.23636 115.846468,59.99192 -2.84445,-92.18586 10.21414,-167.04647 27.92727,-226.26263 2.84444,-9.82626 6.07677,-19.13535 9.30909,-27.92727 -20.42828,-9.82627 -37.23636,-19.52324 -49.77778,-27.02223 C 102.4,275.13535 66.973738,351.41818 52.622222,432.87273 Z')
119 |
120 | earth.append("path").attr('d', 'm 845.31717,511.09495 c 1.42222,-104.72727 -9.82626,-192.25859 -25.6,-262.46465 -96.8404,40.98586 -187.60404,58.69899 -269.05858,61.93132 v 255.09495 c 100.07272,-2.97374 199.2404,-21.59192 294.65858,-54.56162 z')
121 |
122 | earth.append("path").attr('d', 'M 845.70505,727.53131 C 882.94142,708.00808 918.75556,685.6404 952.7596,660.0404 969.0505,612.07273 976.93737,562.29495 976.93737,512 c 0,-10.73131 -0.51717,-21.46263 -1.42222,-32.06465 -37.23636,18.10101 -73.95555,33.09899 -110.28687,46.02829 -1.8101,67.49091 -8.27474,134.98181 -19.52323,201.56767 z M 169.50303,516.13737 C 128.12929,499.32929 88.048485,478.90101 49.389899,455.62828 41.890909,516.65455 46.545455,578.45657 62.836364,637.67273 107.05455,667.4101 153.08283,694.04444 201.56768,716.8 182.9495,642.84444 172.73535,576.25859 169.50303,516.13737 Z m 360.2101,283.5394 V 586.47273 h -3.23232 c -114.55354,0 -228.5899,-20.94546 -335.64445,-61.93132 4.13738,60.50909 14.86869,128.51718 35.42627,202.9899 35.81414,15.77374 74.9899,30.25455 117.26868,42.40808 60.50909,17.58384 122.82829,27.41011 186.18182,29.73738 z M 208.93737,742.4 C 162.00404,720.93737 116.88081,696.7596 73.050505,668.83232 108.86465,768.38788 177.26061,852.68687 267.11919,908.02424 244.36364,854.10909 224.8404,798.77172 208.93737,742.4 Z m 320.77576,76.8 c -65.16364,-1.8101 -129.8101,-11.63636 -192.25859,-29.73737 -35.42626,-10.21415 -69.81818,-22.36768 -103.30505,-36.33132 15.77374,53.52728 36.7192,111.19192 63.35354,171.70101 65.68081,34.90909 139.63636,52.62222 214.10909,52.62222 6.07677,0 12.15354,-0.51717 18.10101,-0.51717 z M 213.20404,219.28081 c 15.77374,-40.46869 34.39192,-74.9899 52.62222,-102.91717 -35.42626,21.8505 -67.49091,48.87272 -95.93535,79.12727 11.24848,6.98182 25.6,15.38586 43.31313,23.7899 z m 628.36364,532.94545 c -6.46465,35.42627 -13.96364,71.62829 -23.27273,109.89899 52.10505,-46.02828 93.60808,-102.91717 121.01818,-166.65858 -30.77172,20.81616 -63.35353,39.95151 -97.74545,56.75959 z')
123 |
124 | earth.append("path").attr('d', 'm 550.14141,585.95556 v 213.59191 c 95.41819,0 186.69899,-20.42828 272.80809,-61.02626 13.05858,-74.47272 19.52323,-142.86869 21.46262,-205.70505 -95.0303,32.71111 -194.19798,50.42424 -294.27071,53.1394 z')
125 |
126 | earth.append("path").attr('d', "m 818.81212,762.82828 c -84.81616,37.23637 -175.96768,56.88889 -268.67071,56.88889 v 155.92727 c 87.53132,-6.98181 171.31314,-39.04646 241.5192,-92.18585 10.73131,-41.50303 20.0404,-81.97172 27.15151,-120.63031 z m 15.25657,-543.15959 c 9.30909,-4.13738 18.61818,-8.79192 27.92727,-13.44647 -13.96364,-16.29091 -29.34949,-31.15959 -45.12323,-45.12323 6.07677,17.58384 11.63636,37.23636 17.19596,58.5697 z m -602.24647,8.40404 c 93.60808,42.40808 194.97374,63.35353 297.37374,62.31919 V 47.062626 C 453.30101,43.830303 377.40606,59.60404 309.0101,92.70303 c -25.08283,31.54748 -53.91515,76.67071 -77.18788,135.3697 z")
127 | // .attr('style', "fill:#297aff")
128 | earth.selectAll("path").attr('style', "fill:#297aff")
129 | }
130 |
131 | //
132 | //初始化链接线
133 | initLink () {
134 | this.drawLinkLine();
135 | // this.drawLinkText();
136 | }
137 |
138 | //初始化节点
139 | initNode () {
140 | var self = this;
141 | //节点容器
142 | this.nodes = this.container.selectAll(".node")
143 | .data(this.data)
144 | .enter()
145 | .append("g")
146 | .attr("transform", function (d) {
147 | return "translate(" + d.x + "," + d.y + ")";
148 | })
149 | .call(d3.drag()
150 | // 给每一个节点添加拖拽事件
151 | .on("drag", function (d) {
152 | console.log("111")
153 | self.onDrag(this, d)
154 | })
155 | )
156 | // 给每一个节点添加点击事件
157 | .on('click', function (d) {
158 | console.log(d.name)
159 | that.dialogVisible = true
160 | })
161 | //节点背景默认背景层
162 | this.nodes.append('circle')
163 | .attr('r', symbolSize / 2 + padding)
164 | .attr('class', 'node-bg').attr("opacity", "1");
165 |
166 | //节点图标
167 | this.drawNodeSymbol();
168 | //节点标题
169 | this.drawNodeTitle();
170 | //节点其他说明
171 | // this.drawNodeOther();
172 | this.drawNodeCode("database");
173 | this.drawNodeCode("cloud");
174 | this.drawNodeCode("earth");
175 |
176 | // console.log(d3)
177 | // var force = d3.layout.force()
178 | // .nodes(this.nodes) //指定节点数组
179 | // .links(this.data) //指定连线数组
180 | // .size([1000, 800]) //指定作用域范围
181 | // .linkDistance(150) //指定连线长度
182 | // .charge([-400]); //相互之间的作用力
183 | // force.start(); //开始作用
184 | }
185 | // 画节点语言标识
186 | drawNodeCode (code) {
187 | this.nodeCodes = this.nodes.filter(item => item.type == code)
188 | .append('g')
189 | .attr('class', 'node-code')
190 | .attr('transform', 'translate(' + -symbolSize / 2 + ',' + symbolSize / 3 + ')')
191 |
192 | this.nodeCodes
193 | .append('circle')
194 | .attr('r', () => fontSize * 1.5)
195 |
196 | this.nodeCodes
197 | .append('text')
198 | .attr('dy', fontSize / 1.5)
199 | // .text(item => item.code);
200 | .attr("style", "font-size:18px;line-height:27px")
201 | .text("√");
202 |
203 | }
204 |
205 | //画节点图标
206 | drawNodeSymbol () {
207 | //绘制圆点图标
208 | this.nodes.filter(item => item.type == 'app')
209 | .append("circle")
210 | .attr("r", symbolSize / 2)
211 | .attr("fill", '#fff')
212 | .attr('class', function (d) {
213 | return 'health' + d.health;
214 | })
215 | .attr('stroke-width', '5px')
216 |
217 | // 在元素中定义的图形不会直接显示在SVG图像上。要显示它们需要使用