Skip to content

Instantly share code, notes, and snippets.

@ableuler
Created October 11, 2017 16:13
Show Gist options
  • Save ableuler/3ee99fe0c14638e0749192e8a3eae60f to your computer and use it in GitHub Desktop.
Save ableuler/3ee99fe0c14638e0749192e8a3eae60f to your computer and use it in GitHub Desktop.
Experiment on triggering updates of a d3 force layout through a web socket connection
Just a little experiment about triggering updates of a d3 force layout through a web socket connection.
Dependincies
"http-server" and "websocket" packages from npm
Run it
- Start the connection: node ws-server.js
- Serve index.html: http-server
- Load the webpage in a browser
- Go back to the terminal window where the web socket is running and press enter while observing the browser window.
[
{
"operation": "add",
"nodes": [
{
"id": "1"
},
{
"id": "2"
},
{
"id": "3"
},
{
"id": "4"
},
{
"id": "5"
}
],
"links": []
},
{
"operation": "add",
"nodes": [],
"links": [
{
"source": "1",
"target": "2"
},
{
"source": "2",
"target": "3"
}
]
},
{
"operation": "add",
"nodes": [],
"links": [
{
"source": "3",
"target": "4"
},
{
"source": "4",
"target": "5"
}
]
},
{
"operation": "add",
"nodes": [
{
"id": "6"
}
],
"links": [
{
"source": "1",
"target": "6"
},
{
"source": "3",
"target": "6"
}
]
},
{
"operation": "add",
"nodes": [],
"links": [
{
"source": "2",
"target": "6"
}
]
},
{
"operation": "remove",
"nodes": [
{
"id": "1"
}
],
"links": []
},
{
"operation": "remove",
"nodes": [],
"links": [
{
"source": "3",
"target": "4"
}
]
}
]
<!DOCTYPE html>
<meta charset='utf-8'>
<style>
.links line {
stroke: #999;
stroke-opacity: 0.6;
stroke-width: 2px;
}
.nodes circle {
stroke: #fff;
stroke-width: 1.5px;
}
</style>
<svg width='960' height='600'></svg>
<script src='https://d3js.org/d3.v4.min.js'></script>
<script>
var svg = d3.select('svg'),
width = +svg.attr('width'),
height = +svg.attr('height');
// Append the groups for links and nodes to SVG
svg.append('g').attr('class', 'links');
svg.append('g').attr('class', 'nodes');
// Want access to graph data outside fo initial callback
var graph = {
'nodes': [],
'links': []
};
// Initiate simulation
var simulation = d3.forceSimulation()
.force('link', d3.forceLink().id(function(d) { return d.id; }))
.force('charge', d3.forceManyBody())
.force('center', d3.forceCenter(width / 2, height / 2))
.alphaTarget(0.2)
.alphaMin(0.201);
// Launch web socket and start listening
var ws = new WebSocket('ws://localhost:1337');
ws.onmessage = function(event) {
var messageData = JSON.parse(event.data);
if (messageData.operation === 'add') {
// Initialize new nodes to the center of the box
for (let i in messageData.nodes) {
messageData.nodes[i].x = width / 2;
messageData.nodes[i].y = height / 2;
}
graph.nodes = graph.nodes.concat(messageData.nodes);
graph.links = graph.links.concat(messageData.links);
} else if (messageData.operation === 'remove') {
// Remove nodes and attached links
for (let iNode in messageData.nodes){
removeNode(messageData.nodes[iNode].id);
}
// Remove links
for (let iLink in messageData.links){
removeLink(messageData.links[iLink].source, messageData.links[iLink].target);
}
}
updateGraph();
};
// Remove a node by its id from the graph together with all links it connects to.
function removeNode(nodeId){
for (let iremove in graph.nodes) {
if (nodeId == graph.nodes[iremove].id) {
// Remove node
graph.nodes.splice(iremove, 1);
// Find and remove all links that connect to that node.
for (ilink = graph.links.length - 1; ilink >= 0; ilink--) {
if (graph.links[ilink].source.index == iremove || graph.links[ilink].target.index == iremove) {
graph.links.splice(ilink, 1);
}
}
}
return;
}
}
function removeLink(sourceId, targetId){
for (let iremove in graph.links) {
if (graph.links[iremove].source.id == sourceId && graph.links[iremove].target.id == targetId) {
graph.links.splice(iremove, 1);
return;
}
}
console.log('Can not remove link ' + sourceId + ' to ' + targetId + '. Link not found.');
}
function updateGraph() {
link = svg.selectAll('.links').selectAll('line').data(graph.links);
link.exit().remove();
link.enter().append('line');
node = svg.selectAll('.nodes').selectAll('circle').data(graph.nodes);
node.exit().remove();
node.enter()
.append('circle')
.attr('r', 8);
node = svg.selectAll('.nodes').selectAll('circle');
simulation
.nodes(graph.nodes)
.on('tick', ticked);
link = svg.selectAll('.links').selectAll('line');
simulation.force('link')
.links(graph.links);
// Need to reset alpha value before restarting, otherwise nodes don't move!
simulation.alpha(1.0);
simulation.restart();
}
function ticked() {
link
.attr('x1', function(d) { return d.source.x; })
.attr('y1', function(d) { return d.source.y; })
.attr('x2', function(d) { return d.target.x; })
.attr('y2', function(d) { return d.target.y; });
node
.attr('cx', function(d) { return d.x; })
.attr('cy', function(d) { return d.y; });
}
</script>
var graphPieces = require('./graph-pieces.json');
var isend;
var webSocketsServerPort = 1337;
var webSocketServer = require('websocket').server;
var http = require('http');
// Listen for keyboard input
var stdin = process.openStdin();
// Global variable to have access to the connected server outside of the callback
var client = null;
// Create a dummy http server and start listening
var server = http.createServer(function(request, response) {});
server.listen(webSocketsServerPort, function() {
console.log((new Date()) + " Server is listening on port "
+ webSocketsServerPort);
});
var wsServer = new webSocketServer({
httpServer: server
});
wsServer.on('request', function(request) {
console.log((new Date()) + ' Connection from origin '
+ request.origin + '.');
if (client !== null) {
console.log( 'Only one simultaneous connection allowed!');
return;
}
client = request.accept(null, request.origin);
console.log((new Date()) + ' Connection accepted.');
isend = 0;
stdin.write('Press enter to trigger graph modifications on the client side.')
// Client sent some message
client.on('message', function(message) {
// Accept only text for now
if (message.type === 'utf8') {
console.log((new Date()) + ' Received message:' + message.utf8Data);
}
});
// Client disconnected
client.on('close', function() {
client = null;
});
});
stdin.on('data', function(chunk) {
if (client === null) {
console.log('No client connected.')
} else if (isend == graphPieces.length) {
console.log('That was all, no more modifications.')
} else {
client.send(JSON.stringify(graphPieces[isend]));
console.log('Sent to client: ' + JSON.stringify(graphPieces[isend]));
isend++;
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment