Статьи

Как использовать Sigma.js с Neo4j

Недавно я написал несколько постов с использованием D3.js, и теперь я хочу показать вам, как использовать две другие замечательные библиотеки Javascript для визуализации ваших графиков. Мы начнем с Sigma.js и скоро я сделаю еще один пост с Three.js .

Мы собираемся создать наш график и сгруппировать наши узлы в пять кластеров. Позже вы заметите, что мы собираемся дать нашим кластеризованным узлам цвета, используя значения rgb, чтобы мы могли видеть их перемещение, пока они не найдут свое правильное место в нашем макете. Мы будем использовать два плагина Sigma.js, парсер GEFX (Graph Exchange XML Format) и макет ForceAtlas2.

Вы можете увидеть, как выглядит файл GEFX ниже. Обратите внимание, что она исходит от Gephi, которая представляет собой интерактивную платформу визуализации и исследования, которая работает во всех основных операционных системах, имеет открытый исходный код и является бесплатной.

<?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>

Чтобы построить этот файл, нам нужно получить узлы и ребра графа и создать файл XML.

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

Мы будем использовать Cypher для получения наших узлов и ребер:

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  

Нам нужны идентификаторы узлов и отношений, поэтому обратите внимание, что в обоих случаях я использую функцию ID ().

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

До сих пор мы видели графики, представленные в формате JSON, и построили их вручную. Сегодня мы будем использовать Builder Ruby Gem для построения нашего графика в 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", 😡 => 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

Вы можете получить код на github как обычно и посмотреть, как он работает на Heroku на neosigma.herokuapp.com . Вам захочется увидеть его вживую на Heroku, чтобы вы могли видеть узлы в случайных положениях, а затем перемещаться для формирования кластеров. Используйте колесо мыши, чтобы увеличить масштаб, и нажмите и перетащите для перемещения.

Кредит идет на Алексис Жакоми и Матье Жакоми .

Вы видели, как я создавал множество случайных графов, но для полноты здесь приведен код этого графа. Обратите внимание, как я создаю 5 кластеров, и для каждого узла я назначаю половину его отношений другим узлам в их кластере и половину случайным узлам? Именно так плагин макета ForceAtlas2 аккуратно группирует наши узлы.

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],
                                😡 => 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