diff --git a/.jshintrc b/.jshintrc
index 2f044640a..d7a73c7f7 100644
--- a/.jshintrc
+++ b/.jshintrc
@@ -10,7 +10,7 @@
"Terminal",
"Prism",
"Ui",
- "cytoscape"
+ "dagreD3"
],
"browser" : true,
"boss" : true,
diff --git a/Brocfile.js b/Brocfile.js
index ae4cad2f8..229ce1461 100644
--- a/Brocfile.js
+++ b/Brocfile.js
@@ -59,7 +59,9 @@ app.import('bower_components/bootstrap-multiselect/dist/js/bootstrap-multiselect
app.import('bower_components/bootstrap-multiselect/dist/css/bootstrap-multiselect.css');
app.import('bower_components/prism/prism.js');
app.import('bower_components/prism/components/prism-yaml.js');
-app.import('bower_components/cytoscape/dist/cytoscape.js');
-app.import('bower_components/cytoscape/lib/dagre.js');
+app.import('bower_components/lodash/dist/lodash.js');
+app.import('bower_components/graphlib/dist/graphlib.core.js');
+app.import('bower_components/dagre/dist/dagre.core.js');
+app.import('bower_components/dagre-d3/dist/dagre-d3.core.js');
module.exports = app.toTree();
diff --git a/app/environment/graph/view.js b/app/environment/graph/view.js
index 15ccfe908..06a5169c8 100644
--- a/app/environment/graph/view.js
+++ b/app/environment/graph/view.js
@@ -1,173 +1,129 @@
import Ember from 'ember';
-import ThrottledResize from 'ui/mixins/throttled-resize';
+import Util from 'ui/utils/util';
+//import ThrottledResize from 'ui/mixins/throttled-resize';
-export default Ember.View.extend(ThrottledResize,{
- cy: null,
- cyElem: null,
-
- onResize: function() {
- var cy = this.get('cy');
- if ( cy )
- {
- setTimeout(function() {
- cy.fit(null, 20);
- },100);
- }
- },
+export default Ember.View.extend({
+ graphZoom: null,
+ graphInner: null,
+ graphOuter: null,
+ graphRender: null,
+ graph: null,
didInsertElement: function() {
this._super();
- var elem = $('
').appendTo('BODY');
+ var elem = $('').appendTo('BODY');
elem.css('top', $('MAIN').position().top + $('MAIN').height() + 'px');
- this.set('cyElem', elem[0]);
+ this.set('graphElem', elem[0]);
- Ember.run.later(this,'graph',250);
+ Ember.run.later(this,'initGraph',100);
+ },
+
+ initGraph: function() {
+ var outer = d3.select("#environment-graph svg");
+ var inner = outer.select("g");
+ var zoom = d3.behavior.zoom().on("zoom", function() {
+ // inner.attr("transform", "translate(" + d3.event.translate + ")" +
+ // "scale(" + d3.event.scale + ")");
+ });
+
+ outer.call(zoom);
+
+ var g = new dagreD3.graphlib.Graph().setGraph({
+ rankdir: "TB",
+ nodesep: 70,
+ ranksep: 50,
+ marginx: 30,
+ marginy: 30
+ });
+
+ var render = new dagreD3.render();
+ this.setProperties({
+ graphZoom: zoom,
+ graphOuter: outer,
+ graphInner: inner,
+ graphRender: render,
+ graph: g
+ });
+
+ this.updateGraph();
},
updateGraph: function() {
- console.log('starting updateGraph');
- var cy = this.get('cy');
- cy.startBatch();
-
+ var g = this.get('graph');
var services = this.get('context.services');
-
- var relayout = false;
- var startNodes = cy.nodes().length;
- var startEdges = cy.edges().length;
- var expectedNodes = cy.collection();
- var expectedEdges = cy.collection();
var unremovedServices = services.filter(function(service) {
return ['removed','purging','purged'].indexOf(service.get('state')) === -1;
});
unremovedServices.forEach(function(service) {
var serviceId = service.get('id');
- var node = cy.getElementById(serviceId)[0];
- if ( !node )
- {
- relayout = true;
- node = cy.add({
- group: 'nodes',
- data: {
- id: serviceId,
- shape: 'ellipse',
- },
- position: {x: 0, y: 0}
- })[0];
- }
- node.data({
- name: service.get('name'),
- color: (service.get('state') === 'active' ? '#41c77d' : (service.get('state') === 'inactive' ? '#f00' : '#f3bd24')),
+ var html = ''+ Util.escapeHtml(service.get('name')) + '
' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '
';
+
+ g.setNode(serviceId, {
+ labelType: "html",
+ label: html,
+ padding: 0,
+ class: (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow')),
});
+ });
- expectedNodes = expectedNodes.add(node);
-
- var edge;
+ unremovedServices.forEach(function(service) {
+ var serviceId = service.get('id');
(service.get('consumedservices')||[]).map(function(target) {
var targetId = target.get('id');
- edge = cy.edges().filter(function(i, e) {
- return e.source().id() === serviceId && e.target().id() === targetId;
- })[0];
-
- if ( !edge )
+ var color = '#f3bd24';
+ if ( target.get('state') === 'active' )
{
- relayout = true;
- edge = cy.add({
- group: 'edges',
- data: {
- source: serviceId,
- target: targetId,
- },
- })[0];
+ color = '#41c77d';
+ }
+ else if (target.get('state') === 'inactive' )
+ {
+ color = '#ff0000';
}
- edge.data({
- color: (target.get('state') === 'active' ? '#41c77d' : (target.get('state') === 'inactive' ? '#f00' : '#f3bd24')),
+ g.setEdge(serviceId, targetId, {
+ label: '',
+ arrowhead: 'vee',
+ lineInterpolate: 'bundle',
+ class: (target.get('state') === 'active' ? 'green' : (target.get('state') === 'inactive' ? 'red' : 'yellow')),
});
-
- expectedEdges = expectedEdges.add(edge);
});
});
// Remove nodes & edges that shouldn't be there
- cy.nodes().not(expectedNodes).remove();
- cy.edges().not(expectedEdges).remove();
- console.log('end batch');
- cy.endBatch();
- if ( relayout || startNodes !== cy.nodes().length || startEdges !== cy.edges().length )
- {
- console.log('layout');
- cy.layout();
- console.log('end layout');
- }
+
+ this.renderGraph();
console.log('done updateGraph');
},
+ renderGraph: function() {
+ console.log('start renderGraph');
+ var zoom = this.get('graphZoom');
+ var render = this.get('graphRender');
+ var inner = this.get('graphInner');
+ var outer = this.get('graphOuter');
+ var g = this.get('graph');
+
+ inner.call(render, g);
+
+ // Zoom and scale to fit
+ var zoomScale = zoom.scale();
+ var graphWidth = g.graph().width + 80;
+ var graphHeight = g.graph().height + 40;
+ var width = $('#environment-graph').width();
+ var height = $('#environment-graph').height();
+ zoomScale = Math.min(width / graphWidth, height / graphHeight);
+ var translate = [(width/2) - ((graphWidth*zoomScale)/2), (height/2) - ((graphHeight*zoomScale)/2)];
+ zoom.translate(translate);
+ zoom.scale(zoomScale);
+ zoom.event(outer.transition().duration(500));
+ console.log('done renderGraph');
+ },
+
throttledUpdateGraph: function() {
Ember.run.throttle(this,'updateGraph',500);
}.observes('context.services.@each.{id,name,state,consumedServicesUpdated}'),
- graph: function() {
- var style = cytoscape.stylesheet()
- .selector('node')
- .css({
- 'shape': 'data(shape)',
- 'content': 'data(name)',
- 'text-valign': 'center',
- 'text-outline-width': 2,
- 'text-outline-color': 'data(color)',
- 'border-width': 2,
- 'border-color': '#f4f5f8',
- 'background-color': 'data(color)',
- 'font-family': '"Open Sans"',
- 'font-weight': 300,
- 'font-size': 13,
- 'color': '#fff',
- })
- .selector(':selected')
- .css({
- 'border-width': 3,
- 'border-color': '#333'
- })
- .selector('edge')
- .css({
- 'opacity': 0.8,
- 'width': 'mapData(strength, 70, 100, 2, 6)',
- 'target-arrow-shape': 'triangle-backcurve',
- 'source-arrow-shape': 'none',
- 'line-color': 'data(color)',
- 'source-arrow-color': 'data(color)',
- 'target-arrow-color': 'data(color)'
- })
- .selector('edge.questionable')
- .css({
- 'line-style': 'dotted',
- 'target-arrow-shape': 'diamond'
- })
- .selector('.faded')
- .css({
- 'opacity': 0.25,
- 'text-opacity': 0
- });
- // End: style
-
- var cy = cytoscape({
- container: this.get('cyElem'),
- style: style,
- userZoomingEnabled: false,
- userPanningEnabled: false,
- layout: {
- name: 'dagre',
- animate: false,
- padding: 20,
- edgeSep: 20,
- },
- });
-
- this.set('cy', cy);
- this.updateGraph();
- },
-
willDestroyElement: function() {
this._super();
var cy = this.get('cy');
@@ -176,7 +132,7 @@ export default Ember.View.extend(ThrottledResize,{
cy.destroy();
}
- var elem = this.get('cyElem');
+ var elem = this.get('graphElem');
if ( elem )
{
$(elem).remove();
diff --git a/app/styles/environment.scss b/app/styles/environment.scss
index 29ed34f23..575f2dbc3 100644
--- a/app/styles/environment.scss
+++ b/app/styles/environment.scss
@@ -6,4 +6,64 @@
bottom: 0px;
z-index: 3;
overflow: hidden;
+
+ .node {
+ &.green rect {
+ stroke: #41c77d;
+ }
+
+ &.yellow rect {
+ stroke: #f3bd24;
+ }
+
+ &.red rect {
+ stroke: #ff0000;
+ }
+
+ g div {
+ width: 100px;
+ height: 40px;
+ padding: 10px 5px;
+ }
+
+ .label {
+ color: black;
+ display: initial;
+ padding: initial;
+ font-size: initial;
+ font-weight: initial;
+ line-height: initial;
+ color: initial;
+ text-align: center;
+ white-space: initial;
+ vertical-align: initial;
+ border-radius: initial;
+ }
+
+ rect {
+ fill: #fff;
+ stroke-width: 3px;
+ color: black;
+ }
+ }
+
+ .edgePath {
+ path.path {
+ stroke: #333;
+ fill: none;
+ stroke-width: 3px;
+ }
+
+ &.green path.path {
+ stroke: #41c77d;
+ }
+
+ &.yellow path.path {
+ stroke: #f3bd24;
+ }
+
+ &.red path.path {
+ stroke: #ff0000;
+ }
+ }
}
diff --git a/bower.json b/bower.json
index 405bcc272..1809e7e93 100644
--- a/bower.json
+++ b/bower.json
@@ -20,7 +20,7 @@
"bootstrap-multiselect": "~0.9.10",
"zeroclipboard": "~2.2.0",
"prism": "gh-pages",
- "cytoscape": "~2.3.15",
- "dagre": "~0.7.1"
+ "dagre": "~0.7.1",
+ "dagre-d3": "~0.4.3"
}
}