Статьи

Визуализация сети с использованием Cypher и D3.JS

Мы видели довольно красивые визуализации узлов и их непосредственных соседей, но мы хотим иметь возможность визуализировать больше. Итак , мы собираемся подготовить сеть к 200 узлов, используйте Cypher для извлечения данных , которые мы хотим и визуализировать его с D3.js .

Я не собираюсь вводить имена 200 человек, поэтому мы создадим метод для генерации случайного текста.

def generate_text(length=8)
  chars = 'abcdefghjkmnpqrstuvwxyz'
  name = ''
  length.times { |i| name << chars[rand(chars.length)] }
  name
end

Мы снова собираемся использовать нашу команду Batch. Мы создаем 200 узлов со случайными именами и случайным образом соединяем их с 0-9 другими узлами. Это будет отправлять от 500 до 1000 команд в одной пакетной транзакции. Это настолько большой показатель, насколько мы хотим использовать одну пакетную команду. Если вы хотите создать график большего размера, лучше всего разбить его на несколько групп по 1000 команд.

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

  names = 200.times.collect{|x| generate_text}

  commands = names.map{ |n| [:create_node, {"name" => n}]}
  names.each_index do |x| 
    follows = names.size.times.map{|y| y}
    follows.delete_at(x)
    follows.sample(rand(10)).each do |f|
      commands << [:create_relationship, "follows", "{#{x}}", "{#{f}}"]    
    end
  end

  batch_result = neo.batch *commands
end

Я хочу, чтобы эта визуализация была немного более динамичной, чем предыдущая, поэтому, когда вы посмотрите, вы случайным образом увидите сеть одного из узлов. С помощью Cypher мы параметризуем узел и передадим ему случайное число, которое будет служить нашим node_id. Если вы следите за новостями, вы заметите, что этот запрос выглядит примерно так, как когда мы использовали Cypher, чтобы завести друзей . Мы просто поднимаем его на ступеньку выше, посещая друзей друзей друзей, а также подсчитываем количество других.

def follower_matrix
  neo = Neography::Rest.new
  cypher_query =  " START me = node({node_id})"
  cypher_query << " MATCH (me)-[?:follows]->(friends)-[?:follows]->(fof)-[?:follows]->(fofof)-[?:follows]->others"
  cypher_query << " RETURN me.name, friends.name, fof.name, fofof.name, count(others)"
  cypher_query << " ORDER BY friends.name, fof.name, fofof.name, count(others) DESC"
  neo.execute_query(cypher_query, {:node_id => 1 + rand(200)})["data"]
end  

Когда мы получим наши данные, это будет массив массивов с количеством других в конце. Вы в основном смотрите на пути без отношений.

[["Max", "Ben", "Rob", "James", 5],
 ["Max", "Ben", "Rob", "Peter", 2],
 ["Max", "Ben", "Musannif", "Pramod", 2]
]

Наша визуализация ищет объект JSON, который выглядит примерно так:

{"name":"Max",
 "children":[{"name":"Ben",
               "children":[{"name":"Rob",
                            "children":[{"name":"James",
                                         "size":2},
                                        {"name":"Peter",
                                         "size":5}
                                       ]},
                           {"name":"Musannif",
                            "children":[{"name":"Pramod",
                                         "size":2}
                                       ]}
                           ]},
            ]
}

Мы сделаем это в два этапа. Первое — создать переменную с именем data и заполнить ее вложенным хешем нашего массива массивов.

get '/followers' do
  data = follower_matrix.inject({}) {|h,i| t = h; i.each {|n| t[n] ||= {}; t = t[n]}; h}
  with_children(data).to_json
end

 Мы могли бы создать хороший класс Nested Hash или использовать один из существующих гемов, которые имеют дело с деревьями, но Ruby достаточно хорош, чтобы позволить нам сделать это в одной строке. Не позволяйте этому лайнеру испугать вас, это просто старый трюк с Ruby.

Нам нужен способ превратить этот вложенный хеш в окончательный формат с дочерними массивами и тому подобным, поэтому мы напишем этот метод следующим образом:

def with_children(node)
  if node[node.keys.first].keys.first.is_a?(Integer)
    { "name" => node.keys.first,
      "size" => 1 + node[node.keys.first].keys.first 
     }
  else
    { "name" => node.keys.first, 
      "children" => node[node.keys.first].collect { |c| 
        with_children(Hash[c[0], c[1]]) } 
    }
  end
end

 

Мы собираемся использовать переменную Size, чтобы выяснить, насколько велики наши листовые узлы. Мы начинаем с 1 и добавляем количество других, потому что если их будет ноль, наш листовой узел будет иметь нулевой размер, и мы этого не хотим.

Наша визуализация длиной около 120 строк, поэтому я не буду здесь все это разбрасывать. Если вы хотите увидеть это, проверьте это на github .

Он следует примеру D3, но добавляет немного цвета с помощью атрибута size, чтобы он не выглядел так мягко:

var colorscale = d3.scale.category10();

function color(d) {
  return d._children ? "#3182bd" : d.children ? "#c6dbef" : colorscale(d.size);
}

 Ее можно посмотреть на Heroku по адресу http://evening-mountain-5731.herokuapp.com/index.html и обновить страницу, чтобы увидеть другие результаты.

Источник: http://maxdemarzi.com/2012/02/13/visualizing-a-network-with-cypher/