Skip to content

Commit

Permalink
Add more layout engines (uber-archive#19)
Browse files Browse the repository at this point in the history
Added NGraph and Webcola layout engine
  • Loading branch information
javidhsueh authored Oct 2, 2017
1 parent e058cc1 commit f0c4253
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 0 deletions.
116 changes: 116 additions & 0 deletions src/demos/graph/common/cola.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import * as d3 from 'd3';
import {window} from 'global';
window.d3 = d3;
import {d3adaptor} from 'webcola';

const defaultOptions = {
width: 900,
height: 800,
iteration: 30
};

export default class Cola {
constructor() {
this._options = defaultOptions;
// custom layout data structure
this._colaGraph = {nodes: [], edges: []};
this._nodeMap = {};
this._edgeMap = {};

// initialize layout engineis
const {width, height} = this._options;
this._simulator = d3adaptor(d3).size([width / 2, height / 2]);
// register event callbacks
this._simulator.on('tick', this._ticked);
}

_ticked = () => {
if (this._onUpdate) {
this._onUpdate();
}
};

registerCallbacks(onUpdate) {
this._onUpdate = onUpdate;
}

unregisterCallbacks() {
this._onUpdate = null;
this._onDone = null;
this._simulator.on('tick', null);
}

start() {
if (this._colaGraph.nodes.length === 0) {
return;
}
const iteration = this._options.iteration;
const graph = this._colaGraph;
this._simulator
.nodes(graph.nodes)
.links(graph.edges)
.jaccardLinkLengths(100, 0.7)
// .symmetricDiffLinkLengths(24)
.handleDisconnected(false)
.start(iteration);
}

update(graph) {
// nodes
const newNodeMap = {};
const newColaNodes = graph.nodes.map(node => {
const oldColaNode = this._nodeMap[node.id];
const newColaNode = oldColaNode
? oldColaNode
: {
id: node.id,
x: 0,
y: 0
};

newNodeMap[node.id] = newColaNode;
return newColaNode;
});
this._nodeMap = newNodeMap;
this._colaGraph.nodes = newColaNodes;
// edges
const newEdgeMap = {};
const newColaEdges = graph.edges.map(edge => {
const newColaEdge = {
id: edge.id,
source: this._colaGraph.nodes.findIndex(n => n.id === edge.source),
target: this._colaGraph.nodes.findIndex(n => n.id === edge.target)
};
newEdgeMap[edge.id] = newColaEdge;
return newColaEdge;
});
this._edgeMap = newEdgeMap;
this._colaGraph.edges = newColaEdges;
}

alpha() {
return this._simulator.alpha();
}

getNodePosition = node => {
const colaNode = this._nodeMap[node.id];
if (colaNode) {
return [colaNode.x, colaNode.y];
}
return [0, 0];
};

getEdgePosition = edge => {
const colaEdge = this._edgeMap[edge.id];
if (colaEdge) {
return {
sourcePosition: [colaEdge.source.x, colaEdge.source.y],
targetPosition: [colaEdge.target.x, colaEdge.target.y]
};
}
return {
sourcePosition: [0, 0],
targetPosition: [0, 0]
};
};
}
126 changes: 126 additions & 0 deletions src/demos/graph/common/ngraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import NGraph from 'ngraph.graph';
import NGraphForceLayout from 'ngraph.forcelayout';
import raf from 'raf';

const defaultOptions = {
stableMomemtumDiff: 0.003,
physicsSetting: {
springLength: 90,
springCoeff: 0.0001,
gravity: -1.2,
theta: 0.3,
dragCoeff: 0.01,
timeStep: 20
}
};

export default class NGraphForce {

constructor() {
this._options = defaultOptions;
// custom layout data structure
this._ngraph = NGraph();
this._lastMomemtum = 0;
// initialize layout engine
this._simulator = NGraphForceLayout(
this._ngraph,
this._options.physicsSetting
);
}

_ticked = () => {
this._lastMomemtum = this._simulator.lastMove;
if (this._onUpdate) {
this._onUpdate();
}
};

registerCallbacks(onUpdate) {
this._onUpdate = onUpdate;
}

unregisterCallbacks() {
this._simulator.dispose();
this._onUpdate = null;
this._onDone = null;
}

_frame = () => {
this._simulator.step();
// check if the layout is stable
const {stableMomemtumDiff} = this._options;
const momemtumDiff = Math.abs(
this._simulator.lastMove - this._lastMomemtum
);
const isStable = momemtumDiff <= stableMomemtumDiff;
// trigger callbacks
this._ticked();

if (!isStable) {
// request animation frame
raf(this._frame);
}
};

start() {
if (this._ngraph.getNodesCount() === 0) {
return;
}
// request animation frame
raf(this._frame);
}

update(graph) {
const _ngraph = this._ngraph;
// remove non-exist node
this._ngraph.forEachNode(nNode => {
const node = graph.findNode(nNode.id);
if (!node) {
this._ngraph.removeNode(nNode.id);
}
});
// add new nodes
graph.nodes.forEach(node => {
const nNode = this._ngraph.getNode(node.id);
if (!nNode) {
// add new node
const newNode = _ngraph.addNode(node.id);
// set to origin
this._simulator.setNodePosition(node.id, 0, 0);
}
});

// remove non-exist edge
this._ngraph.forEachLink(nEdge => {
const edgeId = nEdge.data;
if (!graph.findEdge(edgeId)) {
this._ngraph.removeLink(nEdge);
}
});
graph.edges.forEach(edge => {
const nEdge = this._ngraph.getLink(edge.source, edge.target);
if (!nEdge) {
// add new edge
_ngraph.addLink(edge.source, edge.target, edge.id);
}
});
}

alpha() {
return this._lastMomemtum;
}

getNodePosition = node => {
const nNodePos = this._simulator.getNodePosition(node.id);
return [nNodePos.x, nNodePos.y];
};

getEdgePosition = edge => {
const sourceNodePos = this._simulator.getNodePosition(edge.source);
const targetNodePos = this._simulator.getNodePosition(edge.target);
return {
sourcePosition: [sourceNodePos.x, sourceNodePos.y],
targetPosition: [targetNodePos.x, targetNodePos.y]
};
};
}

0 comments on commit f0c4253

Please sign in to comment.