diff --git a/app/components/architecture-visualisation-cola.js b/app/components/architecture-visualisation-cola.js new file mode 100644 index 0000000000000000000000000000000000000000..14a538d5d32e88391b73b8f395d890cab5442237 --- /dev/null +++ b/app/components/architecture-visualisation-cola.js @@ -0,0 +1,145 @@ +import Ember from 'ember'; +import d3 from 'd3'; +import cola from 'npm:webcola'; +import _ from 'npm:lodash'; + +export default Ember.Component.extend({ + init: function()Â { + this._super(); + const self = this; + const log = self.debug.bind(self); + log('loaded'); + // this.interval = setInterval(function() { + // const graph = self.get('graph'); + + // log('update interval called', graph); + + // if(!graph) { + // return; // eventual consistent + // } + // const edges = graph.edges || []; + // const index = Math.floor(Math.random()*edges.length); + // if(Math.random() > 0.5) { + // edges.splice(index, 1); + // } else { // FIXME: does not render updates + // const children = _.get(graph, 'children', []); + // const randomNodeIndex = Math.floor(Math.random()*children.length); + // const randomNodeId = children[randomNodeIndex].id; + // graph.edges = _.set(edges, `${index}.target`, randomNodeId); + // log(`updating random edge (${index}) to target a random node id ${randomNodeId}`, randomNodeIndex, edges); + // } + // self.set('graph', graph); + // self.renderGraph(); + // }, 1000); + }, + willDestroyElement() { + clearInterval(this.interval); + }, + didInsertElement: function() { + const log = this.debug.bind(this); + const width = window.innerWidth; + const height = window.innerHeight; + this.debug('element', this.element); + const zoom = d3.behavior.zoom() + .on("zoom", redraw); + + const svg = d3.select(this.element) + .append("svg") + .attr("width", width) + .attr("height", height) + .attr('class', 'architectureVisualisation') + .call(zoom) + .append("g"); + const root = svg.append('g').attr('class', 'root'); + + this.svg = svg; + this.layouter = cola.d3adaptor() + .linkDistance(80) + .avoidOverlaps(true) + .handleDisconnected(false) + .size([width, height]); + // group shizzle + + this.debug('element', this.element); + + function redraw() { + svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); + } + + const graph = this.get('graph'); + + const nodes = graph.nodes; + const links = graph.links; + const groups = graph.groups; + this.layouter + .nodes(nodes) + .links(links) + .groups(groups) + .start(100, 0, 50, 50); + + // nodes + const nodeData = root.selectAll('.node') + .data(nodes); + const groupData = root.selectAll('.group') + .data(groups); + const nodeEnter = nodeData.enter() + .append('g'); + + nodeData + .attr('class', 'node leaf') + .attr('width', 10) + .attr('height', 10) + .attr('x', 0) + .attr('y', 0); + + const atoms = nodeEnter.append('rect'); + + nodeEnter.append('title') + .text(d => d.id); + + nodeEnter.append('text') + .attr('x', (d) => d.children? 0 : 2.5) + .attr('y', 5) + .text((d) => _.get(d, 'labels.0.text', d.id)) + .attr('font-size', '4px') + .attr('font-size', '4px'); + + nodeData.exit() + .remove(); + + // edges + // FIXME does not handle updates (neither enter() nor exit()) + const linkData = root.selectAll('.link') + .data(links, p => p.id); + const link = linkData.enter() + .append('path') + .attr('class', 'link') + .attr('d', 'M0 0'); + + + linkData.exit() + .remove(); + + // apply edge routes + // linkData.transition().attr('d', (d) => { + // log('test', this.layouter); + // let path = ''; + // path += 'M' + d.sourcePoint.x + ' ' + d.sourcePoint.y + ' '; + // (d.bendPoints || []).forEach(bp => path += 'L' + bp.x + ' ' + bp.y + ' '); + // path += 'L' + d.targetPoint.x + ' ' + d.targetPoint.y + ' '; + // return path; + // }); + + // apply nodeEnter positions + nodeData.transition() + .attr('transform', (d) => 'translate(' + (d.x || 0) + ' ' + (d.y || 0) + ')'); + + atoms.transition() + .attr('width', (d) => d.width) + .attr('height', (d) => d.height); + // this.layouter.on('finish', onFinish); + // this.renderGraph(); + } +}); + + diff --git a/app/components/architecture-visualisation-cytoscape.js b/app/components/architecture-visualisation-cytoscape.js new file mode 100644 index 0000000000000000000000000000000000000000..e4d7bb53bf17423cc117bf2cb370f4b9cb2ccfc3 --- /dev/null +++ b/app/components/architecture-visualisation-cytoscape.js @@ -0,0 +1,97 @@ +import Ember from 'ember'; +import cytoscape from 'npm:cytoscape'; +import cycola from 'npm:cytoscape-cola'; +// import cola from 'npm:webcola'; +import _ from 'npm:lodash'; + +export default Ember.Component.extend({ + classNames: ['cytoscapeRenderingSpace'], + init: function()Â { + cycola( cytoscape, cola ); + + this._super(); + const self = this; + const log = self.debug.bind(self); + log('loaded'); + // this.interval = setInterval(function() { + // const graph = self.get('graph'); + + // log('update interval called', graph); + + // if(!graph) { + // return; // eventual consistent + // } + // const edges = graph.edges || []; + // const index = Math.floor(Math.random()*edges.length); + // if(Math.random() > 0.5) { + // edges.splice(index, 1); + // } else { // FIXME: does not render updates + // const children = _.get(graph, 'children', []); + // const randomNodeIndex = Math.floor(Math.random()*children.length); + // const randomNodeId = children[randomNodeIndex].id; + // graph.edges = _.set(edges, `${index}.target`, randomNodeId); + // log(`updating random edge (${index}) to target a random node id ${randomNodeId}`, randomNodeIndex, edges); + // } + // self.set('graph', graph); + // self.renderGraph(); + // }, 1000); + }, + willDestroyElement() { + clearInterval(this.interval); + }, + didInsertElement: function() { + this.cytoscape = cytoscape({ + container: this.element, + + boxSelectionEnabled: false, + autounselectify: true, + + style: [ + { + selector: 'node', + css: { + 'content': 'data(id)', + 'text-valign': 'center', + 'text-halign': 'center' + } + }, + { + selector: '$node > node', + css: { + 'padding-top': '10px', + 'padding-left': '10px', + 'padding-bottom': '10px', + 'padding-right': '10px', + 'text-valign': 'top', + 'text-halign': 'center', + 'background-color': '#bbb' + } + }, + { + selector: 'edge', + css: { + 'target-arrow-shape': 'triangle' + } + }, + { + selector: ':selected', + css: { + 'background-color': 'black', + 'line-color': 'black', + 'target-arrow-color': 'black', + 'source-arrow-color': 'black' + } + } + ], + + elements: this.get('graph'), + + layout: { + name: 'cola', + padding: 5 + } + }); + } +}); + + diff --git a/app/components/architecture-visualisation.js b/app/components/architecture-visualisation.js index 715f5db23a424fb9fad72920446db91e52a5530a..a4365ead3a6e6d89d8c0cd9e776b6a0bf931cb3f 100644 --- a/app/components/architecture-visualisation.js +++ b/app/components/architecture-visualisation.js @@ -8,28 +8,28 @@ export default Ember.Component.extend({ this._super(); const self = this; const log = self.debug.bind(self); - this.interval = setInterval(function() { - const graph = self.get('graph'); - - log('update interval called', graph); - - if(!graph) { - return; // eventual consistent - } - const edges = graph.edges || []; - const index = Math.floor(Math.random()*edges.length); - if(Math.random() > 0.5) { - edges.splice(index, 1); - } else { // FIXME: does not render updates - const children = _.get(graph, 'children', []); - const randomNodeIndex = Math.floor(Math.random()*children.length); - const randomNodeId = children[randomNodeIndex].id; - graph.edges = _.set(edges, `${index}.target`, randomNodeId); - log(`updating random edge (${index}) to target a random node id ${randomNodeId}`, randomNodeIndex, edges); - } - self.set('graph', graph); - self.renderGraph(); - }, 1000); + // this.interval = setInterval(function() { + // const graph = self.get('graph'); + + // log('update interval called', graph); + + // if(!graph) { + // return; // eventual consistent + // } + // const edges = graph.edges || []; + // const index = Math.floor(Math.random()*edges.length); + // if(Math.random() > 0.5) { + // edges.splice(index, 1); + // } else { // FIXME: does not render updates + // const children = _.get(graph, 'children', []); + // const randomNodeIndex = Math.floor(Math.random()*children.length); + // const randomNodeId = children[randomNodeIndex].id; + // graph.edges = _.set(edges, `${index}.target`, randomNodeId); + // log(`updating random edge (${index}) to target a random node id ${randomNodeId}`, randomNodeIndex, edges); + // } + // self.set('graph', graph); + // self.renderGraph(); + // }, 1000); }, willDestroyElement() { clearInterval(this.interval); @@ -56,7 +56,10 @@ export default Ember.Component.extend({ .size([width, height]) .transformGroup(root) .options({ - edgeRouting: "ORTHOGONAL" + edgeRouting: "ORTHOGONAL", + aspectRatio: 1.7, + separateConnComp: true, + // layoutHierarchy: false }); // group shizzle @@ -112,7 +115,6 @@ export default Ember.Component.extend({ // apply edge routes linkData.transition().attr('d', (d) => { - log('test'); let path = ''; path += 'M' + d.sourcePoint.x + ' ' + d.sourcePoint.y + ' '; (d.bendPoints || []).forEach(bp => path += 'L' + bp.x + ' ' + bp.y + ' '); diff --git a/app/index.html b/app/index.html index c49346f27f1ff588f6f731877411cf635c0f10db..4763ae76e9389e6fe4090179af1ff19a18647ca4 100644 --- a/app/index.html +++ b/app/index.html @@ -11,6 +11,7 @@ <link rel="stylesheet" href="assets/vendor.css"> <link rel="stylesheet" href="assets/frontend-prototype.css"> + <script src="http://marvl.infotech.monash.edu/webcola/cola.v3.min.js"></script> {{content-for "head-footer"}} </head> diff --git a/app/router.js b/app/router.js index f6c0454579fdefd05864742a53f7090a09a4f9a6..fc46ed2107522b1950e4d202c22c83d0133ae6aa 100644 --- a/app/router.js +++ b/app/router.js @@ -6,8 +6,10 @@ const Router = Ember.Router.extend({ }); Router.map(function() { - this.route('home', {path: '/'}); - this.route('architecture', {path: '/architecture'}); + this.route('home', {path: '/'}); + this.route('architecture', {path: '/architecture'}); + this.route('cola', {path: '/cola'}); + this.route('cytoscape'); }); export default Router; diff --git a/app/routes/architecture.js b/app/routes/architecture.js index d78a1ba4f345803dfb4d3e7e0cff2d1c4846b1a6..b3b46e58c472db2eeeffa6db2e22d40902addb85 100644 --- a/app/routes/architecture.js +++ b/app/routes/architecture.js @@ -7,7 +7,10 @@ export default Ember.Route.extend({ labels: [{text: 'My Architecture'}], 'properties': { 'direction': 'RIGHT', - 'spacing': 10 + 'spacing': 10, + 'de.cau.cs.kieler.aspectRatio': 1.7, + 'de.cau.cs.kieler.separateConnComp': true, + 'separateConnComp': true }, children: [{ id: 'VM_Profile', @@ -128,7 +131,7 @@ export default Ember.Route.extend({ id: 'VM_2>Auth->VM_4>User', source: 'VM_2>Auth', target: 'VM_4>User', - }, { + } /*{ id: 'VM_Big>User->VM_Profile>Database', source: 'VM_Big>User', target: 'VM_Profile>Database', @@ -164,7 +167,7 @@ export default Ember.Route.extend({ id: 'VM_8>User->VM_Profile>Database', source: 'VM_8>User', target: 'VM_Profile>Database', - }] + }*/] }; } }); \ No newline at end of file diff --git a/app/routes/cola.js b/app/routes/cola.js new file mode 100644 index 0000000000000000000000000000000000000000..0a406f9ade27984706d99266a8951c98da479f01 --- /dev/null +++ b/app/routes/cola.js @@ -0,0 +1,31 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + return { + "nodes":[ + {"name":"a","width":60,"height":40}, + {"name":"b","width":60,"height":40}, + {"name":"c","width":60,"height":40}, + {"name":"d","width":60,"height":40}, + {"name":"e","width":60,"height":40}, + {"name":"f","width":60,"height":40}, + {"name":"g","width":60,"height":40} + ], + "links":[ + {"source":1,"target":2}, + {"source":2,"target":3}, + {"source":3,"target":4}, + {"source":0,"target":1}, + {"source":2,"target":0}, + {"source":3,"target":5}, + {"source":0,"target":5} + ], + "groups":[ + {"leaves":[0], "groups":[1]}, + {"leaves":[1,2]}, + {"leaves":[3,4]} + ] + }; + } +}); \ No newline at end of file diff --git a/app/styles/app.scss b/app/styles/app.scss index 1c11d3099cce62f5e51e8a0286aa65b15942c61f..9fb42c09daafbdff166499016efd6792e8e03a19 100644 --- a/app/styles/app.scss +++ b/app/styles/app.scss @@ -3,4 +3,5 @@ body { } @import "components/_architecture"; +@import "components/_cytoscape"; diff --git a/app/styles/components/_cytoscape.scss b/app/styles/components/_cytoscape.scss new file mode 100644 index 0000000000000000000000000000000000000000..4fe0241a91a4e2b770bc88dbbfd579fff4dbce98 --- /dev/null +++ b/app/styles/components/_cytoscape.scss @@ -0,0 +1,7 @@ +.cytoscapeRenderingSpace { + height: 100%; + width: 100%; + position: absolute; + left: 0; + top: 0; +} \ No newline at end of file diff --git a/app/templates/application.hbs b/app/templates/application.hbs index c009e1f31ca94f76ced0bb9adafbd5b0bf5e3744..8231f96b83518b468ace123ecbc3c47ea2965219 100644 --- a/app/templates/application.hbs +++ b/app/templates/application.hbs @@ -2,6 +2,8 @@ <ul> <li>Go to {{#link-to 'home'}}Home{{/link-to}}</li> <li>Go to {{#link-to 'architecture'}}Klay-based Architecture{{/link-to}}</li> + <li>Go to {{#link-to 'cola'}}Webcola Architecture{{/link-to}}</li> + <li>Go to {{#link-to 'cytoscape'}}Cytoscape Architecture{{/link-to}}</li> </ul> {{outlet}} diff --git a/app/templates/cola.hbs b/app/templates/cola.hbs new file mode 100644 index 0000000000000000000000000000000000000000..959c621878f0e2c4072b9a971bb575f62cba133a --- /dev/null +++ b/app/templates/cola.hbs @@ -0,0 +1,2 @@ +Visualisierung: +{{architecture-visualisation-cola graph=model}} \ No newline at end of file diff --git a/app/templates/cytoscape.hbs b/app/templates/cytoscape.hbs new file mode 100644 index 0000000000000000000000000000000000000000..e74a09ba3a3edf4f1585698eac9cb9bb9b2ea931 --- /dev/null +++ b/app/templates/cytoscape.hbs @@ -0,0 +1 @@ +{{architecture-visualisation-cytoscape graph=model}} \ No newline at end of file diff --git a/package.json b/package.json index 46367b26428f9435f1d8ff3aa94034e850a34f3e..6c07f1559fa28fb642d7c1ddf05815e968819b98 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,10 @@ "lodash": "^4.11.1" }, "dependencies": { + "cytoscape": "^2.6.12", + "cytoscape-cola": "^1.4.0", "klayjs": "^0.4.1", - "klayjs-d3": "^0.3.4" + "klayjs-d3": "^0.3.4", + "webcola": "^3.1.2" } } diff --git a/tests/unit/models/baseentity-test.js b/tests/unit/models/baseentity-test.js new file mode 100644 index 0000000000000000000000000000000000000000..479e8f0e0346f8aaabd4b507aeecd38e07269e2c --- /dev/null +++ b/tests/unit/models/baseentity-test.js @@ -0,0 +1,12 @@ +import { moduleForModel, test } from 'ember-qunit'; + +moduleForModel('baseentity', 'Unit | Model | baseentity', { + // Specify the other units that are required for this test. + needs: [] +}); + +test('it exists', function(assert) { + let model = this.subject(); + // let store = this.store(); + assert.ok(!!model); +}); diff --git a/tests/unit/models/system-test.js b/tests/unit/models/system-test.js new file mode 100644 index 0000000000000000000000000000000000000000..fe98d2c93451624611df45ab7f69e7b60d294655 --- /dev/null +++ b/tests/unit/models/system-test.js @@ -0,0 +1,12 @@ +import { moduleForModel, test } from 'ember-qunit'; + +moduleForModel('system', 'Unit | Model | system', { + // Specify the other units that are required for this test. + needs: [] +}); + +test('it exists', function(assert) { + let model = this.subject(); + // let store = this.store(); + assert.ok(!!model); +}); diff --git a/tests/unit/routes/cytoscape-test.js b/tests/unit/routes/cytoscape-test.js new file mode 100644 index 0000000000000000000000000000000000000000..2b8e45a0cbe78e4f197737aaabf8575fcbd8b6c3 --- /dev/null +++ b/tests/unit/routes/cytoscape-test.js @@ -0,0 +1,11 @@ +import { moduleFor, test } from 'ember-qunit'; + +moduleFor('route:cytoscape', 'Unit | Route | cytoscape', { + // Specify the other units that are required for this test. + // needs: ['controller:foo'] +}); + +test('it exists', function(assert) { + let route = this.subject(); + assert.ok(route); +});