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