diff --git a/app/components/architecture-visualisation.js b/app/components/architecture-visualisation.js new file mode 100644 index 0000000000000000000000000000000000000000..999db597a66eced3a989552f667aece0391cc982 --- /dev/null +++ b/app/components/architecture-visualisation.js @@ -0,0 +1,95 @@ +import Ember from 'ember'; +import d3 from 'd3'; +import klay from 'npm:klayjs-d3'; +import _ from 'npm:lodash'; + +export default Ember.Component.extend({ + 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"); + + this.layouter = klay.d3kgraph() + .size([width, height]) + .transformGroup(root) + .options({ + edgeRouting: "ORTHOGONAL" + }); + // group shizzle + + this.debug('element', this.element); + + function redraw() { + svg.attr("transform", "translate(" + d3.event.translate + ") scale(" + d3.event.scale + ")"); + } + + const onFinish = (layout) => { + log('loaded layout', layout); + const nodes = this.layouter.nodes(); + const links = this.layouter.links(nodes); + + const nodeData = root.selectAll('.node') + .data(nodes, p => p.id); + const node = nodeData.enter() + .append('g') + .attr('class', d => d.children? 'node compound' : 'node leaf'); + + const atoms = node.append('rect') + .attr('width', 10) + .attr('height', 10) + .attr('x', 0) + .attr('y', 0); + + node.append('title') + .text(d => d.id); + + node.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'); + + + const linkData = root.selectAll('.link') + .data(links, p => p.id); + const link = linkData.enter() + .append('path') + .attr('class', 'link') + .attr('d', 'M0 0'); + + // apply edge routes + link.transition().attr('d', (d) => { + 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 node positions + node.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.layouter.kgraph(this.get('graph')); + } +}); + + diff --git a/app/router.js b/app/router.js index 3bba78ebc802aa7058429c00823be2bbc1c7e5e1..42acc413d3f8efe20738be8a7c8271bc85121c81 100644 --- a/app/router.js +++ b/app/router.js @@ -6,6 +6,7 @@ const Router = Ember.Router.extend({ }); Router.map(function() { + this.route('architecture', {path: '/architecture'}); }); export default Router; diff --git a/app/routes/architecture.js b/app/routes/architecture.js new file mode 100644 index 0000000000000000000000000000000000000000..ac893ceee1439816536805c3f25ac2f3063e6f10 --- /dev/null +++ b/app/routes/architecture.js @@ -0,0 +1,61 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + model() { + return { + id: 'root', + labels: [{text: 'My Architecture'}], + children: [{ + id: 'VM_Big', + children: [{ + id: 'VM_Big>Auth' + }, { + id: 'VM_Big>User' + }], + edges: [{ + id: 'VM_Big>Auth->VM_Big>User', + labels: [ { text: 'e1' } ], + source: 'VM_Big>Auth', + target: 'VM_Big>User' + }] + }, + { + id: 'VM_1', + children: [{ + id: 'VM_1>Auth', + labels: [{text: "Auth 1"}] + }] + }, + { + id: 'VM_2', + children: [{ + id: 'VM_2>Auth', + labels: [{text: "Auth 2"}] + }] + }, + { + id: 'VM_3', + children: [{ + id: 'VM_3>User', + labels: [{text: "User 1"}] + }] + }, + { + id: 'VM_4', + children: [{ + id: 'VM_4>User', + labels: [{text: "User 2"}] + }] + }], + edges: [{ + id: 'VM_1>Auth->VM_3>User', + source: 'VM_1>Auth', + target: 'VM_3>User', + }, { + id: 'VM_2>Auth->VM_4>User', + source: 'VM_2>Auth', + target: 'VM_4>User', + }] + }; + } +}); \ No newline at end of file diff --git a/app/styles/app.scss b/app/styles/app.scss new file mode 100644 index 0000000000000000000000000000000000000000..22c865c75eb09ad1035034cb569003c581ee4b8a --- /dev/null +++ b/app/styles/app.scss @@ -0,0 +1,29 @@ +@import "components/_architecture"; + +body { + font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; +} +g.leaf > rect { + stroke: #fff; + stroke-width: 1px; + opacity: .5; +} + +g.compound > rect { + opacity: 0.1; +} + +.node { +} + +.link { + stroke: #999; + stroke-opacity: .6; + fill: none; +} + +.port { + stroke: #000; + width: 1px; + opacity: .6; +} diff --git a/app/styles/components/_architecture.scss b/app/styles/components/_architecture.scss new file mode 100644 index 0000000000000000000000000000000000000000..e7d390ac207243eebc01f8c4c7a3400ad3ce96cc --- /dev/null +++ b/app/styles/components/_architecture.scss @@ -0,0 +1,12 @@ +svg.architectureVisualisation { + g.compound > rect:hover { + stroke: white; + stroke-opacity: 1; + fill: red; + } + .link:hover { + stroke: #A000; + stroke-opacity: 1; + fill: black; + } +} \ No newline at end of file diff --git a/app/templates/application.hbs b/app/templates/application.hbs index f8bc38e7b6acef284ef970a869839bfb5940fe6b..3f1cdacb398f1994220046030016ab13ca6f44ac 100644 --- a/app/templates/application.hbs +++ b/app/templates/application.hbs @@ -1,3 +1,4 @@ <h2 id="title">Welcome to Ember</h2> +<h3>Go to {{#link-to 'architecture'}}Klay-based Architecture{{/link-to}}</h3> {{outlet}} diff --git a/app/templates/architecture.hbs b/app/templates/architecture.hbs new file mode 100644 index 0000000000000000000000000000000000000000..a7234d6af09ec983a740a041b0b6f7eb5a40aac5 --- /dev/null +++ b/app/templates/architecture.hbs @@ -0,0 +1,2 @@ +Visualisierung: +{{architecture-visualisation graph=model}} \ No newline at end of file diff --git a/app/templates/components/architecture-visualisation.hbs b/app/templates/components/architecture-visualisation.hbs new file mode 100644 index 0000000000000000000000000000000000000000..0eddf3f82e98a9e7cab17156b2c0add2ef368f26 --- /dev/null +++ b/app/templates/components/architecture-visualisation.hbs @@ -0,0 +1 @@ +<!-- will only contain svg which is dynamically added --> \ No newline at end of file diff --git a/bower.json b/bower.json index 67577b57a936f01893a199428a8f521751096453..826579c2b37b7bd9fe45dc79191aa061d1050a18 100644 --- a/bower.json +++ b/bower.json @@ -4,6 +4,8 @@ "ember": "~2.4.1", "ember-cli-shims": "0.1.0", "ember-cli-test-loader": "0.2.2", - "ember-qunit-notifications": "0.1.0" + "ember-qunit-notifications": "0.1.0", + "d3": "^3.5.16", + "visionmedia-debug": "2.2" } } diff --git a/package.json b/package.json index c44e9b06c2026b92f9b560a825866a8d5ec5869b..46367b26428f9435f1d8ff3aa94034e850a34f3e 100644 --- a/package.json +++ b/package.json @@ -21,22 +21,32 @@ "devDependencies": { "broccoli-asset-rev": "^2.2.0", "ember-ajax": "0.7.1", + "ember-browserify": "^1.1.8", "ember-cli": "2.4.2", "ember-cli-app-version": "^1.0.0", "ember-cli-babel": "^5.1.5", + "ember-cli-d3": "1.1.6", "ember-cli-dependency-checker": "^1.2.0", "ember-cli-htmlbars": "^1.0.1", "ember-cli-htmlbars-inline-precompile": "^0.3.1", "ember-cli-inject-live-reload": "^1.3.1", "ember-cli-qunit": "^1.2.1", "ember-cli-release": "0.2.8", + "ember-cli-sass": "5.3.1", "ember-cli-sri": "^2.1.0", "ember-cli-uglify": "^1.2.0", + "ember-d3": "0.1.0", "ember-data": "^2.4.0", + "ember-debug-logger": "0.2.0", "ember-disable-proxy-controllers": "^1.0.1", "ember-export-application-global": "^1.0.4", "ember-load-initializers": "^0.5.0", "ember-resolver": "^2.0.3", - "loader.js": "^4.0.0" + "loader.js": "^4.0.0", + "lodash": "^4.11.1" + }, + "dependencies": { + "klayjs": "^0.4.1", + "klayjs-d3": "^0.3.4" } }