├── _config.yml ├── .npmrc ├── babel.config.js ├── public ├── favicon.ico └── index.html ├── resources ├── sample.png └── svg.xml ├── .eslintignore ├── .editorconfig ├── src └── components │ ├── PipelineHighlightNode.vue │ ├── PipelineNodeEnd.vue │ ├── PipelineNodeStart.vue │ ├── PipelineLine.vue │ ├── line.js │ ├── Pipeline.vue │ ├── PipelineNode.vue │ └── service.js ├── .gitignore ├── .travis.yml ├── vue.config.js ├── demo ├── main.js ├── App.vue └── data.js ├── .eslintrc.js ├── LICENSE ├── .github └── workflows │ └── npmpublish.yml ├── index.js ├── package.json ├── docs └── cn.md ├── README.md └── test └── service.test.js /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-minimal -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinfang134/vue-pipeline/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /resources/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jinfang134/vue-pipeline/HEAD/resources/sample.png -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /test/unit/coverage/ 5 | /nodes_modules/ 6 | /demo/nodes_modules/ 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /src/components/PipelineHighlightNode.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | .eslintcache 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | yarn.lock 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 10 # use nodejs v10 LTS 5 | cache: npm 6 | branches: 7 | only: 8 | - master # build master branch only 9 | script: 10 | - npm run build # generate static files 11 | deploy: 12 | provider: pages 13 | skip-cleanup: true 14 | github-token: $GH_TOKEN 15 | keep-history: true 16 | on: 17 | branch: master 18 | local-dir: dist -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // vue.config.js 2 | module.exports = { 3 | publicPath: process.env.NODE_ENV === 'production' 4 | ? '/vue-pipeline/' 5 | : '/', 6 | configureWebpack: { 7 | output: { 8 | path: __dirname + '/dist' 9 | }, 10 | resolve: { 11 | alias: { 12 | '@': __dirname + '/demo' 13 | } 14 | }, 15 | entry: { 16 | app: './demo/main.js' 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /demo/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import VuePipeline from '../index.js' 4 | // import VuePipeline from 'vue-pipeline' 5 | 6 | import ElementUI from 'element-ui' 7 | import 'element-ui/lib/theme-chalk/index.css' 8 | 9 | Vue.config.productionTip = false 10 | 11 | Vue.use(VuePipeline) 12 | Vue.use(ElementUI, { 13 | size: 'small' 14 | }) 15 | 16 | new Vue({ 17 | render: h => h(App), 18 | }).$mount('#app') 19 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | env: { 6 | node: true 7 | }, 8 | extends: [ 9 | "plugin:vue/essential", 10 | "eslint:recommended" 11 | ], 12 | parserOptions: { 13 | parser: "babel-eslint" 14 | }, 15 | // add your custom rules here 16 | rules: { 17 | }, 18 | overrides: [{ 19 | "files": ["*.vue"], 20 | "rules": { 21 | // 'no-console': "off", 22 | } 23 | }] 24 | } 25 | -------------------------------------------------------------------------------- /src/components/PipelineNodeEnd.vue: -------------------------------------------------------------------------------- 1 | 7 | 16 | 17 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | vue-hello 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/components/PipelineNodeStart.vue: -------------------------------------------------------------------------------- 1 | 7 | 16 | 17 | 30 | -------------------------------------------------------------------------------- /resources/svg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | background 5 | 6 | 7 | 8 | 9 | 10 | 11 | Layer 1 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/components/PipelineLine.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 49 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | (The MIT License) 3 | 4 | Copyright (c) 2012-2019 Zuo Jinfang 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /.github/workflows/npmpublish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: 12 15 | - run: npm ci 16 | - run: npm test 17 | 18 | publish-npm: 19 | needs: build 20 | runs-on: ubuntu-latest 21 | steps: 22 | - uses: actions/checkout@v1 23 | - uses: actions/setup-node@v1 24 | with: 25 | node-version: 12 26 | registry-url: https://registry.npmjs.org/ 27 | - run: npm ci 28 | - run: npm publish 29 | env: 30 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} 31 | 32 | publish-gpr: 33 | needs: build 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v1 37 | - uses: actions/setup-node@v1 38 | with: 39 | node-version: 12 40 | registry-url: https://npm.pkg.github.com/ 41 | scope: '@your-github-username' 42 | - run: npm ci 43 | - run: npm publish 44 | env: 45 | NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} 46 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * IMPORTS 3 | ***************************************************************************/ 4 | 5 | // BASE COMPONENTS 6 | import VuePipeline from "./src/components/Pipeline.vue" 7 | 8 | /************************************************************************** 9 | * ENVIRONMENT CONFIGURATIONS 10 | ***************************************************************************/ 11 | 12 | // install function executed by Vue.use() 13 | function install(Vue, options) { 14 | if (install.installed) { 15 | return 16 | } else { 17 | install.installed = true 18 | } 19 | 20 | // Declare the component 21 | Vue.component("vue-pipeline", VuePipeline) 22 | } 23 | 24 | // Create module definition for Vue.use() 25 | const plugin = { 26 | install 27 | } 28 | 29 | // To auto-install when vue is found 30 | /* global window global */ 31 | let GlobalVue = null 32 | 33 | if (typeof window !== "undefined") { 34 | GlobalVue = window.Vue 35 | } else if (typeof global !== "undefined") { 36 | GlobalVue = global.Vue 37 | } 38 | 39 | if (GlobalVue) { 40 | GlobalVue.use(plugin) 41 | } 42 | 43 | // Default export is library as a whole, registered via Vue.use() 44 | export default plugin -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-pipeline", 3 | "version": "1.0.12", 4 | "private": false, 5 | "author": "Zuo Jinfang ", 6 | "license": "MIT", 7 | "description": "One easy-to-use component to show beautiful responsive timeline like jenkins blue ocean plugin.", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/jinfang134/vue-pipeline" 11 | }, 12 | "keywords": [ 13 | "vue", 14 | "components", 15 | "timeline" 16 | ], 17 | "main": "index.js", 18 | "scripts": { 19 | "serve": "vue-cli-service serve", 20 | "build": "vue-cli-service build", 21 | "test": "ava test/*.test.js --verbose", 22 | "lint": "eslint --quiet --cache --ext .vue,.js, src/** ./demo/*.{js,vue}", 23 | "preversion": "npm test && npm run lint", 24 | "version": "git add .", 25 | "postversion": "git push origin master && git push --tags && npm publish --registry=https://registry.npmjs.org/" 26 | }, 27 | "husky": { 28 | "hooks": { 29 | "pre-commit": "yarn lint && yarn test", 30 | "pre-push": "yarn lint" 31 | } 32 | }, 33 | "ava": { 34 | "require": [ 35 | "@babel/register" 36 | ], 37 | "babel": { 38 | "testOptions": { 39 | "babelrc": false, 40 | "configFile": false 41 | } 42 | } 43 | }, 44 | "dependencies": { 45 | "string-width": "^4.2.0" 46 | }, 47 | "devDependencies": { 48 | "@babel/register": "^7.7.4", 49 | "@vue/babel-preset-app": "^4.1.1", 50 | "@vue/cli-plugin-babel": "^3.0.1", 51 | "@vue/cli-plugin-eslint": "^3.0.1", 52 | "@vue/cli-service": "^3.0.1", 53 | "ava": "^2.4.0", 54 | "element-ui": "^2.13.0", 55 | "vue": "^2.6.10", 56 | "babel-eslint": "^10.0.1", 57 | "core-js": "^3.4.5", 58 | "eslint": "^5.16.0", 59 | "eslint-plugin-vue": "^6.0.1", 60 | "vue-template-compiler": "^2.6.10", 61 | "husky": "^3.1.0" 62 | }, 63 | "postcss": { 64 | "plugins": { 65 | "autoprefixer": {} 66 | } 67 | }, 68 | "browserslist": [ 69 | "> 1%", 70 | "last 2 versions" 71 | ] 72 | } 73 | -------------------------------------------------------------------------------- /docs/cn.md: -------------------------------------------------------------------------------- 1 | # vue-pipeline 2 | 3 | 仿照 jenkins blue ocean 的一个 pipeline 组件,可以根据数据结构动态的渲染出复杂的流程图,进度图等树型图或者有向无环图. 4 | 5 | [github 主页](/https://github.com/jinfang134/vue-pipeline) 6 | 7 | ![sample](https://github.com/jinfang134/vue-pipeline/raw/master/resources/sample.png) 8 | 9 | 快速开始: 10 | 11 | - [Demo](https://jinfang134.github.io/vue-pipeline/) 12 | 13 | ## LICENSE 14 | 15 | **NOTE:** Vue Pipeline 是基于 [The MIT License](https://github.com/jinfang134/vue-pipeline/blob/master/LICENSE) 协议完全开源. 如果您觉得这个插件对您有用,请赏个 star,谢谢! 16 | 17 | ## Features 18 | 19 | - 根据源数据动态创建图形 20 | - 响应式设计 21 | - 基于 svg 22 | - 可配置的 23 | - 显示/隐藏箭头 24 | - 支持 3 种不同种类的线型 25 | - 支持树和有向无环图 26 | - 节点事件触发 27 | - 每个节点显示不同的状态 28 | - 不同颜色和权重的边 29 | - 线条样式,节点颜色可以根据 css 进行配置 30 | 31 | ## 安装 32 | 33 | ``` 34 | npm install vue-pipeline 35 | ``` 36 | 37 | ``` 38 | import Vue from 'vue' 39 | import VuePipeline from 'vue-pipeline' 40 | 41 | Vue.use(VuePipeline) 42 | 43 | ``` 44 | 45 | ## 属性 46 | 47 | ### Pipeline 的属性 48 | 49 | | Name | Type | Default | Description | 50 | | --------- | ------- | ------- | -------------------------------------------------------- | 51 | | x | number | 50 | 第一个节点的 x 坐标 | 52 | | y | number | 55 | 第一个节点的 y 坐标 | 53 | | xstep | number | 120 | 相邻两个节点之间 x 轴上的间隔 | 54 | | ystep | number | 50 | 相邻两个节点 y 轴上的间隔 | 55 | | data | Array | [] | 节点数据 | 56 | | lineStyle | string | default | 线型,目前支持三种不同的线型: ' default',' bessel','line' | 57 | | showArrow | boolean | false | 是否显示箭头 | 58 | | | | | | 59 | 60 | ### 各个节点的属性 61 | 62 | | Name | Type | Default | Description | 63 | | ------------ | ------ | ------- | ------------------------------------------------------------------------------------ | 64 | | name | string | null | 各节点的标题 | 65 | | hint | string | null | 各个节点的提示 | 66 | | status | string | null | 各个节点的状态,包括:`start`,`succeed`,`running`,`failure`,`paused`,`unstable`,`end` | 67 | | next | Array | [] | 邻接表(表示与当前节点相连的节点的列表) | 68 | | next: index | number | null | 相邻节点的索引 | 69 | | next: weight | number | null | 权重(表现为不同的颜色) | 70 | 71 | **范例:** 72 | 73 | ```javascript 74 | let data = [ 75 | { 76 | name: "Start", 77 | hint: "1m23s", 78 | status: "start", 79 | next: [{ index: 1, weight: 2 }] 80 | }, 81 | { 82 | name: "Ammouncement Import", 83 | hint: "1m23s", 84 | status: "success", 85 | next: [ 86 | { index: 2, weight: 0 }, 87 | { index: 4, weight: 2 } 88 | ] 89 | }, 90 | { 91 | name: "Employee ID to Onboarding", 92 | hint: "2m23s", 93 | status: "failure", 94 | next: [{ index: 3, weight: 0 }] 95 | }, 96 | { 97 | name: "Personal Basic Info", 98 | hint: "2m23s", 99 | status: "paused", 100 | next: [{ index: 4, weight: 0 }] 101 | }, 102 | { name: "End ", hint: "2m23s", status: "end", next: [] } 103 | ]; 104 | ``` 105 | 106 | ## 事件 107 | 108 | | Name | Params | Description | 109 | | ------ | ------ | ---------------------- | 110 | | @click | node | 当节点被点击的时候触发 | 111 | 112 | ## 方法 113 | 114 | | Name | params | Description | 115 | | ------ | ------ | ----------------------------------- | 116 | | render | Node | 重新渲染整个图,当参数变化时可以调用 | 117 | 118 | ## Contributing 119 | 120 | 如果发现有任何 bug 或者建议,欢迎创建新的 issue 或者发 PR. 121 | 122 | Thanks! 123 | 124 | ## 本地开发 125 | 126 | ``` 127 | cd demo 128 | yarn install 129 | 130 | // Compiles and hot-reloads for development 131 | yarn run serve 132 | ``` 133 | 134 | ### Lints and fixes files 135 | 136 | ``` 137 | yarn run lint 138 | ``` 139 | -------------------------------------------------------------------------------- /src/components/line.js: -------------------------------------------------------------------------------- 1 | export class EdgeService { 2 | constructor(xstep, ystep) { 3 | this.xstep = xstep; 4 | this.ystep = ystep; 5 | } 6 | 7 | static getDrawEdgeService(lineStyle, step) { 8 | switch (lineStyle) { 9 | case "default": 10 | return new DefaultStyleService(step.x, step.y); 11 | case "bessel": 12 | return new BesselStyleService(step.x, step.y); 13 | case "line": 14 | return new LineStyleService(step.x, step.y); 15 | default: 16 | break; 17 | } 18 | } 19 | 20 | // eslint-disable-next-line no-unused-vars 21 | drawEdge(start, end) {} 22 | 23 | drawVerticalEdge(start, end) { 24 | const radius=14 25 | return `M ${start.x},${start.y + radius} L${start.x},${start.y + radius} ${end.x},${ 26 | end.y - radius 27 | }`; 28 | } 29 | 30 | drawHEdge(start, end) { 31 | if (end.x > start.x + this.xstep) { 32 | if (!start) { 33 | // console.log(start, end); 34 | } 35 | // let start = start.x + 10; 36 | let number = parseInt((end.x - start.x) / this.xstep); 37 | 38 | let control1 = this.xstep / 2 + 40; 39 | let control2 = this.xstep / 2 + 30; 40 | 41 | let d = `M ${start.x + 10} ${start.y} \ 42 | l 20 0\ 43 | C ${start.x + control1},${start.y} \ 44 | ${start.x + control2},${start.y + 30} \ 45 | ${start.x + this.xstep},${start.y + 30}`; 46 | if (number > 2) { 47 | d += `l ${this.xstep * (number - 2)} 0`; 48 | } 49 | 50 | d += `C ${end.x - control2},${start.y + 30} \ 51 | ${end.x - control1},${start.y} \ 52 | ${end.x - 10 - 20},${end.y} \ 53 | l 20 0`; 54 | 55 | return d; 56 | } 57 | return this.getStraightLinePath(start, end); 58 | } 59 | 60 | /** 61 | * 生成直线的指令 62 | * @param {*} start 63 | * @param {*} end 64 | */ 65 | getStraightLinePath(start, end) { 66 | return `M ${start.x + 12},${start.y} L${start.x + 12},${start.y} ${ 67 | end.x - 15 68 | },${end.y}`; 69 | } 70 | } 71 | 72 | class DefaultStyleService extends EdgeService { 73 | constructor(xstep, ystep) { 74 | super(xstep, ystep); 75 | } 76 | 77 | drawEdge(start, end) { 78 | if (start.y == end.y) { 79 | return this.drawHEdge(start, end); 80 | } 81 | if (start.x == end.x) { 82 | return this.drawVerticalEdge(start, end); 83 | } 84 | const lb = "c 0 12 12 12 12 12"; 85 | const rb = "c 12 0 12 -12 12 -12"; 86 | const rt = "c 12 0 12 12 12 12"; 87 | const lt = "c 0 -12 12 -12 12 -12"; 88 | let midy = Math.abs(end.y - start.y); 89 | if (end.y > start.y) { 90 | // 左上到右下 91 | let firstCorner = end.x - start.x - 50; 92 | const d = `M ${start.x + 10} ${start.y}\ 93 | l ${20} 0\ 94 | ${rt} \ 95 | l 0 ${midy - 24} \ 96 | ${lb} \ 97 | l ${firstCorner - 20} 0 98 | `; 99 | return d; 100 | } else { 101 | let lastCorner = end.x - start.x - 50; 102 | const d = `M ${start.x + 14} ${start.y}\ 103 | l ${lastCorner - 20} 0\ 104 | ${rb} \ 105 | l 0 -${midy - 24} \ 106 | ${lt} \ 107 | l ${20} 0 108 | `; 109 | // console.log(d) 110 | return d; 111 | } 112 | } 113 | } 114 | 115 | class BesselStyleService extends EdgeService { 116 | drawEdge(start, end) { 117 | if (start.y == end.y) { 118 | return this.drawHEdge(start, end); 119 | } 120 | if (start.x == end.x) { 121 | return this.drawVerticalEdge(start, end); 122 | } 123 | if (end.y > start.y) { 124 | let path = `M ${start.x + 12},${start.y}\ 125 | C ${end.x},${start.y}\ 126 | ${start.x + 50},${end.y}\ 127 | ${end.x - 15},${end.y} 128 | `; 129 | return path; 130 | } else { 131 | let path = `M ${start.x},${start.y}\ 132 | C ${end.x - 50},${start.y}\ 133 | ${start.x},${end.y}\ 134 | ${end.x - 12},${end.y} 135 | `; 136 | return path; 137 | } 138 | } 139 | } 140 | 141 | class LineStyleService extends EdgeService { 142 | drawEdge(start, end) { 143 | if (start.y == end.y) { 144 | return this.drawHEdge(start, end); 145 | } 146 | return this.getStraightLinePath(start, end); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/components/Pipeline.vue: -------------------------------------------------------------------------------- 1 | 2 | 18 | 121 | 122 | 183 | -------------------------------------------------------------------------------- /demo/App.vue: -------------------------------------------------------------------------------- 1 | 88 | 89 | 156 | 157 | 194 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-pipeline 2 | 3 | [![npm](https://img.shields.io/npm/v/vue-pipeline.svg)](https://www.npmjs.com/package/vue-pipeline) 4 | [![npm](https://img.shields.io/npm/dt/vue-pipeline.svg)](https://www.npmjs.com/package/vue-pipeline) 5 | [![GitHub stars](https://img.shields.io/github/stars/jinfang134/vue-pipeline.svg?style=social&label=Stars&style=for-the-badge)](https://github.com/jinfang134/vue-pipeline/stargazers) 6 | [![GitHub forks](https://img.shields.io/github/forks/jinfang134/vue-pipeline.svg?style=social&label=Fork&style=for-the-badge)](https://github.com/jinfang134/vue-pipeline/network) 7 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://github.com/jinfang134/vue-pipeline/blob/master/LICENSE) 8 | [![Build Status](https://travis-ci.com/jinfang134/vue-pipeline.svg?branch=master)](https://travis-ci.com/jinfang134/vue-pipeline) 9 | 10 | One easy-to-use component to show beautiful responsive timeline like jenkins blue ocean plugin. 11 | 12 | [中文](https://github.com/jinfang134/vue-pipeline/blob/master/docs/cn.md) 13 | 14 | ![sample](https://github.com/jinfang134/vue-pipeline/raw/master/resources/sample.png) 15 | 16 | To get started, check out: 17 | 18 | - [Demo](https://jinfang134.github.io/vue-pipeline/) 19 | 20 | ## LICENSE 21 | 22 | **NOTE:** Vue Pipeline is licensed under [The MIT License](https://github.com/jinfang134/vue-pipeline/blob/master/LICENSE). Completely free, you can arbitrarily use and modify this plugin. If this plugin is useful to you, you can **Star** this repo, your support is my biggest motive force, thanks. 23 | 24 | ## Features 25 | 26 | - Created Graph according your data dynamiclly 27 | - Responsive web design 28 | - svg component 29 | - Fully configurable 30 | - Via data attributes 31 | - Show/Hide arrow 32 | - 3 kinds of lines 33 | - support graph and tree view 34 | - Single node selection 35 | - Different status for each node 36 | - Different weight for each edge 37 | - Different color for each node and edge 38 | 39 | ## Install 40 | 41 | ``` 42 | npm install vue-pipeline 43 | ``` 44 | 45 | ``` 46 | import Vue from 'vue' 47 | import VuePipeline from 'vue-pipeline' 48 | 49 | Vue.use(VuePipeline) 50 | 51 | ``` 52 | 53 | ## Props 54 | 55 | ### Props of Pipeline 56 | 57 | | Name | Type | Default | Description | 58 | | --------- | ------- | ------- | ------------------------------------------------------ | 59 | | x | number | 50 | The x coordinate of the starting point of the graph | 60 | | y | number | 55 | The y coordinate of the starting point of the graph | 61 | | xstep | number | 120 | The position horizontally from a previous node. | 62 | | ystep | number | 50 | The position vertically from a previous node. | 63 | | data | Array | [] | data | 64 | | lineStyle | string | default | There are 3 types of line: ' default',' bessel','line' | 65 | | showArrow | boolean | false | whether show arrow for each line. | 66 | | | | | | 67 | 68 | ### Props for each node 69 | 70 | | Name | Type | Default | Description | 71 | | ------------ | ------ | ------- | ---------------------------------------------------------------------------------------------------------------- | 72 | | name | string | null | The title of each node | 73 | | hint | string | null | The hint of each node | 74 | | status | string | null | Status of each node, There are 6 type of status: `start`,`succeed`,`running`,`failure`,`paused`,`unstable`,`end` | 75 | | next | Array | [] | The edge connected with this node | 76 | | next: index | number | null | The index of another node of this edge | 77 | | next: weight | number | null | The weight of this edge | 78 | 79 | **Sample Data:** 80 | 81 | ```javascript 82 | let data = [ 83 | { 84 | name: "Start", 85 | hint: "1m23s", 86 | status: "start", 87 | next: [{ index: 1, weight: 2 }] 88 | }, 89 | { 90 | name: "Ammouncement Import", 91 | hint: "1m23s", 92 | status: "success", 93 | next: [ 94 | { index: 2, weight: 0 }, 95 | { index: 4, weight: 2 } 96 | ] 97 | }, 98 | { 99 | name: "Employee ID to Onboarding", 100 | hint: "2m23s", 101 | status: "failure", 102 | next: [{ index: 3, weight: 0 }] 103 | }, 104 | { 105 | name: "Personal Basic Info", 106 | hint: "2m23s", 107 | status: "paused", 108 | next: [{ index: 4, weight: 0 }] 109 | }, 110 | { name: "End ", hint: "2m23s", status: "end", next: [] } 111 | ]; 112 | ``` 113 | 114 | ## Events 115 | 116 | | Name | Params | Description | 117 | | ------ | ------ | --------------------------- | 118 | | @click | node | Occurs when node is clicked | 119 | 120 | ## Function 121 | 122 | | Name | params | Description | 123 | | ------ | ------ | ---------------------------- | 124 | | render | Node | render the whole graph again | 125 | 126 | ## Contributing 127 | 128 | If you find any bugs and/or want to contribute, feel free to create issues or submit pull requests. 129 | 130 | Thanks! 131 | 132 | ## Local Development 133 | 134 | ``` 135 | yarn install 136 | // Compiles and hot-reloads for development 137 | yarn run serve 138 | ``` 139 | 140 | ### publish a new version 141 | 142 | ``` 143 | npm version patch 144 | npm version minor 145 | npm version major 146 | ``` 147 | 148 | ### Lints and fixes files 149 | 150 | ``` 151 | yarn run lint 152 | ``` 153 | -------------------------------------------------------------------------------- /src/components/PipelineNode.vue: -------------------------------------------------------------------------------- 1 | 51 | 134 | 188 | -------------------------------------------------------------------------------- /test/service.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { Pipeline } from "../src/components/service"; 3 | 4 | const sample = { 5 | nodes: [ 6 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 7 | { 8 | name: "test1 long", hint: '1m23s', status: 'success', next: [ 9 | { index: 2, weight: 2 }, { index: 3, weight: 2 }, { index: 4, weight: 2 } 10 | ] 11 | }, 12 | { name: "test2 hello world", hint: '2m23s', status: 'unstable', next: [{ index: 5 }] }, 13 | { name: "test3", hint: '2m23s', status: 'success', next: [{ index: 7, weight: 1 }] }, 14 | { name: "test4 ha", hint: '2m23s', status: 'failure', next: [{ index: 8 }, { index: 9 }] },//4 15 | { name: "test5", hint: '2m23s', status: 'failure', next: [{ index: 6 }] }, 16 | { name: "test6", hint: '2m23s', status: 'success', next: [{ index: 10 }] },//6 17 | { name: "test7 hello", hint: '2m23s', status: 'paused', next: [{ index: 10 }] }, 18 | { name: "test8 2344", hint: '2m23s', status: 'paused', next: [{ index: 10 }] },//8 19 | { name: "test9", hint: '2m23s', status: 'failure', next: [{ index: 10 }] }, 20 | { name: "test10", hint: '2m23s', status: 'failure', next: [{ index: 11 }] }, //10 21 | { name: "test11", hint: '2m23s', status: 'failure' }, 22 | ], 23 | } 24 | 25 | test('find the longest way from start position', t => { 26 | let service = new Pipeline(sample.nodes, 50, 55, 120, 60) 27 | let list = service.findLongestWay(0) 28 | t.deepEqual(list, [0, 1, 2, 5, 6, 10, 11], 'longest way is different!!'); 29 | }); 30 | 31 | test('findParents', async t => { 32 | let service = new Pipeline(sample.nodes, 50, 55, 120, 60) 33 | let list = service.findParents(10) 34 | t.deepEqual(list, [6, 7, 8, 9], 'Parents is wrong!'); 35 | }); 36 | 37 | 38 | test('DFS', async t => { 39 | let service = new Pipeline(sample.nodes, 50, 55, 120, 60) 40 | let list = service.dfs(0) 41 | t.deepEqual(list.length, sample.nodes.length, 'DFS is wrong!'); 42 | }); 43 | 44 | 45 | test('topologicalSorting', t => { 46 | const testdata = [ 47 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 48 | { 49 | name: "test1 long", hint: '1m23s', status: 'success', next: [ 50 | { index: 2, weight: 2 }, { index: 3, weight: 2 }, { index: 5, weight: 2 } 51 | ] 52 | }, 53 | { name: "test2 hello world", hint: '2m23s', status: 'unstable', next: [{ index: 3 }, { index: 4 }] }, 54 | { name: "test3", hint: '2m23s', status: 'success', next: [{ index: 4 }] }, 55 | { name: "test4 ha", hint: '2m23s', status: 'failure', next: [] },//4 56 | { name: "test5", hint: '2m23s', status: 'failure', next: [{ index: 4 }] }, 57 | ] 58 | 59 | let service = new Pipeline(testdata, 50, 55, 120, 60) 60 | let list = service.topologicalSorting() 61 | t.deepEqual(list, [0, 1, 2, 3, 5, 4]) 62 | }) 63 | 64 | test('hasCircle', t => { 65 | const test2 = [ 66 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 67 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 2, weight: 2 }] }, 68 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 69 | ] 70 | 71 | let service = new Pipeline(test2, 50, 55, 120, 60) 72 | let result = service.hasCircle() 73 | t.deepEqual(result, true, 'this graph has circle.') 74 | }) 75 | 76 | 77 | test('isTree', t => { 78 | const test2 = [ 79 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 80 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 2, weight: 2 }] }, 81 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 82 | ] 83 | let service = new Pipeline(test2, 50, 55, 120, 60) 84 | let result = service.isTree() 85 | t.deepEqual(result, false, 'this graph is not a tree.') 86 | 87 | const data = [ 88 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }, { index: 2, weight: 2 }] }, 89 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 3, weight: 2 }] }, 90 | { name: "test0", hint: '1m23s', status: 'success', next: [] }, 91 | { name: "test0", hint: '1m23s', status: 'success', next: [] }, 92 | ] 93 | let service2 = new Pipeline(data, 50, 55, 120, 60) 94 | let result2 = service2.isTree() 95 | t.deepEqual(result2, true, 'this graph is a tree.') 96 | 97 | }) 98 | 99 | 100 | test('assignNodeForTree',t=>{ 101 | const data = [ 102 | { name: "test0", hint: '1m23s', status: 'success', next: [{ index: 1, weight: 2 }] }, 103 | { 104 | name: "test1 long", hint: '1m23s', status: 'success', next: [ 105 | { index: 2, weight: 2 }, { index: 3, weight: 2 }, { index: 4, weight: 2 } 106 | ] 107 | }, 108 | { name: "test2 hello world", hint: '2m23s', status: 'unstable', next: [{ index: 5 }] }, 109 | { name: "test3", hint: '2m23s', status: 'success', next: [{ index: 7, weight: 1 }] }, 110 | { name: "test4 ha", hint: '2m23s', status: 'failure', next: [{ index: 8 }, { index: 9 }] },//4 111 | { name: "test5", hint: '2m23s', status: 'failure', next: [{ index: 6 }] }, 112 | { name: "test6", hint: '2m23s', status: 'success', next: [{ index: 10 }] },//6 113 | { name: "test7 hello", hint: '2m23s', status: 'paused', next: [{ index: 12 }, { index: 13 }] }, 114 | { name: "test8 2344", hint: '2m23s', status: 'paused', next: [{ index: 14 }, { index: 15 }] },//8 115 | { name: "test9", hint: '2m23s', status: 'failure', next: [{ index: 16 }] }, 116 | { name: "test10", hint: '2m23s', status: 'failure', next: [{ index: 11 }] }, //10 117 | { name: "test11", hint: '2m23s', status: 'failure' }, 118 | { name: "test12", hint: '2m23s', status: 'failure' }, 119 | { name: "test13", hint: '2m23s', status: 'failure' }, 120 | { name: "test14", hint: '2m23s', status: 'failure' }, 121 | { name: "test15", hint: '2m23s', status: 'failure' }, 122 | { name: "test16", hint: '2m23s', status: 'failure' }, 123 | ] 124 | let service2 = new Pipeline(data, 50, 55, 120, 60) 125 | service2.assignNodeForTree(0,0,0) 126 | t.deepEqual(service2.matrix[0], [0,1,2,5,6,10,11], 'this graph is a tree.') 127 | t.deepEqual(service2.matrix[1], [undefined,undefined,3,7,12], 'this graph is a tree.') 128 | }) 129 | -------------------------------------------------------------------------------- /src/components/service.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | // const data = require('./data.js') 4 | import { EdgeService } from "./line"; 5 | 6 | class Pipeline { 7 | /** 8 | * 9 | * @param {Array} nodes 10 | * @param {Number} startx 11 | * @param {*} starty 12 | * @param {*} xstep 13 | * @param {*} ystep 14 | * @param {*} lineStyle 线型,目前支持三种线型: default(默认),line(直线),bessel(贝塞尔曲线) 15 | */ 16 | constructor(nodes, startx, starty, xstep, ystep, lineStyle = "default") { 17 | this.nodes = nodes; 18 | this.startx = startx; 19 | this.starty = starty; 20 | this.xstep = xstep; 21 | this.ystep = ystep; 22 | this.positionList = new Set(); 23 | this.solvedList = []; 24 | this.lineStyle = lineStyle; 25 | this.sortedList = this.topologicalSorting(); 26 | this.matrix = []; //存放各个顶点的相对坐标 27 | for (let i = 0; i < nodes.length; i++) { 28 | this.matrix[i] = []; 29 | } 30 | this.width = 0; 31 | this.height = 0; 32 | } 33 | 34 | /** 35 | * 判断当前的图是否是一棵树 36 | */ 37 | isTree() { 38 | let set = new Set(); 39 | for (let i = 0; i < this.nodes.length; i++) { 40 | if (this.nodes[i].next) { 41 | if (this.nodes[i].next.some((it) => set.has(it.index))) { 42 | return false; 43 | } 44 | this.nodes[i].next.forEach((it) => set.add(it.index)); 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | /** 51 | * 计算一个树要占的宽度 52 | * @param {*} index 53 | */ 54 | getWidthOfTree(index) { 55 | let node = this.nodes[index]; 56 | if (!node.next || node.next.length == 0) { 57 | return 1; 58 | } 59 | 60 | let width = 0; 61 | for (let i = 0; i < node.next.length; i++) { 62 | width += this.getWidthOfTree(node.next[i].index); 63 | } 64 | return width; 65 | } 66 | 67 | /** 68 | * 为树分配节点的位置 69 | * @param {*} index 70 | * @param {*} x 71 | * @param {*} y 72 | */ 73 | assignNodeForTree(index, x, y) { 74 | this.matrix[y][x] = index; 75 | let node = this.nodes[index]; 76 | if (!node.next || node.next.length == 0) { 77 | return; 78 | } 79 | 80 | let xx = x + 1; 81 | let yy = y; 82 | 83 | for (let i = 0; i < node.next.length; i++) { 84 | let width = this.getWidthOfTree(node.next[i].index); 85 | this.assignNodeForTree(node.next[i].index, xx, yy); 86 | yy += width; 87 | } 88 | } 89 | 90 | getLines() { 91 | let list = []; 92 | var drawService = EdgeService.getDrawEdgeService(this.lineStyle, { 93 | x: this.xstep, 94 | y: this.ystep, 95 | }); 96 | for (let i = 0; i < this.nodes.length; i++) { 97 | let node = this.nodes[i]; 98 | if (!node.next) { 99 | continue; 100 | } 101 | for (let j = 0; j < node.next.length; j++) { 102 | let edge = node.next[j]; 103 | let child = this.nodes[edge.index]; 104 | list.push({ 105 | path: drawService.drawEdge(node, child), 106 | weight: edge.weight, 107 | }); 108 | } 109 | } 110 | list.sort((a, b) => a.weight - b.weight); 111 | return list; 112 | } 113 | 114 | 115 | getPositionInMatrix(index) { 116 | for (let i = 0; i < this.matrix.length; i++) { 117 | for (let j = 0; j < this.matrix[i].length; j++) { 118 | if (this.matrix[i][j] == index) { 119 | return [i, j]; 120 | } 121 | } 122 | } 123 | return []; 124 | } 125 | 126 | /** 127 | * 计算每个点的坐标 128 | */ 129 | calculateAllPosition() { 130 | if (this.isTree()) { 131 | this.assignNodeForTree(0, 0, 0); 132 | } else { 133 | this.assignNodeForGraph(); 134 | } 135 | this.calCoordinateForMatrix(); 136 | } 137 | 138 | /** 139 | * 为图的每个节点计算坐标位置 140 | */ 141 | assignNodeForGraph() { 142 | // 查找最长的路径,并为其分配坐标 143 | let list = this.findLongestWay(0); 144 | list.forEach((it, index) => { 145 | this.matrix[0][index] = it; 146 | this.solvedList[it] = true; 147 | }); 148 | 149 | for (let i = 0; i < this.sortedList.length; i++) { 150 | let sindex = this.sortedList[i]; 151 | if (!this.solvedList[sindex]) { 152 | let fatherIndex = this.findSolvedFather(sindex); 153 | let [y, x] = this.getPositionInMatrix(fatherIndex); //找到父节点在矩阵中的坐标 154 | let list = this.findLongestWay(sindex); 155 | let startx = x + 1; 156 | 157 | let starty = y; 158 | while (this.matrix[starty][startx]) { 159 | starty++; 160 | } 161 | // starty-=1; 162 | list.forEach((it) => { 163 | this.matrix[starty][startx++] = it; 164 | this.solvedList[it] = true; 165 | }); 166 | } 167 | } 168 | } 169 | 170 | calCoordinateForMatrix() { 171 | for (let i = 0; i < this.matrix.length; i++) { 172 | for (let j = 0; j < this.matrix.length; j++) { 173 | let index = this.matrix[i][j]; 174 | if (index != undefined) { 175 | this.nodes[index].x = this.startx + this.xstep * j; 176 | this.nodes[index].y = this.starty + this.ystep * i; 177 | this.width = Math.max(this.width, this.nodes[index].x + this.startx); 178 | this.height = Math.max( 179 | this.height, 180 | this.nodes[index].y + this.starty 181 | ); 182 | } 183 | } 184 | } 185 | } 186 | 187 | /** 188 | * 优化节点的位置,使其在x轴上左右居中,线的处理上还有bug 189 | */ 190 | optimize() { 191 | for (let i = 0; i < this.nodes.length; i++) { 192 | let node = this.nodes[i]; 193 | if (node.y == this.starty) { 194 | // 第一行不变 195 | continue; 196 | } 197 | let parents = this.findParents(i); 198 | let children = this.findChildren(i); 199 | // eslint-disable-next-line no-console 200 | console.log(parents, children); 201 | let startx = Math.max(...parents.map((item) => this.nodes[item].x)); 202 | let endx = Math.min(...children.map((item) => this.nodes[item].x)); 203 | node.x = (startx + endx) / 2; 204 | this.nodes[i] = node; 205 | } 206 | } 207 | 208 | /** 209 | * 图的拓扑排序 210 | */ 211 | topologicalSorting() { 212 | let visited = []; 213 | let result = []; 214 | for (let i = 0; i < this.nodes.length; i++) { 215 | if (visited[i] == true) { 216 | continue; 217 | } 218 | let list = this.findParents(i); 219 | if (list.length == 0 || list.every((it) => visited[it] == true)) { 220 | visited[i] = true; 221 | result.push(i); 222 | i = 0; 223 | } 224 | } 225 | return result; 226 | } 227 | 228 | /** 229 | * 判断是否有环 230 | * 如果有环,返回true 231 | */ 232 | hasCircle() { 233 | let list = this.topologicalSorting(); 234 | return list.length < this.nodes.length; 235 | } 236 | 237 | /** 238 | * 往前找到第一个解决的父节点 239 | * @param {*} index 240 | */ 241 | findSolvedFather(index) { 242 | let list = this.findParents(index); 243 | if (list.length == 0) { 244 | return null; 245 | } 246 | for (let i = 0; i < list.length; i++) { 247 | if (this.solvedList[list[i]]) { 248 | return list[i]; 249 | } else { 250 | return this.findSolvedFather(list[i]); 251 | } 252 | } 253 | } 254 | 255 | /** 256 | * 查找某个顶点的父顶点 257 | * @param {*} nodes 258 | * @param {*} index 259 | */ 260 | findParents(index) { 261 | let arr = []; 262 | for (let i = 0; i < this.nodes.length; i++) { 263 | if ( 264 | this.nodes[i].next && 265 | this.nodes[i].next.some((it) => it.index == index) 266 | ) { 267 | arr.push(i); 268 | } 269 | } 270 | return arr; 271 | } 272 | 273 | findChildren(index) { 274 | if (!this.nodes[index].next) { 275 | return []; 276 | } 277 | return this.nodes[index].next.map((it) => it.index); 278 | } 279 | 280 | /** 281 | * 查找从第{index}个节点开始的最长路径,返回经过的未被计算位置的节点, 282 | * @param {*} index 283 | */ 284 | findLongestWay(index) { 285 | let children = this.findChildren(index); 286 | if (children.length == 0) { 287 | return [index]; 288 | } 289 | let arr = [], 290 | maxLength = 0; 291 | for (let i = 0; i < children.length; i++) { 292 | if (this.solvedList[children[i]]) { 293 | continue; 294 | } 295 | let list = this.findLongestWay(children[i]); 296 | if (list.length > maxLength) { 297 | maxLength = list.length; 298 | arr = list.slice(); 299 | } 300 | } 301 | return [index].concat(arr); 302 | } 303 | 304 | /** 305 | * 从第{index}个节点出发,深度优先搜索图 306 | * @param {*} nodes 307 | * @param {*} index 308 | */ 309 | dfs(index) { 310 | const queue = []; 311 | const visited = []; 312 | const result = []; 313 | visited[index] = true; 314 | queue.push(index); 315 | 316 | while (queue.length > 0) { 317 | let first = queue.pop(); 318 | visited[first] = true; 319 | console.log(first); 320 | result.push(first); 321 | let children = this.findChildren(first); 322 | for (let i = 0; i < children.length; i++) { 323 | let j = children[i]; 324 | if (!visited[j]) { 325 | queue.push(j); 326 | visited[j] = true; 327 | } 328 | } 329 | } 330 | return result; 331 | } 332 | } 333 | 334 | export { Pipeline }; 335 | -------------------------------------------------------------------------------- /demo/data.js: -------------------------------------------------------------------------------- 1 | const hue3 = { 2 | nodes: [ 3 | { 4 | name: "Start", 5 | hint: "1m23s", 6 | status: "start", 7 | next: [{ index: 1, weight: 2 }] 8 | }, 9 | { 10 | name: "Ammouncement Import", 11 | hint: "1m23s", 12 | status: "success", 13 | next: [ 14 | { index: 2, weight: 0 }, 15 | { index: 3, weight: 2 } 16 | ] 17 | }, 18 | { 19 | name: "Employee ID to Onboarding", 20 | hint: "2m23s", 21 | status: "failure", 22 | next: [{ index: 3, weight: 0 }] 23 | }, 24 | { 25 | name: "Personal Basic Info", 26 | hint: "2m23s", 27 | status: "paused", 28 | next: [{ index: 4, weight: 0 }] 29 | }, 30 | { name: "End ", hint: "2m23s", status: "end", next: [] } 31 | ] 32 | }; 33 | 34 | const hue1 = { 35 | nodes: [ 36 | { 37 | name: "Start", 38 | hint: "1m23s", 39 | status: "start", 40 | next: [{ index: 1, weight: 2 }] 41 | }, 42 | { 43 | name: "Ammouncement Import", 44 | hint: "1m23s", 45 | status: "success", 46 | next: [{ index: 2, weight: 2 }] 47 | }, 48 | { 49 | name: "Employee ID to Onboarding", 50 | hint: "2m23s", 51 | status: "success", 52 | next: [ 53 | { index: 3, weight: 2 }, 54 | { index: 4, weight: 2 }, 55 | { index: 5, weight: 2 }, 56 | { index: 6, weight: 0 }, 57 | { index: 7, weight: 1 }, 58 | { index: 8, weight: 2 }, 59 | { index: 9, weight: 0 } 60 | ] 61 | }, 62 | { 63 | name: "Personal Basic Info", 64 | hint: "2m23s", 65 | status: "unstable", 66 | next: [{ index: 10, weight: 2 }] 67 | }, 68 | { 69 | name: "地址信息", 70 | hint: "2m23s", 71 | status: "success", 72 | next: [{ index: 10, weight: 0 }] 73 | }, 74 | { 75 | name: "Family Info", 76 | hint: "2m23s", 77 | status: "failure", 78 | next: [{ index: 10, weight: 0 }] 79 | }, 80 | { 81 | name: "Education Info", 82 | hint: "2m23s", 83 | status: "running", 84 | next: [{ index: 10, weight: 0 }] 85 | }, 86 | { 87 | name: "Degree Info", 88 | hint: "2m23s", 89 | status: "failure", 90 | next: [{ index: 10, weight: 0 }] 91 | }, 92 | { 93 | name: "Career Info", 94 | hint: "2m23s", 95 | status: "failure", 96 | next: [{ index: 10, weight: 0 }] 97 | }, 98 | { 99 | name: "Qualification Info", 100 | hint: "2m23s", 101 | status: "failure", 102 | next: [{ index: 10, weight: 0 }] 103 | }, 104 | { name: "End", hint: "2m23s", status: "end", next: [] } 105 | ] 106 | }; 107 | 108 | const hue2 = { 109 | nodes: [ 110 | { 111 | name: "Start", 112 | hint: "1m23s", 113 | status: "success", 114 | next: [ 115 | { 116 | index: 1, 117 | weight: 0 118 | } 119 | ] 120 | }, 121 | { 122 | name: "Ammouncement Import", 123 | hint: "1m23s", 124 | status: "success", 125 | next: [2, 3, 4, 5, 6] 126 | }, 127 | { 128 | name: "Personal Basic Info", 129 | hint: "2m23s", 130 | status: "unstable", 131 | next: [4] 132 | }, 133 | { name: "Address Info", hint: "2m23s", status: "success", next: [7] }, 134 | { name: "Family Info", hint: "2m23s", status: "failure", next: [7] }, 135 | { name: "Family In", hint: "2m23s", status: "failure", next: [7] }, 136 | { name: "Education Info", hint: "2m23s", status: "success", next: [7] }, 137 | { name: "Degree Info", hint: "2m23s", status: "paused", next: [8] }, 138 | { name: "End", hint: "2m23s", status: "failure" } 139 | ] 140 | }; 141 | 142 | const sample = { 143 | nodes: [ 144 | { 145 | name: "test0", 146 | hint: "1m23s", 147 | status: "success", 148 | next: [{ index: 1, weight: 2 }] 149 | }, 150 | { 151 | name: "test1 long", 152 | hint: "1m23s", 153 | status: "success", 154 | next: [ 155 | { index: 2, weight: 2 }, 156 | { index: 3, weight: 2 }, 157 | { index: 4, weight: 2 } 158 | ] 159 | }, 160 | { 161 | name: "test2 hello world", 162 | hint: "2m23s", 163 | status: "unstable", 164 | next: [{ index: 5 }] 165 | }, 166 | { 167 | name: "test3", 168 | hint: "2m23s", 169 | status: "success", 170 | next: [{ index: 7, weight: 1 }] 171 | }, 172 | { 173 | name: "test4 ha", 174 | hint: "2m23s", 175 | status: "running", 176 | next: [{ index: 8 }, { index: 9 }] 177 | }, //4 178 | { name: "test5", hint: "2m23s", status: "failure", next: [{ index: 6 }] }, 179 | { name: "test6", hint: "2m23s", status: "success", next: [{ index: 10 }] }, //6 180 | { 181 | name: "test7 hello", 182 | hint: "2m23s", 183 | status: "paused", 184 | next: [{ index: 12 }, { index: 13 }] 185 | }, 186 | { 187 | name: "test8 2344", 188 | hint: "2m23s", 189 | status: "paused", 190 | next: [{ index: 14 }, { index: 15 }] 191 | }, //8 192 | { name: "test9", hint: "2m23s", status: "failure", next: [{ index: 16 }] }, 193 | { name: "test10", hint: "2m23s", status: "failure", next: [{ index: 11 }] }, //10 194 | { name: "test11", hint: "2m23s", status: "failure" }, 195 | { name: "test12", hint: "2m23s", status: "failure" }, 196 | { name: "test13", hint: "2m23s", status: "failure" }, 197 | { name: "test14", hint: "2m23s", status: "failure" }, 198 | { name: "test15", hint: "2m23s", status: "failure" }, 199 | { name: "test16", hint: "2m23s", status: "failure" } 200 | ] 201 | }; 202 | 203 | const sample3 = { 204 | nodes: [ 205 | { 206 | name: "test0", 207 | hint: "1m23s", 208 | status: "success", 209 | next: [{ index: 1, weight: 2 }] 210 | }, 211 | { 212 | name: "test1 long", 213 | hint: "1m23s", 214 | status: "success", 215 | next: [ 216 | { index: 2, weight: 2 }, 217 | { index: 3, weight: 2 }, 218 | { index: 4, weight: 2 } 219 | ] 220 | }, 221 | { 222 | name: "test2 hello world", 223 | hint: "2m23s", 224 | status: "unstable", 225 | next: [{ index: 5 }] 226 | }, 227 | { 228 | name: "test3", 229 | hint: "2m23s", 230 | status: "success", 231 | next: [{ index: 7, weight: 1 }] 232 | }, 233 | { 234 | name: "test4 ha", 235 | hint: "2m23s", 236 | status: "failure", 237 | next: [{ index: 8 }, { index: 9 }] 238 | }, //4 239 | { name: "test5", hint: "2m23s", status: "failure", next: [{ index: 6 }] }, 240 | { name: "test6", hint: "2m23s", status: "success", next: [{ index: 10 }] }, //6 241 | { 242 | name: "test7 hello", 243 | hint: "2m23s", 244 | status: "paused", 245 | next: [{ index: 10 }] 246 | }, 247 | { 248 | name: "test8 2344", 249 | hint: "2m23s", 250 | status: "paused", 251 | next: [{ index: 10 }] 252 | }, //8 253 | { name: "test9", hint: "2m23s", status: "failure", next: [{ index: 10 }] }, 254 | { name: "test10", hint: "2m23s", status: "failure", next: [{ index: 11 }] }, //10 255 | { name: "test11", hint: "2m23s", status: "failure" } 256 | ] 257 | }; 258 | const sample2 = { 259 | nodes: [ 260 | { 261 | name: "test0", 262 | hint: "1m23s", 263 | status: "success", 264 | next: [{ index: 1, weight: 2 }], 265 | x: 50, 266 | y: 55 267 | }, 268 | { 269 | name: "test1 long", 270 | hint: "1m23s", 271 | status: "success", 272 | next: [ 273 | { index: 2, weight: 2 }, 274 | { index: 3, weight: 2 }, 275 | { index: 4, weight: 2 } 276 | ], 277 | x: 170, 278 | y: 55 279 | }, 280 | { 281 | name: "test2 hello world", 282 | hint: "2m23s", 283 | status: "unstable", 284 | next: [{ index: 5 }], 285 | x: 290, 286 | y: 55 287 | }, 288 | { 289 | name: "test3", 290 | hint: "2m23s", 291 | status: "success", 292 | next: [{ index: 7, weight: 1 }], 293 | x: 290, 294 | y: 125 295 | }, 296 | { 297 | name: "test4 ha", 298 | hint: "2m23s", 299 | status: "failure", 300 | next: [{ index: 8 }, { index: 9 }], 301 | x: 290, 302 | y: 55 303 | }, 304 | { 305 | name: "test5", 306 | hint: "2m23s", 307 | status: "failure", 308 | next: [{ index: 6 }], 309 | x: 410, 310 | y: 55 311 | }, 312 | { 313 | name: "test6", 314 | hint: "2m23s", 315 | status: "running", 316 | next: [{ index: 10 }], 317 | x: 530, 318 | y: 55 319 | }, 320 | { 321 | name: "test7 hello", 322 | hint: "2m23s", 323 | status: "paused", 324 | next: [{ index: 12 }, { index: 13 }, { index: "10" }], 325 | x: 650, 326 | y: 55 327 | }, 328 | { 329 | name: "test8 2344", 330 | hint: "2m23s", 331 | status: "paused", 332 | next: [{ index: 14 }, { index: 15 }], 333 | x: 410, 334 | y: 55 335 | }, 336 | { 337 | name: "test9", 338 | hint: "2m23s", 339 | status: "failure", 340 | next: [{ index: 16 }], 341 | x: 410, 342 | y: 265 343 | }, 344 | { 345 | name: "test10", 346 | hint: "2m23s", 347 | status: "failure", 348 | next: [{ index: 11 }, { index: "11" }], 349 | x: 770, 350 | y: 55 351 | }, 352 | { name: "test11", hint: "2m23s", status: "failure", x: 890, y: 55 }, 353 | { name: "test12", hint: "2m23s", status: "failure", x: 530, y: 125 }, 354 | { name: "test13", hint: "2m23s", status: "failure", x: 530, y: 195 }, 355 | { 356 | name: "test14", 357 | hint: "2m23s", 358 | status: "failure", 359 | x: 530, 360 | y: 55, 361 | next: [{ index: "7" }] 362 | }, 363 | { 364 | name: "test15", 365 | hint: "2m23s", 366 | status: "failure", 367 | x: 530, 368 | y: 335, 369 | next: [{ index: "7" }] 370 | }, 371 | { name: "test16", hint: "2m23s", status: "failure", x: 530, y: 405 } 372 | ] 373 | }; 374 | 375 | module.exports = { 376 | hue3, 377 | hue2, 378 | hue1, 379 | sample, 380 | sample2, 381 | sample3, 382 | bug: [ 383 | { name: "0", next: [{ index: 1 }, { index: 3 }] }, 384 | { name: "1", next: [{ index: 2 }, { index: 3 }] }, 385 | { name: "2", next: [] }, 386 | { name: "3", next: [] } 387 | ] 388 | }; 389 | // export default hue1 390 | --------------------------------------------------------------------------------