Created
October 11, 2017 16:13
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"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" | |
} | |
] | |
} | |
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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