mirror of https://github.com/rancher/ui.git
D3Graph WIP
This commit is contained in:
parent
65ffd0e504
commit
f5780a4936
|
|
@ -10,7 +10,7 @@
|
||||||
"Terminal",
|
"Terminal",
|
||||||
"Prism",
|
"Prism",
|
||||||
"Ui",
|
"Ui",
|
||||||
"cytoscape"
|
"dagreD3"
|
||||||
],
|
],
|
||||||
"browser" : true,
|
"browser" : true,
|
||||||
"boss" : true,
|
"boss" : true,
|
||||||
|
|
|
||||||
|
|
@ -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/bootstrap-multiselect/dist/css/bootstrap-multiselect.css');
|
||||||
app.import('bower_components/prism/prism.js');
|
app.import('bower_components/prism/prism.js');
|
||||||
app.import('bower_components/prism/components/prism-yaml.js');
|
app.import('bower_components/prism/components/prism-yaml.js');
|
||||||
app.import('bower_components/cytoscape/dist/cytoscape.js');
|
app.import('bower_components/lodash/dist/lodash.js');
|
||||||
app.import('bower_components/cytoscape/lib/dagre.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();
|
module.exports = app.toTree();
|
||||||
|
|
|
||||||
|
|
@ -1,173 +1,129 @@
|
||||||
import Ember from 'ember';
|
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,{
|
export default Ember.View.extend({
|
||||||
cy: null,
|
graphZoom: null,
|
||||||
cyElem: null,
|
graphInner: null,
|
||||||
|
graphOuter: null,
|
||||||
onResize: function() {
|
graphRender: null,
|
||||||
var cy = this.get('cy');
|
graph: null,
|
||||||
if ( cy )
|
|
||||||
{
|
|
||||||
setTimeout(function() {
|
|
||||||
cy.fit(null, 20);
|
|
||||||
},100);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
didInsertElement: function() {
|
didInsertElement: function() {
|
||||||
this._super();
|
this._super();
|
||||||
var elem = $('<div id="environment-graph"></div>').appendTo('BODY');
|
var elem = $('<div id="environment-graph"><svg style="width: 100%; height: 100%;"><g/></svg></div>').appendTo('BODY');
|
||||||
elem.css('top', $('MAIN').position().top + $('MAIN').height() + 'px');
|
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() {
|
updateGraph: function() {
|
||||||
console.log('starting updateGraph');
|
var g = this.get('graph');
|
||||||
var cy = this.get('cy');
|
|
||||||
cy.startBatch();
|
|
||||||
|
|
||||||
var services = this.get('context.services');
|
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) {
|
var unremovedServices = services.filter(function(service) {
|
||||||
return ['removed','purging','purged'].indexOf(service.get('state')) === -1;
|
return ['removed','purging','purged'].indexOf(service.get('state')) === -1;
|
||||||
});
|
});
|
||||||
|
|
||||||
unremovedServices.forEach(function(service) {
|
unremovedServices.forEach(function(service) {
|
||||||
var serviceId = service.get('id');
|
var serviceId = service.get('id');
|
||||||
var node = cy.getElementById(serviceId)[0];
|
var html = '<h4>'+ Util.escapeHtml(service.get('name')) + '</h4><h6 class="state">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</h6>';
|
||||||
if ( !node )
|
|
||||||
{
|
g.setNode(serviceId, {
|
||||||
relayout = true;
|
labelType: "html",
|
||||||
node = cy.add({
|
label: html,
|
||||||
group: 'nodes',
|
padding: 0,
|
||||||
data: {
|
class: (service.get('state') === 'active' ? 'green' : (service.get('state') === 'inactive' ? 'red' : 'yellow')),
|
||||||
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')),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expectedNodes = expectedNodes.add(node);
|
unremovedServices.forEach(function(service) {
|
||||||
|
var serviceId = service.get('id');
|
||||||
var edge;
|
|
||||||
(service.get('consumedservices')||[]).map(function(target) {
|
(service.get('consumedservices')||[]).map(function(target) {
|
||||||
var targetId = target.get('id');
|
var targetId = target.get('id');
|
||||||
edge = cy.edges().filter(function(i, e) {
|
var color = '#f3bd24';
|
||||||
return e.source().id() === serviceId && e.target().id() === targetId;
|
if ( target.get('state') === 'active' )
|
||||||
})[0];
|
|
||||||
|
|
||||||
if ( !edge )
|
|
||||||
{
|
{
|
||||||
relayout = true;
|
color = '#41c77d';
|
||||||
edge = cy.add({
|
}
|
||||||
group: 'edges',
|
else if (target.get('state') === 'inactive' )
|
||||||
data: {
|
{
|
||||||
source: serviceId,
|
color = '#ff0000';
|
||||||
target: targetId,
|
|
||||||
},
|
|
||||||
})[0];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
edge.data({
|
g.setEdge(serviceId, targetId, {
|
||||||
color: (target.get('state') === 'active' ? '#41c77d' : (target.get('state') === 'inactive' ? '#f00' : '#f3bd24')),
|
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
|
// Remove nodes & edges that shouldn't be there
|
||||||
cy.nodes().not(expectedNodes).remove();
|
|
||||||
cy.edges().not(expectedEdges).remove();
|
this.renderGraph();
|
||||||
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');
|
|
||||||
}
|
|
||||||
console.log('done updateGraph');
|
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() {
|
throttledUpdateGraph: function() {
|
||||||
Ember.run.throttle(this,'updateGraph',500);
|
Ember.run.throttle(this,'updateGraph',500);
|
||||||
}.observes('context.services.@each.{id,name,state,consumedServicesUpdated}'),
|
}.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() {
|
willDestroyElement: function() {
|
||||||
this._super();
|
this._super();
|
||||||
var cy = this.get('cy');
|
var cy = this.get('cy');
|
||||||
|
|
@ -176,7 +132,7 @@ export default Ember.View.extend(ThrottledResize,{
|
||||||
cy.destroy();
|
cy.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
var elem = this.get('cyElem');
|
var elem = this.get('graphElem');
|
||||||
if ( elem )
|
if ( elem )
|
||||||
{
|
{
|
||||||
$(elem).remove();
|
$(elem).remove();
|
||||||
|
|
|
||||||
|
|
@ -6,4 +6,64 @@
|
||||||
bottom: 0px;
|
bottom: 0px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
overflow: hidden;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@
|
||||||
"bootstrap-multiselect": "~0.9.10",
|
"bootstrap-multiselect": "~0.9.10",
|
||||||
"zeroclipboard": "~2.2.0",
|
"zeroclipboard": "~2.2.0",
|
||||||
"prism": "gh-pages",
|
"prism": "gh-pages",
|
||||||
"cytoscape": "~2.3.15",
|
"dagre": "~0.7.1",
|
||||||
"dagre": "~0.7.1"
|
"dagre-d3": "~0.4.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue