Недавно я написал несколько постов с использованием 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