├── .browserslistrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── README.md
├── babel.config.js
├── examples
├── App.vue
├── assets
│ ├── iconfont
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.js
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ ├── images
│ │ ├── 404-images
│ │ │ ├── 404-cloud.png
│ │ │ └── 404.png
│ │ ├── blue.svg
│ │ ├── client.png
│ │ ├── database.png
│ │ ├── firewall.png
│ │ ├── green.svg
│ │ ├── purple.svg
│ │ ├── server.png
│ │ ├── topology.gif
│ │ └── topology.png
│ └── logo.png
├── components
│ ├── FooterBar.vue
│ ├── HeaderBar.vue
│ └── HelloWorld.vue
├── main.js
├── router.js
├── store.js
├── styles
│ ├── common.scss
│ └── index.scss
├── utils
│ └── index.js
└── views
│ ├── demos
│ └── topology.vue
│ ├── error-pages
│ └── 404.vue
│ └── pages
│ ├── about.vue
│ ├── home.vue
│ ├── index.vue
│ ├── mobile.vue
│ └── test.vue
├── package-lock.json
├── package.json
├── packages
├── assets
│ ├── iconfont
│ │ ├── iconfont.css
│ │ ├── iconfont.eot
│ │ ├── iconfont.js
│ │ ├── iconfont.svg
│ │ ├── iconfont.ttf
│ │ ├── iconfont.woff
│ │ └── iconfont.woff2
│ ├── images
│ │ ├── client.png
│ │ ├── database.png
│ │ ├── firewall.png
│ │ └── server.png
│ └── logo.png
├── cc-elements
│ ├── button.vue
│ ├── checkbox.vue
│ ├── dropdown.vue
│ ├── index.js
│ └── loading.vue
├── cc-topology
│ ├── index.js
│ └── src
│ │ ├── behavior
│ │ ├── click-add-edge.js
│ │ ├── click-event-edit.js
│ │ ├── drag-add-edge.js
│ │ ├── drag-event-edit.js
│ │ ├── hover-event-edit.js
│ │ ├── index.js
│ │ └── keyup-event-edit.js
│ │ ├── cc-topology.vue
│ │ ├── config
│ │ ├── edge.js
│ │ └── index.js
│ │ ├── edge
│ │ ├── base.js
│ │ ├── cc-brokenline.js
│ │ ├── cc-cubic.js
│ │ ├── cc-line.js
│ │ ├── cc-polyline.js
│ │ ├── index.js
│ │ └── polyline-finding.js
│ │ ├── graph
│ │ └── index.js
│ │ ├── node
│ │ ├── base.js
│ │ ├── cc-image.js
│ │ ├── cc-rect.js
│ │ └── index.js
│ │ ├── plugins
│ │ └── d3-installer.js
│ │ ├── theme
│ │ ├── dark-style.js
│ │ ├── default-style.js
│ │ ├── index.js
│ │ └── office-style.js
│ │ ├── toolbar-edit.vue
│ │ ├── toolbar-preview.vue
│ │ └── utils
│ │ ├── anchor
│ │ ├── draw.js
│ │ ├── index.js
│ │ ├── set-state.js
│ │ └── update.js
│ │ ├── edge
│ │ ├── index.js
│ │ └── set-state.js
│ │ ├── index.js
│ │ └── node
│ │ ├── index.js
│ │ └── set-state.js
└── index.js
├── postcss.config.js
├── prettier.config.js
├── public
├── cceditor.png
├── favicon.ico
└── index.html
└── vue.config.js
/.browserslistrc:
--------------------------------------------------------------------------------
1 | > 1%
2 | last 2 versions
3 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | node: true
5 | },
6 | extends: ["plugin:vue/essential", "@vue/prettier"],
7 | rules: {
8 | "no-console": process.env.NODE_ENV === "production" ? "error" : "off",
9 | "no-debugger": process.env.NODE_ENV === "production" ? "error" : "off",
10 | // 强制使用单引号
11 | 'quotes': ['error', 'single'],
12 | // 关闭行末分号提示/报错
13 | 'semi': 0
14 | },
15 | parserOptions: {
16 | parser: "babel-eslint"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/.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 |
14 | # Editor directories and files
15 | .idea
16 | .vscode
17 | *.suo
18 | *.ntvs*
19 | *.njsproj
20 | *.sln
21 | *.sw?
22 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # 忽略目录
2 | .idea/
3 | examples/
4 | packages/
5 |
6 | # 忽略指定文件
7 | vue.config.js
8 | babel.config.js
9 | *.map
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # CCEditor
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | > A visual graph editor component library based on G6 and Vue.js.
18 | > 基于 G6 和 Vue 的可视化图形编辑组件库。
19 |
20 | ## 一、 版本
21 |
22 | | 开发版本 |
23 | | :-------- |
24 | | [v0.1.0](https://github.com/wenyuan/cceditor/tree/master) |
25 |
26 | ## 二、预览
27 |
28 | 
29 |
30 | ## 三、项目依赖
31 |
32 | * [Vue.js(2.x)](https://cn.vuejs.org/)
33 | * [Element UI(2.x)](https://element.eleme.cn/2.0/#/zh-CN)
34 | * [AntV G6(4.x)](http://antv.alipay.com/zh-cn/index.html)
35 |
36 | ## 四、项目结构
37 |
38 | ```
39 | .
40 | ├── packages // 源码
41 | ├── examples // 用例(开发调试用)
42 | ├── 其它
43 | |
44 | ```
45 |
46 | ## 五、开发方式
47 |
48 | ### 1. 调试
49 |
50 | ```bash
51 | npm install
52 | npm run serve
53 | ```
54 |
55 | ### 2. 发布
56 |
57 | ```bash
58 | npm run lib
59 | npm pack
60 | ```
61 |
62 | ## 六、使用方式
63 |
64 | ### 1. 安装CCEditor
65 |
66 | ```bash
67 | # 本地安装
68 | npm install chaincloud-cceditor-0.1.0.tgz
69 |
70 | # npm安装(暂不支持)
71 | npm install @chaincloud/cceditor --save
72 | ```
73 | 本地安装:[安装包下载](https://github.com/wenyuan/cceditor/releases)
74 |
75 | ### 2. 引入CCEditor
76 |
77 | #### 全局注册
78 |
79 | 在 `main.js` 中写入以下内容:
80 | * 完整引入:引入整个CCEditor:
81 |
82 | ```javascript
83 | import Vue from 'vue'
84 | import router from './router'
85 | import store from './store'
86 | import ElementUI from 'element-ui'
87 |
88 | import App from './App.vue'
89 |
90 | Vue.use(ElementUI)
91 |
92 | /* 全局注册:引入整个CCEditor */
93 | import CCEditor from '@chaincloud/cceditor'
94 | Vue.use(CCEditor)
95 |
96 | Vue.config.productionTip = false
97 |
98 | new Vue({
99 | router,
100 | store,
101 | render: h => h(App)
102 | }).$mount('#app')
103 | ```
104 | * 按需引入:引入需要的模块,例如CCTopology:
105 |
106 | ```javascript
107 | import Vue from 'vue'
108 | import router from './router'
109 | import store from './store'
110 | import ElementUI from 'element-ui'
111 |
112 | import App from './App.vue'
113 |
114 | Vue.use(ElementUI)
115 |
116 | /* 全局注册:只加载CCTopology */
117 | import { CCTopology } from '@chaincloud/cceditor'
118 | Vue.use(CCTopology)
119 |
120 | Vue.config.productionTip = false
121 |
122 | new Vue({
123 | router,
124 | store,
125 | render: h => h(App)
126 | }).$mount('#app')
127 | ```
128 |
129 | #### 局部注册
130 |
131 | 在 `ComponentA.vue` 中进行局部注册:
132 | ```vue
133 |
143 | ```
144 |
145 | ### 3. 开始使用
146 |
147 | #### 属性
148 |
149 | TODO...
150 |
151 | #### 方法
152 |
153 | TODO...
154 |
155 | ## 七、备注
156 |
157 | * **\[已解决]** 升级 G6 到 3.5.0 后,发现 G6 底层有 [bug](https://github.com/antvis/G6/issues/1613):调用addItem方法报错:`p.getType is not a function`。该 bug 导致编辑(连线)功能不可用,需等待下个小版本的修复。—— 2020.06.02
158 | * **\[已解决]** 由于 G6 在 3.3.x 版本作了 breaking change,且遗留了一些 bug。本项目先暂停更新。预计 6 月份或 G6 底层这些重要 bug 修复后,继续跟进。—— 2020.03.01
159 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ["@vue/app"]
3 | };
4 |
--------------------------------------------------------------------------------
/examples/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "iconfont";
2 | src: url('iconfont.eot?t=1570676544597'); /* IE9 */
3 | src: url('iconfont.eot?t=1570676544597#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAU8AAsAAAAACfQAAATtAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDMgqHAIV5ATYCJAMUCwwABCAFhG0HXBuZCBHVo4GR/UyIN14+uxup00M/BAkNRvqeiYev/V7P3d0HIVSgopKvMvGANiwskVBlMr5CVqiOZwkkdH76TX3HkYU1NZfQUtFZZCYSNiebE6ZmB3Quf2YJgP3fFum0lIWCRl6Hfta/7IsBY2dHl6YpTQNWx87ispfSqvVGnQIYCnAsVkr5WjWg/8Dy7v8Qb3WZxwl0m9IRqxV1BmAU+rhAnPbp1QWYhFGpYIZWUxcsLRCvQKNNs6gbAF56vx9/IT0YkFQZ9KmbPcu7g/5tzVuCLfZb0N4hgGg4O9hVZHRQiOtC5yngINyRdctB2iKAEmwlaahOF99ef0vsdgDphtTdxj88kpAVogZd2YvURUU5OJGFkfhxESP4cR0j84OIH4yywtHdqIkpEE8JdcKRBF5YL1dFs+w48togvzGq78zK29EbJB/E7/JWVTOvxC9W03liuCZUKQrskb22EJBlo7jDd4NSyp/KljbELT6aJMpmsInRimJqjfcRIATzqs6wygVgaTQIkoSM50Z8saoivuV+0yayxfOSwEnblEhivAMW2bglkJQqVeLGo15Btq1HL+aAd1AseCsPtR6Me7vRdrMt9kbkns+qbQ7bNntt3Ak7rTGrY+ibrS+R1oVIF+sPW7NuX4aVazxPhhFisK30M1hsPrPHyxMIGbdf8XZe3HOTIpBISTGJJE0iRlk2CzuCxCvZx04h6VamsDQvaeteuRIs4gZR52SVNi12+qpdDrJqVpVQPKmRDg2lGw2EuC2Nro333wy3Xx6FFz0y0zO6YSHOnx1TreU8MKutSDpjOQPI/xbsoeWqx/izcViY0c2k2QI5p0bIzH05/ZGLrslx0VxND2eDgz1cyjj2m9qkuefrfV9jIAcFKIEyXWBU7+YnZ8482fyOiVwU/t29N59zWJLBjNDSCPFF24Z6DJncwTypxL9iL+v95crqefM2n6v0PxtQ6aUfZO4wiB/m+XiJwdO9uJ1fz+DcuXKtlVpS1l2Dw1tqmy/OPMXALQDn3xAvXhgV++KGtrS5rq/nncuoc8/Q+8/xUS6uRxCH+zVQ/Z1HVxrfo4rJf3v+nZxNve+R0zuY6t+/nqO3vNTBEXPixejYRRcQjzc8e7gZb8FPH0Lm3PEW3m3DWdwijfrcV6ND3WcKbCwOz7kYVp830GWoT5/59XpEHcbU5NrUEspzbj3OYPVJto5BJwaiNofjmDbauM1t0PGIw5Qeza/36eMyNG8g9AbAjYWGfMsbvYBGeDQA+R6u+xeH2Zb+3OnkUviN0X2Dlk8XzugR70ZCwh9KpbHozyuhgRs7HZxEN1VFqrSEkruyOYAyyfpuqgZ077Dvw6jHGmcTWmMRJI1JyFrTxELtQKXHPNRaC9Bt1sbqHiOuAYvSFJhxn0AY9B6Sfh8gG/SRWKjfoDLuP9QGA0O3feSxZQ87OvyljFAwapB/8GopxrTwxc+9oX0nQXGJx3oh7d4HfdOlYzMWpCFm7B87MBswtGSYyGmY0gIrLQEVN455HdvWFL1Ro5ZcXZQRCkYN5B94tRTjNOdL5udvaN9JUEVVmf+FtPv2Qa/RNSDOytKo6lJ67x87YDbUKENLBhPYMRnhBazFgwIqbrgOwXXUYiPTVNlMr843UMDWod9HExTCiEYa1AScDjy7t6yVJ5XwkFBfc+R0zJIoRQuKVQUAAAA=') format('woff2'),
5 | url('iconfont.woff?t=1570676544597') format('woff'),
6 | url('iconfont.ttf?t=1570676544597') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1570676544597#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family: "iconfont" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-github-circle:before {
19 | content: "\e6c5";
20 | }
21 |
22 | .icon-github:before {
23 | content: "\e64a";
24 | }
25 |
26 | .icon-github-rect:before {
27 | content: "\e6cb";
28 | }
29 |
30 | .icon-github-dark:before {
31 | content: "\e6d4";
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.js:
--------------------------------------------------------------------------------
1 | !function(d){var t,n='',e=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(e&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(t){console&&console.log(t)}}!function(t){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(t,0);else{var e=function(){document.removeEventListener("DOMContentLoaded",e,!1),t()};document.addEventListener("DOMContentLoaded",e,!1)}else document.attachEvent&&(o=t,i=d.document,c=!1,(a=function(){try{i.documentElement.doScroll("left")}catch(t){return void setTimeout(a,50)}n()})(),i.onreadystatechange=function(){"complete"==i.readyState&&(i.onreadystatechange=null,n())});function n(){c||(c=!0,o())}var o,i,c,a}(function(){var t,e;(t=document.createElement("div")).innerHTML=n,n=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",function(t,e){e.firstChild?function(t,e){e.parentNode.insertBefore(t,e)}(t,e.firstChild):e.appendChild(t)}(e,document.body))})}(window);
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
39 |
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/examples/assets/iconfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/iconfont/iconfont.woff2
--------------------------------------------------------------------------------
/examples/assets/images/404-images/404-cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/404-images/404-cloud.png
--------------------------------------------------------------------------------
/examples/assets/images/404-images/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/404-images/404.png
--------------------------------------------------------------------------------
/examples/assets/images/blue.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/assets/images/client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/client.png
--------------------------------------------------------------------------------
/examples/assets/images/database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/database.png
--------------------------------------------------------------------------------
/examples/assets/images/firewall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/firewall.png
--------------------------------------------------------------------------------
/examples/assets/images/green.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/assets/images/purple.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/examples/assets/images/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/server.png
--------------------------------------------------------------------------------
/examples/assets/images/topology.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/topology.gif
--------------------------------------------------------------------------------
/examples/assets/images/topology.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/images/topology.png
--------------------------------------------------------------------------------
/examples/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/examples/assets/logo.png
--------------------------------------------------------------------------------
/examples/components/FooterBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
52 |
53 |
98 |
--------------------------------------------------------------------------------
/examples/components/HeaderBar.vue:
--------------------------------------------------------------------------------
1 |
2 |
25 |
26 |
27 |
56 |
57 |
96 |
--------------------------------------------------------------------------------
/examples/components/HelloWorld.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ msg }}
4 |
5 | For a guide and recipes on how to configure / customize this project,
6 | check out the
7 | vue-cli documentation.
10 |
11 |
Installed CLI Plugins
12 |
30 |
Essential Links
31 |
54 |
Ecosystem
55 |
86 |
87 |
88 |
89 |
97 |
98 |
99 |
118 |
--------------------------------------------------------------------------------
/examples/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import router from './router'
3 | import store from './store'
4 | import ElementUI from 'element-ui'
5 |
6 | import '@/styles/index.scss' // global css
7 | import '@/styles/common.scss' // common css
8 |
9 | import App from './App.vue'
10 |
11 | Vue.use(ElementUI)
12 |
13 | /* 全局注册 */
14 |
15 | /* 统一加载: prod */
16 | // import CCEditor from '@chaincloud/cceditor'
17 | // Vue.use(CCEditor)
18 |
19 | /* 只加载CCTopology: prod */
20 | // import { CCTopology } from '@chaincloud/cceditor'
21 | // Vue.use(CCTopology)
22 |
23 | /* 统一加载: dev */
24 | // import CCEditor from '../packages/index'
25 | // Vue.use(CCEditor)
26 |
27 | /* 只加载CCTopology: dev */
28 | // import { CCTopology } from '../packages/index';
29 | // Vue.use(CCTopology);
30 |
31 | Vue.config.productionTip = false
32 |
33 | new Vue({
34 | router,
35 | store,
36 | render: h => h(App)
37 | }).$mount('#app')
38 |
--------------------------------------------------------------------------------
/examples/router.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Router from 'vue-router'
3 |
4 | Vue.use(Router)
5 |
6 | const router = new Router({
7 | mode: 'history',
8 | base: process.env.BASE_URL,
9 | routes: [
10 | {
11 | path: '/',
12 | component: () =>
13 | import('@/views/pages/index.vue'),
14 | redirect: '/home',
15 | children: [
16 | {
17 | path: 'home',
18 | name: 'Home',
19 | component: () =>
20 | import(/* webpackChunkName: "home" */ '@/views/pages/home.vue')
21 | },
22 | {
23 | path: 'about',
24 | name: 'About',
25 | // route level code-splitting
26 | // this generates a separate chunk (about.[hash].js) for this route
27 | // which is lazy-loaded when the route is visited.
28 | component: () =>
29 | import(/* webpackChunkName: "about" */ '@/views/pages/about.vue')
30 | }
31 | ]
32 | },
33 | {
34 | path: '/topology',
35 | name: 'Topology',
36 | component: () =>
37 | import(/* webpackChunkName: "topology" */ '@/views/demos/topology.vue')
38 | },
39 | {
40 | path: '/mobile',
41 | name: 'Mobile',
42 | component: () =>
43 | import(/* webpackChunkName: "mobile" */ '@/views/pages/mobile.vue')
44 | },
45 | {
46 | path: '*',
47 | component: () =>
48 | import(/* webpackChunkName: "home" */ '@/views/error-pages/404.vue')
49 | }
50 | ]
51 | })
52 |
53 | /**
54 | * 验证
55 | */
56 | router.beforeEach((to, from, next) => {
57 | if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {
58 | if (to.path === '/mobile') {
59 | next()
60 | } else {
61 | next('/mobile')
62 | }
63 | } else {
64 | if (to.path === '/mobile') {
65 | next('/home')
66 | } else {
67 | next()
68 | }
69 | }
70 | })
71 |
72 | export default router
73 |
--------------------------------------------------------------------------------
/examples/store.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import Vuex from 'vuex'
3 |
4 | Vue.use(Vuex)
5 |
6 | export default new Vuex.Store({
7 | state: {},
8 | mutations: {},
9 | actions: {}
10 | })
11 |
--------------------------------------------------------------------------------
/examples/styles/common.scss:
--------------------------------------------------------------------------------
1 | .pull-left {
2 | float: left;
3 | }
4 |
5 | .pull-right {
6 | float: right;
7 | }
--------------------------------------------------------------------------------
/examples/styles/index.scss:
--------------------------------------------------------------------------------
1 | @import '../../node_modules/element-ui/lib/theme-chalk/index.css';
2 | @import '../assets/iconfont/iconfont.css';
3 |
4 | /* global css */
5 | body {
6 | height: 100%;
7 | -moz-osx-font-smoothing: grayscale;
8 | -webkit-font-smoothing: antialiased;
9 | text-rendering: optimizeLegibility;
10 | font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
11 | }
12 |
13 | html {
14 | height: 100%;
15 | box-sizing: border-box;
16 | }
17 |
18 | #app {
19 | height: 100%;
20 | /*overflow-x: hidden;*/
21 | /*overflow-y: hidden;*/
22 | }
23 |
24 | * {
25 | padding: 0;
26 | margin: 0;
27 | }
28 |
29 | *,
30 | *:before,
31 | *:after {
32 | box-sizing: inherit;
33 | }
34 |
35 | a:focus,
36 | a:active {
37 | outline: none;
38 | }
39 |
40 | a,
41 | a:focus,
42 | a:hover {
43 | cursor: pointer;
44 | color: inherit;
45 | text-decoration: none;
46 | }
47 |
48 | div:focus {
49 | outline: none;
50 | }
51 |
52 | .clearfix {
53 | &:after {
54 | visibility: hidden;
55 | display: block;
56 | font-size: 0;
57 | content: " ";
58 | clear: both;
59 | height: 0;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/examples/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by wenyuan on 2019/10/14
3 | * Description: common utils
4 | */
5 |
6 | /**
7 | * This is just a simple version of deep copy
8 | * Has a lot of edge cases bug
9 | * If you want to use a perfect deep copy, use lodash's _.cloneDeep
10 | * @param {Object} source
11 | * @returns {Object} targetObj
12 | */
13 | export function deepClone(source) {
14 | if (!source && typeof source !== 'object') {
15 | throw new Error('error arguments: deepClone')
16 | }
17 | let targetObj = source.constructor === Array ? [] : {}
18 | Object.keys(source).forEach(key => {
19 | if (source[key] && typeof source[key] === 'object') {
20 | targetObj[key] = deepClone(source[key])
21 | } else {
22 | targetObj[key] = source[key]
23 | }
24 | })
25 | return targetObj
26 | }
27 |
28 | /**
29 | * Randomly extract one or more elements from an array
30 | * If you want to use a perfect solution, use lodash's _.sample or _.sampleSize
31 | * @param {Array} arr
32 | * @param {number} count
33 | * @returns {Array} arr
34 | */
35 | export function getRandomArrayElements(arr, count = 1) {
36 | if (count > arr.length) {
37 | throw new Error('error arguments: count is greater than length of array')
38 | }
39 | let shuffled = arr.slice(0), i = arr.length, min = i - count, temp, index
40 | while (i-- > min) {
41 | index = Math.floor((i + 1) * Math.random())
42 | temp = shuffled[index]
43 | shuffled[index] = shuffled[i]
44 | shuffled[i] = temp
45 | }
46 | return shuffled.slice(min)
47 | }
48 |
--------------------------------------------------------------------------------
/examples/views/error-pages/404.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
14 |
15 |
OOPS!
16 |
All rights reserved
17 |
wenyuan
18 |
19 |
{{ message }}
20 |
Please check that the URL you entered is correct, or click the button below to return to the homepage.
21 |
Back to home
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
49 |
50 |
271 |
--------------------------------------------------------------------------------
/examples/views/pages/about.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
可视化图形编辑组件库
4 |
5 | CCEditor,一套基于 G6 和 Vue2.5 的可视化图形编辑组件库。
6 | 提供了一系列图可视化解决方案,适用于对图有
7 |
8 | 节点和连线的拖拽等鼠标键盘事件,
节点和连线的动态参数配置和数据绑定等。
9 | 复杂交互需求
10 |
11 | 的场景。
12 | 简单、易用,在开箱即用的基础上,可以很方便的进行 业务定制 和 功能扩展。
13 |
14 |
15 |
![]()
16 |
17 |
18 |
19 |
20 |
32 |
33 |
65 |
66 |
67 |
86 |
--------------------------------------------------------------------------------
/examples/views/pages/home.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
9 |
10 |
15 |
16 | {{ demo.title }}
17 |
18 |
19 | Developer: {{ demo.developer }}
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
50 |
51 |
82 |
--------------------------------------------------------------------------------
/examples/views/pages/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
28 |
29 |
60 |
--------------------------------------------------------------------------------
/examples/views/pages/mobile.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |

7 |
CCEditor
8 |
9 |
10 |
11 |
12 |
13 |
15 |
16 |
21 |
22 | {{ demo.title }}
23 |
24 |
25 | Developer: {{ demo.developer }}
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | -
35 | CCEditor 0.1.0
36 |
37 | -
38 | |
39 |
40 | -
41 | ChainCloud FE
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
83 |
84 |
172 |
--------------------------------------------------------------------------------
/examples/views/pages/test.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Header
5 |
6 | Aside
7 |
8 | 默认
9 | 添加节点
10 | 添加连线
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
144 |
145 |
180 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@chaincloud/cceditor",
3 | "version": "0.1.0",
4 | "description": "A visual graph editor based on G6 and Vue.js.",
5 | "author": "wenyuan ",
6 | "main": "lib/cceditor.common.js",
7 | "keywords": [
8 | "vue",
9 | "graph",
10 | "graph editor",
11 | "topo",
12 | "topology"
13 | ],
14 | "license": "MIT",
15 | "private": true,
16 | "scripts": {
17 | "serve": "vue-cli-service serve",
18 | "build": "vue-cli-service build",
19 | "lint": "vue-cli-service lint",
20 | "lib": "vue-cli-service build --target lib --name cceditor --dest lib packages/index.js"
21 | },
22 | "dependencies": {
23 | "@antv/g6": "^4.5.0",
24 | "core-js": "^2.6.5",
25 | "element-ui": "^2.14.1",
26 | "lodash": "^4.17.15",
27 | "moment": "^2.27.0",
28 | "vue": "^2.6.10",
29 | "vue-router": "^3.0.3",
30 | "vuex": "^3.0.1"
31 | },
32 | "devDependencies": {
33 | "@vue/cli-plugin-babel": "^3.9.0",
34 | "@vue/cli-plugin-eslint": "^3.9.0",
35 | "@vue/cli-service": "^3.9.0",
36 | "@vue/eslint-config-prettier": "^4.0.1",
37 | "babel-eslint": "^10.0.1",
38 | "eslint": "^5.16.0",
39 | "eslint-plugin-vue": "^5.0.0",
40 | "sass": "^1.18.0",
41 | "sass-loader": "^7.1.0",
42 | "vue-template-compiler": "^2.6.10"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.css:
--------------------------------------------------------------------------------
1 | @font-face {font-family: "iconfont";
2 | src: url('iconfont.eot?t=1568972645985'); /* IE9 */
3 | src: url('iconfont.eot?t=1568972645985#iefix') format('embedded-opentype'), /* IE6-IE8 */
4 | url('data:application/x-font-woff2;charset=utf-8;base64,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') format('woff2'),
5 | url('iconfont.woff?t=1568972645985') format('woff'),
6 | url('iconfont.ttf?t=1568972645985') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
7 | url('iconfont.svg?t=1568972645985#iconfont') format('svg'); /* iOS 4.1- */
8 | }
9 |
10 | .iconfont {
11 | font-family: "iconfont" !important;
12 | font-size: 16px;
13 | font-style: normal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | .icon-download:before {
19 | content: "\e68d";
20 | }
21 |
22 | .icon-zoom-out:before {
23 | content: "\e69c";
24 | }
25 |
26 | .icon-image:before {
27 | content: "\e752";
28 | }
29 |
30 | .icon-iconedit:before {
31 | content: "\e650";
32 | }
33 |
34 | .icon-actualsize:before {
35 | content: "\e665";
36 | }
37 |
38 | .icon-copy:before {
39 | content: "\e61c";
40 | }
41 |
42 | .icon-zoom-in:before {
43 | content: "\e600";
44 | }
45 |
46 | .icon-clear:before {
47 | content: "\e700";
48 | }
49 |
50 | .icon-flow-line:before {
51 | content: "\e660";
52 | }
53 |
54 | .icon-redo:before {
55 | content: "\e716";
56 | }
57 |
58 | .icon-undo:before {
59 | content: "\e71a";
60 | }
61 |
62 | .icon-fit:before {
63 | content: "\e7cb";
64 | }
65 |
66 | .icon-to-front:before {
67 | content: "\e7cc";
68 | }
69 |
70 | .icon-to-back:before {
71 | content: "\e7cd";
72 | }
73 |
74 | .icon-roi-select:before {
75 | content: "\e7ce";
76 | }
77 |
78 | .icon-json:before {
79 | content: "\e623";
80 | }
81 |
82 | .icon-fullscreen:before {
83 | content: "\e648";
84 | }
85 |
86 | .icon-broken:before {
87 | content: "\e9ad";
88 | }
89 |
90 | .icon-curve:before {
91 | content: "\e9b0";
92 | }
93 |
94 | .icon-paste:before {
95 | content: "\e963";
96 | }
97 |
98 | .icon-group:before {
99 | content: "\e915";
100 | }
101 |
102 | .icon-ungroup:before {
103 | content: "\e917";
104 | }
105 |
106 | .icon-arrow-dropdown:before {
107 | content: "\e601";
108 | }
109 |
110 |
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.eot
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.js:
--------------------------------------------------------------------------------
1 | !function(i){var c,o='',h=(c=document.getElementsByTagName("script"))[c.length-1].getAttribute("data-injectcss");if(h&&!i.__iconfont__svg__cssinject__){i.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(c){console&&console.log(c)}}!function(c){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(c,0);else{var h=function(){document.removeEventListener("DOMContentLoaded",h,!1),c()};document.addEventListener("DOMContentLoaded",h,!1)}else document.attachEvent&&(t=c,l=i.document,e=!1,(v=function(){try{l.documentElement.doScroll("left")}catch(c){return void setTimeout(v,50)}o()})(),l.onreadystatechange=function(){"complete"==l.readyState&&(l.onreadystatechange=null,o())});function o(){e||(e=!0,t())}var t,l,e,v}(function(){var c,h;(c=document.createElement("div")).innerHTML=o,o=null,(h=c.getElementsByTagName("svg")[0])&&(h.setAttribute("aria-hidden","true"),h.style.position="absolute",h.style.width=0,h.style.height=0,h.style.overflow="hidden",function(c,h){h.firstChild?function(c,h){h.parentNode.insertBefore(c,h)}(c,h.firstChild):h.appendChild(c)}(h,document.body))})}(window);
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
96 |
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.ttf
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.woff
--------------------------------------------------------------------------------
/packages/assets/iconfont/iconfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/iconfont/iconfont.woff2
--------------------------------------------------------------------------------
/packages/assets/images/client.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/client.png
--------------------------------------------------------------------------------
/packages/assets/images/database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/database.png
--------------------------------------------------------------------------------
/packages/assets/images/firewall.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/firewall.png
--------------------------------------------------------------------------------
/packages/assets/images/server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/images/server.png
--------------------------------------------------------------------------------
/packages/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/packages/assets/logo.png
--------------------------------------------------------------------------------
/packages/cc-elements/button.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
55 |
56 |
94 |
--------------------------------------------------------------------------------
/packages/cc-elements/checkbox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
14 |
29 |
30 |
80 |
--------------------------------------------------------------------------------
/packages/cc-elements/dropdown.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
72 |
73 |
185 |
--------------------------------------------------------------------------------
/packages/cc-elements/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/11/14
4 | * @repository: https://github.com/wenyuan
5 | * @description: cceditor内部的通用组件
6 | */
7 |
8 | import Checkbox from './checkbox'
9 | import Button from './button'
10 | import Dropdown from './dropdown'
11 | import Loading from './loading'
12 |
13 | export {
14 | Checkbox,
15 | Button,
16 | Dropdown,
17 | Loading
18 | }
19 |
--------------------------------------------------------------------------------
/packages/cc-elements/loading.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 |
8 |
{{ loadingText }}
9 |
10 |
11 |
12 |
23 |
24 |
93 |
--------------------------------------------------------------------------------
/packages/cc-topology/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/20
4 | * @repository: https://github.com/wenyuan
5 | * @description: 导入组件,组件必须声明 name
6 | */
7 |
8 | import CCTopology from './src/cc-topology'
9 |
10 | // 为组件提供 install 安装方法,供按需引入
11 | CCTopology.install = function(Vue) {
12 | console.info('install----CCEditor: CCTopology----')
13 | Vue.component(CCTopology.name, CCTopology)
14 | }
15 |
16 | // 默认导出组件
17 | export default CCTopology
18 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/click-add-edge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 通过先后点击两个节点来添加连线(容易和节点点击动作交叉,已弃用)
6 | */
7 |
8 | import G6 from '@antv/g6'
9 | import theme from '../theme'
10 |
11 | export default {
12 | name: 'click-add-edge',
13 | options: {
14 | getEvents() {
15 | return {
16 | 'node:click': 'onNodeClick',
17 | 'canvas:mousemove': 'onMousemove',
18 | 'edge:click': 'onEdgeClick' // 点击空白处,取消边
19 | }
20 | },
21 | onNodeClick(event) {
22 | let graph = this.graph
23 | let node = event.item
24 | let point = { x: event.x, y: event.y }
25 | let model = node.getModel()
26 | let edgeShape = self.currentEdgeShape.guid || 'line'
27 | if (this.addingEdge && this.edge) {
28 | // 点击第二个节点
29 | graph.updateItem(this.edge, {
30 | target: model.id
31 | })
32 | this.edge = null
33 | this.addingEdge = false
34 | // 记录【连线】前后的数据状态
35 | if (this.historyData) {
36 | let graph = this.graph
37 | // 如果当前点过【撤销】了,连线后没有【重做】功能
38 | // 重置undoCount,连线后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
39 | if (self.undoCount > 0) {
40 | self.historyIndex = self.historyIndex - self.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
41 | for (let i = 1; i <= self.undoCount; i++) {
42 | let key = `graph_history_${self.historyIndex + i}`
43 | self.removeHistoryData(key)
44 | }
45 | self.undoCount = 0
46 | } else {
47 | // 正常顺序执行的情况,记录【连线】前的数据状态
48 | let key = `graph_history_${self.historyIndex}`
49 | self.addHistoryData(key, this.historyData)
50 | }
51 | // 记录【连线】后的数据状态
52 | self.historyIndex += 1
53 | let key = `graph_history_${self.historyIndex}`
54 | let currentData = JSON.stringify(graph.save())
55 | self.addHistoryData(key, currentData)
56 | }
57 | } else {
58 | // 点击第一个节点
59 | this.historyData = JSON.stringify(graph.save())
60 | if (edgeShape === 'stepline') {
61 | this.edge = graph.addItem('edge', {
62 | source: model.id,
63 | target: point,
64 | type: edgeShape,
65 | controlPoints: [{ x: 100, y: 70 }]
66 | })
67 | } else {
68 | this.edge = graph.addItem('edge', {
69 | source: model.id,
70 | target: point,
71 | type: edgeShape
72 | })
73 | }
74 | this.addingEdge = true
75 | }
76 | },
77 | onMousemove(event) {
78 | const point = { x: event.x, y: event.y }
79 | if (this.addingEdge && this.edge) {
80 | this.graph.updateItem(this.edge, {
81 | target: point
82 | })
83 | }
84 | },
85 | onEdgeClick(ev) {
86 | let graph = this.graph
87 | const currentEdge = ev.item
88 | // 拖拽过程中,点击会点击到新增的边上
89 | if (this.addingEdge && this.edge === currentEdge) {
90 | graph.removeItem(this.edge)
91 | this.edge = null
92 | this.addingEdge = false
93 | }
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/click-event-edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 鼠标点击交互
6 | */
7 |
8 | // 用来获取调用此js的vue组件实例(this)
9 | let vm = null
10 |
11 | const sendThis = (_this) => {
12 | vm = _this
13 | }
14 |
15 | export default {
16 | sendThis, // 暴露函数
17 | name: 'click-event-edit',
18 | options: {
19 | getEvents () {
20 | return {
21 | 'node:click': 'onNodeClick',
22 | 'node:contextmenu': 'onNodeRightClick',
23 | 'edge:click': 'onEdgeClick',
24 | 'edge:contextmenu': 'onEdgeRightClick',
25 | 'combo:click': 'onComboClick',
26 | 'combo:contextmenu': 'onComboRightClick',
27 | 'canvas:click': 'onCanvasClick'
28 | }
29 | },
30 | onNodeClick (event) {
31 | // todo..."selected"是g6自带的状态,在"drag-add-edge"中的"node:mouseup"事件也会触发,故此处不需要设置"selected"状态
32 | // let clickNode = event.item;
33 | // clickNode.setState('selected', !clickNode.hasState('selected'));
34 | vm.currentFocus = 'node'
35 | vm.rightMenuShow = false
36 | this.updateVmData(event)
37 | },
38 | onNodeRightClick (event) {
39 | let graph = vm.graph
40 | let clickNode = event.item
41 | let clickNodeModel = clickNode.getModel()
42 | let selectedNodes = graph.findAllByState('node', 'selected')
43 | let selectedNodeIds = selectedNodes.map(item => {
44 | return item.getModel().id
45 | })
46 | vm.selectedNode = clickNode
47 | // 如果当前点击节点是之前选中的某个节点,就进行下面的处理
48 | if (selectedNodes.length > 1 && selectedNodeIds.indexOf(clickNodeModel.id) > -1) {
49 | vm.rightMenuShow = true
50 | let rightMenu = vm.$refs.rightMenu
51 | rightMenu.style.left = event.clientX + 'px'
52 | rightMenu.style.top = event.clientY + 'px'
53 | } else {
54 | // 隐藏右键菜单
55 | vm.rightMenuShow = false
56 | // 先取消所有节点的选中状态
57 | selectedNodes.forEach(node => {
58 | node.setState('selected', false)
59 | })
60 | // 再添加该节点的选中状态
61 | clickNode.setState('selected', true)
62 | vm.currentFocus = 'node'
63 | this.updateVmData(event)
64 | }
65 | graph.paint()
66 | },
67 | onEdgeClick (event) {
68 | let clickEdge = event.item
69 | clickEdge.setState('selected', !clickEdge.hasState('selected'))
70 | vm.currentFocus = 'edge'
71 | this.updateVmData(event)
72 | },
73 | onEdgeRightClick (event) {
74 | let graph = vm.graph
75 | let clickEdge = event.item
76 | let clickEdgeModel = clickEdge.getModel()
77 | let selectedEdges = graph.findAllByState('edge', 'selected')
78 | // 如果当前点击节点不是之前选中的单个节点,才进行下面的处理
79 | if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) {
80 | // 先取消所有节点的选中状态
81 | graph.findAllByState('edge', 'selected').forEach(edge => {
82 | edge.setState('selected', false)
83 | })
84 | // 再添加该节点的选中状态
85 | clickEdge.setState('selected', true)
86 | vm.currentFocus = 'edge'
87 | this.updateVmData(event)
88 | }
89 | // let point = { x: event.x, y: event.y }
90 | },
91 | onComboClick (event) {
92 | vm.currentFocus = 'combo'
93 | this.updateVmData(event)
94 | },
95 | onComboRightClick (event) {
96 | vm.rightMenuShow = true
97 | let rightMenu = vm.$refs.rightMenu
98 | rightMenu.style.left = event.clientX + 'px'
99 | rightMenu.style.top = event.clientY + 'px'
100 | vm.currentFocus = 'combo'
101 | this.updateVmData(event)
102 | },
103 | onCanvasClick () {
104 | vm.currentFocus = 'canvas'
105 | vm.rightMenuShow = false
106 | },
107 | updateVmData (event) {
108 | if (event.item._cfg.type === 'node') {
109 | // 更新vm的data: selectedNode 和 selectedNodeParams
110 | let clickNode = event.item
111 | if (clickNode.hasState('selected')) {
112 | let clickNodeModel = clickNode.getModel()
113 | vm.selectedNode = clickNode
114 | let nodeAppConfig = { ...vm.nodeAppConfig }
115 | Object.keys(nodeAppConfig).forEach(function (key) {
116 | nodeAppConfig[key] = ''
117 | })
118 | vm.selectedNodeParams = {
119 | label: clickNodeModel.label || '',
120 | appConfig: { ...nodeAppConfig, ...clickNodeModel.appConfig }
121 | }
122 | }
123 | } else if (event.item._cfg.type === 'edge') {
124 | // 更新vm的data: selectedEdge 和 selectedEdgeParams
125 | let clickEdge = event.item
126 | if (clickEdge.hasState('selected')) {
127 | let clickEdgeModel = clickEdge.getModel()
128 | vm.selectedEdge = clickEdge
129 | let edgeAppConfig = { ...vm.edgeAppConfig }
130 | Object.keys(edgeAppConfig).forEach(function (key) {
131 | edgeAppConfig[key] = ''
132 | })
133 | vm.selectedEdgeParams = {
134 | label: clickEdgeModel.label || '',
135 | appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig }
136 | }
137 | }
138 | } else if (event.item._cfg.type === 'combo') {
139 | // 更新vm的data: selectedCombo 和 selectedComboParams
140 | let clickCombo = event.item
141 | let clickComboModel = clickCombo.getModel()
142 | vm.selectedCombo = clickCombo
143 | vm.selectedComboParams = {
144 | label: clickComboModel.label || '',
145 | labelPosition: clickComboModel.labelCfg.position || '',
146 | labelRefX: clickComboModel.labelCfg.refX || 0,
147 | labelRefY: clickComboModel.labelCfg.refY || 0,
148 | type: clickComboModel.type || ''
149 | }
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/drag-add-edge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 通过拖拽节点上的锚点添加连线
6 | */
7 | import { mix } from '@antv/util'
8 | import utils from '../utils'
9 | import theme from '../theme'
10 |
11 | // 用来获取调用此js的vue组件实例(this)
12 | let vm = null
13 |
14 | const sendThis = (_this) => {
15 | vm = _this
16 | }
17 |
18 | export default {
19 | sendThis, // 暴露函数
20 | name: 'drag-add-edge',
21 | options: {
22 | getEvents () {
23 | return {
24 | 'node:mousedown': 'onNodeMousedown',
25 | 'node:mouseup': 'onNodeMouseup',
26 | 'edge:mouseup': 'onEdgeMouseup',
27 | 'mousemove': 'onMousemove'
28 | }
29 | },
30 | onNodeMousedown (event) {
31 | let self = this
32 | // 交互过程中的信息
33 | self.evtInfo = {
34 | action: null,
35 | node: event.item,
36 | target: event.target
37 | }
38 | if (self.evtInfo.target && self.evtInfo.target.attrs.name) {
39 | // todo...未来可能针对锚点增加其它功能(例如拖拽调整大小)
40 | switch (self.evtInfo.target.attrs.name) {
41 | case 'anchor':
42 | self.evtInfo.action = 'drawEdge'
43 | break
44 | }
45 | }
46 | if (self.evtInfo && self.evtInfo.action) {
47 | self[self.evtInfo.action].start.call(self, event)
48 | }
49 | },
50 | onNodeMouseup (event) {
51 | let self = this
52 | if (self.evtInfo && self.evtInfo.action) {
53 | self[self.evtInfo.action].stop.call(self, event)
54 | }
55 | },
56 | onEdgeMouseup (event) {
57 | let self = this
58 | if (self.evtInfo && self.evtInfo.action === 'drawEdge') {
59 | self[self.evtInfo.action].stop.call(self, event)
60 | }
61 | },
62 | onMousemove (event) {
63 | let self = this
64 | if (self.evtInfo && self.evtInfo.action) {
65 | self[self.evtInfo.action].move.call(self, event)
66 | }
67 | },
68 | drawEdge: {
69 | isMoving: false,
70 | currentLine: null,
71 | start: function (event) {
72 | let self = this
73 | let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
74 |
75 | // ************** 暂存【连线】前的数据状态 start **************
76 | let graph = vm.graph
77 | self.historyData = JSON.stringify(graph.save())
78 | // ************** 暂存【连线】前的数据状态 end **************
79 |
80 | let sourceAnchor
81 | let sourceNodeModel = self.evtInfo.node.getModel()
82 | // 锚点数据
83 | let anchorPoints = self.evtInfo.node.getAnchorPoints()
84 | // 处理线条目标点
85 | if (anchorPoints && anchorPoints.length) {
86 | // 获取距离指定坐标最近的一个锚点
87 | sourceAnchor = self.evtInfo.node.getLinkPoint({
88 | x: event.x,
89 | y: event.y
90 | })
91 | }
92 | self.drawEdge.currentLine = self.graph.addItem('edge', {
93 | // id: G6.Util.uniqueId(), // 这种生成id的方式有bug,会重复
94 | id: utils.generateUUID(),
95 | // 起始节点
96 | source: sourceNodeModel.id,
97 | sourceAnchor: sourceAnchor ? sourceAnchor.anchorIndex : '',
98 | // 终止节点/位置
99 | target: {
100 | x: event.x,
101 | y: event.y
102 | },
103 | type: self.graph.$C.edge.type || 'cc-line',
104 | style: mix({}, themeStyle.edgeStyle.default, self.graph.$C.edge.style)
105 | })
106 | self.drawEdge.isMoving = true
107 | },
108 | move (event) {
109 | let self = this
110 | if (self.drawEdge.isMoving && self.drawEdge.currentLine) {
111 | self.graph.updateItem(self.drawEdge.currentLine, {
112 | target: {
113 | x: event.x,
114 | y: event.y
115 | }
116 | })
117 | }
118 | },
119 | stop (event) {
120 | let self = this
121 | if (self.drawEdge.isMoving) {
122 | if (self.drawEdge.currentLine === event.item) {
123 | // 画线过程中点击则移除当前画线
124 | self.graph.removeItem(event.item)
125 | } else {
126 | let targetNode = event.item
127 | let targetNodeModel = targetNode.getModel()
128 | let targetAnchor = null
129 | // 锚点数据
130 | let anchorPoints = targetNode.getAnchorPoints()
131 | // 处理线条目标点
132 | if (anchorPoints && anchorPoints.length) {
133 | // 获取距离指定坐标最近的一个锚点
134 | targetAnchor = targetNode.getLinkPoint({ x: event.x, y: event.y })
135 | }
136 | self.graph.updateItem(self.drawEdge.currentLine, {
137 | target: targetNodeModel.id,
138 | targetAnchor: targetAnchor ? targetAnchor.anchorIndex : ''
139 | })
140 |
141 | // ************** 记录historyData的逻辑 start **************
142 | if (this.historyData) {
143 | let graph = this.graph
144 | // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能
145 | // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
146 | if (vm.undoCount > 0) {
147 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
148 | for (let i = 1; i <= vm.undoCount; i++) {
149 | let key = `graph_history_${vm.historyIndex + i}`
150 | vm.removeHistoryData(key)
151 | }
152 | vm.undoCount = 0
153 | } else {
154 | // 正常顺序执行的情况,记录拖拽前的数据状态
155 | let key = `graph_history_${vm.historyIndex}`
156 | vm.addHistoryData(key, this.historyData)
157 | }
158 | // 记录拖拽后的数据状态
159 | vm.historyIndex += 1
160 | let key = `graph_history_${vm.historyIndex}`
161 | let currentData = JSON.stringify(graph.save())
162 | vm.addHistoryData(key, currentData)
163 | }
164 | // ************** 记录historyData的逻辑 end **************
165 | }
166 | }
167 | self.drawEdge.currentLine = null
168 | self.drawEdge.isMoving = false
169 | self.evtInfo = null
170 | }
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/drag-event-edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 鼠标拖动节点的交互(记录拖拽前后的数据,用于【撤销】和【重做】)
6 | */
7 |
8 | // 用来获取调用此js的vue组件实例(this)
9 | let vm = null
10 |
11 | const sendThis = (_this) => {
12 | vm = _this
13 | }
14 |
15 | export default {
16 | sendThis, // 暴露函数
17 | name: 'drag-event-edit',
18 | options: {
19 | getEvents () {
20 | return {
21 | 'node:dragstart': 'onNodeDragstart',
22 | 'node:dragend': 'onNodeDragend'
23 | }
24 | },
25 | onNodeDragstart () {
26 | let graph = vm.graph
27 | this.historyData = JSON.stringify(graph.save())
28 | },
29 | onNodeDragend () {
30 | if (this.historyData) {
31 | let graph = this.graph
32 | // 如果当前点过【撤销】了,拖拽节点后没有【重做】功能
33 | // 重置undoCount,拖拽后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
34 | if (vm.undoCount > 0) {
35 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
36 | for (let i = 1; i <= vm.undoCount; i++) {
37 | let key = `graph_history_${vm.historyIndex + i}`
38 | vm.removeHistoryData(key)
39 | }
40 | vm.undoCount = 0
41 | } else {
42 | // 正常顺序执行的情况,记录拖拽前的数据状态
43 | let key = `graph_history_${vm.historyIndex}`
44 | vm.addHistoryData(key, this.historyData)
45 | }
46 | // 记录拖拽后的数据状态
47 | vm.historyIndex += 1
48 | let key = `graph_history_${vm.historyIndex}`
49 | let currentData = JSON.stringify(graph.save())
50 | vm.addHistoryData(key, currentData)
51 | }
52 | }
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/hover-event-edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 悬浮交互
6 | */
7 |
8 | export default {
9 | name: 'hover-event-edit',
10 | options: {
11 | getEvents () {
12 | return {
13 | 'node:mouseover': 'onNodeHover',
14 | 'node:mouseout': 'onNodeOut',
15 | 'combo:mouseover': 'onComboHover',
16 | 'combo:mouseout': 'onComboOut'
17 | }
18 | },
19 | onNodeHover (event) {
20 | let hoverNode = event.item
21 | hoverNode.setState('hover', true)
22 | },
23 | onNodeOut (event) {
24 | let hoverNode = event.item
25 | hoverNode.setState('hover', false)
26 | },
27 | onComboHover (event) {
28 | let hoverCombo = event.item
29 | hoverCombo.setState('hover', true)
30 | },
31 | onComboOut (event) {
32 | let hoverCombo = event.item
33 | hoverCombo.setState('hover', false)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: register behaviors
6 | */
7 |
8 | import dragAddEdge from './drag-add-edge'
9 | import hoverEventEdit from './hover-event-edit'
10 | import clickEventEdit from './click-event-edit'
11 | import dragEventEdit from './drag-event-edit'
12 | import keyupEventEdit from './keyup-event-edit'
13 |
14 | const obj = {
15 | dragAddEdge,
16 | hoverEventEdit,
17 | clickEventEdit,
18 | dragEventEdit,
19 | keyupEventEdit
20 | }
21 |
22 | export default {
23 | obj,
24 | register(G6) {
25 | Object.values(obj).map(item => {
26 | G6.registerBehavior(item.name, item.options)
27 | })
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/behavior/keyup-event-edit.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: edit mode: 键盘事件的交互,主要是删除节点和连线(记录删除前后的数据,用于【撤销】和【重做】)
6 | */
7 |
8 | // 用来获取调用此js的vue组件实例(this)
9 | let vm = null
10 |
11 | const sendThis = (_this) => {
12 | vm = _this
13 | }
14 |
15 | export default {
16 | sendThis, // 暴露函数
17 | name: 'keyup-event-edit',
18 | options: {
19 | getEvents () {
20 | return {
21 | 'keyup': 'onKeyup'
22 | }
23 | },
24 | onKeyup (event) {
25 | let graph = this.graph
26 | let selectedNodes = graph.findAllByState('node', 'selected')
27 | let selectedEdges = graph.findAllByState('edge', 'selected')
28 | if (event.keyCode === 46 && (selectedNodes.length > 0 || selectedEdges.length > 0)) {
29 | // ************** 记录【删除】前的数据状态 start **************
30 | let historyData = JSON.stringify(graph.save())
31 | let key = `graph_history_${vm.historyIndex}`
32 | vm.addHistoryData(key, historyData)
33 | // ************** 记录【删除】前的数据状态 end **************
34 | // 开始删除
35 | for (let i = 0; i < selectedNodes.length; i++) {
36 | graph.removeItem(selectedNodes[i])
37 | }
38 | for (let i = 0; i < selectedEdges.length; i++) {
39 | graph.removeItem(selectedEdges[i])
40 | }
41 | // ************** 记录【删除】后的数据状态 start **************
42 | // 如果当前点过【撤销】了,拖拽节点后将取消【重做】功能
43 | // 重置undoCount,【删除】后的数据状态给(当前所在historyIndex + 1),且清空这个时间点之后的记录
44 | if (vm.undoCount > 0) {
45 | vm.historyIndex = vm.historyIndex - vm.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
46 | for (let i = 1; i <= vm.undoCount; i++) {
47 | let key = `graph_history_${vm.historyIndex + i}`
48 | vm.removeHistoryData(key)
49 | }
50 | vm.undoCount = 0
51 | }
52 | // 记录【删除】后的数据状态
53 | vm.historyIndex += 1
54 | key = `graph_history_${vm.historyIndex}`
55 | let currentData = JSON.stringify(graph.save())
56 | vm.addHistoryData(key, currentData)
57 | // ************** 记录【删除】后的数据状态 end **************
58 | }
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/config/edge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: 线条的后期设置
6 | */
7 |
8 | export default {
9 | type: 'cc-line',
10 | style: {
11 | startArrow: false,
12 | endArrow: false
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/config/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/16
4 | * @repository: https://github.com/wenyuan
5 | * @description: 配置
6 | */
7 |
8 | import edge from './edge'
9 |
10 | export default {
11 | edge
12 | }
13 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/18
4 | * @repository: https://github.com/wenyuan
5 | * @description: 线公共方法
6 | */
7 |
8 | import utils from '../utils'
9 |
10 | export default {
11 | drawShape(cfg, group) {
12 | const { startPoint, endPoint } = cfg
13 | let style = cfg.style
14 | // combo 收缩后的连线样式
15 | style.stroke = '#A3B1BF'
16 | style.lineWidth = 2
17 | const keyShape = group.addShape('path', {
18 | className: 'edge-shape',
19 | attrs: {
20 | ...style,
21 | path: [
22 | ['M', startPoint.x, startPoint.y],
23 | ['L', endPoint.x, endPoint.y]
24 | ]
25 | },
26 | name: 'edge-shape'
27 | })
28 | return keyShape
29 | },
30 | setState(name, value, item) {
31 | // 设置边状态
32 | utils.edge.setState(name, value, item)
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/cc-brokenline.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/10/22
4 | * @repository: https://github.com/wenyuan
5 | * @description: 折线
6 | */
7 |
8 | import base from './base'
9 | import theme from '../theme'
10 |
11 | /**
12 | * fix: 继承 polyline 在 G6 3.x 里面有bug
13 | * 现实现方法参考 https://g6.antv.vision/zh/examples/shape/customEdge#customPolyline
14 | */
15 | export default {
16 | name: 'cc-brokenline',
17 | extendName: 'line',
18 | options: {
19 | ...base,
20 | getPath(points) {
21 | const startPoint = points[0]
22 | const endPoint = points[1]
23 | return [
24 | ['M', startPoint.x, startPoint.y],
25 | ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, startPoint.y],
26 | ['L', endPoint.x / 3 + 2 / 3 * startPoint.x, endPoint.y],
27 | ['L', endPoint.x, endPoint.y]
28 | ]
29 | },
30 | getShapeStyle(cfg) {
31 | const { startPoint, endPoint } = cfg
32 | const controlPoints = this.getControlPoints(cfg)
33 | let points = [startPoint] // 添加起始点
34 | // 添加控制点
35 | if (controlPoints) {
36 | points = points.concat(controlPoints)
37 | }
38 | // 添加结束点
39 | points.push(endPoint)
40 | const path = this.getPath(points)
41 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
42 | const style = {
43 | stroke: '#BBB',
44 | lineWidth: 1,
45 | path,
46 | startArrow: false,
47 | endArrow: false,
48 | ...themeStyle.edgeStyle.default
49 | }
50 | return style
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/cc-cubic.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/18
4 | * @repository: https://github.com/wenyuan
5 | * @description: 曲线
6 | */
7 |
8 | import base from './base'
9 |
10 | export default {
11 | name: 'cc-cubic',
12 | extendName: 'cubic',
13 | options: {
14 | ...base
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/cc-line.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/18
4 | * @repository: https://github.com/wenyuan
5 | * @description: 直线
6 | */
7 |
8 | import base from './base'
9 |
10 | export default {
11 | name: 'cc-line',
12 | extendName: 'line',
13 | options: {
14 | ...base
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/cc-polyline.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/18
4 | * @repository: https://github.com/wenyuan
5 | * @description: 多段线
6 | */
7 |
8 | import base from './base'
9 | import { polylineFinding } from './polyline-finding'
10 |
11 | export default {
12 | name: 'cc-polyline',
13 | extendName: 'single-edge',
14 | options: {
15 | ...base,
16 | drawShape(cfg, group) {
17 | const { startPoint, endPoint } = cfg
18 | const controlPoints = this.getControlPoints(cfg)
19 | let points = [startPoint]
20 | if (controlPoints) {
21 | points.push(controlPoints)
22 | }
23 | points.push(endPoint)
24 | let path = this.getPath(points)
25 | const keyShape = group.addShape('path', {
26 | className: 'edge-shape',
27 | attrs: {
28 | ...cfg,
29 | path: path
30 | },
31 | draggable: true,
32 | name: 'edge-shape'
33 | })
34 | return keyShape
35 | },
36 | getPath(points) {
37 | const path = []
38 | for (let i = 0; i < points.length; i++) {
39 | const point = points[i]
40 | if (i === 0) {
41 | path.push(['M', point.x, point.y])
42 | } else if (i === points.length - 1) {
43 | path.push(['L', point.x, point.y])
44 | } else {
45 | const prevPoint = points[i - 1]
46 | let nextPoint = points[i + 1]
47 | let cornerLen = 5
48 | if (Math.abs(point.y - prevPoint.y) > cornerLen || Math.abs(point.x - prevPoint.x) > cornerLen) {
49 | if (prevPoint.x === point.x) {
50 | path.push(['L', point.x, point.y > prevPoint.y ? point.y - cornerLen : point.y + cornerLen])
51 | } else if (prevPoint.y === point.y) {
52 | path.push(['L', point.x > prevPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
53 | }
54 | }
55 | const yLen = Math.abs(point.y - nextPoint.y)
56 | const xLen = Math.abs(point.x - nextPoint.x)
57 | if (yLen > 0 && yLen < cornerLen) {
58 | cornerLen = yLen
59 | } else if (xLen > 0 && xLen < cornerLen) {
60 | cornerLen = xLen
61 | }
62 | if (prevPoint.x !== nextPoint.x && nextPoint.x === point.x) {
63 | path.push(['Q', point.x, point.y, point.x, point.y > nextPoint.y ? point.y - cornerLen : point.y + cornerLen])
64 | } else if (prevPoint.y !== nextPoint.y && nextPoint.y === point.y) {
65 | path.push(['Q', point.x, point.y, point.x > nextPoint.x ? point.x - cornerLen : point.x + cornerLen, point.y])
66 | }
67 | }
68 | }
69 | return path
70 | },
71 | getControlPoints(cfg) {
72 | if (!cfg.sourceNode) {
73 | return cfg.controlPoints
74 | }
75 | return polylineFinding(cfg.sourceNode, cfg.targetNode, cfg.startPoint, cfg.endPoint, 40)
76 | }
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/18
4 | * @repository: https://github.com/wenyuan
5 | * @description: register edges
6 | */
7 |
8 | import ccLine from './cc-line'
9 | import ccBrokenline from './cc-brokenline'
10 | import ccPolyline from './cc-polyline'
11 | import ccCubic from './cc-cubic'
12 |
13 | const obj = {
14 | ccLine,
15 | ccBrokenline,
16 | ccPolyline,
17 | ccCubic
18 | }
19 |
20 | export default function(G6) {
21 | Object.values(obj).map(item => {
22 | G6.registerEdge(item.name, item.options, item.extendName)
23 | })
24 | }
25 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/edge/polyline-finding.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 带圆角折线连线的策略
3 | * 文档:https://www.yuque.com/antv/blog/eyi70n
4 | * 参考:https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js
5 | * 引用:https://github.com/OXOYO/X-Flowchart-Vue/blob/master/src/global/lib/g6/edge/polylineFinding.js
6 | */
7 |
8 | // 折线寻径
9 | export const polylineFinding = function(sNode, tNode, sPort, tPort, offset = 10) {
10 | const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort)
11 | const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort)
12 | // 获取节点带 offset 的区域(扩展区域)
13 | const sBBox = getExpandedBBox(sourceBBox, offset)
14 | const tBBox = getExpandedBBox(targetBBox, offset)
15 | // 获取扩展区域上的起始和终止连接点
16 | const sPoint = getExpandedPort(sBBox, sPort)
17 | const tPoint = getExpandedPort(tBBox, tPort)
18 | // 获取合法折点集
19 | let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint)
20 | // 过滤合法点集,预处理、剪枝等
21 | filterConnectablePoints(points, sBBox)
22 | // 过滤合法点集,预处理、剪枝等
23 | filterConnectablePoints(points, tBBox)
24 | // 用 A-Star 算法寻径
25 | let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox)
26 | return polylinePoints
27 | }
28 |
29 | const getPointBBox = function(t) {
30 | return {
31 | centerX: t.x,
32 | centerY: t.y,
33 | minX: t.x,
34 | minY: t.y,
35 | maxX: t.x,
36 | maxY: t.y,
37 | height: 0,
38 | width: 0
39 | }
40 | }
41 |
42 | // 获取扩展区域
43 | const getExpandedBBox = function(bbox, offset) {
44 | if (bbox.width === 0 && bbox.height === 0) {
45 | return bbox
46 | }
47 | return {
48 | centerX: bbox.centerX,
49 | centerY: bbox.centerY,
50 | minX: bbox.minX - offset,
51 | minY: bbox.minY - offset,
52 | maxX: bbox.maxX + offset,
53 | maxY: bbox.maxY + offset,
54 | height: bbox.height + 2 * offset,
55 | width: bbox.width + 2 * offset
56 | }
57 | }
58 |
59 | // 获取扩展区域上的连接点
60 | const getExpandedPort = function(bbox, point) {
61 | // 判断连接点在上下左右哪个区域,相应地给x或y加上或者减去offset
62 | if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) {
63 | return {
64 | x: point.x > bbox.centerX ? bbox.maxX : bbox.minX,
65 | y: point.y
66 | }
67 | }
68 | return {
69 | x: point.x,
70 | y: point.y > bbox.centerY ? bbox.maxY : bbox.minY
71 | }
72 | }
73 |
74 | // 获取合法折点集合
75 | const getConnectablePoints = function(sBBox, tBBox, sPoint, tPoint) {
76 | let lineBBox = getBBoxFromVertexes(sPoint, tPoint)
77 | let outerBBox = combineBBoxes(sBBox, tBBox)
78 | let sLineBBox = combineBBoxes(sBBox, lineBBox)
79 | let tLineBBox = combineBBoxes(tBBox, lineBBox)
80 | let points = [
81 | ...vertexOfBBox(sLineBBox),
82 | ...vertexOfBBox(tLineBBox),
83 | ...vertexOfBBox(outerBBox)
84 | ]
85 | const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY }
86 | let bboxes = [outerBBox, sLineBBox, tLineBBox, lineBBox]
87 | bboxes.forEach(bbox => {
88 | // 包含 bbox 延长线和线段的相交线
89 | points = [
90 | ...points,
91 | ...crossPointsByLineAndBBox(bbox, centerPoint)
92 | ]
93 | })
94 | points.push({ x: sPoint.x, y: tPoint.y })
95 | points.push({ x: tPoint.x, y: sPoint.y })
96 | return points
97 | }
98 |
99 | const getBBoxFromVertexes = function(sPoint, tPoint) {
100 | const minX = Math.min(sPoint.x, tPoint.x)
101 | const maxX = Math.max(sPoint.x, tPoint.x)
102 | const minY = Math.min(sPoint.y, tPoint.y)
103 | const maxY = Math.max(sPoint.y, tPoint.y)
104 |
105 | return {
106 | centerX: (minX + maxX) / 2,
107 | centerY: (minY + maxY) / 2,
108 | maxX: maxX,
109 | maxY: maxY,
110 | minX: minX,
111 | minY: minY,
112 | height: maxY - minY,
113 | width: maxX - minX
114 | }
115 | }
116 |
117 | const combineBBoxes = function(sBBox, tBBox) {
118 | const minX = Math.min(sBBox.minX, tBBox.minX)
119 | const minY = Math.min(sBBox.minY, tBBox.minY)
120 | const maxX = Math.max(sBBox.maxX, tBBox.maxX)
121 | const maxY = Math.max(sBBox.maxY, tBBox.maxY)
122 |
123 | return {
124 | centerX: (minX + maxX) / 2,
125 | centerY: (minY + maxY) / 2,
126 | minX: minX,
127 | minY: minY,
128 | maxX: maxX,
129 | maxY: maxY,
130 | height: maxY - minY,
131 | width: maxX - minX
132 | }
133 | }
134 |
135 | const vertexOfBBox = function(bbox) {
136 | return [
137 | { x: bbox.minX, y: bbox.minY },
138 | { x: bbox.maxX, y: bbox.minY },
139 | { x: bbox.maxX, y: bbox.maxY },
140 | { x: bbox.minX, y: bbox.maxY }
141 | ]
142 | }
143 |
144 | const crossPointsByLineAndBBox = function(bbox, centerPoint) {
145 | let crossPoints = []
146 | if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) {
147 | crossPoints = [
148 | ...crossPoints,
149 | { x: centerPoint.x, y: bbox.minY },
150 | { x: centerPoint.x, y: bbox.maxY }
151 | ]
152 | }
153 | if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) {
154 | crossPoints = [
155 | ...crossPoints,
156 | { x: bbox.minX, y: centerPoint.y },
157 | { x: bbox.maxX, y: centerPoint.y }
158 | ]
159 | }
160 |
161 | return crossPoints
162 | }
163 |
164 | // 过滤连接点
165 | const filterConnectablePoints = function(points, bbox) {
166 | return points.filter(point => {
167 | return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY
168 | })
169 | }
170 |
171 | const crossBBox = function(bboxes, p1, p2) {
172 | for (let i = 0; i < bboxes.length; i++) {
173 | const bbox = bboxes[i]
174 | if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) {
175 | if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) {
176 | return true
177 | }
178 | } else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) {
179 | if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) {
180 | return true
181 | }
182 | }
183 | }
184 | return false
185 | }
186 |
187 | const getCost = function(p1, p2) {
188 | return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
189 | }
190 |
191 | // aStar 寻径
192 | const AStar = function(points, sPoint, tPoint, sBBox, tBBox) {
193 | const openList = [sPoint]
194 | const closeList = []
195 | points.forEach(item => {
196 | item.id = item.x + '-' + item.y
197 | })
198 | let tmpArr = []
199 | points.forEach(item => {
200 | if (!tmpArr.includes(target => target.id === item.id)) {
201 | tmpArr.push(item)
202 | }
203 | })
204 | points = [
205 | ...tmpArr,
206 | tPoint
207 | ]
208 | let endPoint
209 | while (openList.length > 0) {
210 | let minCostPoint
211 | openList.forEach((p, i) => {
212 | if (!p.parent) {
213 | p.f = 0
214 | }
215 | if (!minCostPoint) {
216 | minCostPoint = p
217 | }
218 | if (p.f < minCostPoint.f) {
219 | minCostPoint = p
220 | }
221 | })
222 | if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) {
223 | endPoint = minCostPoint
224 | break
225 | }
226 | openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1)
227 | closeList.push(minCostPoint)
228 | const neighbor = points.filter(p => {
229 | return (p.x === minCostPoint.x || p.y === minCostPoint.y) &&
230 | !(p.x === minCostPoint.x && p.y === minCostPoint.y) &&
231 | !crossBBox([sBBox, tBBox], minCostPoint, p)
232 | }
233 | )
234 | neighbor.forEach(p => {
235 | const inOpen = openList.find(o => o.x === p.x && o.y === p.y)
236 | const currentG = getCost(p, minCostPoint)
237 | if (!closeList.find(o => o.x === p.x && o.y === p.y)) {
238 | if (inOpen) {
239 | if (p.g > currentG) {
240 | p.parent = minCostPoint
241 | p.g = currentG
242 | p.f = p.g + p.h
243 | }
244 | } else {
245 | p.parent = minCostPoint
246 | p.g = currentG
247 | let h = getCost(p, tPoint)
248 | if (crossBBox([tBBox], p, tPoint)) {
249 | // 如果穿过bbox则增加该点的预估代价为bbox周长的一半
250 | h += (tBBox.width / 2 + tBBox.height / 2)
251 | }
252 | p.h = h
253 | p.f = p.g + p.h
254 | openList.push(p)
255 | }
256 | }
257 | })
258 | }
259 | if (endPoint) {
260 | const result = []
261 | result.push({
262 | x: endPoint.x,
263 | y: endPoint.y
264 | })
265 | while (endPoint.parent) {
266 | endPoint = endPoint.parent
267 | result.push({
268 | x: endPoint.x,
269 | y: endPoint.y
270 | })
271 | }
272 | return result.reverse()
273 | }
274 | return []
275 | }
276 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/graph/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: 图的布局方式/图的初始化
6 | */
7 |
8 | import theme from '../theme'
9 |
10 | /**
11 | * 图的布局方式/图的初始化
12 | * @type {{commonGraph: (function(*, *): G6.Graph)}}
13 | */
14 | const initGraph = {
15 | /**
16 | * 一般布局
17 | * @param G6
18 | * @param options
19 | * @returns {G6.Graph}
20 | */
21 | commonGraph: function (G6, options) {
22 | let graphData = options.graphData
23 | let themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
24 | // 生成G6实例
25 | let graph = new G6.Graph({
26 | plugins: options.plugins,
27 | container: options.container,
28 | width: options.width,
29 | height: options.height,
30 | groupByTypes: options.graphMode === 'edit', // TODO...G6 节点与边的层级(临时方案)
31 | defaultNode: {
32 | type: 'cc-rect',
33 | labelCfg: themeStyle.nodeLabelCfg
34 | },
35 | defaultEdge: {
36 | type: 'cc-line',
37 | labelCfg: themeStyle.edgeLabelCfg
38 | },
39 | defaultCombo: {
40 | type: 'circle',
41 | style: themeStyle.comboStyle.default,
42 | labelCfg: themeStyle.comboLabelCfg
43 | },
44 | nodeStateStyles: themeStyle.nodeStyle,
45 | edgeStateStyles: themeStyle.edgeStyle,
46 | comboStateStyles: themeStyle.comboStyle,
47 | modes: options.modes
48 | })
49 | // 将 read 方法分解成 data() 和 render 方法,便于整个生命周期的管理
50 | graph.read(graphData)
51 | graph.render()
52 | // 返回G6实例
53 | return graph
54 | }
55 | }
56 |
57 | export default initGraph
58 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/node/base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: 节点基础方法
6 | */
7 |
8 | import utils from '../utils'
9 |
10 | export default {
11 | setState(name, value, item) {
12 | // 设置节点状态
13 | utils.node.setState(name, value, item)
14 | // 设置锚点状态
15 | utils.anchor.setState(name, value, item)
16 | },
17 | // 绘制后附加锚点
18 | afterDraw(cfg, group) {
19 | // 绘制锚点
20 | utils.anchor.draw(cfg, group)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/node/cc-image.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: 图片节点
6 | */
7 |
8 | import utils from '../utils'
9 |
10 | // 用来获取调用此js的vue组件实例(this)
11 | let vm = null
12 |
13 | const sendThis = (_this) => {
14 | vm = _this
15 | }
16 |
17 | export default {
18 | sendThis,
19 | name: 'cc-image',
20 | extendName: 'image',
21 | options: {
22 | setState (name, value, item) {
23 | // 设置节点状态
24 | utils.node.setState(name, value, item)
25 | // 设置锚点状态
26 | if (vm.graphMode === 'edit') {
27 | utils.anchor.setState(name, value, item)
28 | }
29 | },
30 | // 绘制后附加锚点
31 | afterDraw (cfg, group) {
32 | // 绘制锚点
33 | if (vm.graphMode === 'edit') {
34 | utils.anchor.draw(cfg, group)
35 | }
36 | },
37 | // 设置告警状态
38 | afterUpdate (cfg, node) {
39 | const group = node.getContainer()
40 | // 获取children
41 | const halos = group.findAll(function (item) {
42 | return item.attrs.name === 'halo'
43 | })
44 | // 告警
45 | if (cfg.appState && cfg.appState.alert) {
46 | if (halos.length > 0) {
47 | return
48 | }
49 | let size = this.getSize(cfg) || [40, 40]
50 | let r = size[0] / 2
51 | let { id } = cfg
52 | let halo1 = group.addShape('circle', {
53 | id: id + '_halo_' + 1,
54 | attrs: {
55 | name: 'halo',
56 | x: 0,
57 | y: 0,
58 | r: r,
59 | fill: cfg.color || '#F56C6C',
60 | opacity: 0.6
61 | },
62 | name: 'halo',
63 | zIndex: -3
64 | })
65 | let halo2 = group.addShape('circle', {
66 | id: id + '_halo_' + 2,
67 | attrs: {
68 | name: 'halo',
69 | x: 0,
70 | y: 0,
71 | r: r,
72 | fill: cfg.color || '#F56C6C', // 为了显示清晰,随意设置了颜色
73 | opacity: 0.6
74 | },
75 | name: 'halo',
76 | zIndex: -2
77 | })
78 | let halo3 = group.addShape('circle', {
79 | id: id + '_halo_' + 3,
80 | attrs: {
81 | name: 'halo',
82 | x: 0,
83 | y: 0,
84 | r: r,
85 | fill: cfg.color || '#F56C6C',
86 | opacity: 0.6
87 | },
88 | name: 'halo',
89 | zIndex: -1
90 | })
91 | group.sort() // 排序,根据zIndex 排序
92 | halo1.animate({ // 逐渐放大,并消失
93 | r: r + 10,
94 | opacity: 0.1
95 | }, {
96 | repeat: true, // 循环
97 | duration: 3000,
98 | easing: 'easeCubic',
99 | delay: 0 // 无延迟
100 | })
101 | halo2.animate({ // 逐渐放大,并消失
102 | r: r + 10,
103 | opacity: 0.1
104 | }, {
105 | repeat: true, // 循环
106 | duration: 3000,
107 | easing: 'easeCubic',
108 | delay: 1000 // 1 秒延迟
109 | })
110 | halo3.animate({ // 逐渐放大,并消失
111 | r: r + 10,
112 | opacity: 0.1
113 | }, {
114 | repeat: true, // 循环
115 | duration: 3000,
116 | easing: 'easeCubic',
117 | delay: 2000 // 2 秒延迟
118 | })
119 | } else {
120 | halos.forEach(halo => {
121 | group.removeChild(halo)
122 | })
123 | }
124 | }
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/node/cc-rect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: 矩形节点
6 | */
7 |
8 | import base from './base'
9 | import theme from '../theme'
10 |
11 | export default {
12 | name: 'cc-rect',
13 | extendName: 'rect',
14 | options: {
15 | ...base,
16 | getShapeStyle(cfg) {
17 | const size = this.getSize(cfg) || [48, 48]
18 | const width = size[0]
19 | const height = size[1]
20 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
21 | const style = {
22 | x: 0 - width / 2,
23 | y: 0 - height / 2,
24 | width: width,
25 | height: height,
26 | ...themeStyle.nodeStyle.default
27 | }
28 | return style
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/node/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: register nodes
6 | */
7 |
8 | import ccRect from './cc-rect'
9 | import ccImage from './cc-image'
10 |
11 | const obj = {
12 | ccRect,
13 | ccImage
14 | }
15 |
16 | export default {
17 | obj,
18 | register(G6) {
19 | Object.values(obj).map(item => {
20 | G6.registerNode(item.name, item.options, item.extendName)
21 | })
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/plugins/d3-installer.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/07/05
4 | * @repository: https://github.com/wenyuan
5 | * @description: install 3rd plugins
6 | */
7 |
8 | import * as d3 from 'd3-force/dist/d3-force'
9 |
10 | export default d3
11 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/theme/dark-style.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/11/20
4 | * @repository: https://github.com/wenyuan
5 | * @description: dark style
6 | */
7 |
8 | export default {
9 | // 节点样式
10 | nodeStyle: {
11 | default: {
12 | stroke: '#CED4D9',
13 | fill: 'transparent',
14 | shadowOffsetX: 0,
15 | shadowOffsetY: 4,
16 | shadowBlur: 10,
17 | shadowColor: 'rgba(13, 26, 38, 0.08)',
18 | lineWidth: 1,
19 | radius: 4,
20 | strokeOpacity: 0.7
21 | },
22 | selected: {
23 | shadowColor: '#ff240b',
24 | shadowBlur: 4,
25 | shadowOffsetX: 0,
26 | shadowOffsetY: 0
27 | // shadowColor: '#626262',
28 | // shadowBlur: 8,
29 | // shadowOffsetX: -1,
30 | // shadowOffsetY: 3
31 | },
32 | unselected: {
33 | shadowColor: ''
34 | }
35 | },
36 | // 节点标签样式
37 | nodeLabelCfg: {
38 | positions: 'center',
39 | style: {
40 | fill: '#FFF'
41 | }
42 | },
43 | // 连线样式
44 | edgeStyle: {
45 | default: {
46 | stroke: '#53da3a',
47 | lineWidth: 2,
48 | strokeOpacity: 0.92,
49 | lineAppendWidth: 10
50 | // endArrow: true
51 | },
52 | active: {
53 | shadowColor: 'red',
54 | shadowBlur: 4,
55 | shadowOffsetX: 0,
56 | shadowOffsetY: 0
57 | },
58 | inactive: {
59 | shadowColor: ''
60 | },
61 | selected: {
62 | shadowColor: '#ff240b',
63 | shadowBlur: 4,
64 | shadowOffsetX: 0,
65 | shadowOffsetY: 0
66 | },
67 | unselected: {
68 | shadowColor: ''
69 | }
70 | },
71 | // 连线标签样式
72 | edgeLabelCfg: {
73 | position: 'center',
74 | autoRotate: false,
75 | style: {
76 | fill: '#000'
77 | }
78 | },
79 | // 锚点样式
80 | anchorStyle: {
81 | default: {
82 | radius: 3,
83 | symbol: 'circle',
84 | fill: '#FFFFFF',
85 | fillOpacity: 0,
86 | stroke: '#1890FF',
87 | strokeOpacity: 0,
88 | cursor: 'crosshair'
89 | },
90 | hover: {
91 | fillOpacity: 1,
92 | strokeOpacity: 1
93 | },
94 | unhover: {
95 | fillOpacity: 0,
96 | strokeOpacity: 0
97 | },
98 | active: {
99 | fillOpacity: 1,
100 | strokeOpacity: 1
101 | },
102 | inactive: {
103 | fillOpacity: 0,
104 | strokeOpacity: 0
105 | }
106 | },
107 | // 锚点背景样式
108 | anchorBgStyle: {
109 | default: {
110 | radius: 10,
111 | symbol: 'circle',
112 | fill: '#1890FF',
113 | fillOpacity: 0,
114 | stroke: '#1890FF',
115 | strokeOpacity: 0,
116 | cursor: 'crosshair'
117 | },
118 | hover: {
119 | fillOpacity: 1,
120 | strokeOpacity: 1
121 | },
122 | unhover: {
123 | fillOpacity: 0,
124 | strokeOpacity: 0
125 | },
126 | active: {
127 | fillOpacity: 0.3,
128 | strokeOpacity: 0.5
129 | },
130 | inactive: {
131 | fillOpacity: 0,
132 | strokeOpacity: 0
133 | }
134 | },
135 | // 分组样式
136 | comboStyle: {
137 | default: {
138 | fill: 'transparent',
139 | lineWidth: 1
140 | },
141 | hover: {
142 | lineWidth: 3
143 | }
144 | },
145 | // 分组标签样式
146 | comboLabelCfg: {
147 | position: 'top',
148 | style: {
149 | fontSize: 20,
150 | fill: '#4682B4'
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/theme/default-style.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: default style
6 | */
7 |
8 | export default {
9 | // 节点样式
10 | nodeStyle: {
11 | default: {
12 | stroke: '#CED4D9',
13 | fill: 'transparent',
14 | shadowOffsetX: 0,
15 | shadowOffsetY: 4,
16 | shadowBlur: 10,
17 | shadowColor: 'rgba(13, 26, 38, 0.08)',
18 | lineWidth: 1,
19 | radius: 4,
20 | strokeOpacity: 0.7
21 | },
22 | selected: {
23 | shadowColor: '#ff240b',
24 | shadowBlur: 4,
25 | shadowOffsetX: 0,
26 | shadowOffsetY: 0
27 | // shadowColor: '#626262',
28 | // shadowBlur: 8,
29 | // shadowOffsetX: -1,
30 | // shadowOffsetY: 3
31 | },
32 | unselected: {
33 | shadowColor: ''
34 | }
35 | },
36 | // 节点标签样式
37 | nodeLabelCfg: {
38 | positions: 'bottom',
39 | style: {
40 | fill: '#000'
41 | }
42 | },
43 | // 连线样式
44 | edgeStyle: {
45 | default: {
46 | stroke: '#A3B1BF',
47 | lineWidth: 2,
48 | strokeOpacity: 0.92,
49 | lineAppendWidth: 10
50 | // endArrow: true
51 | },
52 | active: {
53 | shadowColor: 'red',
54 | shadowBlur: 4,
55 | shadowOffsetX: 0,
56 | shadowOffsetY: 0
57 | },
58 | inactive: {
59 | shadowColor: ''
60 | },
61 | selected: {
62 | shadowColor: '#ff240b',
63 | shadowBlur: 4,
64 | shadowOffsetX: 0,
65 | shadowOffsetY: 0
66 | },
67 | unselected: {
68 | shadowColor: ''
69 | }
70 | },
71 | // 连线标签样式
72 | edgeLabelCfg: {
73 | position: 'center',
74 | autoRotate: false,
75 | style: {
76 | fill: '#000'
77 | }
78 | },
79 | // 锚点样式
80 | anchorStyle: {
81 | default: {
82 | r: 3,
83 | symbol: 'circle',
84 | fill: '#FFFFFF',
85 | fillOpacity: 0,
86 | stroke: '#1890FF',
87 | strokeOpacity: 0,
88 | cursor: 'crosshair'
89 | },
90 | hover: {
91 | fillOpacity: 1,
92 | strokeOpacity: 1
93 | },
94 | unhover: {
95 | fillOpacity: 0,
96 | strokeOpacity: 0
97 | },
98 | active: {
99 | fillOpacity: 1,
100 | strokeOpacity: 1
101 | },
102 | inactive: {
103 | fillOpacity: 0,
104 | strokeOpacity: 0
105 | }
106 | },
107 | // 锚点背景样式
108 | anchorBgStyle: {
109 | default: {
110 | r: 6,
111 | symbol: 'circle',
112 | fill: '#1890FF',
113 | fillOpacity: 0,
114 | stroke: '#1890FF',
115 | strokeOpacity: 0,
116 | cursor: 'crosshair'
117 | },
118 | hover: {
119 | fillOpacity: 1,
120 | strokeOpacity: 1
121 | },
122 | unhover: {
123 | fillOpacity: 0,
124 | strokeOpacity: 0
125 | },
126 | active: {
127 | fillOpacity: 0.3,
128 | strokeOpacity: 0.5
129 | },
130 | inactive: {
131 | fillOpacity: 0,
132 | strokeOpacity: 0
133 | }
134 | },
135 | // 分组样式
136 | comboStyle: {
137 | default: {
138 | fill: 'transparent',
139 | lineWidth: 1
140 | },
141 | hover: {
142 | lineWidth: 3
143 | }
144 | },
145 | // 分组标签样式
146 | comboLabelCfg: {
147 | position: 'top',
148 | style: {
149 | fontSize: 20,
150 | fill: '#4682B4'
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/theme/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: 编辑器主题样式 - 节点、连线的预设样式
6 | */
7 |
8 | import defaultStyle from './default-style'
9 | import darkStyle from './dark-style'
10 | import officeStyle from './office-style'
11 |
12 | export default {
13 | defaultStyle,
14 | darkStyle,
15 | officeStyle
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/theme/office-style.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/11/21
4 | * @repository: https://github.com/wenyuan
5 | * @description: office style
6 | */
7 |
8 | export default {
9 | // 节点样式
10 | nodeStyle: {
11 | default: {
12 | stroke: '#CED4D9',
13 | fill: '#FFFFFF',
14 | shadowOffsetX: 0,
15 | shadowOffsetY: 4,
16 | shadowBlur: 10,
17 | shadowColor: 'rgba(13, 26, 38, 0.08)',
18 | lineWidth: 1,
19 | radius: 4,
20 | strokeOpacity: 0.7
21 | },
22 | selected: {
23 | shadowColor: '#ff240b',
24 | shadowBlur: 4,
25 | shadowOffsetX: 0,
26 | shadowOffsetY: 0
27 | // shadowColor: '#626262',
28 | // shadowBlur: 8,
29 | // shadowOffsetX: -1,
30 | // shadowOffsetY: 3
31 | },
32 | unselected: {
33 | shadowColor: ''
34 | }
35 | },
36 | // 节点标签样式
37 | nodeLabelCfg: {
38 | positions: 'center',
39 | style: {
40 | fill: '#000'
41 | }
42 | },
43 | // 连线样式
44 | edgeStyle: {
45 | default: {
46 | stroke: '#41c23a',
47 | lineWidth: 2,
48 | strokeOpacity: 0.92,
49 | lineAppendWidth: 10
50 | // endArrow: true
51 | },
52 | active: {
53 | shadowColor: 'red',
54 | shadowBlur: 4,
55 | shadowOffsetX: 0,
56 | shadowOffsetY: 0
57 | },
58 | inactive: {
59 | shadowColor: ''
60 | },
61 | selected: {
62 | shadowColor: '#ff240b',
63 | shadowBlur: 4,
64 | shadowOffsetX: 0,
65 | shadowOffsetY: 0
66 | },
67 | unselected: {
68 | shadowColor: ''
69 | }
70 | },
71 | // 连线标签样式
72 | edgeLabelCfg: {
73 | position: 'center',
74 | autoRotate: false,
75 | style: {
76 | fill: '#000'
77 | }
78 | },
79 | // 锚点样式
80 | anchorStyle: {
81 | default: {
82 | radius: 3,
83 | symbol: 'circle',
84 | fill: '#FFFFFF',
85 | fillOpacity: 0,
86 | stroke: '#1890FF',
87 | strokeOpacity: 0,
88 | cursor: 'crosshair'
89 | },
90 | hover: {
91 | fillOpacity: 1,
92 | strokeOpacity: 1
93 | },
94 | unhover: {
95 | fillOpacity: 0,
96 | strokeOpacity: 0
97 | },
98 | active: {
99 | fillOpacity: 1,
100 | strokeOpacity: 1
101 | },
102 | inactive: {
103 | fillOpacity: 0,
104 | strokeOpacity: 0
105 | }
106 | },
107 | // 锚点背景样式
108 | anchorBgStyle: {
109 | default: {
110 | radius: 10,
111 | symbol: 'circle',
112 | fill: '#1890FF',
113 | fillOpacity: 0,
114 | stroke: '#1890FF',
115 | strokeOpacity: 0,
116 | cursor: 'crosshair'
117 | },
118 | hover: {
119 | fillOpacity: 1,
120 | strokeOpacity: 1
121 | },
122 | unhover: {
123 | fillOpacity: 0,
124 | strokeOpacity: 0
125 | },
126 | active: {
127 | fillOpacity: 0.3,
128 | strokeOpacity: 0.5
129 | },
130 | inactive: {
131 | fillOpacity: 0,
132 | strokeOpacity: 0
133 | }
134 | },
135 | // 分组样式
136 | comboStyle: {
137 | default: {
138 | fill: 'transparent',
139 | lineWidth: 1
140 | },
141 | hover: {
142 | lineWidth: 3
143 | }
144 | },
145 | // 分组标签样式
146 | comboLabelCfg: {
147 | position: 'top',
148 | style: {
149 | fontSize: 20,
150 | fill: '#4682B4'
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/toolbar-edit.vue:
--------------------------------------------------------------------------------
1 |
2 |
39 |
40 |
41 |
47 |
48 |
109 |
125 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/toolbar-preview.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
20 |
26 |
27 |
84 |
100 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/anchor/draw.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: draw anchor
6 | */
7 |
8 | import theme from '../../theme'
9 |
10 | export default function(cfg, group) {
11 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
12 | let { anchorPoints, width, height, id } = cfg
13 | if (anchorPoints && anchorPoints.length) {
14 | for (let i = 0, len = anchorPoints.length; i < len; i++) {
15 | let [x, y] = anchorPoints[i]
16 | // 计算Marker中心点坐标
17 | let originX = -width / 2
18 | let originY = -height / 2
19 | let anchorX = x * width + originX
20 | let anchorY = y * height + originY
21 | // 添加锚点背景
22 | let anchorBgShape = group.addShape('marker', {
23 | id: id + '_anchor_bg_' + i,
24 | attrs: {
25 | name: 'anchorBg',
26 | x: anchorX,
27 | y: anchorY,
28 | // 锚点默认样式
29 | ...themeStyle.anchorBgStyle.default
30 | },
31 | draggable: false,
32 | name: 'markerBg-shape'
33 | })
34 | // 添加锚点Marker形状
35 | let anchorShape = group.addShape('marker', {
36 | id: id + '_anchor_' + i,
37 | attrs: {
38 | name: 'anchor',
39 | x: anchorX,
40 | y: anchorY,
41 | // 锚点默认样式
42 | ...themeStyle.anchorStyle.default
43 | },
44 | draggable: false,
45 | name: 'marker-shape'
46 | })
47 |
48 | anchorShape.on('mouseenter', function() {
49 | anchorBgShape.attr({
50 | ...themeStyle.anchorBgStyle.active
51 | })
52 | })
53 | anchorShape.on('mouseleave', function() {
54 | anchorBgShape.attr({
55 | ...themeStyle.anchorBgStyle.inactive
56 | })
57 | })
58 | }
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/anchor/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: anchor
6 | */
7 |
8 | import draw from './draw'
9 | import setState from './set-state'
10 | import update from './update'
11 |
12 | export default {
13 | draw,
14 | setState,
15 | update
16 | }
17 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/anchor/set-state.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: set anchor state
6 | */
7 |
8 | import theme from '../../theme'
9 |
10 | export default function(name, value, item) {
11 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
12 | if (name === 'hover') {
13 | let group = item.getContainer()
14 | let children = group.get('children')
15 | for (let i = 0, len = children.length; i < len; i++) {
16 | let child = children[i]
17 | // 处理锚点状态
18 | if (child.attrs.name === 'anchor') {
19 | if (value) {
20 | child.attr(themeStyle.anchorStyle.hover)
21 | } else {
22 | child.attr(themeStyle.anchorStyle.unhover)
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/anchor/update.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: update anchor
6 | */
7 |
8 | export default function(cfg, group) {
9 | let { anchorPoints, width, height, id } = cfg
10 | if (anchorPoints && anchorPoints.length) {
11 | for (let i = 0, len = anchorPoints.length; i < len; i++) {
12 | let [x, y] = anchorPoints[i]
13 | // 计算Marker中心点坐标
14 | let originX = -width / 2
15 | let originY = -height / 2
16 | let anchorX = x * width + originX
17 | let anchorY = y * height + originY
18 | // 锚点背景
19 | let anchorBgShape = group.findById(id + '_anchor_bg_' + i)
20 | // 锚点
21 | let anchorShape = group.findById(id + '_anchor_' + i)
22 | anchorBgShape.attr({
23 | x: anchorX,
24 | y: anchorY
25 | })
26 | anchorShape.attr({
27 | x: anchorX,
28 | y: anchorY
29 | })
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/edge/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: edge
6 | */
7 |
8 | import setState from './set-state'
9 |
10 | export default {
11 | setState
12 | }
13 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/edge/set-state.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: set edge state
6 | */
7 |
8 | import theme from '../../theme'
9 |
10 | export default function(name, value, item) {
11 | const group = item.getContainer()
12 | const shape = group.get('children')[0] // 顺序根据 draw 时确定
13 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
14 | if (name === 'active') {
15 | if (value) {
16 | shape.attr(themeStyle.edgeStyle.active)
17 | } else {
18 | shape.attr(themeStyle.edgeStyle.inactive)
19 | }
20 | } else if (name === 'selected') {
21 | if (value) {
22 | shape.attr(themeStyle.edgeStyle.selected)
23 | } else {
24 | shape.attr(themeStyle.edgeStyle.unselected)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: graph utils
6 | */
7 |
8 | import node from './node'
9 | import anchor from './anchor'
10 | import edge from './edge'
11 |
12 | /**
13 | * 比较两个对象的内容是否相同(两个对象的键值都相同)
14 | * @param obj1
15 | * @param obj2
16 | * @returns {*}
17 | */
18 | const isObjectValueEqual = function(obj1, obj2) {
19 | let o1 = obj1 instanceof Object
20 | let o2 = obj2 instanceof Object
21 | // 不是对象的情况
22 | if (!o1 || !o2) {
23 | return obj1 === obj2
24 | }
25 | // 对象的属性(key值)个数不相等
26 | if (Object.keys(obj1).length !== Object.keys(obj2).length) {
27 | return false
28 | }
29 | // 判断每个属性(如果属性值也是对象则需要递归)
30 | for (let attr in obj1) {
31 | let t1 = obj1[attr] instanceof Object
32 | let t2 = obj2[attr] instanceof Object
33 | if (t1 && t2) {
34 | return isObjectValueEqual(obj1[attr], obj2[attr])
35 | } else if (obj1[attr] !== obj2[attr]) {
36 | return false
37 | }
38 | }
39 | return true
40 | }
41 |
42 |
43 | /**
44 | * 生成uuid算法,碰撞率低于1/2^^122
45 | * @returns {string}
46 | */
47 | const generateUUID = function() {
48 | let d = new Date().getTime()
49 | // x 是 0-9 或 a-f 范围内的一个32位十六进制数
50 | let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
51 | let r = (d + Math.random() * 16) % 16 | 0
52 | d = Math.floor(d / 16)
53 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16)
54 | })
55 | return uuid
56 | }
57 |
58 | export default {
59 | node,
60 | anchor,
61 | edge,
62 | // 通用工具类函数
63 | isObjectValueEqual,
64 | generateUUID
65 | }
66 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/node/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: node
6 | */
7 |
8 | import setState from './set-state'
9 |
10 | export default {
11 | setState
12 | }
13 |
--------------------------------------------------------------------------------
/packages/cc-topology/src/utils/node/set-state.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/15
4 | * @repository: https://github.com/wenyuan
5 | * @description: set node state
6 | */
7 |
8 | import theme from '../../theme'
9 |
10 | export default function(name, value, item) {
11 | const group = item.getContainer()
12 | const shape = group.get('children')[0] // 顺序根据 draw 时确定
13 | const themeStyle = theme.defaultStyle // todo...先使用默认主题,后期可能增加其它风格的主体
14 | if (name === 'active') {
15 | if (value) {
16 | shape.attr(themeStyle.nodeStyle.active)
17 | } else {
18 | shape.attr(themeStyle.nodeStyle.inactive)
19 | }
20 | } else if (name === 'selected') {
21 | if (value) {
22 | shape.attr(themeStyle.nodeStyle.selected)
23 | } else {
24 | shape.attr(themeStyle.nodeStyle.default)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author: wenyuan
3 | * @data: 2019/08/20
4 | * @repository: https://github.com/wenyuan
5 | * @description: 整合所有的组件,对外导出,即一个完整的组件库
6 | */
7 |
8 | import CCTopology from './cc-topology'
9 |
10 | // 存储组件列表
11 | const components = [
12 | CCTopology
13 | ]
14 |
15 | // 定义 install 方法,接收 Vue 作为参数。如果使用 use 注册插件,则所有的组件都将被注册
16 | const install = function(Vue) {
17 | // 判断是否安装
18 | if (install.installed) return
19 | // 遍历注册全局组件
20 | console.info('install----CCEditor: All----')
21 | components.map(component => Vue.component(component.name, component))
22 | }
23 |
24 | // 判断是否是直接引入文件
25 | if (typeof window !== 'undefined' && window.Vue) {
26 | install(window.Vue)
27 | }
28 |
29 | export default {
30 | // 导出的对象必须具有 install,才能被 Vue.use() 方法安装
31 | install,
32 | // 以下是具体的组件列表
33 | CCTopology
34 | }
35 |
36 | export {
37 | install,
38 | // 以下是具体的组件列表
39 | CCTopology
40 | }
41 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | autoprefixer: {}
4 | }
5 | };
6 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | // prettier.config.js or .prettierrc.js 返回对象
2 | module.exports = {
3 | eslintIntegration: true, // 开启 eslint 支持
4 | singleQuote: true, // 使用单引号
5 | tabWidth: 2, // 一个tab代表几个空格数,默认为80
6 | useTabs: false, // 是否使用tab进行缩进,默认为false,表示用空格进行缩减
7 | semi: false, // 行位是否使用分号,默认为true
8 | bracketSpacing: true //对象大括号直接是否有空格,默认为true,效果:{ foo: bar }
9 | }
10 |
--------------------------------------------------------------------------------
/public/cceditor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/public/cceditor.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/wenyuan/cceditor/aea390e876345971cf371b44ab39626c3f986b3b/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | CCEditor
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/vue.config.js:
--------------------------------------------------------------------------------
1 | let path = require('path')
2 |
3 | function resolve(dir) {
4 | return path.join(__dirname, dir)
5 | }
6 |
7 | module.exports = {
8 | // 修改src为examples
9 | pages: {
10 | index: {
11 | entry: 'examples/main.js',
12 | template: 'public/index.html',
13 | filename: 'index.html'
14 | }
15 | },
16 | // 强制内联CSS
17 | // 默认true: 使用CSS分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用