Несколько недель назад я показал вам, как визуализировать график, используя визуализацию вспышек аккордов, и как визуализировать сеть, используя принудительную визуализацию графа из D3.js.
В твиттере Клэр Уиллетт из Riparian Data спросила:
Визуальное сеть 200-узла ш / # Сайфер + # D3js —cool метод, но , возможно , узел квилт будет работать лучше? ow.ly/92wDE @ maxdemarzi
Этот пост Роберта Косара « Графики за пределами волосяного шарика » объясняет, почему некоторые нетрадиционные визуализации графиков могут работать лучше, и связывает нас со статьей, объясняющей, что такое Node Quilt и как он полезен. Мы просто сделаем первый шаг и построим матричное представление графа. Мы будем использовать один из алгоритмов кластеризации JUNG, чтобы помочь нам понять его.
Изучение социальных сетей восходит, по крайней мере, к древним грекам, но сегодня мы не вернемся так далеко … только к 1977 году. Человек по имени Уэйн Захари записал взаимодействия клуба каратэ в университете в течение 2 лет. За это время возник конфликт между администратором и инструктором, а клуб распался на две части. Оказывается, вы можете предсказать, к какому клубу будет принадлежать каждый член, построив график их взвешенных отношений и разделив его по минимальному разрезу .
Мы работаем с Neo4j , и вы все знаете, что Neo знает кунг-фу , поэтому мы сделаем что-то немного другое. Плюс, смотреть на кластер из двух узлов — это немного неубедительно … давайте пойдем дальше. Мы собираемся использовать Neo4j с файлами JUNG, уже находящимися в каталоге lib. Обратитесь к JUNG в Neo4j — Часть 1, если вам нужна помощь в этом. Давайте создадим наш график:
def create_graph neo = Neography::Rest.new graph_exists = neo.get_node_properties(1) return if graph_exists && graph_exists['name'] names = %w[Aaron Achyuta Adam Adel Agam Alex Allison Amit Andreas Andrey Andy Anne Barry Ben Bill Bob Brian Bruce Chris Corey Dan Dave Dean Denis Eli Eric Esteban Ezl Fawad Gabriel James Jason Jeff Jennifer Jim Jon Joe John Jonathan Justin Kim Kiril LeRoy Lester Mark Max Maykel Michael Musannif Neil] commands = names.map{ |n| [:create_node, {"name" => n}]} names.each_index do |from| commands << [:add_node_to_index, "nodes_index", "type", "User", "{#{from}}"] connected = [] # create clustered relationships members = 5.times.collect{|x| x * 10 + (from % 10)} members.delete(from) rels = 1 + rand(4) rels.times do |x| to = members[x] connected << to commands << create_rel(from, to) unless to == from end # create random relationships rels = 1 + rand(4) rels.times do |x| to = rand(names.size) commands << create_rel(from, to) unless (to == from) || connected.include?(to) end end batch_result = neo.batch *commands end
Я снова использую первые 50 имен из группы Graph DataBase — Chicago Meet-Up . Вы уже видели, как я делаю это один или два раза, поэтому я не буду подробно останавливаться на этом, но, как вы можете видеть выше, я заставляю небольшие группы отношений существовать вместе со случайными отношениями. Наши отношения имеют свойство weight, поэтому метод create_rel выглядит так:
def create_rel(x,y,z= 1 + rand(10)) [:create_relationship, "knows", "{#{x}}", "{#{y}}", {:weight => z}] end
Наша визуализация предполагает получение списка узлов, уже входящих в группы, и списка отношений, которые включают его силу. Объект JSON выглядит так:
{"nodes":[{"name":"Myriel","group":1}, {"name":"Napoleon","group":1}, {"name":"Mlle.Baptistine","group":2}], "links":[{"source":1,"target":0,"value":1}, {"source":2,"target":0,"value":8}, {"source":3,"target":0,"value":10}] }
Получить наши узлы довольно просто, мы будем использовать Gremlin, чтобы получить все, кроме корневого узла.
def get_nodes neo = Neography::Rest.new neo.execute_script("g.V.filter{it.id != 0}.transform{[it.id,it.name]}") end
Установить отношения довольно легко, чтобы их изменить, мы будем использовать Cypher.
def get_relationships neo = Neography::Rest.new cypher_query = " START a = node:nodes_index(type='User')" cypher_query << " MATCH a-[r:knows]-b" cypher_query << " RETURN ID(a), ID(b), r.weight" neo.execute_query(cypher_query)["data"] end
Теперь самое интересное. Чтобы получить наши кластеры, мы будем использовать класс кластера напряжения. Мы не хотим, чтобы корневой узел мешал, поэтому мы создадим подграф, используя TinkerGraph, который исключает его (вы могли бы сделать что-то похожее на кластер, только небольшую часть большего графа). Затем мы передаем этот график в GraphJung и настраиваем наш VoltageClusterer, чтобы попытаться получить 10 кластеров для нас.
def get_voltage_clusters neo = Neography::Rest.new lg = neo.execute_script("import edu.uci.ics.jung.algorithms.cluster.VoltageClusterer; to = new TinkerGraph(); g.V.filter{it.id != 0}.sideEffect{toVertex = to.addVertex(it.getId()); ElementHelper.copyProperties(it, toVertex); }.iterate(); g.E.sideEffect{toEdge = to.addEdge(it.getId(), to.v(it.getOutVertex().getId()), to.v(it.getInVertex().getId()), it.getLabel()); ElementHelper.copyProperties(it, toEdge); }.iterate(); vc = new VoltageClusterer(new GraphJung(to), 10); vc.cluster(10).id; ") end
Затем мы можем собрать все это вместе для создания этого объекта JSON.
get '/cluster' do clusters = Hash.new get_voltage_clusters.each_with_index {|item, index| item.each{|i| clusters[i.to_i] = index + 1} } nodes = get_nodes.map{|n| {"name" => n[1], "group" => clusters[n[0]]}} relationships = get_relationships.map{|r| {"source" => r[0] - 1, "target" => r[1] - 1, "value" => r[2]} } {:nodes => nodes, :links => relationships}.to_json end
Наша визуализация была сделана Майком Босток с D3.js . Как всегда, код на Github . Нажмите на изображение ниже, чтобы увидеть живой пример на Heroku .
Примечание. В версии Heroku используется предварительно созданный файл JSON.