mirror of https://github.com/rancher/ui.git
D3Graph WIP
This commit is contained in:
parent
65ffd0e504
commit
f5780a4936
|
|
@ -10,7 +10,7 @@
|
|||
"Terminal",
|
||||
"Prism",
|
||||
"Ui",
|
||||
"cytoscape"
|
||||
"dagreD3"
|
||||
],
|
||||
"browser" : 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/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();
|
||||
|
|
|
|||
|
|
@ -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 = $('<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');
|
||||
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 = '<h4>'+ Util.escapeHtml(service.get('name')) + '</h4><h6 class="state">' + Util.escapeHtml(Util.ucFirst(service.get('state'))) + '</h6>';
|
||||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue