Last active
October 7, 2024 15:27
-
-
Save jexp/c643d1694d0967ea395c4574e720058c to your computer and use it in GitHub Desktop.
Loading the Galaxy Network of the "Cosmic Web" into Neo4j (source http://cosmicweb.barabasilab.com/)
This file contains hidden or 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
// source | |
// https://cosmicweb.kimalbrecht.com | |
// https://cosmicweb.kimalbrecht.com/viz/#1 | |
// data-source | |
// http://cosmicweb.kimalbrecht.com/viz/data/12-05-15/ccnr-universe-nodes-nn.csv | |
create constraint on (g:Galaxy) assert g.id is unique; | |
// create galaxies | |
// need row-id for d3 based linking | |
with "http://cosmicweb.kimalbrecht.com/viz/data/12-05-15/ccnr-universe-nodes-nn.csv" as nodes | |
load csv with headers from nodes as row | |
with collect(row) as rows | |
unwind range(0,size(rows)-1) as id | |
create (g:Galaxy {id:id}) set g+=rows[id]; | |
// Fixed Length Model - All galaxies within a set distance of l are connected by an undirected link. | |
with "http://cosmicweb.kimalbrecht.com/viz/data/12-05-15/ccnr-universe-fll-t-1-15.csv" as relationships | |
load csv with headers from relationships as row | |
match (g1:Galaxy {id:toInteger(row.source)}),(g2:Galaxy {id:toInteger(row.target)}) | |
create (g1)-[:FLL]->(g2); | |
// Varying Length Model The length of each link is proportional to the “size” of the galaxy, l = a * R(i) ^ (1/2) | |
with "http://cosmicweb.kimalbrecht.com/viz/data/12-05-15/ccnr-universe-vll-t-1-10.csv" as relationships | |
load csv with headers from relationships as row | |
match (g1:Galaxy {id:toInteger(row.source)}),(g2:Galaxy {id:toInteger(row.target)}) | |
create (g1)-[:VLL]->(g2); | |
// Nearest Neighbors Model - Each galaxy is connected to its closest neighbors with a directed links. | |
// In this model the length of each link depends on the distance to the nearest galaxy. | |
with "http://cosmicweb.kimalbrecht.com/viz/data/12-05-15/ccnr-universe-nn-t-1-10.csv" as relationships | |
load csv with headers from relationships as row | |
match (g1:Galaxy {id:toInteger(row.source)}),(g2:Galaxy {id:toInteger(row.target)}) | |
create (g1)-[:NN]->(g2); | |
// top 10 galaxies by degree | |
match (g:Galaxy) | |
return g, size( (g)-[:NN]-() ) as degree | |
order by degree desc limit 10; | |
// top 10 galaxies by degree, visualized | |
match (g:Galaxy) | |
with g, size( (g)--() ) as degree | |
order by degree desc limit 10 | |
MATCH path = (g)-[:NN]-() | |
return path; | |
// neighborhood of an average galaxy | |
match (g:Galaxy) WHERE size( (g)--() ) = 10 | |
WITH g LIMIT 1 | |
MATCH path = (g)-[:NN*..5]-() | |
return path; |
This file contains hidden or 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
<!-- | |
bower install neo4j-driver | |
bower isntall vivagraphjs | |
python -m SimpleHTTPServer 8002 | |
open http://localhost:8002 | |
--> | |
<html> | |
<head> | |
<meta http-equiv="Content-type" content="text/html; charset=utf-8"> | |
<title>Neo4j NGraph Test</title> | |
<script src="bower_components/neo4j-driver/lib/browser/neo4j-web.min.js"></script> | |
<script src="bower_components/vivagraphjs/dist/vivagraph.js"></script> | |
<script type="text/javascript" charset="utf-8"> | |
function onload() { | |
var neo = neo4j.v1; | |
var driver = neo.driver("bolt://localhost", neo.auth.basic("neo4j", "test")); | |
var session = driver.session(); | |
var dump = { | |
onNext: function(record) { console.log(record.keys, record.length, record._fields, record._fieldLookup); }, | |
onCompleted: function() { console.log("Completed"); }, | |
onError: console.log | |
} | |
// session.run("MATCH (n) RETURN COUNT(*)").subscribe(dump); | |
var counter = function() { | |
var start = Date.now(); | |
return { | |
count : 0, | |
onNext: function(r) { this.count++; }, | |
onCompleted: function() { console.log("rows",this.count,"took",(Date.now()-start)); }} | |
}; | |
// session.run("CYPHER runtime=compiled MATCH (n) RETURN id(n)").subscribe(counter()); | |
// var graphGenerator = Viva.Graph.generator(); | |
// var graph = graphGenerator.balancedBinTree(10); | |
var graph = Viva.Graph.graph(); | |
// graph.addLink(1, 2); | |
var layout = Viva.Graph.Layout.forceDirected(graph, { | |
springLength : 30, | |
springCoeff : 0.0008, | |
dragCoeff : 0.01, | |
gravity : -1.2, | |
theta : 1 | |
}); | |
var nodeColor = 0x009ee8FF, // hex rrggbb | |
nodeSize = 6; | |
var colors = {Member:0x008cc1FF, Topic: 0x58b535FF,Group:0xf58220FF}; | |
var graphics = Viva.Graph.View.webglGraphics(); | |
// shader program is overkill and circles make it slow | |
// first, tell webgl graphics we want to use custom shader | |
// to render nodes: | |
// var circleNode = buildCircleNodeShader(); | |
// graphics.setNodeProgram(circleNode); | |
// second, change the node ui model, which can be understood | |
// by the custom shader: | |
graphics.node(function (node) { | |
var color = colors[node.data] || 0x0f5788; | |
var degree = node.links.length; | |
var size = Math.log(degree + 1)*5; | |
// console.log("color",color,"data",node.data,"size",size,"degree",degree) | |
return new Viva.Graph.View.webglSquare(size, color); | |
// return new WebglCircle(nodeSize, nodeColor); | |
}); | |
graphics.link(function (link) { | |
return Viva.Graph.View.webglLine(0x909090A0); // light transparent gray | |
}); | |
var renderer = Viva.Graph.View.renderer(graph, | |
{ | |
layout : layout, | |
graphics : graphics, | |
renderLinks : true, | |
prerender : true, | |
container: document.getElementById('graph') | |
}); | |
var count = 0; | |
var finished = 0; | |
var viva = { | |
onNext: function(record) { | |
count ++; | |
var n1 = record._fields[0]; | |
// console.log(n1); | |
if (record.length == 2) { | |
graph.addNode(n1); | |
} | |
if (record.length == 2) { | |
var n2 = record._fields[1]; | |
graph.addLink(n1, n2); | |
} | |
if (record.length == 4) { | |
var n2 = record._fields[2]; | |
graph.addNode(n1, record._fields[1]) | |
graph.addNode(n2, record._fields[3]) | |
graph.addLink(n1, n2); | |
} | |
if (count % 5000 == 0) console.log("Currently",count,"links"); | |
}, | |
onCompleted: function() { | |
console.log("Query finished, currently ",count,"links"); | |
// render after all data was added | |
// renderer.run(); | |
finished ++; | |
if (finished == 3) { | |
setTimeout(function() { console.log("Pausing renderer"); renderer.pause(); },10000); | |
} | |
} | |
}; | |
function query(pattern, limit) { | |
limit = limit||10000; | |
var statement = "CYPHER runtime=compiled MATCH "+pattern+" RETURN id(from) as n, from.type as nt, id(to) as m, to.type as mt LIMIT "+limit; | |
console.log("Running",statement); | |
session.run(statement).subscribe(viva); | |
} | |
// session.run("CYPHER runtime=compiled MATCH (n) RETURN id(n) as id LIMIT 10").subscribe(viva); | |
query("(to:Galaxy)<-[:NN]-(from:Galaxy)",250000); | |
renderer.run(); // render incrementally as data is added | |
} | |
// Lets start from the easiest part - model object for node ui in webgl | |
function WebglCircle(size, color) { | |
this.size = size; | |
this.color = color; | |
} | |
// Next comes the hard part - implementation of API for custom shader | |
// program, used by webgl renderer: | |
function buildCircleNodeShader() { | |
// For each primitive we need 4 attributes: x, y, color and size. | |
var ATTRIBUTES_PER_PRIMITIVE = 4, | |
nodesFS = [ | |
'precision mediump float;', | |
'varying vec4 color;', | |
'void main(void) {', | |
' if ((gl_PointCoord.x - 0.5) * (gl_PointCoord.x - 0.5) + (gl_PointCoord.y - 0.5) * (gl_PointCoord.y - 0.5) < 0.25) {', | |
' gl_FragColor = color;', | |
' } else {', | |
' gl_FragColor = vec4(0);', | |
' }', | |
'}'].join('\n'), | |
nodesVS = [ | |
'attribute vec2 a_vertexPos;', | |
// Pack color and size into vector. First elemnt is color, second - size. | |
// Since it's floating point we can only use 24 bit to pack colors... | |
// thus alpha channel is dropped, and is always assumed to be 1. | |
'attribute vec2 a_customAttributes;', | |
'uniform vec2 u_screenSize;', | |
'uniform mat4 u_transform;', | |
'varying vec4 color;', | |
'void main(void) {', | |
' gl_Position = u_transform * vec4(a_vertexPos/u_screenSize, 0, 1);', | |
' gl_PointSize = a_customAttributes[1] * u_transform[0][0];', | |
' float c = a_customAttributes[0];', | |
' color.b = mod(c, 256.0); c = floor(c/256.0);', | |
' color.g = mod(c, 256.0); c = floor(c/256.0);', | |
' color.r = mod(c, 256.0); c = floor(c/256.0); color /= 255.0;', | |
' color.a = 1.0;', | |
'}'].join('\n'); | |
var program, | |
gl, | |
buffer, | |
locations, | |
utils, | |
nodes = new Float32Array(64), | |
nodesCount = 0, | |
canvasWidth, canvasHeight, transform, | |
isCanvasDirty; | |
return { | |
/** | |
* Called by webgl renderer to load the shader into gl context. | |
*/ | |
load : function (glContext) { | |
gl = glContext; | |
webglUtils = Viva.Graph.webgl(glContext); | |
program = webglUtils.createProgram(nodesVS, nodesFS); | |
gl.useProgram(program); | |
locations = webglUtils.getLocations(program, ['a_vertexPos', 'a_customAttributes', 'u_screenSize', 'u_transform']); | |
gl.enableVertexAttribArray(locations.vertexPos); | |
gl.enableVertexAttribArray(locations.customAttributes); | |
buffer = gl.createBuffer(); | |
}, | |
/** | |
* Called by webgl renderer to update node position in the buffer array | |
* | |
* @param nodeUI - data model for the rendered node (WebGLCircle in this case) | |
* @param pos - {x, y} coordinates of the node. | |
*/ | |
position : function (nodeUI, pos) { | |
var idx = nodeUI.id; | |
nodes[idx * ATTRIBUTES_PER_PRIMITIVE] = pos.x; | |
nodes[idx * ATTRIBUTES_PER_PRIMITIVE + 1] = -pos.y; | |
nodes[idx * ATTRIBUTES_PER_PRIMITIVE + 2] = nodeUI.color; | |
nodes[idx * ATTRIBUTES_PER_PRIMITIVE + 3] = nodeUI.size; | |
}, | |
/** | |
* Request from webgl renderer to actually draw our stuff into the | |
* gl context. This is the core of our shader. | |
*/ | |
render : function() { | |
gl.useProgram(program); | |
gl.bindBuffer(gl.ARRAY_BUFFER, buffer); | |
gl.bufferData(gl.ARRAY_BUFFER, nodes, gl.DYNAMIC_DRAW); | |
if (isCanvasDirty) { | |
isCanvasDirty = false; | |
gl.uniformMatrix4fv(locations.transform, false, transform); | |
gl.uniform2f(locations.screenSize, canvasWidth, canvasHeight); | |
} | |
gl.vertexAttribPointer(locations.vertexPos, 2, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 0); | |
gl.vertexAttribPointer(locations.customAttributes, 2, gl.FLOAT, false, ATTRIBUTES_PER_PRIMITIVE * Float32Array.BYTES_PER_ELEMENT, 2 * 4); | |
gl.drawArrays(gl.POINTS, 0, nodesCount); | |
}, | |
/** | |
* Called by webgl renderer when user scales/pans the canvas with nodes. | |
*/ | |
updateTransform : function (newTransform) { | |
transform = newTransform; | |
isCanvasDirty = true; | |
}, | |
/** | |
* Called by webgl renderer when user resizes the canvas with nodes. | |
*/ | |
updateSize : function (newCanvasWidth, newCanvasHeight) { | |
canvasWidth = newCanvasWidth; | |
canvasHeight = newCanvasHeight; | |
isCanvasDirty = true; | |
}, | |
/** | |
* Called by webgl renderer to notify us that the new node was created in the graph | |
*/ | |
createNode : function (node) { | |
nodes = webglUtils.extendArray(nodes, nodesCount, ATTRIBUTES_PER_PRIMITIVE); | |
nodesCount += 1; | |
}, | |
/** | |
* Called by webgl renderer to notify us that the node was removed from the graph | |
*/ | |
removeNode : function (node) { | |
if (nodesCount > 0) { nodesCount -=1; } | |
if (node.id < nodesCount && nodesCount > 0) { | |
// we do not really delete anything from the buffer. | |
// Instead we swap deleted node with the "last" node in the | |
// buffer and decrease marker of the "last" node. Gives nice O(1) | |
// performance, but make code slightly harder than it could be: | |
webglUtils.copyArrayPart(nodes, node.id*ATTRIBUTES_PER_PRIMITIVE, nodesCount*ATTRIBUTES_PER_PRIMITIVE, ATTRIBUTES_PER_PRIMITIVE); | |
} | |
}, | |
/** | |
* This method is called by webgl renderer when it changes parts of its | |
* buffers. We don't use it here, but it's needed by API (see the comment | |
* in the removeNode() method) | |
*/ | |
replaceProperties : function(replacedNode, newNode) {}, | |
}; | |
} | |
</script> | |
<style type="text/css" media="screen"> | |
html, body, svg { width: 100%; height: 100%;} | |
</style> | |
</head> | |
<body id="index" onload="onload();"> | |
<div id="graph" style="width:100%;height:100%;background-color: white;"></div> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment