├── .babelrc
├── .babelrc.build
├── .babelrc.dev
├── .gitignore
├── LICENSE
├── README.md
├── bundle.css
├── dist
├── vue-flowchart.css
├── vue-flowchart.esm.js
├── vue-flowchart.esm.js.map
├── vue-flowchart.js
└── vue-flowchart.js.map
├── examples
├── server.dev.js
└── simple
│ ├── app.vue
│ ├── index.html
│ ├── index.js
│ └── scene
│ ├── custom
│ └── CustomWidget.vue
│ └── scene.vue
├── index.js
├── lib
├── helpers
│ └── index.js
├── mixins
│ └── setState.js
└── src
│ ├── Engine.js
│ ├── components
│ ├── BasicNodeWidget.vue
│ ├── CanvasWidget.vue
│ ├── LinkWidget.vue
│ ├── NodeViewWidget.vue
│ ├── NodeWidget.vue
│ ├── PortWidget.vue
│ └── SVGWidget.vue
│ └── vue-flowchart.vue
├── package.json
├── rollup.config.js
├── test
└── fixtureDatas.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "es2017"],
3 | "plugins": [
4 | "transform-object-rest-spread"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.babelrc.build:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "es2015",
5 | {
6 | "modules": false
7 | }
8 | ], "es2017"],
9 | "plugins": [
10 | "transform-object-rest-spread"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.babelrc.dev:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["es2015", "es2017"],
3 | "plugins": [
4 | "transform-object-rest-spread"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Alexandre Bonaventure Geissmann
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # vue-flowchart
2 | This lib is under heavy development. It is not production-ready and API could change dramatically. Feel free to use it anyway.
3 | # Usage
4 | Soon
5 |
6 | ### Graph Data
7 | ```
8 | {
9 | links:[
10 | {
11 | id :String,
12 | source :String, // a node ID
13 | sourcePort :String,
14 | target: node3,
15 | targetPort: 'in'
16 | },
17 | ...
18 | ],
19 | nodes: [
20 | {
21 | id :String,
22 | type: :String, // default: 'default'
23 | data: {
24 | name: "Caption",
25 | outVariables: ['out']
26 | },
27 | x :Number, // in px
28 | y :Number, // in px
29 | },
30 | ],
31 | }
32 | ```
33 |
34 | # Example
35 |
36 |
37 | # TO-DO
38 |
39 |
40 | # Contributions
41 | are welcome :)
42 |
--------------------------------------------------------------------------------
/bundle.css:
--------------------------------------------------------------------------------
1 | .vue-flowchart {
2 | width: 100%;
3 | height: 100%; }
4 |
5 | .storm-flow-canvas {
6 | position: relative;
7 | flex-grow: 1;
8 | width: 100%;
9 | height: 100%;
10 | display: flex;
11 | cursor: move;
12 | overflow: hidden; }
13 | .storm-flow-canvas svg {
14 | position: absolute;
15 | height: 100%;
16 | width: 100%;
17 | transform-origin: 0 0;
18 | overflow: visible; }
19 | .storm-flow-canvas .node-view {
20 | top: 0;
21 | left: 0;
22 | right: 0;
23 | bottom: 0;
24 | position: absolute;
25 | pointer-events: none;
26 | transform-origin: 0 0; }
27 | .storm-flow-canvas .node {
28 | position: absolute;
29 | -webkit-touch-callout: none;
30 | /* iOS Safari */
31 | -webkit-user-select: none;
32 | /* Chrome/Safari/Opera */
33 | user-select: none;
34 | cursor: move;
35 | pointer-events: all; }
36 | .storm-flow-canvas .node.selected > * {
37 | border-color: #00c0ff;
38 | box-shadow: 0 0 10px rgba(0, 192, 255, 0.5); }
39 |
40 | @keyframes dash {
41 | from {
42 | stroke-dashoffset: 24; }
43 | to {
44 | stroke-dashoffset: 0; } }
45 | .storm-flow-canvas path {
46 | fill: none;
47 | stroke: black;
48 | pointer-events: all; }
49 | .storm-flow-canvas path.selected {
50 | stroke: #00c0ff !important;
51 | stroke-dasharray: 10,2;
52 | animation: dash 1s linear infinite; }
53 | .storm-flow-canvas .port {
54 | width: 15px;
55 | height: 15px;
56 | background: rgba(255, 255, 255, 0.1); }
57 | .storm-flow-canvas .port:hover, .storm-flow-canvas .port.selected {
58 | background: #c0ff00; }
59 | .storm-flow-canvas .basic-node {
60 | background-color: #1e1e1e;
61 | border-radius: 5px;
62 | font-family: Arial;
63 | color: white;
64 | border: solid 2px black;
65 | overflow: hidden;
66 | font-size: 11px;
67 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); }
68 | .storm-flow-canvas .basic-node .title {
69 | /* background-image: linear-gradient(rgba(black,0.1),rgba(black,0.2));*/
70 | background: rgba(0, 0, 0, 0.3);
71 | display: flex;
72 | white-space: nowrap; }
73 | .storm-flow-canvas .basic-node .title > * {
74 | align-self: center; }
75 | .storm-flow-canvas .basic-node .title .fa {
76 | padding: 5px;
77 | opacity: 0.2;
78 | cursor: pointer; }
79 | .storm-flow-canvas .basic-node .title .fa:hover {
80 | opacity: 1.0; }
81 | .storm-flow-canvas .basic-node .title .name {
82 | flex-grow: 1;
83 | padding: 5px 5px; }
84 | .storm-flow-canvas .basic-node .ports {
85 | display: flex;
86 | background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2)); }
87 | .storm-flow-canvas .basic-node .ports .in, .storm-flow-canvas .basic-node .ports .out {
88 | flex-grow: 1;
89 | display: flex;
90 | flex-direction: column; }
91 | .storm-flow-canvas .basic-node .ports .in-port, .storm-flow-canvas .basic-node .ports .out-port {
92 | display: flex;
93 | margin-top: 1px; }
94 | .storm-flow-canvas .basic-node .ports .in-port > *, .storm-flow-canvas .basic-node .ports .out-port > * {
95 | align-self: center; }
96 | .storm-flow-canvas .basic-node .ports .in-port .name, .storm-flow-canvas .basic-node .ports .out-port .name {
97 | padding: 0 5px; }
98 | .storm-flow-canvas .basic-node .ports .out-port {
99 | justify-content: flex-end; }
100 | .storm-flow-canvas .basic-node .ports .out-port .name {
101 | justify-content: flex-end;
102 | text-align: right; }
103 |
104 | .iframe {
105 | pointer-events: none;
106 | background-color: grey; }
107 |
--------------------------------------------------------------------------------
/dist/vue-flowchart.css:
--------------------------------------------------------------------------------
1 | .vue-flowchart {
2 | width: 100%;
3 | height: 100%; }
4 |
5 | .storm-flow-canvas {
6 | position: relative;
7 | flex-grow: 1;
8 | width: 100%;
9 | height: 100%;
10 | display: flex;
11 | cursor: move;
12 | overflow: hidden; }
13 | .storm-flow-canvas svg {
14 | position: absolute;
15 | height: 100%;
16 | width: 100%;
17 | transform-origin: 0 0;
18 | overflow: visible; }
19 | .storm-flow-canvas .node-view {
20 | top: 0;
21 | left: 0;
22 | right: 0;
23 | bottom: 0;
24 | position: absolute;
25 | pointer-events: none;
26 | transform-origin: 0 0; }
27 | .storm-flow-canvas .node {
28 | position: absolute;
29 | -webkit-touch-callout: none;
30 | /* iOS Safari */
31 | -webkit-user-select: none;
32 | /* Chrome/Safari/Opera */
33 | user-select: none;
34 | cursor: move;
35 | pointer-events: all; }
36 | .storm-flow-canvas .node.selected > * {
37 | border-color: #00c0ff;
38 | box-shadow: 0 0 10px rgba(0, 192, 255, 0.5); }
39 |
40 | @keyframes dash {
41 | from {
42 | stroke-dashoffset: 24; }
43 | to {
44 | stroke-dashoffset: 0; } }
45 | .storm-flow-canvas path {
46 | fill: none;
47 | stroke: black;
48 | pointer-events: all; }
49 | .storm-flow-canvas path.selected {
50 | stroke: #00c0ff !important;
51 | stroke-dasharray: 10,2;
52 | animation: dash 1s linear infinite; }
53 | .storm-flow-canvas .port {
54 | width: 15px;
55 | height: 15px;
56 | background: rgba(255, 255, 255, 0.1); }
57 | .storm-flow-canvas .port:hover, .storm-flow-canvas .port.selected {
58 | background: #c0ff00; }
59 | .storm-flow-canvas .basic-node {
60 | background-color: #1e1e1e;
61 | border-radius: 5px;
62 | font-family: Arial;
63 | color: white;
64 | border: solid 2px black;
65 | overflow: hidden;
66 | font-size: 11px;
67 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.5); }
68 | .storm-flow-canvas .basic-node .title {
69 | /* background-image: linear-gradient(rgba(black,0.1),rgba(black,0.2));*/
70 | background: rgba(0, 0, 0, 0.3);
71 | display: flex;
72 | white-space: nowrap; }
73 | .storm-flow-canvas .basic-node .title > * {
74 | align-self: center; }
75 | .storm-flow-canvas .basic-node .title .fa {
76 | padding: 5px;
77 | opacity: 0.2;
78 | cursor: pointer; }
79 | .storm-flow-canvas .basic-node .title .fa:hover {
80 | opacity: 1.0; }
81 | .storm-flow-canvas .basic-node .title .name {
82 | flex-grow: 1;
83 | padding: 5px 5px; }
84 | .storm-flow-canvas .basic-node .ports {
85 | display: flex;
86 | background-image: linear-gradient(rgba(0, 0, 0, 0.1), rgba(0, 0, 0, 0.2)); }
87 | .storm-flow-canvas .basic-node .ports .in, .storm-flow-canvas .basic-node .ports .out {
88 | flex-grow: 1;
89 | display: flex;
90 | flex-direction: column; }
91 | .storm-flow-canvas .basic-node .ports .in-port, .storm-flow-canvas .basic-node .ports .out-port {
92 | display: flex;
93 | margin-top: 1px; }
94 | .storm-flow-canvas .basic-node .ports .in-port > *, .storm-flow-canvas .basic-node .ports .out-port > * {
95 | align-self: center; }
96 | .storm-flow-canvas .basic-node .ports .in-port .name, .storm-flow-canvas .basic-node .ports .out-port .name {
97 | padding: 0 5px; }
98 | .storm-flow-canvas .basic-node .ports .out-port {
99 | justify-content: flex-end; }
100 | .storm-flow-canvas .basic-node .ports .out-port .name {
101 | justify-content: flex-end;
102 | text-align: right; }
103 |
104 | .iframe {
105 | pointer-events: none;
106 | background-color: grey; }
107 |
--------------------------------------------------------------------------------
/examples/server.dev.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexandreBonaventure/vue-flowchart/c66eebe90747cd0944344d4f7b7eb828aebe6c48/examples/server.dev.js
--------------------------------------------------------------------------------
/examples/simple/app.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 | div#app
14 | scene
15 |
16 |
17 |
30 |
--------------------------------------------------------------------------------
/examples/simple/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vue Flowchart
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/examples/simple/index.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 |
3 | //PLUGINS
4 |
5 |
6 | import app from './app.vue'
7 |
8 | const App = Vue.extend(app)
9 | const vm = new App().$mount('#app')
10 |
--------------------------------------------------------------------------------
/examples/simple/scene/custom/CustomWidget.vue:
--------------------------------------------------------------------------------
1 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | custom: {{ name }}
57 |
58 |
59 |
60 |
70 |
71 |
72 |
73 |
74 |
75 |
91 |
--------------------------------------------------------------------------------
/examples/simple/scene/scene.vue:
--------------------------------------------------------------------------------
1 |
65 |
66 |
67 |
68 | div.scene
69 | button(type="button", @click="addNode") Add a node
70 | button(type="button", @click="removeRandomNode") Remove a node
71 | vue-flowchart(
72 | ref="flowchart",
73 | :data="data",
74 | :node-templates="templates",
75 | :options="options",
76 | @link:select="log('selected link', $arguments)",
77 | @link:add="log('new link', $arguments)",
78 | @link:remove="log('deleted link', $arguments)",
79 | @node:select="log('selected node', $arguments)",
80 | @node:add="log('new node', $arguments)",
81 | @node:remove="log('deleted node', $arguments)",
82 | )
83 | //- customWidget(slot="custom")
84 |
85 |
86 |
87 |
94 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // export default {}
2 | export { default } from './lib/src/vue-flowchart.vue'
3 | export { default as portWidget } from './lib/src/components/PortWidget.vue'
4 | // export Engine from './lib/Engine.js'
5 |
--------------------------------------------------------------------------------
/lib/helpers/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AlexandreBonaventure/vue-flowchart/c66eebe90747cd0944344d4f7b7eb828aebe6c48/lib/helpers/index.js
--------------------------------------------------------------------------------
/lib/mixins/setState.js:
--------------------------------------------------------------------------------
1 | import { forEach } from 'lodash-es'
2 |
3 | export default {
4 | methods: {
5 | setState(data) {
6 | forEach(data, (val, key) => this.$set(this, key, val))
7 | }
8 | },
9 | }
10 |
--------------------------------------------------------------------------------
/lib/src/Engine.js:
--------------------------------------------------------------------------------
1 | import { defaults, filter, values, forEach, flatMap, find, mapValues } from 'lodash-es'
2 | import Vue from 'vue'
3 | /**
4 | * @author Dylan Vorster
5 | */
6 |
7 | const validatorNoop = (node) => { return Promise.resolve(true) }
8 |
9 | export default function() {
10 | return {
11 | state:{
12 | links:{},
13 | nodes:{},
14 | factories: {},
15 | canvas: null,
16 | offsetX:0,
17 | offsetY:0,
18 | zoom: 100,
19 | listeners:{},
20 | selectedLink: null,
21 | selectedNode: null,
22 |
23 | updatingNodes: null,
24 | updatingLinks: null,
25 | validators: {
26 | onNodeRemove: validatorNoop,
27 | onEdgeRemove: validatorNoop,
28 | onEdgeUpdate: validatorNoop,
29 | },
30 | },
31 |
32 | repaintLinks: function(links){
33 | Vue.set(this.state, 'updatingNodes', {});
34 | Vue.set(this.state, 'updatingLinks', {});
35 | links.forEach((link) => {
36 | Vue.set(this.state.updatingLinks, link.id, link);
37 | })
38 | this.update()
39 | },
40 |
41 | repaintNodes: function(nodes){
42 | // this.state.updatingNodes = {};
43 | // this.state.updatingLinks = {};
44 | Vue.set(this.state, 'updatingNodes', {});
45 | Vue.set(this.state, 'updatingLinks', {});
46 |
47 | //store the updating node is's
48 | nodes.forEach((node) => {
49 | Vue.set(this.state.updatingNodes, node.id, node);
50 | this.getNodeLinks(node).forEach((link) => {
51 | Vue.set(this.state.updatingLinks, link.id, link)
52 | if(link.points.length < 2) {
53 | return;
54 | }else{
55 | if(link.source !== null) {
56 | Vue.set(link.points, 0, this.getPortCenter(this.getNode(link.source),link.sourcePort))
57 | }
58 | if(link.target !== null) {
59 | Vue.set(link.points, link.points.length-1, this.getPortCenter(this.getNode(link.target),link.targetPort))
60 | }
61 | }
62 | })
63 | })
64 |
65 | this.update();
66 | },
67 |
68 | update: function(){
69 | this.fireEvent({type:'repaint'});
70 | },
71 |
72 | getNodeDimensions(){
73 | const nodes = this.state.nodes
74 | const dimensions = mapValues(nodes, (node, id) => {
75 | const el = this.getNodeElement(id)
76 | const { width, height } = el.getBoundingClientRect()
77 | return { width, height }
78 | })
79 | return dimensions
80 | },
81 |
82 | getRelativeMousePoint: function(event){
83 | var point = this.getRelativePoint(event.pageX,event.pageY);
84 | return {
85 | x:(point.x/(this.state.zoom/100.0))-this.state.offsetX,
86 | y:(point.y/(this.state.zoom/100.0))-this.state.offsetY
87 | };
88 | },
89 |
90 | getRelativePoint: function(x,y){
91 | var canvasRect = this.state.canvas.getBoundingClientRect();
92 | return {x: x-canvasRect.left,y:y-canvasRect.top};
93 | },
94 |
95 | fireEvent: function(event){
96 | forEach(this.state.listeners,function(listener){
97 | listener(event);
98 | });
99 | },
100 |
101 | removeListener: function(id){
102 | Vue.delete(this.state.listeners, id)
103 | },
104 |
105 | removeAllListeners: function(id){
106 | this.state.listeners = {}
107 | },
108 |
109 | registerListener: function(cb){
110 | var id = this.UID();
111 | this.state.listeners[id] = cb;
112 | return id;
113 | },
114 |
115 | setZoom: function(zoom){
116 | this.state.zoom = zoom;
117 | this.update();
118 | },
119 |
120 | setOffset: function(x,y){
121 | this.state.offsetX = x;
122 | this.state.offsetY = y;
123 | this.update();
124 | },
125 |
126 | loadModel: function(model){
127 | this.state.links = {};
128 | this.state.nodes = {};
129 |
130 | model.nodes.forEach(function(node){
131 | this.addNode(node);
132 | }.bind(this));
133 |
134 | model.links.forEach(function(link){
135 | this.addLink(link);
136 | }.bind(this));
137 | },
138 |
139 | generateLinkPoints(){
140 | forEach(this.state.links, (link) => {
141 | if(link.points.length === 0){
142 | link.points.push({
143 | ...this.getPortCenter(this.getNode(link.source),link.sourcePort),
144 | id: this.UID()
145 | });
146 | link.points.push({
147 | ...this.getPortCenter(this.getNode(link.target),link.targetPort),
148 | id: this.UID()
149 | });
150 | }
151 | })
152 | },
153 |
154 |
155 |
156 | updateNode: function(node){
157 |
158 | //find the links and move those as well
159 | this.getNodeLinks(node);
160 | this.fireEvent({type:'repaint'});
161 | },
162 |
163 | UID: function(){
164 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
165 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
166 | return v.toString(16);
167 | });
168 | },
169 |
170 | getNodeElement: function(id){
171 | return this.state.canvas.querySelector(`.node[data-nodeid="${id}"]`);
172 | },
173 | getNodePortElement: function(node,port){
174 | return this.state.canvas.querySelector('.port[data-name="'+port+'"][data-nodeid="'+node.id+'"]');
175 | },
176 |
177 | getNodePortLinks: function(node,port){
178 | var nodeID = this.getNodeID(node);
179 | var links = this.getNodeLinks(nodeID);
180 | return links.filter(function(link){
181 | if(link.target === nodeID && link.targetPort === port){
182 | return true;
183 | }
184 | else if(link.source === nodeID && link.sourcePort === port){
185 | return true;
186 | }
187 | return false;
188 | });
189 | },
190 |
191 | getNodeID: function(node){
192 | if(typeof node === 'object'){
193 | node = node.id;
194 | }
195 | return node;
196 | },
197 |
198 | getNodeLinks: function(node){
199 | var nodeID = this.getNodeID(node);
200 | return values(filter(this.state.links,function(link,index){
201 | return link.source == nodeID || link.target == nodeID;
202 | }));
203 | },
204 |
205 | removeLink(link, bypassValidation = false) {
206 | if (typeof link !== 'object') link = this.getLink(link)
207 | const validator$ = bypassValidation ? validatorNoop() : this.state.validators.onEdgeRemove(link)
208 | validator$.then(valid => {
209 | if (valid) {
210 | Vue.delete(this.state.links, link.id)
211 | this.update();
212 | this.fireEvent({
213 | type:'link:remove',
214 | data: link
215 | })
216 | }
217 | })
218 |
219 | },
220 |
221 | removeNode(node, bypassValidation = false){
222 | if (typeof node !== 'object') node = this.getNode(node)
223 | const validator$ = bypassValidation ? validatorNoop() : this.state.validators.onNodeRemove(node)
224 | validator$.then(valid => {
225 | if (valid) {
226 | //remove the links
227 | var links = this.getNodeLinks(node)
228 | links.forEach((link) => {
229 | this.removeLink(link, true)
230 | })
231 |
232 | //remove the node
233 | Vue.delete(this.state.nodes, node.id)
234 | // this.update()
235 | this.fireEvent({
236 | type:'node:remove',
237 | data: node
238 | })
239 | }
240 | })
241 | },
242 |
243 | getPortCenter: function(node,port){
244 | var sourceElement = this.getNodePortElement(node,port);
245 | var sourceRect = sourceElement.getBoundingClientRect();
246 |
247 | var rel = this.getRelativePoint(sourceRect.left,sourceRect.top);
248 |
249 | return {
250 | x: ((sourceElement.offsetWidth/2)+rel.x/(this.state.zoom/100.0)) -(this.state.offsetX),
251 | y: ((sourceElement.offsetHeight/2)+rel.y/(this.state.zoom/100.0)) -(this.state.offsetY)
252 | };
253 | },
254 |
255 | setSelectedNode: function(node){
256 | // this.state.selectedLink = null;
257 | this.state.selectedNode = node;
258 | if (node) {
259 | this.fireEvent({
260 | type:'node:select',
261 | data: node
262 | })
263 | }
264 |
265 | // this.state.updatingNodes = null;
266 | // this.state.updatingLinks = null;
267 | this.update();
268 | },
269 |
270 | setSelectedLink: function(link){
271 | // this.state.selectedNode = null;
272 | this.state.selectedLink = link;
273 | this.fireEvent({
274 | type:'link:select',
275 | data: link
276 | })
277 | // this.state.updatingNodes = null;
278 | // this.state.updatingLinks = null;
279 | this.update();
280 | },
281 |
282 | addLink: function(link){
283 | var FinalLink = link = {
284 | id: this.UID(),
285 | source: null,
286 | sourcePort: null,
287 | target: null,
288 | targetPort: null,
289 | points: [],
290 | ...link,
291 | }
292 |
293 | Vue.set(this.state.links, FinalLink.id, FinalLink)
294 | this.fireEvent({
295 | type:'link:add',
296 | data: FinalLink
297 | })
298 | return FinalLink;
299 | },
300 |
301 | addNode: function(node,event){
302 | var point = {x:0,y:0};
303 | if(event !== undefined){
304 | point = this.getRelativeMousePoint(event);
305 | }
306 |
307 | var FinalNode = defaults(node,{
308 | id: this.UID(),
309 | type: 'default',
310 | data:{},
311 | x: point.x,
312 | y: point.y
313 | });
314 | Vue.set(this.state.nodes, FinalNode.id, FinalNode)
315 | this.fireEvent({
316 | type:'node:add',
317 | data: FinalNode,
318 | })
319 |
320 | },
321 |
322 | getLink: function(id){
323 | return this.state.links[id];
324 | },
325 |
326 | getPoint: function(id){
327 | const allPoints = flatMap(this.state.links, ({ points }) => points)
328 | const point = find(allPoints, { id });
329 | console.log(point);
330 | return point
331 | },
332 |
333 | getNode: function(id){
334 | return this.state.nodes[id];
335 | },
336 |
337 | getNodeFactory: function(type){
338 | if(this.state.factories[type] === undefined){
339 | throw "Cannot find node factory for: "+type;
340 | }
341 | return this.state.factories[type];
342 | },
343 |
344 | registerNodeFactory: function(factory){
345 | var FinalModel = defaults(factory,{
346 | type: "factory",
347 | isPortAllowed: function(sourceNode,sourceport,targetNode,targetPort){
348 | return true;
349 | },
350 | generateModel: function(model,engine){
351 | return null;
352 | }
353 | });
354 | this.state.factories[FinalModel.type] = FinalModel;
355 | },
356 |
357 | registerValidators: function(validators){
358 | this.state.validators = defaults(
359 | validators,
360 | this.state.validators
361 | )
362 | }
363 | };
364 | };
365 |
--------------------------------------------------------------------------------
/lib/src/components/BasicNodeWidget.vue:
--------------------------------------------------------------------------------
1 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | {{ name }}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {{ getDisplay(port) }}
75 |
76 |
77 |
78 |
79 |
80 | {{ getDisplay(port) }}
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
93 |
--------------------------------------------------------------------------------
/lib/src/components/CanvasWidget.vue:
--------------------------------------------------------------------------------
1 |
242 |
243 |
244 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
393 |
--------------------------------------------------------------------------------
/lib/src/components/LinkWidget.vue:
--------------------------------------------------------------------------------
1 |
204 |
205 |
210 |
211 |
214 |
--------------------------------------------------------------------------------
/lib/src/components/NodeViewWidget.vue:
--------------------------------------------------------------------------------
1 |
66 |
73 |
74 |
75 |
78 |
--------------------------------------------------------------------------------
/lib/src/components/NodeWidget.vue:
--------------------------------------------------------------------------------
1 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
--------------------------------------------------------------------------------
/lib/src/components/PortWidget.vue:
--------------------------------------------------------------------------------
1 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
--------------------------------------------------------------------------------
/lib/src/components/SVGWidget.vue:
--------------------------------------------------------------------------------
1 |
36 |
37 |
38 |
39 |
42 |
43 |
44 |
45 |
51 |
--------------------------------------------------------------------------------
/lib/src/vue-flowchart.vue:
--------------------------------------------------------------------------------
1 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
123 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "vue-flowchart",
3 | "version": "0.1.1",
4 | "description": "vue-flowchart",
5 | "main": "dist/vue-flowchart.js",
6 | "module": "dist/vue-flowchart.esm.js",
7 | "scripts": {
8 | "test": "echo \"Error: no test specified\" && exit 1",
9 | "build:umd": "rollup -c --name vueFlowchart --input index.js --output dist/vue-flowchart.js --format umd",
10 | "build:es": "rollup -c --name vueFlowchart --input index.js --output dist/vue-flowchart.esm.js --format es",
11 | "build": "npm run build:umd && npm run build:es",
12 | "serve": "NODE_PATH=$NODE_PATH:lib:examples/simple budo examples/simple/index.js --open -d . --live -- -t [ vueify ] -g babelify",
13 | "//dist": "NODE_PATH=$NODE_PATH:examples/simple browserify -e examples/simple/index.js -o dist/index.js -t vueify -t [ babelify ] -t uglifyify -p [ minifyify --no-map ]"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "git+https://github.com/AlexandreBonaventure/vue-flowchart.git"
18 | },
19 | "keywords": [
20 | "vue",
21 | "vue.js",
22 | "flow",
23 | "chart",
24 | "diagram"
25 | ],
26 | "author": "Alexandre Bonaventure Geissmann",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/AlexandreBonaventure/vue-flowchart/issues"
30 | },
31 | "homepage": "https://github.com/AlexandreBonaventure/vue-flowchart#readme",
32 | "peerDependencies": {
33 | "vue": "2.x"
34 | },
35 | "dependencies": {
36 | "lodash-es": "^4.17.4"
37 | },
38 | "devDependencies": {
39 | "babel-plugin-external-helpers": "^6.8.0",
40 | "babel-plugin-transform-object-rest-spread": "^6.22.0",
41 | "babel-plugin-transform-runtime": "^6.15.0",
42 | "babel-preset-es2015": "^6.18.0",
43 | "babel-preset-es2017": "^6.22.0",
44 | "babelify": "^7.3.0",
45 | "babelify-external-helpers": "^1.1.0",
46 | "browserify": "^13.1.1",
47 | "browserify-shim": "^3.8.12",
48 | "budo": "^9.2.2",
49 | "bundle-collapser": "^1.2.1",
50 | "jade": "^1.11.0",
51 | "js-data": "^2.9.0",
52 | "minifyify": "^7.3.4",
53 | "node-sass": "^4.2.0",
54 | "rollup": "^0.36.1",
55 | "rollup-plugin-babel": "^2.7.1",
56 | "rollup-plugin-buble": "^0.15.0",
57 | "rollup-plugin-commonjs": "^7.0.0",
58 | "rollup-plugin-node-resolve": "^2.0.0",
59 | "rollup-plugin-scss": "^0.2.0",
60 | "rollup-plugin-vue": "2.3.1",
61 | "uglifyify": "^3.0.4",
62 | "vue": "2.2.4",
63 | "vue-hot-reload-api": "^1.3.3",
64 | "vueify": "^9.4.0",
65 | "vueify-insert-css": "^1.0.0",
66 | "vuex": "^2.0.0"
67 | },
68 | "browserify-shim": {}
69 | }
70 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel'
2 | import vue from 'rollup-plugin-vue'
3 | import nodeResolve from 'rollup-plugin-node-resolve';
4 | import commonjs from 'rollup-plugin-commonjs';
5 | import scss from 'rollup-plugin-scss';
6 | import buble from 'rollup-plugin-buble';
7 |
8 | export default {
9 | plugins: [
10 | vue({
11 | styleToImports: true,
12 | // css: null,
13 | }),
14 | scss({
15 | //Choose *one* of these possible "output:..." options
16 | // Default behaviour is to write all styles to the bundle destination where .js is replaced by .css
17 | // output: true,
18 | //
19 | // // Filename to write all styles to
20 | output: 'dist/vue-flowchart.css',
21 | //
22 | // // Callback that will be called ongenerate with two arguments:
23 | // // - styles: the contents of all style tags combined: 'body { color: green }'
24 | // // - styleNodes: an array of style objects: { filename: 'body { ... }' }
25 | // output: function (styles, styleNodes) {
26 | // writeFileSync('bundle.css', styles)
27 | // },
28 | //
29 | // // Disable any style output or callbacks, import as string
30 | // output: false
31 | }),
32 | // babel({
33 | // exclude: ['node_modules/**', '*.vue'],
34 | // }),
35 | buble({objectAssign: 'Object.assign'}),
36 | nodeResolve({
37 | browser: true,
38 | jsnext: true,
39 | main: true
40 | }),
41 | commonjs({
42 | include: 'node_modules/**',
43 | }),
44 | ],
45 | format: 'umd',
46 | external: [
47 | 'vue',
48 | ],
49 | sourceMap: true,
50 | }
51 |
--------------------------------------------------------------------------------
/test/fixtureDatas.js:
--------------------------------------------------------------------------------
1 | import EngineCore from '../lib/src/Engine.js'
2 |
3 | const Engine = EngineCore()
4 |
5 | export default function factory() {
6 | const model = {links:[],nodes: []};
7 |
8 | generateSet(model,0,0);
9 | // generateSet(model,800,0);
10 | // generateSet(model,1600,0);
11 | // generateSet(model,2400,0);
12 | //
13 | // generateSet(model,0,300);
14 | // generateSet(model,800,300);
15 | // generateSet(model,1600,300);
16 | // generateSet(model,2400,300);
17 | //
18 | // generateSet(model,0,600);
19 | // generateSet(model,800,600);
20 | // generateSet(model,1600,600);
21 | // generateSet(model,2400,600);
22 | //
23 | // generateSet(model,0,900);
24 | // generateSet(model,800,900);
25 | // generateSet(model,1600,900);
26 | // generateSet(model,2400,900);
27 |
28 | return model
29 |
30 |
31 | function generateSet(model,offsetX,offsetY) {
32 | var node1 = Engine.UID();
33 | var node2 = Engine.UID();
34 | var node3 = Engine.UID();
35 | var node4 = Engine.UID();
36 | var node5 = Engine.UID();
37 |
38 |
39 | model.links = model.links.concat([
40 | {
41 | id: Engine.UID(),
42 | source: node1,
43 | sourcePort: 'inout',
44 | target: node2,
45 | targetPort: 'in',
46 | },
47 | {
48 | id: Engine.UID(),
49 | source: node1,
50 | sourcePort: 'inout',
51 | target: node3,
52 | targetPort: 'in'
53 | },
54 | {
55 | id: Engine.UID(),
56 | source: node2,
57 | sourcePort: 'out',
58 | target: node4,
59 | targetPort: 'in'
60 | },
61 | {
62 | id: Engine.UID(),
63 | source: node4,
64 | sourcePort: 'out',
65 | target: node5,
66 | targetPort: 'default'
67 | },
68 | {
69 | id: Engine.UID(),
70 | source: node2,
71 | sourcePort: 'out',
72 | target: node5,
73 | targetPort: 'default'
74 | }
75 | ]);
76 |
77 | model.nodes = model.nodes.concat([
78 | {
79 | id:node1,
80 | type: 'custom',
81 | data: {
82 | name: "I'm custom",
83 | inOutVariables: ['inout']
84 | },
85 | x: Math.random(50) * 10 + offsetX,
86 | y: Math.random(50) * 10 + offsetY
87 | },
88 | {
89 | id:node2,
90 | type: 'default',
91 | data: {
92 | name: "Add Card to User",
93 | inPorts: ['in','in 2'],
94 | outPorts: ['out']
95 | },
96 | x:250 +offsetX,
97 | y:50 + offsetY
98 | },
99 | {
100 | id:node3,
101 | type: 'default',
102 | data: {
103 | color: 'rgb(0,192,255)',
104 | name: "Remove User",
105 | inPorts: ['in']
106 | },
107 | x:250 + offsetX,
108 | y:150 + offsetY
109 | },
110 | {
111 | id:node4,
112 | type: 'default',
113 | data: {
114 | color: 'rgb(0,192,255)',
115 | name: "Remove User",
116 | inPorts: ['in'],
117 | outPorts: ['out']
118 | },
119 | x:500 + offsetX,
120 | y:150 + offsetY
121 | },
122 | {
123 | id:node5,
124 | type: 'default',
125 | data: {
126 | color: 'rgb(192,255,0)',
127 | name: "Complex Action 2",
128 | // port: ['in','in2','in3']
129 | },
130 | x:800 + offsetX,
131 | y:100 + offsetY
132 | },
133 | ]);
134 | }
135 |
136 | }
137 |
--------------------------------------------------------------------------------