NoSQL Zone is brought to you in partnership with:

Max De Marzi, is a seasoned web developer. He started building websites in 1996 and has worked with Ruby on Rails since 2006. The web forced Max to wear many hats and master a wide range of technologies. He can be a system admin, database developer, graphic designer, back-end engineer and data scientist in the course of one afternoon. Max is a graph database enthusiast. He built the Neography Ruby Gem, a rest api wrapper to the Neo4j Graph Database. He is addicted to learning new things, loves a challenge and finding pragmatic solutions. Max is very easy to work with, focuses under pressure and has the patience of a rock. Max is a DZone MVB and is not an employee of DZone and has posted 59 posts at DZone. You can read more from them at their website. View Full User Profile

How to Use Sigma.js with Neo4j

04.12.2012
| 8131 views |
  • submit to reddit

I’ve done a few posts recently using D3.js and now I want to show you how to use two other great Javascript libraries to visualize your graphs. We’ll start with Sigma.js and soon I’ll do another post with Three.js.

We’re going to create our graph and group our nodes into five clusters. You’ll notice later on that we’re going to give our clustered nodes colors using rgb values so we’ll be able to see them move around until they find their right place in our layout. We’ll be using two Sigma.js plugins, the GEFX (Graph Exchange XML Format) parser and the ForceAtlas2 layout.

You can see what a GEFX file looks like below. Notice it comes from Gephi which is an interactive visualization and exploration platform, which runs on all major operating systems, is open source, and is free.

<?xml version="1.0" encoding="UTF-8"?>
<gexf xmlns="http://www.gephi.org/gexf" xmlns:viz="http://www.gephi.org/gexf/viz">
  <graph defaultedgetype="directed" idtype="string" type="static">
    <nodes count="500">
      <node id="1" label="tnabcuff">
        <viz:size value="12.0"/>
        <viz:color b="113" g="42" r="78"/>
        <viz:position x="-195" y="-53"/>
      </node>
      <node id="2" label="khnvxggh">
        <viz:size value="14.0"/>
        <viz:color b="237" g="250" r="36"/>
        <viz:position x="277" y="-73"/>
      </node>
      ...
    </nodes>
    <edges count="2985">
      <edge id="0" source="1" target="11"/>
      <edge id="1" source="1" target="21"/>
      <edge id="2" source="1" target="31"/>
      ...
    </edges>
  </graph>
</gexf>

In order to build this file, we will need to get the nodes and edges from the graph and create an XML file.

get '/graph.xml' do
  @nodes = nodes  
  @edges = edges
  builder :graph
end

We’ll use Cypher to get our nodes and edges:

def nodes
  neo = Neography::Rest.new
  cypher_query =  " START node = node:nodes_index(type='User')"
  cypher_query << " RETURN ID(node), node"
  neo.execute_query(cypher_query)["data"].collect{|n| {"id" => n[0]}.merge(n[1]["data"])}
end  

We need the node and relationship ids, so notice I’m using the ID() function in both cases.

def edges
  neo = Neography::Rest.new
  cypher_query =  " START source = node:nodes_index(type='User')"
  cypher_query << " MATCH source -[rel]-> target"
  cypher_query << " RETURN ID(rel), ID(source), ID(target)"
  neo.execute_query(cypher_query)["data"].collect{|n| {"id" => n[0], 
                                                       "source" => n[1], 
                                                       "target" => n[2]} 
                                                      }
end

So far we have seen graphs represented as JSON, and we’ve built these manually. Today we’ll take advantage of the Builder Ruby Gem to build our graph in XML.

xml.instruct! :xml
xml.gexf 'xmlns' => "http://www.gephi.org/gexf", 'xmlns:viz' => "http://www.gephi.org/gexf/viz"  do
  xml.graph 'defaultedgetype' => "directed", 'idtype' => "string", 'type' => "static" do
    xml.nodes :count => @nodes.size do
      @nodes.each do |n|
        xml.node :id => n["id"],    :label => n["name"] do
	   xml.tag!("viz:size",     :value => n["size"])
	   xml.tag!("viz:color",    :b => n["b"], :g => n["g"], :r => n["r"])
	   xml.tag!("viz:position", :x => n["x"], :y => n["y"]) 
	 end
      end
    end
    xml.edges :count => @edges.size do
      @edges.each do |e|
        xml.edge:id => e["id"], :source => e["source"], :target => e["target"] 
      end
    end
  end
end

You can get the code on github as usual and see it running live on Heroku at neosigma.herokuapp.com. You will want to see it live on Heroku so you can see the nodes in random positions and then move to form clusters. Use your mouse wheel to zoom in, and click and drag to move around.

Credit goes out to Alexis Jacomy and Mathieu Jacomy.

You’ve seen me create numerous random graphs, but for completeness here is the code for this graph. Notice how I create 5 clusters and for each node I assign half its relationships to other nodes in their cluster and half to random nodes? This is so the ForceAtlas2 layout plugin clusters our nodes neatly.

def create_graph
  neo = Neography::Rest.new
  graph_exists = neo.get_node_properties(1)
  return if graph_exists && graph_exists['name']

  names = 500.times.collect{|x| generate_text}
  clusters = 5.times.collect{|x| {:r => rand(256),
                                  :g => rand(256),
                                  :b => rand(256)} }
  commands = []
  names.each_index do |n|
    cluster = clusters[n % clusters.size]
    commands << [:create_node, {:name => names[n], 
                                :size => 5.0 + rand(20.0), 
                                :r => cluster[:r],
                                :g => cluster[:g],
                                :b => cluster[:b],
                                :x => rand(600) - 300,
                                :y => rand(150) - 150
                                 }]
  end
 
  names.each_index do |from| 
    commands << [:add_node_to_index, "nodes_index", "type", "User", "{#{from}}"]
    connected = []

    # create clustered relationships
    members = 20.times.collect{|x| x * 10 + (from % clusters.size)}
    members.delete(from)
    rels = 3
    rels.times do |x|
      to = members[x]
      connected << to
      commands << [:create_relationship, "follows", "{#{from}}", "{#{to}}"]  unless to == from
    end    

    # create random relationships
    rels = 3
    rels.times do |x|
      to = rand(names.size)
      commands << [:create_relationship, "follows", "{#{from}}", "{#{to}}"] unless (to == from) || connected.include?(to)
    end

  end

  batch_result = neo.batch *commands
end

 

Published at DZone with permission of Max De Marzi, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)