Skip to content
Snippets Groups Projects
Commit ba23a38d authored by Mathis Neumann's avatar Mathis Neumann
Browse files

prepare layout diffing, infinite mode for cola

parent 1c1ec32d
No related branches found
No related tags found
No related merge requests found
......@@ -5,7 +5,7 @@ import cytoscapeStyle from './style';
import _ from 'npm:lodash';
import coseBilkent from 'npm:cytoscape-cose-bilkent';
const { Component, inject, observer, on } = Ember;
const { Component, inject, observer, on, computed } = Ember;
coseBilkent(cytoscape); // register
......@@ -37,6 +37,37 @@ export default Component.extend({
visualisationEvents.off('resizeEnd', resizeListener);
});
},
layoutOptions: computed('visualisationSettings.layoutAlgorithm', function() {
return {
name: this.get('visualisationSettings.layoutAlgorithm'),
randomize: false, // kose-bilkent will randomize node positions
// maxSimulationTime: 1000,
// padding: 6,
// ungrabifyWhileSimulating: false,
// webcola options
// infinite: false // blocks all interaction
refresh: 4, // fast animation
avoidOverlap: true,
edgeLength: 250, // should be at least two times the diagonal of a block, blocks are 100x60, therefore around 2*116
unconstrIter: 1, // unconstrained initial layout iterations
userConstIter: 0, // initial layout iterations with user-specified constraints - we don't have any user constraints
allConstIter: 1, // initial layout iterations with all constraints including non-overlap
infinite: true,
fit: false
};
}),
/**
* Since not every layout algorthim supports the reuse of the same cytoscape instance, we need to filter them.
* Currently cose-bilkent and cose require a complete rerender
* @property
* @return {Boolean}
* @readonly
*/
layoutSupportsReuse: computed('visualisationSettings.layoutAlgorithm', function() {
return ['cose-bilkent', 'cose'].indexOf(this.get('visualisationSettings.layoutAlgorithm')) < 0;
}),
/**
* Observer method that renders the visualisation in a canvas using Cytoscape
* @method renderGraph
......@@ -44,34 +75,34 @@ export default Component.extend({
renderGraph: on('didInsertElement', observer('visualisationSettings.{layoutAlgorithm,theme}', 'graph', function() {
this.debug('graph', this.get('visualisationSettings.theme'), this.get('visualisationSettings.layoutAlgorithm'), this.get('graph'));
// if(!this._rendering) { // FIXME: does not handle layout/theme changes
// do not use this.set('rendering') since it would trigger rendering updates within didInsertElement
const elements = _.cloneDeep(this.get('graph'));
const stylesheet = cytoscapeStyle(this.get('visualisationSettings.themeStyle'));
if(!this._rendering || !this.get('layoutSupportsReuse')) {
if(this._rendering) {
// this._rendering.destroy(); // remove previous listeners TODO: requires https://github.com/cytoscape/cytoscape.js-cola/issues/15
}
this._rendering = cytoscape({
container: this.element,
boxSelectionEnabled: false,
autounselectify: true,
style: cytoscapeStyle(this.get('visualisationSettings.themeStyle')),
style: stylesheet,
elements: _.cloneDeep(this.get('graph')),
elements: elements,
layout: {
name: this.get('visualisationSettings.layoutAlgorithm'),
randomize: false, // kose-bilkent will randomize node positions
// maxSimulationTime: 1000,
// padding: 6,
// ungrabifyWhileSimulating: true,
// infinite: false
layout: this.get('layoutOptions')
});
const isCola = this.get('layoutOptions.name') === 'cola';
// webcola options
refresh: 5, // fast animation
avoidOverlap: true,
edgeLength: 250, // should be at least two times the diagonal of a block, blocks are 100x60, therefore around 2*116
unconstrIter: 100, // unconstrained initial layout iterations
userConstIter: 0, // initial layout iterations with user-specified constraints - we don't have any user constraints
allConstIter: 10 // initial layout iterations with all constraints including non-overlap
}
if(isCola) {
this._rendering.on('layoutready', () => {
this.debug('layoutready, using fit');
this._rendering.fit();
});
}
this._rendering.on('click', (event) => {
const target = event.cyTarget;
......@@ -89,13 +120,74 @@ export default Component.extend({
} else {
this.debug('clicked on non-selectable entity', event);
}
});
} else {
const cy = this._rendering;
cy.batch(() => {
cy.style(stylesheet);
this.applyDiff(cy, elements);
});
if(this._layout) {
this._layout.stop(); // may still be running, e.g. cola uses infinite mode
}
const layout = cy.makeLayout(this.get('layoutOptions'));
this._layout = layout;
layout.run();
}
// just for development purposes - TODO: remove
window.cy = cytoscape;
window.layout = this._layout;
window.cytoscape = this._rendering;
})),
applyDiff(cytoscapeInstance, newElements) {
cytoscapeInstance.batch(() => {
const renderedElementsById = {}; // stores cytoscape node/edge instances by id
const updatedElementsById = {}; // stores all elements that will have to be updated/added
newElements.forEach((element) => {
const id = element.data.id;
updatedElementsById[id] = element;
});
const renderedElements = newElements
.map((element) => {
const id = element.data.id;
const existingElement = cytoscapeInstance.getElementById(id);
return existingElement;
})
.filter((renderedElement) => !!renderedElement); // null if not in cytoscape
renderedElements.forEach((renderedElement) => renderedElementsById[renderedElement.id()] = renderedElement);
// existing update existing nodes/edges
renderedElements
.map((renderedElement) => {
const id = renderedElement.id();
const updatedElement = renderedElementsById[id];
renderedElement.data(updatedElement.data);
});
// remove unnecessary. Do this before adding new ones, otherwise they would new elements will be directly removed
cytoscapeInstance.elements()
.filterFn((el) => !renderedElementsById[el.id()]) // elements that are no longer in source data
.forEach((el) => el.remove());
// add new
newElements
.filter((element) => !renderedElementsById[element.data.id]) // should not have rendered counterpart
.forEach((element) => {
element.position = { // new nodes require an initial position
x: Math.random()* 10,
y: Math.random()* 10
};
cytoscapeInstance.add(element);
});
});
},
resize() {
if(this._rendering) {
this._rendering.resize();
......
......@@ -20,15 +20,12 @@ export default Ember.Service.extend({
// services not used in current view
const {services, /* serviceInstances,*/ communications} = prepared;
var network = {
nodes: [],
edges: []
};
const elements = [];
services.forEach(data => {
data.label = data.name;
network.nodes.push({
elements.push({
group: 'nodes',
data: data
});
});
......@@ -55,12 +52,13 @@ export default Ember.Service.extend({
//TODO Normalize
network.edges.push({
elements.push({
group: 'edges',
data,
classes: data.status || ''
});
});
return network;
return elements;
}
});
......@@ -22,16 +22,13 @@ export default Ember.Service.extend({
// services not used in current view
const {serviceInstances, communicationInstances, nodeGroups, nodes} = prepared;
var network = {
nodes: [],
edges: []
};
const elements = [];
nodeGroups.forEach(data => {
data.label = data.name;
network.nodes.push({
elements.push({
group: 'nodes',
data: data
});
});
......@@ -40,7 +37,8 @@ export default Ember.Service.extend({
data.label = data.name;
data.parent = data.nodeGroupId;
network.nodes.push({
elements.push({
group: 'nodes',
data: data,
classes: data.status || ''
});
......@@ -50,8 +48,10 @@ export default Ember.Service.extend({
data.label = data.name;
data.parent = data.nodeId;
network.nodes.push({
elements.push({
group: 'nodes',
data: data,
parent: data.parent,
classes: data.status || ''
});
});
......@@ -62,12 +62,13 @@ export default Ember.Service.extend({
data.technology = this.get('store').peekRecord('communication', data.communicationId).get('technology');
data.label = data.technology;
network.edges.push({
elements.push({
group: 'edges',
data,
classes: data.status || ''
});
});
return network;
return elements;
}
});
......@@ -61,7 +61,7 @@
},
"dependencies": {
"cytoscape": "^2.7.3",
"cytoscape-cola": "git+https://github.com/EyMaddis/cytoscape.js-cola.git#5c0957e60726def6d7e849956939079c5a491ba2",
"cytoscape-cola": "https://github.com/cytoscape/cytoscape.js-cola.git#8436961cd4ac1899a2a429d6c99dc2a140adcf0e",
"cytoscape-cose-bilkent": "^1.3.6",
"ember-cli": "^2.7.0",
"yuidocjs": "^0.10.1"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment