├── README.md
├── connect-gesture.html
├── connect-gesture.js
├── react-svg.html
└── react
├── font-awesome-unicode-map.js
├── index.html
├── photobooth.json.js
├── the-graph-app.js
├── the-graph-edge.js
├── the-graph-graph.js
├── the-graph-group.js
├── the-graph-node.js
├── the-graph-port.js
├── the-graph-svg.css
├── the-graph-tooltip.js
└── the-graph.js
/README.md:
--------------------------------------------------------------------------------
1 | prototyping
2 | ===========
3 |
4 | prototypes for making meemoo + dataflow + noflo
5 |
6 | The React nodal graph editor prototype is now a real project here: https://github.com/the-grid/the-graph
7 |
--------------------------------------------------------------------------------
/connect-gesture.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | connect gesture
5 |
6 |
7 |
8 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/connect-gesture.js:
--------------------------------------------------------------------------------
1 | (function(){
2 | "use strict";
3 |
4 | var w = window.innerWidth;
5 | var h = window.innerHeight;
6 |
7 | function randomInt (max) {
8 | return Math.floor(Math.random()*max);
9 | }
10 |
11 | // Make some random nodes
12 | var count = 5;
13 | var graph = {};
14 | var nodes = graph.nodes = [];
15 | var ioMax = 5;
16 | var nodeW = 75;
17 | var nodeH = 75;
18 | var nodeW2 = Math.floor(nodeW/2);
19 | var nodeH2 = Math.floor(nodeH/2);
20 | var margin = 10;
21 | var gridW = nodeW + margin*2;
22 | var gridH = nodeH + margin*2;
23 | var gridCols = Math.floor( (w-margin*2)/(nodeW+margin*2) );
24 | var gridRows = Math.floor( (h-margin*2)/(nodeH+margin*2) );
25 |
26 | // CSS grid bg
27 | document.body.style.backgroundSize = gridW+"px "+gridH+"px";
28 |
29 | var i, j, k;
30 | for (i=0; i 0) {
142 | event.clientX = event.touches[0].clientX;
143 | event.clientY = event.touches[0].clientY;
144 | }
145 | }
146 |
147 | function gestureStart (event) {
148 | event.preventDefault();
149 | var model = event.target.model;
150 | if (!model || model.type!=="node") { return; }
151 | gestureItem = model;
152 | var gesture = model.gesture = {};
153 | touchNormalize(event);
154 | gesture.startX = event.clientX;
155 | gesture.startY = event.clientY;
156 | gesture.action = null;
157 | };
158 |
159 | function gestureMove (event) {
160 | event.preventDefault();
161 | if (!gestureItem) { return; }
162 |
163 | console.log(event);
164 |
165 | var gesture = gestureItem.gesture;
166 | touchNormalize(event);
167 | gesture.deltaX = event.clientX - gesture.startX;
168 | gesture.deltaY = event.clientY - gesture.startY;
169 |
170 | if (!gesture.action) {
171 | if (Math.abs(gesture.deltaY) > threshold) {
172 | // Move
173 | gesture.action = "move";
174 | } else if (gesture.deltaX > threshold) {
175 | // Start wire from output
176 | gesture.action = "wire-out";
177 | nodeShowOutputs(gestureItem);
178 | } else if (gesture.deltaX < 0-threshold) {
179 | // Start wire from input
180 | gesture.action = "wire-in";
181 | nodeShowInputs(gestureItem);
182 | }
183 | }
184 | else {
185 | switch (gesture.action) {
186 | case "move":
187 | translate(gestureItem.view, event.clientX-nodeW2, event.clientY-nodeH2);
188 | break;
189 | case "wire-out":
190 | var model = event.target.model;
191 | if (event.type === "touchmove" && event.target===gestureItem.view) {
192 | var over = document.elementFromPoint(event.clientX, event.clientY);
193 | model = over.model;
194 | }
195 | if (!model) { break; }
196 | else if (model.type === "output" || model.type === "input") {
197 | portSelect(model);
198 | // Dont mouseover node
199 | event.stopPropagation();
200 | }
201 | else if (model.type === "node") {
202 | nodeShowInputs(model);
203 | }
204 | break;
205 | case "wire-in":
206 | var model = event.target.model;
207 | if (event.type === "touchmove" && event.target===gestureItem.view) {
208 | var over = document.elementFromPoint(event.clientX, event.clientY);
209 | model = over.model;
210 | }
211 | if (!model) { break; }
212 | else if (model.type === "output" || model.type === "input") {
213 | portSelect(model);
214 | // Dont mouseover node
215 | event.stopPropagation();
216 | }
217 | else if (model.type === "node") {
218 | nodeShowOutputs(model);
219 | }
220 | break;
221 | default:
222 | break;
223 | }
224 | }
225 | };
226 |
227 | function gestureStop (event) {
228 | console.log("stop", event);
229 | event.preventDefault();
230 | if (!gestureItem) { return; }
231 | if (gestureItem.gesture.action === "move"){
232 | //Snap
233 | if (event.changedTouches && event.changedTouches[0]) {
234 | event.clientX = event.changedTouches[0].clientX;
235 | event.clientY = event.changedTouches[0].clientY;
236 | }
237 | nodeSnapToGrid(gestureItem, event.clientX, event.clientY);
238 | } else {
239 | graphHidePorts(gestureItem.parent);
240 | }
241 | gestureItem = null;
242 | };
243 |
244 | // IO helpers
245 |
246 | function nodeBringToTop (node) {
247 | node.view.parentNode.appendChild(node.view);
248 | }
249 |
250 | function nodeShowInputs (node) {
251 | nodeBringToTop(node);
252 | if (node.insShown) { return; }
253 | for( var i=0; i
2 |
3 |
4 | redirect
5 |
6 |
7 |
8 | moved here
9 |
10 |
--------------------------------------------------------------------------------
/react/font-awesome-unicode-map.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | context.TheGraph.FONT_AWESOME = {
5 | "glass": "\uf000",
6 | "music": "\uf001",
7 | "search": "\uf002",
8 | "envelope-o": "\uf003",
9 | "heart": "\uf004",
10 | "star": "\uf005",
11 | "star-o": "\uf006",
12 | "user": "\uf007",
13 | "film": "\uf008",
14 | "th-large": "\uf009",
15 | "th": "\uf00a",
16 | "th-list": "\uf00b",
17 | "check": "\uf00c",
18 | "times": "\uf00d",
19 | "search-plus": "\uf00e",
20 | "search-minus": "\uf010",
21 | "power-off": "\uf011",
22 | "signal": "\uf012",
23 | "cog": "\uf013",
24 | "trash-o": "\uf014",
25 | "home": "\uf015",
26 | "file-o": "\uf016",
27 | "clock-o": "\uf017",
28 | "road": "\uf018",
29 | "download": "\uf019",
30 | "arrow-circle-o-down": "\uf01a",
31 | "arrow-circle-o-up": "\uf01b",
32 | "inbox": "\uf01c",
33 | "play-circle-o": "\uf01d",
34 | "repeat": "\uf01e",
35 | "refresh": "\uf021",
36 | "list-alt": "\uf022",
37 | "lock": "\uf023",
38 | "flag": "\uf024",
39 | "headphones": "\uf025",
40 | "volume-off": "\uf026",
41 | "volume-down": "\uf027",
42 | "volume-up": "\uf028",
43 | "qrcode": "\uf029",
44 | "barcode": "\uf02a",
45 | "tag": "\uf02b",
46 | "tags": "\uf02c",
47 | "book": "\uf02d",
48 | "bookmark": "\uf02e",
49 | "print": "\uf02f",
50 | "camera": "\uf030",
51 | "font": "\uf031",
52 | "bold": "\uf032",
53 | "italic": "\uf033",
54 | "text-height": "\uf034",
55 | "text-width": "\uf035",
56 | "align-left": "\uf036",
57 | "align-center": "\uf037",
58 | "align-right": "\uf038",
59 | "align-justify": "\uf039",
60 | "list": "\uf03a",
61 | "outdent": "\uf03b",
62 | "indent": "\uf03c",
63 | "video-camera": "\uf03d",
64 | "picture-o": "\uf03e",
65 | "pencil": "\uf040",
66 | "map-marker": "\uf041",
67 | "adjust": "\uf042",
68 | "tint": "\uf043",
69 | "pencil-square-o": "\uf044",
70 | "share-square-o": "\uf045",
71 | "check-square-o": "\uf046",
72 | "arrows": "\uf047",
73 | "step-backward": "\uf048",
74 | "fast-backward": "\uf049",
75 | "backward": "\uf04a",
76 | "play": "\uf04b",
77 | "pause": "\uf04c",
78 | "stop": "\uf04d",
79 | "forward": "\uf04e",
80 | "fast-forward": "\uf050",
81 | "step-forward": "\uf051",
82 | "eject": "\uf052",
83 | "chevron-left": "\uf053",
84 | "chevron-right": "\uf054",
85 | "plus-circle": "\uf055",
86 | "minus-circle": "\uf056",
87 | "times-circle": "\uf057",
88 | "check-circle": "\uf058",
89 | "question-circle": "\uf059",
90 | "info-circle": "\uf05a",
91 | "crosshairs": "\uf05b",
92 | "times-circle-o": "\uf05c",
93 | "check-circle-o": "\uf05d",
94 | "ban": "\uf05e",
95 | "arrow-left": "\uf060",
96 | "arrow-right": "\uf061",
97 | "arrow-up": "\uf062",
98 | "arrow-down": "\uf063",
99 | "share": "\uf064",
100 | "expand": "\uf065",
101 | "compress": "\uf066",
102 | "plus": "\uf067",
103 | "minus": "\uf068",
104 | "asterisk": "\uf069",
105 | "exclamation-circle": "\uf06a",
106 | "gift": "\uf06b",
107 | "leaf": "\uf06c",
108 | "fire": "\uf06d",
109 | "eye": "\uf06e",
110 | "eye-slash": "\uf070",
111 | "exclamation-triangle": "\uf071",
112 | "plane": "\uf072",
113 | "calendar": "\uf073",
114 | "random": "\uf074",
115 | "comment": "\uf075",
116 | "magnet": "\uf076",
117 | "chevron-up": "\uf077",
118 | "chevron-down": "\uf078",
119 | "retweet": "\uf079",
120 | "shopping-cart": "\uf07a",
121 | "folder": "\uf07b",
122 | "folder-open": "\uf07c",
123 | "arrows-v": "\uf07d",
124 | "arrows-h": "\uf07e",
125 | "bar-chart-o": "\uf080",
126 | "twitter-square": "\uf081",
127 | "facebook-square": "\uf082",
128 | "camera-retro": "\uf083",
129 | "key": "\uf084",
130 | "cogs": "\uf085",
131 | "comments": "\uf086",
132 | "thumbs-o-up": "\uf087",
133 | "thumbs-o-down": "\uf088",
134 | "star-half": "\uf089",
135 | "heart-o": "\uf08a",
136 | "sign-out": "\uf08b",
137 | "linkedin-square": "\uf08c",
138 | "thumb-tack": "\uf08d",
139 | "external-link": "\uf08e",
140 | "sign-in": "\uf090",
141 | "trophy": "\uf091",
142 | "github-square": "\uf092",
143 | "upload": "\uf093",
144 | "lemon-o": "\uf094",
145 | "phone": "\uf095",
146 | "square-o": "\uf096",
147 | "bookmark-o": "\uf097",
148 | "phone-square": "\uf098",
149 | "twitter": "\uf099",
150 | "facebook": "\uf09a",
151 | "github": "\uf09b",
152 | "unlock": "\uf09c",
153 | "credit-card": "\uf09d",
154 | "rss": "\uf09e",
155 | "hdd-o": "\uf0a0",
156 | "bullhorn": "\uf0a1",
157 | "bell": "\uf0f3",
158 | "certificate": "\uf0a3",
159 | "hand-o-right": "\uf0a4",
160 | "hand-o-left": "\uf0a5",
161 | "hand-o-up": "\uf0a6",
162 | "hand-o-down": "\uf0a7",
163 | "arrow-circle-left": "\uf0a8",
164 | "arrow-circle-right": "\uf0a9",
165 | "arrow-circle-up": "\uf0aa",
166 | "arrow-circle-down": "\uf0ab",
167 | "globe": "\uf0ac",
168 | "wrench": "\uf0ad",
169 | "tasks": "\uf0ae",
170 | "filter": "\uf0b0",
171 | "briefcase": "\uf0b1",
172 | "arrows-alt": "\uf0b2",
173 | "users": "\uf0c0",
174 | "link": "\uf0c1",
175 | "cloud": "\uf0c2",
176 | "flask": "\uf0c3",
177 | "scissors": "\uf0c4",
178 | "files-o": "\uf0c5",
179 | "paperclip": "\uf0c6",
180 | "floppy-o": "\uf0c7",
181 | "square": "\uf0c8",
182 | "bars": "\uf0c9",
183 | "list-ul": "\uf0ca",
184 | "list-ol": "\uf0cb",
185 | "strikethrough": "\uf0cc",
186 | "underline": "\uf0cd",
187 | "table": "\uf0ce",
188 | "magic": "\uf0d0",
189 | "truck": "\uf0d1",
190 | "pinterest": "\uf0d2",
191 | "pinterest-square": "\uf0d3",
192 | "google-plus-square": "\uf0d4",
193 | "google-plus": "\uf0d5",
194 | "money": "\uf0d6",
195 | "caret-down": "\uf0d7",
196 | "caret-up": "\uf0d8",
197 | "caret-left": "\uf0d9",
198 | "caret-right": "\uf0da",
199 | "columns": "\uf0db",
200 | "sort": "\uf0dc",
201 | "sort-asc": "\uf0dd",
202 | "sort-desc": "\uf0de",
203 | "envelope": "\uf0e0",
204 | "linkedin": "\uf0e1",
205 | "undo": "\uf0e2",
206 | "gavel": "\uf0e3",
207 | "tachometer": "\uf0e4",
208 | "comment-o": "\uf0e5",
209 | "comments-o": "\uf0e6",
210 | "bolt": "\uf0e7",
211 | "sitemap": "\uf0e8",
212 | "umbrella": "\uf0e9",
213 | "clipboard": "\uf0ea",
214 | "lightbulb-o": "\uf0eb",
215 | "exchange": "\uf0ec",
216 | "cloud-download": "\uf0ed",
217 | "cloud-upload": "\uf0ee",
218 | "user-md": "\uf0f0",
219 | "stethoscope": "\uf0f1",
220 | "suitcase": "\uf0f2",
221 | "bell-o": "\uf0a2",
222 | "coffee": "\uf0f4",
223 | "cutlery": "\uf0f5",
224 | "file-text-o": "\uf0f6",
225 | "building-o": "\uf0f7",
226 | "hospital-o": "\uf0f8",
227 | "ambulance": "\uf0f9",
228 | "medkit": "\uf0fa",
229 | "fighter-jet": "\uf0fb",
230 | "beer": "\uf0fc",
231 | "h-square": "\uf0fd",
232 | "plus-square": "\uf0fe",
233 | "angle-double-left": "\uf100",
234 | "angle-double-right": "\uf101",
235 | "angle-double-up": "\uf102",
236 | "angle-double-down": "\uf103",
237 | "angle-left": "\uf104",
238 | "angle-right": "\uf105",
239 | "angle-up": "\uf106",
240 | "angle-down": "\uf107",
241 | "desktop": "\uf108",
242 | "laptop": "\uf109",
243 | "tablet": "\uf10a",
244 | "mobile": "\uf10b",
245 | "circle-o": "\uf10c",
246 | "quote-left": "\uf10d",
247 | "quote-right": "\uf10e",
248 | "spinner": "\uf110",
249 | "circle": "\uf111",
250 | "reply": "\uf112",
251 | "github-alt": "\uf113",
252 | "folder-o": "\uf114",
253 | "folder-open-o": "\uf115",
254 | "smile-o": "\uf118",
255 | "frown-o": "\uf119",
256 | "meh-o": "\uf11a",
257 | "gamepad": "\uf11b",
258 | "keyboard-o": "\uf11c",
259 | "flag-o": "\uf11d",
260 | "flag-checkered": "\uf11e",
261 | "terminal": "\uf120",
262 | "code": "\uf121",
263 | "reply-all": "\uf122",
264 | "mail-reply-all": "\uf122",
265 | "star-half-o": "\uf123",
266 | "location-arrow": "\uf124",
267 | "crop": "\uf125",
268 | "code-fork": "\uf126",
269 | "chain-broken": "\uf127",
270 | "question": "\uf128",
271 | "info": "\uf129",
272 | "exclamation": "\uf12a",
273 | "superscript": "\uf12b",
274 | "subscript": "\uf12c",
275 | "eraser": "\uf12d",
276 | "puzzle-piece": "\uf12e",
277 | "microphone": "\uf130",
278 | "microphone-slash": "\uf131",
279 | "shield": "\uf132",
280 | "calendar-o": "\uf133",
281 | "fire-extinguisher": "\uf134",
282 | "rocket": "\uf135",
283 | "maxcdn": "\uf136",
284 | "chevron-circle-left": "\uf137",
285 | "chevron-circle-right": "\uf138",
286 | "chevron-circle-up": "\uf139",
287 | "chevron-circle-down": "\uf13a",
288 | "html5": "\uf13b",
289 | "css3": "\uf13c",
290 | "anchor": "\uf13d",
291 | "unlock-alt": "\uf13e",
292 | "bullseye": "\uf140",
293 | "ellipsis-h": "\uf141",
294 | "ellipsis-v": "\uf142",
295 | "rss-square": "\uf143",
296 | "play-circle": "\uf144",
297 | "ticket": "\uf145",
298 | "minus-square": "\uf146",
299 | "minus-square-o": "\uf147",
300 | "level-up": "\uf148",
301 | "level-down": "\uf149",
302 | "check-square": "\uf14a",
303 | "pencil-square": "\uf14b",
304 | "external-link-square": "\uf14c",
305 | "share-square": "\uf14d",
306 | "compass": "\uf14e",
307 | "caret-square-o-down": "\uf150",
308 | "caret-square-o-up": "\uf151",
309 | "caret-square-o-right": "\uf152",
310 | "eur": "\uf153",
311 | "gbp": "\uf154",
312 | "usd": "\uf155",
313 | "inr": "\uf156",
314 | "jpy": "\uf157",
315 | "rub": "\uf158",
316 | "krw": "\uf159",
317 | "btc": "\uf15a",
318 | "file": "\uf15b",
319 | "file-text": "\uf15c",
320 | "sort-alpha-asc": "\uf15d",
321 | "sort-alpha-desc": "\uf15e",
322 | "sort-amount-asc": "\uf160",
323 | "sort-amount-desc": "\uf161",
324 | "sort-numeric-asc": "\uf162",
325 | "sort-numeric-desc": "\uf163",
326 | "thumbs-up": "\uf164",
327 | "thumbs-down": "\uf165",
328 | "youtube-square": "\uf166",
329 | "youtube": "\uf167",
330 | "xing": "\uf168",
331 | "xing-square": "\uf169",
332 | "youtube-play": "\uf16a",
333 | "dropbox": "\uf16b",
334 | "stack-overflow": "\uf16c",
335 | "instagram": "\uf16d",
336 | "flickr": "\uf16e",
337 | "adn": "\uf170",
338 | "bitbucket": "\uf171",
339 | "bitbucket-square": "\uf172",
340 | "tumblr": "\uf173",
341 | "tumblr-square": "\uf174",
342 | "long-arrow-down": "\uf175",
343 | "long-arrow-up": "\uf176",
344 | "long-arrow-left": "\uf177",
345 | "long-arrow-right": "\uf178",
346 | "apple": "\uf179",
347 | "windows": "\uf17a",
348 | "android": "\uf17b",
349 | "linux": "\uf17c",
350 | "dribbble": "\uf17d",
351 | "skype": "\uf17e",
352 | "foursquare": "\uf180",
353 | "trello": "\uf181",
354 | "female": "\uf182",
355 | "male": "\uf183",
356 | "gittip": "\uf184",
357 | "sun-o": "\uf185",
358 | "moon-o": "\uf186",
359 | "archive": "\uf187",
360 | "bug": "\uf188",
361 | "vk": "\uf189",
362 | "weibo": "\uf18a",
363 | "renren": "\uf18b",
364 | "pagelines": "\uf18c",
365 | "stack-exchange": "\uf18d",
366 | "arrow-circle-o-right": "\uf18e",
367 | "arrow-circle-o-left": "\uf190",
368 | "caret-square-o-left": "\uf191",
369 | "dot-circle-o": "\uf192",
370 | "wheelchair": "\uf193",
371 | "vimeo-square": "\uf194",
372 | "try": "\uf195",
373 | "plus-square-o": "\uf196"
374 | }
375 |
376 | })(this);
--------------------------------------------------------------------------------
/react/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | react + svg test
5 |
6 |
7 |
8 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
source ~
40 | wheel to zoom ~
41 |
42 | drag to pan
43 |
44 |
45 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/react/photobooth.json.js:
--------------------------------------------------------------------------------
1 | loadGraph({
2 | "id": "ti6yn",
3 | "project": "",
4 | "properties": {
5 | "name": "photobooth",
6 | "environment": {
7 | "runtime": "html",
8 | "src": "preview/iframe.html",
9 | "width": 300,
10 | "height": 300,
11 | "content": " \n \n\n\n \n \n \n \n\n\n"
12 | }
13 | },
14 | "exports": [],
15 | "groups": [
16 | {
17 | "name": "elements",
18 | "nodes": ["dom/GetElement_ah82a", "dom/GetElement_f4nkd", "dom/GetElement_z64xf"],
19 | "metadata": {
20 | "description": "get the elements from the dom"
21 | }
22 | },
23 | {
24 | "name": "countdown",
25 | "nodes": ["core/Split_lbwyz", "interaction/ListenMouse_1u0rk", "strings/SendString_zry4n", "core/RunTimeout_3wulz", "dom/AddClass_9rihj", "core/Split_ho5ib", "strings/SendString_lnf0z", "core/Kick_7efi8", "dom/RemoveClass_ec7ug"],
26 | "metadata": {
27 | "description": ""
28 | }
29 | },
30 | {
31 | "name": "changefilter",
32 | "nodes": ["dom/GetElement_e16dy", "dom/GetElement_85so0", "interaction/ListenMouse_bil4d", "interaction/ListenMouse_aii7r"],
33 | "metadata": {
34 | "description": ""
35 | }
36 | },
37 | {
38 | "name": "save",
39 | "nodes": ["dom/GetElement_ah36i", "core/MakeFunction_t17n", "core/Split_xyb8x", "strings/SendString_7g9h8", "dom/GetElement_4houj", "core/RepeatAsync_grqs3", "dom/CreateElement_sf6ec", "dom/SetAttribute_3bmlw"],
40 | "metadata": {
41 | "description": ""
42 | }
43 | },
44 | {
45 | "name": "filter",
46 | "nodes": ["dom/GetElement_j674o", "routers/KickRouter_bzaiw", "interaction/ListenChange_z7m5u", "math/Multiply_rbxrn", "math/Multiply_3v13k", "seriously/HueSaturation_bzfvt", "core/Split_jx318", "math/Multiply_3eldx", "seriously/TVGlitch_e1qre", "seriously/Hex_lx162", "seriously/Ascii_17c9q", "seriously/Invert_7xnl3", "seriously/Edge_egmkb", "seriously/Split_7oj15", "seriously/SetTarget_kii7s"],
47 | "metadata": {
48 | "description": ""
49 | }
50 | }
51 | ],
52 | "processes": {
53 | "dom/GetElement_f4nkd": {
54 | "component": "dom/GetElement",
55 | "metadata": {
56 | "x": 324,
57 | "y": 144,
58 | "label": "startButton"
59 | }
60 | },
61 | "interaction/ListenMouse_1w3vt": {
62 | "component": "interaction/ListenMouse",
63 | "metadata": {
64 | "x": 324,
65 | "y": 288,
66 | "label": "clickStart"
67 | }
68 | },
69 | "gum/GetUserMedia_9e9i4": {
70 | "component": "gum/GetUserMedia",
71 | "metadata": {
72 | "x": 324,
73 | "y": 648,
74 | "label": "webCam"
75 | }
76 | },
77 | "dom/GetElement_z64xf": {
78 | "component": "dom/GetElement",
79 | "metadata": {
80 | "x": 504,
81 | "y": 144,
82 | "label": "videoEl"
83 | }
84 | },
85 | "dom/SetAttribute_uto4k": {
86 | "component": "dom/SetAttribute",
87 | "metadata": {
88 | "x": 432,
89 | "y": 648,
90 | "label": "setVideoSrc"
91 | }
92 | },
93 | "seriously/SetSource_szf33": {
94 | "component": "seriously/SetSource",
95 | "metadata": {
96 | "x": 576,
97 | "y": 648,
98 | "label": "setFilterSource"
99 | }
100 | },
101 | "seriously/Ascii_17c9q": {
102 | "component": "seriously/Ascii",
103 | "metadata": {
104 | "x": 1080,
105 | "y": 396,
106 | "label": "seriously/Ascii"
107 | }
108 | },
109 | "dom/GetElement_ah82a": {
110 | "component": "dom/GetElement",
111 | "metadata": {
112 | "x": 1404,
113 | "y": 144,
114 | "label": "canvasEl"
115 | }
116 | },
117 | "seriously/SetTarget_kii7s": {
118 | "component": "seriously/SetTarget",
119 | "metadata": {
120 | "x": 1404,
121 | "y": 648,
122 | "label": "filterTarget"
123 | }
124 | },
125 | "dom/GetElement_85so0": {
126 | "component": "dom/GetElement",
127 | "metadata": {
128 | "x": 720,
129 | "y": 144,
130 | "label": "prevButton"
131 | }
132 | },
133 | "interaction/ListenMouse_aii7r": {
134 | "component": "interaction/ListenMouse",
135 | "metadata": {
136 | "x": 720,
137 | "y": 288,
138 | "label": "clickPrev"
139 | }
140 | },
141 | "dom/GetElement_e16dy": {
142 | "component": "dom/GetElement",
143 | "metadata": {
144 | "x": 864,
145 | "y": 144,
146 | "label": "nextButton"
147 | }
148 | },
149 | "interaction/ListenMouse_bil4d": {
150 | "component": "interaction/ListenMouse",
151 | "metadata": {
152 | "x": 864,
153 | "y": 288,
154 | "label": "clickNext"
155 | }
156 | },
157 | "routers/KickRouter_bzaiw": {
158 | "component": "routers/KickRouter",
159 | "metadata": {
160 | "x": 828,
161 | "y": 648,
162 | "label": "chooseFilter"
163 | }
164 | },
165 | "seriously/TVGlitch_e1qre": {
166 | "component": "seriously/TVGlitch",
167 | "metadata": {
168 | "x": 1080,
169 | "y": 504,
170 | "label": "seriously/TVGlitch"
171 | }
172 | },
173 | "seriously/Hex_lx162": {
174 | "component": "seriously/Hex",
175 | "metadata": {
176 | "x": 1008,
177 | "y": 612,
178 | "label": "seriously/Hex"
179 | }
180 | },
181 | "seriously/Edge_egmkb": {
182 | "component": "seriously/Edge",
183 | "metadata": {
184 | "x": 1152,
185 | "y": 612,
186 | "label": "seriously/Edge"
187 | }
188 | },
189 | "seriously/Invert_7xnl3": {
190 | "component": "seriously/Invert",
191 | "metadata": {
192 | "x": 1008,
193 | "y": 828,
194 | "label": "negative"
195 | }
196 | },
197 | "seriously/Split_7oj15": {
198 | "component": "seriously/Split",
199 | "metadata": {
200 | "x": 1152,
201 | "y": 756,
202 | "label": "halfScreen"
203 | }
204 | },
205 | "core/Split_jx318": {
206 | "component": "core/Split",
207 | "metadata": {
208 | "x": 1008,
209 | "y": 720,
210 | "label": "core/Split"
211 | }
212 | },
213 | "core/Split_occbw": {
214 | "component": "core/Split",
215 | "metadata": {
216 | "x": 684,
217 | "y": 648,
218 | "label": "core/Split"
219 | }
220 | },
221 | "core/Split_y0bla": {
222 | "component": "core/Split",
223 | "metadata": {
224 | "x": 504,
225 | "y": 468,
226 | "label": "core/Split"
227 | }
228 | },
229 | "seriously/HueSaturation_bzfvt": {
230 | "component": "seriously/HueSaturation",
231 | "metadata": {
232 | "x": 1080,
233 | "y": 972,
234 | "label": "seriously/HueSaturation"
235 | }
236 | },
237 | "core/Split_jzzu2": {
238 | "component": "core/Split",
239 | "metadata": {
240 | "x": 1404,
241 | "y": 288,
242 | "label": "core/Split"
243 | }
244 | },
245 | "core/Kick_7efi8": {
246 | "component": "core/Kick",
247 | "metadata": {
248 | "x": 1476,
249 | "y": 792,
250 | "label": "sendCanvas"
251 | }
252 | },
253 | "core/MakeFunction_t17n": {
254 | "component": "core/MakeFunction",
255 | "metadata": {
256 | "x": 1296,
257 | "y": 936,
258 | "label": "canvasToJPEG"
259 | }
260 | },
261 | "dom/GetElement_ah36i": {
262 | "component": "dom/GetElement",
263 | "metadata": {
264 | "x": 1584,
265 | "y": 144,
266 | "label": "saveButton"
267 | }
268 | },
269 | "interaction/ListenMouse_1u0rk": {
270 | "component": "interaction/ListenMouse",
271 | "metadata": {
272 | "x": 1584,
273 | "y": 288,
274 | "label": "clickSave"
275 | }
276 | },
277 | "dom/CreateElement_sf6ec": {
278 | "component": "dom/CreateElement",
279 | "metadata": {
280 | "x": 1584,
281 | "y": 1008,
282 | "label": "dom/CreateElement"
283 | }
284 | },
285 | "dom/SetAttribute_3bmlw": {
286 | "component": "dom/SetAttribute",
287 | "metadata": {
288 | "x": 1584,
289 | "y": 1152,
290 | "label": "dom/SetAttribute"
291 | }
292 | },
293 | "core/RepeatAsync_grqs3": {
294 | "component": "core/RepeatAsync",
295 | "metadata": {
296 | "x": 1440,
297 | "y": 1152,
298 | "label": "core/RepeatAsync"
299 | }
300 | },
301 | "dom/GetElement_4houj": {
302 | "component": "dom/GetElement",
303 | "metadata": {
304 | "x": 1584,
305 | "y": 792,
306 | "label": "savedEl"
307 | }
308 | },
309 | "core/Split_xyb8x": {
310 | "component": "core/Split",
311 | "metadata": {
312 | "x": 1296,
313 | "y": 1080,
314 | "label": "core/Split"
315 | }
316 | },
317 | "strings/SendString_7g9h8": {
318 | "component": "strings/SendString",
319 | "metadata": {
320 | "x": 1440,
321 | "y": 1008,
322 | "label": "strings/SendString"
323 | }
324 | },
325 | "core/RepeatAsync_647ff": {
326 | "component": "core/RepeatAsync",
327 | "metadata": {
328 | "x": 612,
329 | "y": 792,
330 | "label": "core/RepeatAsync"
331 | }
332 | },
333 | "core/Kick_4njgs": {
334 | "component": "core/Kick",
335 | "metadata": {
336 | "x": 756,
337 | "y": 792,
338 | "label": "kickFirstFilter"
339 | }
340 | },
341 | "dom/GetElement_j674o": {
342 | "component": "dom/GetElement",
343 | "metadata": {
344 | "x": 432,
345 | "y": 1044,
346 | "label": "sliderEl"
347 | }
348 | },
349 | "interaction/ListenChange_z7m5u": {
350 | "component": "interaction/ListenChange",
351 | "metadata": {
352 | "x": 612,
353 | "y": 1044,
354 | "label": "slid"
355 | }
356 | },
357 | "math/Multiply_3eldx": {
358 | "component": "math/Multiply",
359 | "metadata": {
360 | "x": 828,
361 | "y": 1152,
362 | "label": "x2xPI"
363 | }
364 | },
365 | "math/Multiply_3v13k": {
366 | "component": "math/Multiply",
367 | "metadata": {
368 | "x": 828,
369 | "y": 1044,
370 | "label": "tenth"
371 | }
372 | },
373 | "math/Multiply_rbxrn": {
374 | "component": "math/Multiply",
375 | "metadata": {
376 | "x": 828,
377 | "y": 936,
378 | "label": "tenth"
379 | }
380 | },
381 | "core/RunTimeout_3wulz": {
382 | "component": "core/RunTimeout",
383 | "metadata": {
384 | "x": 1584,
385 | "y": 432,
386 | "label": "core/RunTimeout"
387 | }
388 | },
389 | "core/Split_ho5ib": {
390 | "component": "core/Split",
391 | "metadata": {
392 | "x": 1584,
393 | "y": 576,
394 | "label": "core/Split"
395 | }
396 | },
397 | "dom/AddClass_9rihj": {
398 | "component": "dom/AddClass",
399 | "metadata": {
400 | "x": 1836,
401 | "y": 360,
402 | "label": "dom/AddClass"
403 | }
404 | },
405 | "core/Split_lbwyz": {
406 | "component": "core/Split",
407 | "metadata": {
408 | "x": 1728,
409 | "y": 144,
410 | "label": "core/Split"
411 | }
412 | },
413 | "dom/RemoveClass_ec7ug": {
414 | "component": "dom/RemoveClass",
415 | "metadata": {
416 | "x": 1836,
417 | "y": 648,
418 | "label": "dom/RemoveClass"
419 | }
420 | },
421 | "strings/SendString_zry4n": {
422 | "component": "strings/SendString",
423 | "metadata": {
424 | "x": 1728,
425 | "y": 360,
426 | "label": "countdown"
427 | }
428 | },
429 | "strings/SendString_lnf0z": {
430 | "component": "strings/SendString",
431 | "metadata": {
432 | "x": 1728,
433 | "y": 648,
434 | "label": "countdown"
435 | }
436 | }
437 | },
438 | "connections": [
439 | {
440 | "src": {
441 | "process": "dom/GetElement_f4nkd",
442 | "port": "element"
443 | },
444 | "tgt": {
445 | "process": "interaction/ListenMouse_1w3vt",
446 | "port": "element"
447 | },
448 | "metadata": {
449 | "route": "10"
450 | }
451 | },
452 | {
453 | "src": {
454 | "process": "interaction/ListenMouse_1w3vt",
455 | "port": "click"
456 | },
457 | "tgt": {
458 | "process": "gum/GetUserMedia_9e9i4",
459 | "port": "start"
460 | },
461 | "metadata": {
462 | "route": "9"
463 | }
464 | },
465 | {
466 | "src": {
467 | "process": "gum/GetUserMedia_9e9i4",
468 | "port": "url"
469 | },
470 | "tgt": {
471 | "process": "dom/SetAttribute_uto4k",
472 | "port": "value"
473 | },
474 | "metadata": {
475 | "route": "5"
476 | }
477 | },
478 | {
479 | "src": {
480 | "process": "seriously/Ascii_17c9q",
481 | "port": "out"
482 | },
483 | "tgt": {
484 | "process": "seriously/SetTarget_kii7s",
485 | "port": "source"
486 | },
487 | "metadata": {
488 | "route": "5"
489 | }
490 | },
491 | {
492 | "src": {
493 | "process": "dom/GetElement_85so0",
494 | "port": "element"
495 | },
496 | "tgt": {
497 | "process": "interaction/ListenMouse_aii7r",
498 | "port": "element"
499 | },
500 | "metadata": {
501 | "route": "10"
502 | }
503 | },
504 | {
505 | "src": {
506 | "process": "dom/GetElement_e16dy",
507 | "port": "element"
508 | },
509 | "tgt": {
510 | "process": "interaction/ListenMouse_bil4d",
511 | "port": "element"
512 | },
513 | "metadata": {
514 | "route": "10"
515 | }
516 | },
517 | {
518 | "src": {
519 | "process": "interaction/ListenMouse_aii7r",
520 | "port": "click"
521 | },
522 | "tgt": {
523 | "process": "routers/KickRouter_bzaiw",
524 | "port": "prev"
525 | },
526 | "metadata": {
527 | "route": "9"
528 | }
529 | },
530 | {
531 | "src": {
532 | "process": "interaction/ListenMouse_bil4d",
533 | "port": "click"
534 | },
535 | "tgt": {
536 | "process": "routers/KickRouter_bzaiw",
537 | "port": "next"
538 | },
539 | "metadata": {
540 | "route": "9"
541 | }
542 | },
543 | {
544 | "src": {
545 | "process": "seriously/TVGlitch_e1qre",
546 | "port": "out"
547 | },
548 | "tgt": {
549 | "process": "seriously/SetTarget_kii7s",
550 | "port": "source"
551 | },
552 | "metadata": {
553 | "route": "5"
554 | }
555 | },
556 | {
557 | "src": {
558 | "process": "routers/KickRouter_bzaiw",
559 | "port": "out"
560 | },
561 | "tgt": {
562 | "process": "seriously/Hex_lx162",
563 | "port": "source"
564 | },
565 | "metadata": {
566 | "route": "5"
567 | }
568 | },
569 | {
570 | "src": {
571 | "process": "seriously/Edge_egmkb",
572 | "port": "out"
573 | },
574 | "tgt": {
575 | "process": "seriously/SetTarget_kii7s",
576 | "port": "source"
577 | },
578 | "metadata": {
579 | "route": "5"
580 | }
581 | },
582 | {
583 | "src": {
584 | "process": "seriously/Hex_lx162",
585 | "port": "out"
586 | },
587 | "tgt": {
588 | "process": "seriously/Edge_egmkb",
589 | "port": "source"
590 | },
591 | "metadata": {
592 | "route": "5"
593 | }
594 | },
595 | {
596 | "src": {
597 | "process": "routers/KickRouter_bzaiw",
598 | "port": "out"
599 | },
600 | "tgt": {
601 | "process": "core/Split_jx318",
602 | "port": "in"
603 | },
604 | "metadata": {
605 | "route": "5"
606 | }
607 | },
608 | {
609 | "src": {
610 | "process": "core/Split_jx318",
611 | "port": "out"
612 | },
613 | "tgt": {
614 | "process": "seriously/Invert_7xnl3",
615 | "port": "source"
616 | },
617 | "metadata": {
618 | "route": "5"
619 | }
620 | },
621 | {
622 | "src": {
623 | "process": "seriously/Split_7oj15",
624 | "port": "out"
625 | },
626 | "tgt": {
627 | "process": "seriously/SetTarget_kii7s",
628 | "port": "source"
629 | },
630 | "metadata": {
631 | "route": "5"
632 | }
633 | },
634 | {
635 | "src": {
636 | "process": "seriously/SetSource_szf33",
637 | "port": "out"
638 | },
639 | "tgt": {
640 | "process": "core/Split_occbw",
641 | "port": "in"
642 | },
643 | "metadata": {
644 | "route": "5"
645 | }
646 | },
647 | {
648 | "src": {
649 | "process": "core/Split_occbw",
650 | "port": "out"
651 | },
652 | "tgt": {
653 | "process": "routers/KickRouter_bzaiw",
654 | "port": "in"
655 | },
656 | "metadata": {
657 | "route": "5"
658 | }
659 | },
660 | {
661 | "src": {
662 | "process": "dom/GetElement_z64xf",
663 | "port": "element"
664 | },
665 | "tgt": {
666 | "process": "core/Split_y0bla",
667 | "port": "in"
668 | },
669 | "metadata": {
670 | "route": "10"
671 | }
672 | },
673 | {
674 | "src": {
675 | "process": "core/Split_y0bla",
676 | "port": "out"
677 | },
678 | "tgt": {
679 | "process": "dom/SetAttribute_uto4k",
680 | "port": "element"
681 | },
682 | "metadata": {
683 | "route": "10"
684 | }
685 | },
686 | {
687 | "src": {
688 | "process": "core/Split_y0bla",
689 | "port": "out"
690 | },
691 | "tgt": {
692 | "process": "seriously/SetSource_szf33",
693 | "port": "source"
694 | },
695 | "metadata": {
696 | "route": "10"
697 | }
698 | },
699 | {
700 | "src": {
701 | "process": "routers/KickRouter_bzaiw",
702 | "port": "out"
703 | },
704 | "tgt": {
705 | "process": "seriously/HueSaturation_bzfvt",
706 | "port": "source"
707 | },
708 | "metadata": {
709 | "route": "5"
710 | }
711 | },
712 | {
713 | "src": {
714 | "process": "seriously/HueSaturation_bzfvt",
715 | "port": "out"
716 | },
717 | "tgt": {
718 | "process": "seriously/SetTarget_kii7s",
719 | "port": "source"
720 | },
721 | "metadata": {
722 | "route": "5"
723 | }
724 | },
725 | {
726 | "src": {
727 | "process": "dom/GetElement_ah82a",
728 | "port": "element"
729 | },
730 | "tgt": {
731 | "process": "core/Split_jzzu2",
732 | "port": "in"
733 | },
734 | "metadata": {
735 | "route": "10"
736 | }
737 | },
738 | {
739 | "src": {
740 | "process": "core/Split_jzzu2",
741 | "port": "out"
742 | },
743 | "tgt": {
744 | "process": "seriously/SetTarget_kii7s",
745 | "port": "target"
746 | },
747 | "metadata": {
748 | "route": "10"
749 | }
750 | },
751 | {
752 | "src": {
753 | "process": "core/Split_jzzu2",
754 | "port": "out"
755 | },
756 | "tgt": {
757 | "process": "core/Kick_7efi8",
758 | "port": "data"
759 | },
760 | "metadata": {
761 | "route": "10"
762 | }
763 | },
764 | {
765 | "src": {
766 | "process": "core/Kick_7efi8",
767 | "port": "out"
768 | },
769 | "tgt": {
770 | "process": "core/MakeFunction_t17n",
771 | "port": "in"
772 | },
773 | "metadata": {
774 | "route": 0
775 | }
776 | },
777 | {
778 | "src": {
779 | "process": "core/Split_jx318",
780 | "port": "out"
781 | },
782 | "tgt": {
783 | "process": "seriously/Split_7oj15",
784 | "port": "sourcea"
785 | },
786 | "metadata": {
787 | "route": "5"
788 | }
789 | },
790 | {
791 | "src": {
792 | "process": "seriously/Invert_7xnl3",
793 | "port": "out"
794 | },
795 | "tgt": {
796 | "process": "seriously/Split_7oj15",
797 | "port": "sourceb"
798 | },
799 | "metadata": {
800 | "route": "5"
801 | }
802 | },
803 | {
804 | "src": {
805 | "process": "dom/CreateElement_sf6ec",
806 | "port": "element"
807 | },
808 | "tgt": {
809 | "process": "dom/SetAttribute_3bmlw",
810 | "port": "element"
811 | },
812 | "metadata": {
813 | "route": "10"
814 | }
815 | },
816 | {
817 | "src": {
818 | "process": "dom/GetElement_4houj",
819 | "port": "element"
820 | },
821 | "tgt": {
822 | "process": "dom/CreateElement_sf6ec",
823 | "port": "container"
824 | },
825 | "metadata": {
826 | "route": "10"
827 | }
828 | },
829 | {
830 | "src": {
831 | "process": "core/RepeatAsync_grqs3",
832 | "port": "out"
833 | },
834 | "tgt": {
835 | "process": "dom/SetAttribute_3bmlw",
836 | "port": "value"
837 | },
838 | "metadata": {
839 | "route": 0
840 | }
841 | },
842 | {
843 | "src": {
844 | "process": "core/Split_xyb8x",
845 | "port": "out"
846 | },
847 | "tgt": {
848 | "process": "core/RepeatAsync_grqs3",
849 | "port": "in"
850 | },
851 | "metadata": {
852 | "route": 0
853 | }
854 | },
855 | {
856 | "src": {
857 | "process": "core/MakeFunction_t17n",
858 | "port": "out"
859 | },
860 | "tgt": {
861 | "process": "core/Split_xyb8x",
862 | "port": "in"
863 | },
864 | "metadata": {
865 | "route": 0
866 | }
867 | },
868 | {
869 | "src": {
870 | "process": "core/Split_xyb8x",
871 | "port": "out"
872 | },
873 | "tgt": {
874 | "process": "strings/SendString_7g9h8",
875 | "port": "in"
876 | },
877 | "metadata": {
878 | "route": 0
879 | }
880 | },
881 | {
882 | "src": {
883 | "process": "strings/SendString_7g9h8",
884 | "port": "out"
885 | },
886 | "tgt": {
887 | "process": "dom/CreateElement_sf6ec",
888 | "port": "tagname"
889 | },
890 | "metadata": {
891 | "route": "3"
892 | }
893 | },
894 | {
895 | "src": {
896 | "process": "core/Split_occbw",
897 | "port": "out"
898 | },
899 | "tgt": {
900 | "process": "core/RepeatAsync_647ff",
901 | "port": "in"
902 | },
903 | "metadata": {
904 | "route": "0"
905 | }
906 | },
907 | {
908 | "src": {
909 | "process": "core/RepeatAsync_647ff",
910 | "port": "out"
911 | },
912 | "tgt": {
913 | "process": "core/Kick_4njgs",
914 | "port": "in"
915 | },
916 | "metadata": {
917 | "route": 0
918 | }
919 | },
920 | {
921 | "src": {
922 | "process": "core/Kick_4njgs",
923 | "port": "out"
924 | },
925 | "tgt": {
926 | "process": "routers/KickRouter_bzaiw",
927 | "port": "index"
928 | },
929 | "metadata": {
930 | "route": 0
931 | }
932 | },
933 | {
934 | "src": {
935 | "process": "routers/KickRouter_bzaiw",
936 | "port": "out"
937 | },
938 | "tgt": {
939 | "process": "seriously/Ascii_17c9q",
940 | "port": "source"
941 | },
942 | "metadata": {
943 | "route": "5"
944 | }
945 | },
946 | {
947 | "src": {
948 | "process": "routers/KickRouter_bzaiw",
949 | "port": "out"
950 | },
951 | "tgt": {
952 | "process": "seriously/TVGlitch_e1qre",
953 | "port": "source"
954 | },
955 | "metadata": {
956 | "route": "5"
957 | }
958 | },
959 | {
960 | "src": {
961 | "process": "dom/GetElement_j674o",
962 | "port": "element"
963 | },
964 | "tgt": {
965 | "process": "interaction/ListenChange_z7m5u",
966 | "port": "element"
967 | },
968 | "metadata": {
969 | "route": "10"
970 | }
971 | },
972 | {
973 | "src": {
974 | "process": "interaction/ListenChange_z7m5u",
975 | "port": "value"
976 | },
977 | "tgt": {
978 | "process": "math/Multiply_3eldx",
979 | "port": "multiplicand"
980 | },
981 | "metadata": {
982 | "route": "3"
983 | }
984 | },
985 | {
986 | "src": {
987 | "process": "math/Multiply_3eldx",
988 | "port": "product"
989 | },
990 | "tgt": {
991 | "process": "seriously/Split_7oj15",
992 | "port": "angle"
993 | },
994 | "metadata": {
995 | "route": "3"
996 | }
997 | },
998 | {
999 | "src": {
1000 | "process": "interaction/ListenChange_z7m5u",
1001 | "port": "value"
1002 | },
1003 | "tgt": {
1004 | "process": "math/Multiply_3v13k",
1005 | "port": "multiplicand"
1006 | },
1007 | "metadata": {
1008 | "route": "3"
1009 | }
1010 | },
1011 | {
1012 | "src": {
1013 | "process": "math/Multiply_3v13k",
1014 | "port": "product"
1015 | },
1016 | "tgt": {
1017 | "process": "seriously/Hex_lx162",
1018 | "port": "size"
1019 | },
1020 | "metadata": {
1021 | "route": "3"
1022 | }
1023 | },
1024 | {
1025 | "src": {
1026 | "process": "interaction/ListenChange_z7m5u",
1027 | "port": "value"
1028 | },
1029 | "tgt": {
1030 | "process": "math/Multiply_rbxrn",
1031 | "port": "multiplicand"
1032 | },
1033 | "metadata": {
1034 | "route": "3"
1035 | }
1036 | },
1037 | {
1038 | "src": {
1039 | "process": "math/Multiply_rbxrn",
1040 | "port": "product"
1041 | },
1042 | "tgt": {
1043 | "process": "seriously/TVGlitch_e1qre",
1044 | "port": "distortion"
1045 | },
1046 | "metadata": {
1047 | "route": "3"
1048 | }
1049 | },
1050 | {
1051 | "src": {
1052 | "process": "interaction/ListenChange_z7m5u",
1053 | "port": "value"
1054 | },
1055 | "tgt": {
1056 | "process": "seriously/HueSaturation_bzfvt",
1057 | "port": "hue"
1058 | },
1059 | "metadata": {
1060 | "route": "3"
1061 | }
1062 | },
1063 | {
1064 | "src": {
1065 | "process": "interaction/ListenMouse_1u0rk",
1066 | "port": "click"
1067 | },
1068 | "tgt": {
1069 | "process": "core/RunTimeout_3wulz",
1070 | "port": "start"
1071 | },
1072 | "metadata": {
1073 | "route": "9"
1074 | }
1075 | },
1076 | {
1077 | "src": {
1078 | "process": "core/RunTimeout_3wulz",
1079 | "port": "out"
1080 | },
1081 | "tgt": {
1082 | "process": "core/Split_ho5ib",
1083 | "port": "in"
1084 | },
1085 | "metadata": {
1086 | "route": "0"
1087 | }
1088 | },
1089 | {
1090 | "src": {
1091 | "process": "core/Split_ho5ib",
1092 | "port": "out"
1093 | },
1094 | "tgt": {
1095 | "process": "core/Kick_7efi8",
1096 | "port": "in"
1097 | },
1098 | "metadata": {
1099 | "route": "0"
1100 | }
1101 | },
1102 | {
1103 | "src": {
1104 | "process": "dom/GetElement_ah36i",
1105 | "port": "element"
1106 | },
1107 | "tgt": {
1108 | "process": "core/Split_lbwyz",
1109 | "port": "in"
1110 | },
1111 | "metadata": {
1112 | "route": "10"
1113 | }
1114 | },
1115 | {
1116 | "src": {
1117 | "process": "core/Split_lbwyz",
1118 | "port": "out"
1119 | },
1120 | "tgt": {
1121 | "process": "interaction/ListenMouse_1u0rk",
1122 | "port": "element"
1123 | },
1124 | "metadata": {
1125 | "route": "10"
1126 | }
1127 | },
1128 | {
1129 | "src": {
1130 | "process": "core/Split_lbwyz",
1131 | "port": "out"
1132 | },
1133 | "tgt": {
1134 | "process": "dom/AddClass_9rihj",
1135 | "port": "element"
1136 | },
1137 | "metadata": {
1138 | "route": "10"
1139 | }
1140 | },
1141 | {
1142 | "src": {
1143 | "process": "core/Split_lbwyz",
1144 | "port": "out"
1145 | },
1146 | "tgt": {
1147 | "process": "dom/RemoveClass_ec7ug",
1148 | "port": "element"
1149 | },
1150 | "metadata": {
1151 | "route": "10"
1152 | }
1153 | },
1154 | {
1155 | "src": {
1156 | "process": "strings/SendString_zry4n",
1157 | "port": "out"
1158 | },
1159 | "tgt": {
1160 | "process": "dom/AddClass_9rihj",
1161 | "port": "class"
1162 | },
1163 | "metadata": {
1164 | "route": "0"
1165 | }
1166 | },
1167 | {
1168 | "src": {
1169 | "process": "strings/SendString_lnf0z",
1170 | "port": "out"
1171 | },
1172 | "tgt": {
1173 | "process": "dom/RemoveClass_ec7ug",
1174 | "port": "class"
1175 | },
1176 | "metadata": {
1177 | "route": "0"
1178 | }
1179 | },
1180 | {
1181 | "src": {
1182 | "process": "core/Split_ho5ib",
1183 | "port": "out"
1184 | },
1185 | "tgt": {
1186 | "process": "strings/SendString_lnf0z",
1187 | "port": "in"
1188 | },
1189 | "metadata": {
1190 | "route": "0"
1191 | }
1192 | },
1193 | {
1194 | "src": {
1195 | "process": "interaction/ListenMouse_1u0rk",
1196 | "port": "click"
1197 | },
1198 | "tgt": {
1199 | "process": "strings/SendString_zry4n",
1200 | "port": "in"
1201 | },
1202 | "metadata": {
1203 | "route": "9"
1204 | }
1205 | },
1206 | {
1207 | "data": "#start",
1208 | "tgt": {
1209 | "process": "dom/GetElement_f4nkd",
1210 | "port": "selector"
1211 | }
1212 | },
1213 | {
1214 | "data": "#vid",
1215 | "tgt": {
1216 | "process": "dom/GetElement_z64xf",
1217 | "port": "selector"
1218 | }
1219 | },
1220 | {
1221 | "data": "src",
1222 | "tgt": {
1223 | "process": "dom/SetAttribute_uto4k",
1224 | "port": "attribute"
1225 | }
1226 | },
1227 | {
1228 | "data": "#out",
1229 | "tgt": {
1230 | "process": "dom/GetElement_ah82a",
1231 | "port": "selector"
1232 | }
1233 | },
1234 | {
1235 | "data": "#prev",
1236 | "tgt": {
1237 | "process": "dom/GetElement_85so0",
1238 | "port": "selector"
1239 | }
1240 | },
1241 | {
1242 | "data": "#next",
1243 | "tgt": {
1244 | "process": "dom/GetElement_e16dy",
1245 | "port": "selector"
1246 | }
1247 | },
1248 | {
1249 | "data": 0.01,
1250 | "tgt": {
1251 | "process": "seriously/Split_7oj15",
1252 | "port": "fuzzy"
1253 | }
1254 | },
1255 | {
1256 | "data": "0.5",
1257 | "tgt": {
1258 | "process": "seriously/Split_7oj15",
1259 | "port": "split"
1260 | }
1261 | },
1262 | {
1263 | "data": "0.25",
1264 | "tgt": {
1265 | "process": "seriously/HueSaturation_bzfvt",
1266 | "port": "saturation"
1267 | }
1268 | },
1269 | {
1270 | "data": "return x.toDataURL(\"image/jpeg\", 0.85);",
1271 | "tgt": {
1272 | "process": "core/MakeFunction_t17n",
1273 | "port": "function"
1274 | }
1275 | },
1276 | {
1277 | "data": "#save",
1278 | "tgt": {
1279 | "process": "dom/GetElement_ah36i",
1280 | "port": "selector"
1281 | }
1282 | },
1283 | {
1284 | "data": "#saved",
1285 | "tgt": {
1286 | "process": "dom/GetElement_4houj",
1287 | "port": "selector"
1288 | }
1289 | },
1290 | {
1291 | "data": "src",
1292 | "tgt": {
1293 | "process": "dom/SetAttribute_3bmlw",
1294 | "port": "attribute"
1295 | }
1296 | },
1297 | {
1298 | "data": "img",
1299 | "tgt": {
1300 | "process": "strings/SendString_7g9h8",
1301 | "port": "string"
1302 | }
1303 | },
1304 | {
1305 | "data": "0",
1306 | "tgt": {
1307 | "process": "core/Kick_4njgs",
1308 | "port": "data"
1309 | }
1310 | },
1311 | {
1312 | "data": "#slider",
1313 | "tgt": {
1314 | "process": "dom/GetElement_j674o",
1315 | "port": "selector"
1316 | }
1317 | },
1318 | {
1319 | "data": "6.283185",
1320 | "tgt": {
1321 | "process": "math/Multiply_3eldx",
1322 | "port": "multiplier"
1323 | }
1324 | },
1325 | {
1326 | "data": 0.01,
1327 | "tgt": {
1328 | "process": "seriously/TVGlitch_e1qre",
1329 | "port": "verticalsync"
1330 | }
1331 | },
1332 | {
1333 | "data": "0.04",
1334 | "tgt": {
1335 | "process": "seriously/TVGlitch_e1qre",
1336 | "port": "linesync"
1337 | }
1338 | },
1339 | {
1340 | "data": 0.01,
1341 | "tgt": {
1342 | "process": "seriously/TVGlitch_e1qre",
1343 | "port": "time"
1344 | }
1345 | },
1346 | {
1347 | "data": "0.01",
1348 | "tgt": {
1349 | "process": "seriously/TVGlitch_e1qre",
1350 | "port": "bars"
1351 | }
1352 | },
1353 | {
1354 | "data": 0.15,
1355 | "tgt": {
1356 | "process": "seriously/TVGlitch_e1qre",
1357 | "port": "scanlines"
1358 | }
1359 | },
1360 | {
1361 | "data": "0.1",
1362 | "tgt": {
1363 | "process": "math/Multiply_3v13k",
1364 | "port": "multiplier"
1365 | }
1366 | },
1367 | {
1368 | "data": "0.1",
1369 | "tgt": {
1370 | "process": "math/Multiply_rbxrn",
1371 | "port": "multiplier"
1372 | }
1373 | },
1374 | {
1375 | "data": "3000",
1376 | "tgt": {
1377 | "process": "core/RunTimeout_3wulz",
1378 | "port": "time"
1379 | }
1380 | },
1381 | {
1382 | "data": "countdown",
1383 | "tgt": {
1384 | "process": "strings/SendString_zry4n",
1385 | "port": "string"
1386 | }
1387 | },
1388 | {
1389 | "data": "countdown",
1390 | "tgt": {
1391 | "process": "strings/SendString_lnf0z",
1392 | "port": "string"
1393 | }
1394 | }
1395 | ]
1396 | })
--------------------------------------------------------------------------------
/react/the-graph-app.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 |
7 | TheGraph.App = React.createClass({
8 | minZoom: 0.15,
9 | mixins: [TheGraph.mixins.FakeMouse],
10 | getInitialState: function() {
11 | // Autofit
12 | var fit = TheGraph.findFit(this.props.graph, this.props.width, this.props.height);
13 |
14 | return {
15 | x: fit.x,
16 | y: fit.y,
17 | scale: fit.scale,
18 | width: this.props.width,
19 | height: this.props.height,
20 | tooltip: "",
21 | tooltipX: 0,
22 | tooltipY: 0,
23 | tooltipVisible: false,
24 | nodeContext: null
25 | };
26 | },
27 | zoomFactor: 0,
28 | zoomX: 0,
29 | zoomY: 0,
30 | onWheel: function (event) {
31 | // TODO: fast transform3d here?
32 |
33 | // Don't bounce
34 | event.preventDefault();
35 |
36 | if (!this.zoomFactor) { // WAT
37 | this.zoomFactor = 0;
38 | }
39 |
40 | this.zoomFactor += event.deltaY;
41 | this.zoomX = event.nativeEvent.pageX;
42 | this.zoomY = event.nativeEvent.pageY;
43 | requestAnimationFrame(this.scheduleZoom);
44 | },
45 | scheduleZoom: function () {
46 | if (isNaN(this.zoomFactor)) { return; };
47 |
48 | var scale = this.state.scale + (this.state.scale * this.zoomFactor/500);
49 | this.zoomFactor = 0;
50 |
51 | if (scale < this.minZoom) {
52 | scale = this.minZoom;
53 | }
54 | if (scale === this.state.scale) { return; }
55 |
56 | // Zoom and pan transform-origin equivalent
57 | var scaleD = scale / this.state.scale;
58 | var currentX = this.state.x;
59 | var currentY = this.state.y;
60 | var oX = this.zoomX;
61 | var oY = this.zoomY;
62 | var x = scaleD * (currentX - oX) + oX;
63 | var y = scaleD * (currentY - oY) + oY;
64 |
65 | this.setState({
66 | scale: scale,
67 | x: x,
68 | y: y,
69 | tooltipVisible: false
70 | });
71 | },
72 | mouseX: 0,
73 | mouseY: 0,
74 | onMouseDown: function (event) {
75 | if (event.button !== 0) {
76 | // Context menu
77 | return;
78 | }
79 |
80 | var x, y;
81 | if (event.touches) {
82 | x = event.touches[0].pageX;
83 | y = event.touches[0].pageY;
84 | } else {
85 | x = event.pageX;
86 | y = event.pageY;
87 | }
88 | this.mouseX = x;
89 | this.mouseY = y;
90 |
91 | window.addEventListener("mousemove", this.onMouseMove);
92 | window.addEventListener("mouseup", this.onMouseUp);
93 | },
94 | onMouseMove: function (event) {
95 | // Pan
96 | var x, y;
97 | if (event.touches) {
98 | x = event.touches[0].pageX;
99 | y = event.touches[0].pageY;
100 | } else {
101 | x = event.pageX;
102 | y = event.pageY;
103 | }
104 | var deltaX = x - this.mouseX;
105 | var deltaY = y - this.mouseY;
106 | this.setState({
107 | x: this.state.x + deltaX,
108 | y: this.state.y + deltaY
109 | });
110 | this.mouseX = x;
111 | this.mouseY = y;
112 | },
113 | onMouseUp: function (event) {
114 | window.removeEventListener("mousemove", this.onMouseMove);
115 | window.removeEventListener("mouseup", this.onMouseUp);
116 | },
117 | showNodeContext: function (event) {
118 | this.setState({
119 | nodeContext: event.detail,
120 | tooltipVisible: false
121 | });
122 | },
123 | hideContext: function (event) {
124 | this.setState({
125 | nodeContext: null
126 | });
127 | },
128 | changeTooltip: function (event) {
129 | var tooltip = event.detail.tooltip;
130 |
131 | // Don't go over right edge
132 | var x = event.detail.x + 10;
133 | var width = tooltip.length*6;
134 | if (x + width > this.props.width) {
135 | x = event.detail.x - width - 10;
136 | }
137 |
138 | this.setState({
139 | tooltip: tooltip,
140 | tooltipVisible: true,
141 | tooltipX: x,
142 | tooltipY: event.detail.y + 20
143 | });
144 | },
145 | hideTooltip: function (event) {
146 | this.setState({
147 | tooltipVisible: false
148 | });
149 | },
150 | // onFit: function (event) {
151 | // this.setState({
152 | // x: event.detail.x,
153 | // y: event.detail.y,
154 | // scale: event.detail.scale
155 | // });
156 | // },
157 | componentDidMount: function (rootNode) {
158 | // Mouse listen to window for drag/release outside
159 |
160 | // Tooltip listener
161 | this.getDOMNode().addEventListener("the-graph-tooltip", this.changeTooltip);
162 | this.getDOMNode().addEventListener("the-graph-tooltip-hide", this.hideTooltip);
163 |
164 | // Custom event listeners
165 | this.getDOMNode().addEventListener("the-graph-node-context", this.showNodeContext);
166 | // this.getDOMNode().addEventListener("the-graph-edge-context", this.showEdgeContext);
167 | // this.getDOMNode().addEventListener("the-graph-group-context", this.showGroupContext);
168 | this.getDOMNode().addEventListener("the-graph-context-hide", this.hideContext);
169 |
170 | // Start zoom from middle if zoom before mouse move
171 | this.mouseX = Math.floor( window.innerWidth/2 );
172 | this.mouseY = Math.floor( window.innerHeight/2 );
173 | },
174 | componentDidUpdate: function (prevProps, prevState, rootNode) {
175 | },
176 | render: function() {
177 | // console.timeEnd("App.render");
178 | // console.time("App.render");
179 |
180 | // pan and zoom
181 | var sc = this.state.scale;
182 | var x = this.state.x;
183 | var y = this.state.y;
184 | var transform = "matrix("+sc+",0,0,"+sc+","+x+","+y+")";
185 |
186 | var scaleClass = sc > TheGraph.zbpBig ? "big" : ( sc > TheGraph.zbpNormal ? "normal" : "small");
187 |
188 | var contextMenu, contextModal;
189 | if ( this.state.nodeContext ) {
190 | contextMenu = this.state.nodeContext.getContext();
191 | }
192 | if (contextMenu) {
193 | contextModal = [
194 | React.DOM.rect({
195 | className: "context-modal-bg",
196 | width: this.state.width,
197 | height: this.state.height,
198 | onMouseDown: this.hideContext
199 | }),
200 | contextMenu
201 | ];
202 | }
203 |
204 | return React.DOM.div(
205 | {
206 | className: "the-graph " + scaleClass,
207 | name:"app",
208 | onWheel: this.onWheel,
209 | onMouseDown: this.onMouseDown,
210 | style: {
211 | width: this.state.width,
212 | height: this.state.height
213 | }
214 | },
215 | React.DOM.svg(
216 | {
217 | width: this.state.width,
218 | height: this.state.height
219 | },
220 | React.DOM.g(
221 | {
222 | className: "view",
223 | transform: transform
224 | },
225 | TheGraph.Graph({
226 | ref: "graph",
227 | graph: this.props.graph,
228 | scale: this.state.scale,
229 | app: this
230 | })
231 | ),
232 | TheGraph.Tooltip({
233 | ref: "tooltip",
234 | x: this.state.tooltipX,
235 | y: this.state.tooltipY,
236 | visible: this.state.tooltipVisible,
237 | label: this.state.tooltip
238 | }),
239 | React.DOM.g(
240 | {
241 | className: "context",
242 | children: contextModal
243 | }
244 | )
245 | )
246 | );
247 | }
248 | });
249 |
250 |
251 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-edge.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 | // Const
7 | var CURVE = 50;
8 |
9 |
10 | // Edge view
11 |
12 | TheGraph.Edge = React.createClass({
13 | mixins: [TheGraph.mixins.Tooltip],
14 | componentWillMount: function() {
15 | // Todo: listen for source/target moving; change state
16 | },
17 | shouldComponentUpdate: function (nextProps, nextState) {
18 | // Only rerender if changed
19 | return (
20 | nextProps.sX !== this.props.sX ||
21 | nextProps.sY !== this.props.sY ||
22 | nextProps.tX !== this.props.tX ||
23 | nextProps.tY !== this.props.tY ||
24 | nextProps.route !== this.props.route
25 | );
26 | },
27 | getTooltipTrigger: function () {
28 | return this.refs.touch.getDOMNode();
29 | },
30 | shouldShowTooltip: function () {
31 | return true;
32 | },
33 | render: function () {
34 | var sourceX = this.props.sX;
35 | var sourceY = this.props.sY;
36 | var targetX = this.props.tX;
37 | var targetY = this.props.tY;
38 |
39 | var c1X, c1Y, c2X, c2Y;
40 | if (targetX < sourceX+CURVE && Math.abs(targetY-sourceY) > TheGraph.nodeSize) {
41 | // Stick out some
42 | c1X = sourceX + CURVE;
43 | c1Y = sourceY;
44 | c2X = targetX - CURVE;
45 | c2Y = targetY;
46 | } else {
47 | // Controls halfway between
48 | c1X = sourceX + (targetX - sourceX)/2;
49 | c1Y = sourceY;
50 | c2X = c1X;
51 | c2Y = targetY;
52 | }
53 |
54 | var path = [
55 | "M",
56 | sourceX, sourceY,
57 | "C",
58 | c1X, c1Y,
59 | c2X, c2Y,
60 | targetX, targetY
61 | ].join(" ");
62 |
63 | return (
64 | React.DOM.g(
65 | {
66 | className: "edge route"+this.props.route,
67 | title: this.props.label
68 | },
69 | React.DOM.path({
70 | className: "edge-bg",
71 | d: path
72 | }),
73 | React.DOM.path({
74 | className: "edge-fg",
75 | d: path
76 | }),
77 | React.DOM.path({
78 | className: "edge-touch",
79 | ref: "touch",
80 | d: path
81 | })
82 | )
83 | );
84 | }
85 | });
86 |
87 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-graph.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 |
7 | // Graph view
8 |
9 | TheGraph.Graph = React.createClass({
10 | mixins: [TheGraph.mixins.FakeMouse],
11 | getInitialState: function() {
12 | return {
13 | graph: this.props.graph
14 | };
15 | },
16 | componentDidMount: function () {
17 | this.getDOMNode().addEventListener("the-graph-node-move", this.markDirty);
18 | this.getDOMNode().addEventListener("the-graph-group-move", this.moveGroup);
19 | this.getDOMNode().addEventListener("the-graph-node-remove", this.removeNode);
20 | },
21 | // triggerFit: function () {
22 | // // Zoom to fit
23 | // var fit = TheGraph.findFit(this.state.graph);
24 | // var fitEvent = new CustomEvent('the-graph-fit', {
25 | // detail: fit,
26 | // bubbles: true
27 | // });
28 | // this.getDOMNode().dispatchEvent(fitEvent);
29 | // },
30 | moveGroup: function (event) {
31 | var graph = this.state.graph;
32 | var group = graph.groups[ event.detail.index ];
33 | var nodes = group.nodes;
34 |
35 | // Move each group member
36 | var len = nodes.length;
37 | for (var i=0; i " +
194 | connection.tgt.port.toUpperCase() + " " + target.metadata.label;
195 | var key = connection.src.process + "() " + connection.src.port.toUpperCase() + " -> " +
196 | connection.tgt.port.toUpperCase() + " " + connection.tgt.process + "()";
197 |
198 | return TheGraph.Edge({
199 | key: key,
200 | sX: source.metadata.x + TheGraph.nodeSize,
201 | sY: source.metadata.y + sourcePort.y,
202 | tX: target.metadata.x,
203 | tY: target.metadata.y + targetPort.y,
204 | label: label,
205 | route: route
206 | });
207 | });
208 |
209 | return React.DOM.g(
210 | {
211 | className: "graph"//,
212 | // onMouseDown: this.onMouseDown
213 | },
214 | React.DOM.g({
215 | className: "groups",
216 | children: groups
217 | }),
218 | React.DOM.g({
219 | className: "edges",
220 | children: edges
221 | }),
222 | React.DOM.g({
223 | className: "nodes",
224 | children: nodes
225 | })
226 | );
227 | }
228 | });
229 |
230 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-group.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 |
7 | // Group view
8 |
9 | TheGraph.Group = React.createClass({
10 | mouseX: 0,
11 | mouseY: 0,
12 | onMouseDown: function (event) {
13 | // Don't drag graph
14 | event.stopPropagation();
15 |
16 | // Touch to mouse
17 | var x, y;
18 | if (event.touches) {
19 | x = event.touches[0].pageX;
20 | y = event.touches[0].pageY;
21 | } else {
22 | x = event.pageX;
23 | y = event.pageY;
24 | }
25 |
26 | this.mouseX = x;
27 | this.mouseY = y;
28 |
29 | if (event.button !== 0) {
30 | // Show context menu
31 | this.highlight();
32 | return;
33 | }
34 |
35 | window.addEventListener("mousemove", this.onMouseMove);
36 | window.addEventListener("mouseup", this.onMouseUp);
37 |
38 | },
39 | highlight: function () {
40 | var highlightEvent = new CustomEvent('the-graph-group-highlight', {
41 | 'detail': {
42 | index: this.props.index,
43 | x: this.mouseX,
44 | y: this.mouseY
45 | },
46 | 'bubbles': true
47 | });
48 | this.getDOMNode().dispatchEvent(highlightEvent);
49 | },
50 | onMouseMove: function (event) {
51 | // Don't fire on graph
52 | event.stopPropagation();
53 |
54 | // Touch to mouse
55 | var x, y;
56 | if (event.touches) {
57 | x = event.touches[0].pageX;
58 | y = event.touches[0].pageY;
59 | } else {
60 | x = event.pageX;
61 | y = event.pageY;
62 | }
63 |
64 | var deltaX = Math.round( (x - this.mouseX) / this.props.scale );
65 | var deltaY = Math.round( (y - this.mouseY) / this.props.scale );
66 | this.mouseX = x;
67 | this.mouseY = y;
68 |
69 | var moveEvent = new CustomEvent('the-graph-group-move', {
70 | detail: {
71 | index: this.props.index,
72 | x: deltaX,
73 | y: deltaY
74 | },
75 | bubbles: true
76 | });
77 | this.getDOMNode().dispatchEvent(moveEvent);
78 | },
79 | onMouseUp: function (event) {
80 | // Don't fire on graph
81 | event.stopPropagation();
82 |
83 | window.removeEventListener("mousemove", this.onMouseMove);
84 | window.removeEventListener("mouseup", this.onMouseUp);
85 | },
86 | render: function() {
87 | var x = this.props.minX - TheGraph.nodeSize/2;
88 | var y = this.props.minY - TheGraph.nodeSize/2;
89 | return (
90 | React.DOM.g(
91 | {
92 | className: "group"
93 | // transform: "translate("+x+","+y+")"
94 | },
95 | React.DOM.rect({
96 | className: "group-box",
97 | x: x,
98 | y: y,
99 | rx: TheGraph.nodeRadius,
100 | ry: TheGraph.nodeRadius,
101 | width: this.props.maxX - this.props.minX + TheGraph.nodeSize*2,
102 | height: this.props.maxY - this.props.minY + TheGraph.nodeSize*2
103 | }),
104 | React.DOM.text({
105 | className: "group-label drag",
106 | x: x + TheGraph.nodeRadius,
107 | y: y,
108 | children: this.props.label,
109 | onMouseDown: this.onMouseDown
110 | }),
111 | React.DOM.text({
112 | className: "group-description",
113 | x: x + TheGraph.nodeRadius,
114 | y: y + 10 + TheGraph.nodeRadius,
115 | children: this.props.description
116 | })
117 | )
118 | );
119 | }
120 | });
121 |
122 |
123 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-node.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 | // Font Awesome
7 | var faKeys = Object.keys(TheGraph.FONT_AWESOME);
8 |
9 | // Node view
10 |
11 | TheGraph.Node = React.createClass({
12 | mixins: [
13 | TheGraph.mixins.FakeMouse,
14 | TheGraph.mixins.Tooltip
15 | ],
16 | getInitialState: function() {
17 | return {
18 | // Random icon just for fun
19 | icon: faKeys[ Math.floor(Math.random()*faKeys.length) ]
20 | };
21 | },
22 | componentDidMount: function () {
23 | // Mouse listen to window for drag/release outside
24 | // window.addEventListener("mousemove", this.onMouseMove);
25 | // window.addEventListener("mouseup", this.onMouseUp);
26 |
27 | // Right-click
28 | this.getDOMNode().addEventListener("contextmenu", this.showContext);
29 | },
30 | mouseX: 0,
31 | mouseY: 0,
32 | onMouseDown: function (event) {
33 | // Don't drag graph
34 | event.stopPropagation();
35 |
36 | // Touch to mouse
37 | var x, y;
38 | if (event.touches) {
39 | x = event.touches[0].pageX;
40 | y = event.touches[0].pageY;
41 | } else {
42 | x = event.pageX;
43 | y = event.pageY;
44 | }
45 |
46 | if (event.button !== 0) {
47 | // Show context menu
48 | // this.showContext();
49 | return;
50 | }
51 |
52 | this.mouseX = x;
53 | this.mouseY = y;
54 |
55 | window.addEventListener("mousemove", this.onMouseMove);
56 | window.addEventListener("mouseup", this.onMouseUp);
57 |
58 | },
59 | onMouseMove: function (event) {
60 | // Don't fire on graph
61 | event.stopPropagation();
62 |
63 | // Touch to mouse
64 | var x, y;
65 | if (event.touches) {
66 | x = event.touches[0].pageX;
67 | y = event.touches[0].pageY;
68 | } else {
69 | x = event.pageX;
70 | y = event.pageY;
71 | }
72 |
73 | var scale = this.props.app.state.scale;
74 |
75 | var deltaX = Math.round( (x - this.mouseX) / scale );
76 | var deltaY = Math.round( (y - this.mouseY) / scale );
77 | this.props.process.metadata.x += deltaX;
78 | this.props.process.metadata.y += deltaY;
79 | this.mouseX = x;
80 | this.mouseY = y;
81 |
82 | var highlightEvent = new CustomEvent('the-graph-node-move', {
83 | detail: null,
84 | bubbles: true
85 | });
86 | this.getDOMNode().dispatchEvent(highlightEvent);
87 | },
88 | onMouseUp: function (event) {
89 | // Don't fire on graph
90 | event.stopPropagation();
91 |
92 | window.removeEventListener("mousemove", this.onMouseMove);
93 | window.removeEventListener("mouseup", this.onMouseUp);
94 | },
95 | triggerRemove: function () {
96 | var contextEvent = new CustomEvent('the-graph-node-remove', {
97 | detail: this.props.key,
98 | bubbles: true
99 | });
100 | this.getDOMNode().dispatchEvent(contextEvent);
101 | },
102 | showContext: function (event) {
103 | // Don't show native context menu
104 | event.preventDefault();
105 |
106 | var contextEvent = new CustomEvent('the-graph-node-context', {
107 | detail: this,
108 | bubbles: true
109 | });
110 | this.getDOMNode().dispatchEvent(contextEvent);
111 | },
112 | getContext: function () {
113 | var scale = this.props.app.state.scale;
114 | var appX = this.props.app.state.x;
115 | var appY = this.props.app.state.y;
116 | var x = (this.props.x + TheGraph.nodeSize/2) * scale + appX;
117 | var y = (this.props.y + TheGraph.nodeSize/2) * scale + appY;
118 | return TheGraph.NodeMenu({
119 | key: "context." + this.props.key,
120 | label: this.props.label,
121 | node: this,
122 | process: this.props.process,
123 | x: x,
124 | y: y
125 | });
126 | },
127 | getTooltipTrigger: function () {
128 | return this.getDOMNode();
129 | },
130 | shouldShowTooltip: function () {
131 | // HACK
132 | return (this.props.app.state.scale < TheGraph.zbpNormal);
133 | },
134 | shouldComponentUpdate: function (nextProps, nextState) {
135 | // Only rerender if moved
136 | return (
137 | nextProps.x !== this.props.x ||
138 | nextProps.y !== this.props.y
139 | );
140 | },
141 | render: function() {
142 | var metadata = this.props.process.metadata;
143 |
144 | var label = this.props.label;
145 | var sublabel = this.props.process.component;
146 | if (sublabel === label) {
147 | sublabel = "";
148 | }
149 | var x = this.props.x;
150 | var y = this.props.y;
151 |
152 | // Ports
153 | var keys, count, index;
154 |
155 | // Inports
156 | var inports = metadata.ports.inports;
157 | keys = Object.keys(inports);
158 | count = keys.length;
159 | index = 0;
160 | var inportViews = keys.map(function(key){
161 | index++;
162 | var info = inports[key];
163 | info.y = TheGraph.nodeRadius + (TheGraph.nodeSide / (count+1) * index);
164 | return TheGraph.Port(info);
165 | });
166 |
167 | // Outports
168 | var outports = metadata.ports.outports;
169 | keys = Object.keys(outports);
170 | count = keys.length;
171 | index = 0;
172 | var outportViews = keys.map(function(key){
173 | index++;
174 | var info = outports[key];
175 | info.y = TheGraph.nodeRadius + (TheGraph.nodeSide / (count+1) * index);
176 | return TheGraph.Port(info);
177 | });
178 |
179 | return (
180 | React.DOM.g(
181 | {
182 | className: "node drag",
183 | name: this.props.key,
184 | key: this.props.key,
185 | title: label,
186 | transform: "translate("+x+","+y+")",
187 | onMouseDown: this.onMouseDown
188 | },
189 | React.DOM.rect({
190 | className: "node-bg", // HACK to make the whole g draggable
191 | width: TheGraph.nodeSize,
192 | height: TheGraph.nodeSize + 35
193 | }),
194 | React.DOM.rect({
195 | className: "node-rect drag",
196 | width: TheGraph.nodeSize,
197 | height: TheGraph.nodeSize,
198 | rx: TheGraph.nodeRadius,
199 | ry: TheGraph.nodeRadius
200 | }),
201 | React.DOM.text({
202 | className: "icon node-icon drag",
203 | x: TheGraph.nodeSize/2,
204 | y: TheGraph.nodeSize/2,
205 | children: TheGraph.FONT_AWESOME[this.state.icon]
206 | }),
207 | React.DOM.g({
208 | className: "inports",
209 | children: inportViews
210 | }),
211 | React.DOM.g({
212 | className: "outports",
213 | children: outportViews
214 | }),
215 | React.DOM.text({
216 | className: "node-label",
217 | x: TheGraph.nodeSize/2,
218 | y: TheGraph.nodeSize + 20,
219 | children: label
220 | }),
221 | React.DOM.text({
222 | className: "node-sublabel",
223 | x: TheGraph.nodeSize/2,
224 | y: TheGraph.nodeSize + 35,
225 | children: sublabel
226 | })
227 | )
228 | );
229 | }
230 | });
231 |
232 |
233 |
234 | TheGraph.NodeMenu = React.createClass({
235 | radius: 72,
236 | arcs: (function(){
237 | var angleToX = function (percent, radius) {
238 | return radius * Math.cos(2*Math.PI * percent);
239 | };
240 | var angleToY = function (percent, radius) {
241 | return radius * Math.sin(2*Math.PI * percent);
242 | };
243 | var makeArcPath = function (startPercent, endPercent, radius) {
244 | return [
245 | "M", angleToX(startPercent, radius), angleToY(startPercent, radius),
246 | "A", radius, radius, 0, 0, 0, angleToX(endPercent, radius), angleToY(endPercent, radius)
247 | ].join(" ")
248 | };
249 | return {
250 | label: makeArcPath(7/8, 5/8, 36),
251 | ins: makeArcPath(5/8, 3/8, 36),
252 | outs: makeArcPath(1/8, -1/8, 36),
253 | remove: makeArcPath(3/8, 1/8, 36)
254 | }
255 | })(),
256 | stopPropagation: function (event) {
257 | // Don't drag graph
258 | event.stopPropagation();
259 | },
260 | triggerRemove: function (event) {
261 | this.props.node.triggerRemove();
262 |
263 | // Hide self (overkill?)
264 | var contextEvent = new CustomEvent('the-graph-context-hide', {
265 | detail: null,
266 | bubbles: true
267 | });
268 | this.getDOMNode().dispatchEvent(contextEvent);
269 | },
270 | render: function() {
271 |
272 | if (this.props.process.metadata && this.props.process.metadata.ports) {
273 | // HACK
274 | var scale = this.props.node.props.app.state.scale;
275 |
276 | var ports = this.props.process.metadata.ports;
277 |
278 | var inkeys = Object.keys(ports.inports);
279 | var h = inkeys.length * TheGraph.contextPortSize;
280 | var i = 0;
281 | var inports = inkeys.map( function (key) {
282 | var inport = ports.inports[key];
283 | var y = 0 - h/2 + i*TheGraph.contextPortSize + TheGraph.contextPortSize/2;
284 | i++;
285 | return TheGraph.PortMenu({
286 | label: key,
287 | isIn: true,
288 | ox: (inport.x - TheGraph.nodeSize/2) * scale,
289 | oy: (inport.y - TheGraph.nodeSize/2) * scale,
290 | x: -100,
291 | y: y
292 | });
293 | });
294 |
295 | var outkeys = Object.keys(ports.outports);
296 | h = outkeys.length * TheGraph.contextPortSize;
297 | i = 0;
298 | var outports = outkeys.map( function (key) {
299 | var outport = ports.outports[key];
300 | var y = 0 - h/2 + i*TheGraph.contextPortSize + TheGraph.contextPortSize/2;
301 | i++;
302 | return TheGraph.PortMenu({
303 | label: key,
304 | isIn: false,
305 | ox: (outport.x - TheGraph.nodeSize/2) * scale,
306 | oy: (outport.y - TheGraph.nodeSize/2) * scale,
307 | x: 100,
308 | y: y
309 | });
310 | });
311 | }
312 |
313 | return (
314 | React.DOM.g(
315 | {
316 | className: "context-node",
317 | transform: "translate("+this.props.x+","+this.props.y+")"
318 | },
319 | React.DOM.text({
320 | className: "context-node-label",
321 | x: 0,
322 | y: 0 - this.radius - 25,
323 | children: this.props.label
324 | }),
325 | React.DOM.g({
326 | className: "context-inports",
327 | children: inports
328 | }),
329 | React.DOM.g({
330 | className: "context-outports",
331 | children: outports
332 | }),
333 | React.DOM.g(
334 | {
335 | className: "context-slice context-node-info"
336 | // onMouseDown: this.stopPropagation,
337 | // onClick: this.triggerRemove
338 | },
339 | React.DOM.path({
340 | className: "context-arc context-node-info-bg",
341 | d: this.arcs.label
342 | }),
343 | React.DOM.text({
344 | className: "icon context-icon context-node-info-icon",
345 | x: 0,
346 | y: -48,
347 | children: TheGraph.FONT_AWESOME["info-circle"]
348 | })
349 | ),
350 | React.DOM.g(
351 | {
352 | className: "context-slice context-node-delete click",
353 | onMouseDown: this.stopPropagation,
354 | onClick: this.triggerRemove
355 | },
356 | React.DOM.path({
357 | className: "context-arc context-node-delete-bg",
358 | d: this.arcs.remove
359 | }),
360 | React.DOM.text({
361 | className: "icon context-icon context-node-delete-icon",
362 | x: 0,
363 | y: 48,
364 | children: TheGraph.FONT_AWESOME["trash-o"]
365 | })
366 | ),
367 | React.DOM.path({
368 | className: "context-arc context-node-ins-bg",
369 | d: this.arcs.ins
370 | }),
371 | React.DOM.path({
372 | className: "context-circle-x",
373 | d: "M -51 -51 L 51 51 M -51 51 L 51 -51"
374 | }),
375 | React.DOM.path({
376 | className: "context-arc context-node-outs-bg",
377 | d: this.arcs.outs
378 | }),
379 | React.DOM.circle({
380 | className: "context-circle",
381 | r: this.radius
382 | }),
383 | React.DOM.rect({
384 | className: "node-rect",
385 | x: -24,
386 | y: -24,
387 | width: 48,
388 | height: 48,
389 | rx: TheGraph.nodeRadius,
390 | ry: TheGraph.nodeRadius
391 | }),
392 | React.DOM.text({
393 | className: "icon context-icon",
394 | children: TheGraph.FONT_AWESOME[this.props.node.state.icon]
395 | })
396 | )
397 | );
398 | }
399 | });
400 |
401 |
402 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-port.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 |
7 | // Port view
8 |
9 | TheGraph.Port = React.createClass({
10 | render: function() {
11 | return (
12 | React.DOM.g(
13 | {
14 | className: "port"
15 | },
16 | React.DOM.circle({
17 | className: "port-circle",
18 | cx: this.props.x,
19 | cy: this.props.y,
20 | r: 4
21 | }),
22 | React.DOM.text({
23 | className: "port-label port-label-"+this.props.label.length,
24 | x: this.props.x,
25 | y: this.props.y,
26 | children: this.props.label
27 | })
28 | )
29 | );
30 | }
31 | });
32 |
33 | TheGraph.PortMenu = React.createClass({
34 | render: function() {
35 | var path = [
36 | "M", this.props.ox, this.props.oy,
37 | "L", this.props.x, this.props.y
38 | ].join(" ");
39 |
40 | return (
41 | React.DOM.g(
42 | {
43 | className: "context-port click context-port-"+(this.props.isIn ? "in" : "out")
44 | },
45 | React.DOM.path({
46 | className: "context-port-path",
47 | d: path
48 | }),
49 | React.DOM.rect({
50 | className: "context-port-bg",
51 | rx: TheGraph.nodeRadius,
52 | ry: TheGraph.nodeRadius,
53 | x: this.props.x + (this.props.isIn ? -120 : 0),
54 | y: this.props.y - TheGraph.contextPortSize/2,
55 | width: 120,
56 | height: TheGraph.contextPortSize-1
57 | }),
58 | React.DOM.circle({
59 | className: "context-port-hole",
60 | cx: this.props.x,
61 | cy: this.props.y,
62 | r: 10
63 | }),
64 | React.DOM.text({
65 | className: "context-port-label",
66 | x: this.props.x + (this.props.isIn ? -20 : 20),
67 | y: this.props.y,
68 | children: this.props.label
69 | })
70 | )
71 | );
72 | }
73 | });
74 |
75 |
76 | })(this);
--------------------------------------------------------------------------------
/react/the-graph-svg.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | /* we want the svg version */
3 | font-family: 'FontAwesome';
4 | src: url('//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/fontawesome-webfont.svg?v=4.0.3#fontawesomeregular') format('svg'),
5 | url('//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/fontawesome-webfont.eot?#iefix&v=4.0.3') format('embedded-opentype'),
6 | url('//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/fontawesome-webfont.woff?v=4.0.3') format('woff'),
7 | url('//netdna.bootstrapcdn.com/font-awesome/4.0.3/fonts/fontawesome-webfont.ttf?v=4.0.3') format('truetype');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 |
12 | svg {
13 | -webkit-touch-callout: none;
14 | -webkit-user-select: none;
15 | -khtml-user-select: none;
16 | -moz-user-select: none;
17 | -ms-user-select: none;
18 | user-select: none;
19 | }
20 | .view {
21 | }
22 | .click {
23 | cursor: pointer;
24 | }
25 | .drag {
26 | cursor: pointer;
27 | cursor: -moz-grab;
28 | cursor: -webkit-grab;
29 | cursor: grab;
30 | }
31 | .node-rect {
32 | fill: hsla(0, 0%, 0%, 0.75);
33 | stroke: hsl(0, 0%, 50%);
34 | stroke-width: 2px;
35 | }
36 | .node:hover .node-rect {
37 | fill: hsla(0, 0%, 20%, 0.75);
38 | }
39 | .small .node-rect {
40 | fill: hsla(0, 0%, 10%, 0.9);
41 | stroke: none;
42 | }
43 | .node-bg {
44 | opacity: 0;
45 | }
46 |
47 | path {
48 | fill: none;
49 | }
50 | .edge-bg {
51 | stroke: black;
52 | stroke-width: 4px;
53 | }
54 | .edge:hover .edge-bg {
55 | stroke: gray;
56 | }
57 | .edge-fg {
58 | stroke: white;
59 | stroke-width: 2px;
60 |
61 | transition-property: stroke-width;
62 | transition-duration: 0.5s;
63 | }
64 | .edge-touch {
65 | stroke: white;
66 | stroke-width: 10px;
67 | opacity: 0;
68 | }
69 | .small .edge-bg {
70 | stroke-width: 8px;
71 | }
72 | .small .edge-fg {
73 | stroke-width: 4px;
74 | }
75 |
76 | text {
77 | font-family: "SourceCodePro", "Source Code Pro", Helvetica, Arial, sans-serif;
78 | text-rendering: geometricPrecision; /* makes text scale smoothly */
79 | font-size: 14px;
80 | fill: white;
81 | text-anchor: start;
82 | }
83 |
84 | .node-label,
85 | .node-sublabel {
86 | text-anchor: middle;
87 | }
88 | .small .node-label {
89 | visibility: hidden;
90 | }
91 |
92 | .node-sublabel {
93 | font-size: 9px;
94 | visibility: hidden;
95 | }
96 | .big .node-sublabel {
97 | visibility: visible;
98 | }
99 |
100 |
101 |
102 | /* Context menu - node */
103 | .context-modal-bg {
104 | fill: rgba(0,0,0,0.5);
105 | }
106 |
107 | .context-icon {
108 | font-size: 30px;
109 | fill: hsl(0, 0%, 100%);
110 | }
111 | .context-node-label {
112 | text-anchor: middle;
113 | }
114 | .context-arc {
115 | stroke-width: 72px;
116 | stroke: hsla(211, 12%, 19%, 0.95);
117 | }
118 | .context-slice:hover .context-arc,
119 | .context-arc:hover {
120 | stroke: hsla(211, 12%, 40%, 0.95);
121 | }
122 | .context-circle {
123 | stroke: hsl(0, 0%, 75%);
124 | fill: none;
125 | stroke-width: 2px;
126 | }
127 | .context-circle-x {
128 | stroke: hsl(0, 0%, 50%);
129 | fill: none;
130 | stroke-width: 1px;
131 | }
132 |
133 |
134 | /* Context menu - port */
135 | .context-port-bg {
136 | fill: hsla(211, 12%, 19%, 0.95);
137 | }
138 | .context-port-hole {
139 | fill: hsla(211, 12%, 19%, 0.95);
140 | stroke: hsl(0, 0%, 75%);
141 | }
142 | .context-port-path {
143 | stroke: hsl(0, 0%, 75%);
144 | }
145 | .context-port-label {
146 | fill: hsl(0, 0%, 75%);
147 | dominant-baseline: central;
148 | }
149 | .context-port-in .context-port-label {
150 | text-anchor: end;
151 | }
152 | .context-port-out .context-port-label {
153 | text-anchor: start;
154 | }
155 |
156 |
157 | .tooltip {
158 | opacity: 1.0;
159 |
160 | transition-property: opacity;
161 | transition-duration: 0.3s;
162 | }
163 | .tooltip.hidden {
164 | opacity: 0;
165 | }
166 | .tooltip-bg {
167 | fill: black;
168 | opacity: 0.75;
169 | }
170 | .tooltip-label {
171 | font-size: 10px;
172 | }
173 |
174 | .icon {
175 | font-family: 'FontAwesome';
176 | text-anchor: middle;
177 | dominant-baseline: central;
178 | }
179 | .node-icon {
180 | font-size: 40px;
181 | fill: hsl(0, 0%, 75%);
182 |
183 | transition-property: font-size, fill;
184 | transition-duration: 0.5s, 0.3s;
185 | }
186 | .small .node-icon {
187 | fill: hsl(0, 0%, 100%);
188 | font-size: 68px;
189 | }
190 | .big .node-icon {
191 | fill: hsl(0, 0%, 25%);
192 | }
193 |
194 | .port-circle {
195 | fill: black;
196 | stroke: hsl(0, 0%, 50%);
197 | stroke-width: 1px;
198 | }
199 | .small .port-circle {
200 | visibility: hidden;
201 | }
202 | .port-label {
203 | visibility: hidden;
204 | font-size: 6px;
205 | /*alignment-baseline: middle;*/
206 | dominant-baseline: central;
207 | }
208 | .port-label-8 { font-size: 5.8px; }
209 | .port-label-9 { font-size: 5.6px; }
210 | .port-label-10 { font-size: 5.4px; }
211 | .port-label-11 { font-size: 5.2px; }
212 | .port-label-12 { font-size: 5px; }
213 |
214 | .inports .port-label {
215 | text-anchor: start;
216 | }
217 | .outports .port-label {
218 | text-anchor: end;
219 | }
220 | .big .port-label {
221 | visibility: visible;
222 | }
223 |
224 |
225 | .group-box {
226 | fill: hsla(60, 100%, 20%, 0.25);
227 |
228 | transition-property: fill;
229 | transition-duration: 0.3s;
230 | }
231 | .group-box:hover {
232 | fill: hsla(60, 100%, 30%, 0.25);
233 | }
234 |
235 | .group-label {
236 | text-anchor: start;
237 | fill: white;
238 | font-size: 20px;
239 | transition-property: font-size;
240 | transition-duration: 0.5s;
241 | }
242 | .small .group-label {
243 | font-size: 30px;
244 | transition-property: font-size;
245 | transition-duration: 0.5s;
246 | }
247 |
248 | .group-description {
249 | fill: hsl(0, 0%, 75%);
250 | font-size: 12px;
251 | text-anchor: start;
252 | }
253 | .small .group-description {
254 | visibility: hidden;
255 | }
256 |
257 | .route0 .edge-fg { stroke: hsl( 10, 100%, 100%); }
258 | .route1 .edge-fg { stroke: hsl( 10, 100%, 70% ); }
259 | .route2 .edge-fg { stroke: hsl( 30, 100%, 70% ); }
260 | .route3 .edge-fg { stroke: hsl( 60, 100%, 70% ); }
261 | .route4 .edge-fg { stroke: hsl( 90, 100%, 70% ); }
262 | .route5 .edge-fg { stroke: hsl(120, 100%, 70% ); }
263 | .route6 .edge-fg { stroke: hsl(150, 100%, 70% ); }
264 | .route7 .edge-fg { stroke: hsl(180, 100%, 70% ); }
265 | .route8 .edge-fg { stroke: hsl(210, 100%, 70% ); }
266 | .route9 .edge-fg { stroke: hsl(240, 100%, 70% ); }
267 | .route10 .edge-fg { stroke: hsl(270, 100%, 70% ); }
268 | .route11 .edge-fg { stroke: hsl(300, 100%, 70% ); }
269 | .route12 .edge-fg { stroke: hsl(330, 100%, 70% ); }
270 |
--------------------------------------------------------------------------------
/react/the-graph-tooltip.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | var TheGraph = context.TheGraph;
5 |
6 |
7 | // Port view
8 |
9 | TheGraph.Tooltip = React.createClass({
10 | componentDidUpdate: function (prevProps, prevState, rootNode) {
11 | // HACK til 0.9.0
12 | if (prevProps.label != this.props.label) {
13 | this.refs.label.getDOMNode().textContent = this.props.label;
14 | }
15 | if (prevProps.visible != this.props.visible) {
16 | var c = "tooltip" + (this.props.visible ? "" : " hidden");
17 | this.getDOMNode().setAttribute("class", c);
18 | }
19 | },
20 | render: function() {
21 | return (
22 | React.DOM.g(
23 | {
24 | className: "tooltip" + (this.props.visible ? "" : " hidden"),
25 | transform: "translate("+this.props.x+","+this.props.y+")",
26 | },
27 | React.DOM.rect({
28 | className: "tooltip-bg",
29 | x: 0,
30 | y: -14,
31 | rx: 3,
32 | ry: 3,
33 | height: 16,
34 | width: this.props.label.length * 6
35 | }),
36 | React.DOM.text({
37 | className: "tooltip-label",
38 | ref: "label",
39 | children: this.props.label
40 | })
41 | )
42 | );
43 | }
44 | });
45 |
46 |
47 | })(this);
--------------------------------------------------------------------------------
/react/the-graph.js:
--------------------------------------------------------------------------------
1 | (function (context) {
2 | "use strict";
3 |
4 | // Dumb module setup
5 | var TheGraph = context.TheGraph = {
6 | nodeSize: 72,
7 | nodeRadius: 8,
8 | nodeSide: 56,
9 | // Context menus
10 | contextPortSize: 36,
11 | // Zoom breakpoints
12 | zbpBig: 1.2,
13 | zbpNormal: 0.4,
14 | zbpSmall: 0.01
15 | };
16 |
17 | // React setup
18 | React.initializeTouchEvents(true);
19 |
20 | // rAF shim
21 | window.requestAnimationFrame = window.requestAnimationFrame ||
22 | window.webkitRequestAnimationFrame ||
23 | window.mozRequestAnimationFrame ||
24 | window.msRequestAnimationFrame;
25 |
26 | // Mixins to use throughout project
27 | TheGraph.mixins = {};
28 |
29 | // Touch to mouse
30 | // Class must have onMouseDown onMouseMove onMouseUp
31 | TheGraph.mixins.FakeMouse = {
32 | onTouchStart: function (event) {
33 | event.preventDefault();
34 | if (event.touches && event.touches.length === 1) {
35 | this.onMouseDown(event);
36 | }
37 | },
38 | onTouchMove: function (event) {
39 | event.preventDefault();
40 | this.onMouseMove(event);
41 | },
42 | onTouchEnd: function (event) {
43 | event.preventDefault();
44 | if (event.touches && event.touches.length === 0) {
45 | this.onMouseUp(event);
46 | }
47 | },
48 | componentDidMount: function (rootNode) {
49 | // First touch maps to mouse
50 | this.getDOMNode().addEventListener("touchstart", this.onTouchStart);
51 | this.getDOMNode().addEventListener("touchmove", this.onTouchMove);
52 | this.getDOMNode().addEventListener("touchend", this.onTouchEnd);
53 | this.getDOMNode().addEventListener("touchcancel", this.onTouchEnd);
54 | }
55 | };
56 |
57 | // Show fake tooltip
58 | // Class must have getTooltipTrigger (dom node) and shouldShowTooltip (boolean)
59 | TheGraph.mixins.Tooltip = {
60 | showTooltip: function (event) {
61 | if ( !this.shouldShowTooltip() ) { return; }
62 |
63 | var tooltipEvent = new CustomEvent('the-graph-tooltip', {
64 | detail: {
65 | tooltip: this.props.label,
66 | x: event.pageX,
67 | y: event.pageY
68 | },
69 | bubbles: true
70 | });
71 | this.getDOMNode().dispatchEvent(tooltipEvent);
72 | },
73 | hideTooltip: function (event) {
74 | if ( !this.shouldShowTooltip() ) { return; }
75 |
76 | var tooltipEvent = new CustomEvent('the-graph-tooltip-hide', {
77 | bubbles: true
78 | });
79 | this.getDOMNode().dispatchEvent(tooltipEvent);
80 | },
81 | componentDidMount: function (rootNode) {
82 | if (navigator && navigator.userAgent.indexOf("Firefox") !== -1) {
83 | // HACK Ff does native tooltips on svg elements
84 | return;
85 | }
86 | var tooltipper = this.getTooltipTrigger();
87 | tooltipper.addEventListener("mouseenter", this.showTooltip);
88 | tooltipper.addEventListener("mouseleave", this.hideTooltip);
89 | }
90 | };
91 |
92 | TheGraph.findMinMax = function (graph, nodes) {
93 | if (nodes === undefined) {
94 | nodes = Object.keys(graph.processes);
95 | }
96 | if (nodes.length < 1) {
97 | return undefined;
98 | }
99 | var minX = Infinity;
100 | var minY = Infinity;
101 | var maxX = -Infinity;
102 | var maxY = -Infinity;
103 |
104 | var len = nodes.length;
105 | for (var i=0; i maxX) { maxX = process.metadata.x; }
114 | if (process.metadata.y > maxY) { maxY = process.metadata.y; }
115 | }
116 | if (!isFinite(minX) || !isFinite(minY) || !isFinite(maxX) || !isFinite(maxY)) {
117 | minX = 0;
118 | minY = 0;
119 | maxX = 0;
120 | maxY = 0;
121 | return;
122 | }
123 | return {
124 | minX: minX,
125 | minY: minY,
126 | maxX: maxX,
127 | maxY: maxY
128 | };
129 | };
130 |
131 | TheGraph.findFit = function (graph, width, height) {
132 | var limits = TheGraph.findMinMax(graph);
133 | if (!limits) {
134 | return {x:0, y:0, scale:1};
135 | }
136 | limits.minX -= TheGraph.nodeSize;
137 | limits.minY -= TheGraph.nodeSize;
138 | limits.maxX += TheGraph.nodeSize * 2;
139 | limits.maxY += TheGraph.nodeSize * 2;
140 |
141 | var gWidth = limits.maxX - limits.minX;
142 | var gHeight = limits.maxY - limits.minY;
143 |
144 | var scaleX = width / gWidth;
145 | var scaleY = height / gHeight;
146 |
147 | var scale, x, y;
148 | if (scaleX < scaleY) {
149 | scale = scaleX;
150 | x = 0 - limits.minX * scale;
151 | y = 0 - limits.minY * scale + (height-(gHeight*scale))/2;
152 | } else {
153 | scale = scaleY;
154 | x = 0 - limits.minX * scale + (width-(gWidth*scale))/2;
155 | y = 0 - limits.minY * scale;
156 | }
157 |
158 | return {
159 | x: x,
160 | y: y,
161 | scale: scale
162 | };
163 | }
164 |
165 |
166 | })(this);
--------------------------------------------------------------------------------