Статьи

Последняя миля: VisualSearch и Neo4j

« Последняя миля » — это термин, используемый в телекоммуникационной отрасли, который относится к предоставлению возможности подключения клиентам, которые фактически будут использовать систему. В смысле баз данных графов, это относится к тому, насколько хорошо конечный пользователь может извлечь ценность и понимание из графика. Мы уже видели пример этой концепции с  Graph Search , позволяющей пользователю выражать свои запросы на естественном языке. Сегодня мы увидим еще один пример. Мы будем использовать возможности  Neo4j  2.0, чтобы сделать эту работу, поэтому обязательно прочитайте  предыдущий пост  по этому вопросу.

Мы собираемся использовать  VisualSearch.js,  созданный  Сэмюэлем Клэем  из  NewsBlur . VisualSearch.js расширяет возможности обычных окон поиска с помощью автозаполнения многогранных поисковых запросов. Это довольно легко настроить, и есть  аннотированное описание  доступных опций. Вы можете увидеть, что он делает на изображении ниже, или щелкните по нему, чтобы попробовать их демо.

VisualSearch

Ранее мы подготовили график Neo4j 2.0, в котором актеры, режиссеры, продюсеры, писатели и пользователи подключены к фильмам.

graph2

Первое, что нам нужно сделать, это найти Facets для visualsearch.js. Мы не хотим настраивать это вручную, потому что это будет болезненно, и наш график может со временем измениться. Поэтому вместо этого мы будем использовать метод list_labels, чтобы получить метки нашего графика:

  get '/facets' do
    content_type :json
    cache_control :public, :max_age => 600
    facets = []
    categories = $neo.list_labels    
    categories.each do |cat| 
      get_properties(cat).each do |label|
        facets << {:category => cat, :label => cat + "." + label} 
      end
    end
    facets.to_json
  end

Одна из приятных вещей, которые мы можем сделать, это сгруппировать свойства метки вместе, у нас нет сложной схемы того, какие свойства есть в каждой метке, но мы можем запросить график, получить один узел и посмотреть свойства, которые у него есть.

    def get_properties(category)
      cypher = "MATCH n:#{category} RETURN n LIMIT 1"
      $neo.execute_query(cypher)["data"].first.first["data"].keys
    end

Это вернет массив JSON, который выглядит следующим образом:

[{"category":"Writer","label":"Writer.born"},
{"category":"Writer","label":"Writer.name"},
{"category":"Actor","label":"Actor.born"},
{"category":"Actor","label":"Actor.name"}
...

Мы передадим это в visualsearch.js, и наш первый выпадающий список будет работать с этими сгруппированными свойствами меток.
visual_search_part1

Как только пользователь щелкнет по одному из свойств, мы заполним некоторые из доступных параметров для этого свойства. Мы можем сделать это с помощью cypher, сопоставив узлы указанной метки, которые имеют свойство, о котором мы заботимся, и сгруппировав его так, чтобы мы получили только первые 25 уникальных значений.

  get '/values/:facet/' do
    content_type :json

    label, key = get_label_and_key(params)
    
    cypher = "MATCH node:#{label} 
              WHERE HAS(node.#{key})
              RETURN node.#{key} AS label, COUNT(*)
              ORDER BY label
              LIMIT 25"
    
    $neo.execute_query(cypher)["data"].collect{|x| x.first.to_s}.compact.flatten.to_json
  end

Теперь мы можем увидеть некоторые значения в нашем окне поиска. В этом примере мы собираем имена актеров в нашем графе.

visual_search2

Лучшие 25 предметов — это хорошо, но что, если мы ищем актера, чьи имена с буквой Z похожи на « Зак Гренье »? Visualsearch.js дает нам возможность начать вводить значение и сбросить наши параметры для соответствия.

visual_search 3

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

  get '/values/:facet/:term' do
    content_type :json
    
    label, key = get_label_and_key(params)
    
    cypher = "MATCH node:#{label} 
              WHERE HAS(node.#{key}) AND node.#{key} =~ {term}
              RETURN node.#{key} AS label, COUNT(*)
              ORDER BY label
              LIMIT 25"
    
    $neo.execute_query(cypher, {:term => "(?i).*" + params[:term] + ".*"})["data"].collect{|x| x.first.to_s}.compact.flatten.to_json
  end

Как только мы нажимаем на Зак Гренье, происходит несколько вещей. Мы получаем небольшое сообщение о том, что:

Вы искали: Actor.name: «Зак Гренье». (1 узел)

Наша панель поиска снова оживает со следующим набором меток для запроса…

визуальный поиск 4

… И наш график (в настоящее время состоящий только из одного узла) заполняется с помощью vivagraph.js. См. Этот предыдущий пост vivagraph.js  для получения дополнительной информации о том, как работает эта великолепная библиотека визуализации графиков.

Снимок экрана 2013-07-02 в 23.24.06 вечера

Теперь … я знаю, что вы можете подумать … мы заполнили узел актера, и теперь в нашем раскрывающемся списке доступен только фильм. Как это произошло? Это магия этого приложения. Вместо случайного захвата любого следующего узла, мы берем контекст нашего первого узла и строим путь доступных соединений оттуда. Если мы нажимаем «Movie.title», мы вызываем следующий метод под обложками, чтобы получить наши возможности:

  post '/connected_values/:facet/' do
    content_type :json
    related_label, related_key = get_label_and_key(params)
    
    match, where, values = prepare_query(params)
    last_node = get_last_node_id(params)
    
    where.pop
    where << "HAS(node#{last_node}.#{related_key})"
    
    cypher  = prepare_cypher(match,where)
    cypher << "WITH LAST(EXTRACT(n in NODES(p) : n.#{related_key}?)) AS label, COUNT(*) AS cnt "
    cypher << "RETURN label ORDER BY label LIMIT 25"    
    
    parameters = prepare_parameters(values)
        
    $neo.execute_query(cypher, parameters)["data"].flatten.collect{|d| d.to_s}.to_json
  end

Это выглядит немного сложно, но все, что мы делаем, это просто динамически создаем запрос шифрования, который в итоге будет выглядеть так:

MATCH p = node0:Actor -- node1:Movie 
WHERE node0.name? = {value0} AND HAS(node1.title) 
WITH LAST(EXTRACT(n in NODES(p) : n.title?)) AS label, COUNT(*) AS cnt 
RETURN label 
ORDER BY label 
LIMIT 25

Этот Cypher-запрос будет выполнен с параметрами {«value0 ″ =>« Zach Grenier »}. Он найдет узел Actor для Zach Grenier на графике, а затем найдет узлы с надписью «Movie», связанные с Zach Grenier, и затем извлечет свойство «title» из последнего узла на нашем пути (который случается с быть в фильмах, в которых участвует Зак Гренье, и дать нам наш ответ.

На нашем графике у нас есть только две вещи, связанные с Заком Гренье: фильм «RescueDawn» и «Twister». Давайте продолжим и нажмем на Twister:

визуальный поиск 5

Мы запрашиваем график с шаблоном «Актер по имени Зак Гренье», который связан с фильмом «Твистер». Граф находит этот шаблон, возвращает узлы и отношения в этом шаблоне, и Twister добавляется к нашему графику, связанному с Заком Гренье.

Например, шаблоны, которые мы можем создать, могут выходить за рамки одного прыжка. Актер 1929 года рождения, который снимался в «Снегу на кедрах» вместе с Риком Юном, который также был в «Ниндзя-ассасине», вместе с другими актерами…

MATCH p = node0:Actor -- node1:Movie -- node2:Actor -- node3:Movie -- node4:Actor 
WHERE node0.born? = {value0} AND node1.title? = {value1} AND node2.name? = {value2} AND node3.title? = {value3} AND HAS(node4.name) 
WITH LAST(EXTRACT(n in NODES(p) : n.name?)) AS label, COUNT(*) AS cnt 
RETURN label 
ORDER BY label 
LIMIT 25"

Этот запрос будет выполнен с параметрами: {«value0 ″ => 1929,« value1 ″ => «Снег на кедрах», «value2 ″ =>« Рик Юн »,« value3 ″ => «Убийца ниндзя»}}. Один из актеров в конце паттерна — «Наоми Харрис», и как только мы нажимаем на нее, мы получаем этот график:

визуальный поиск 6

Не просто поверь мне на слово, подумал. Попробуйте демо-версию , посмотрите на  исходный код и наведите на него свой собственный маркированный график Neo4j 2.0.

Чего не хватает?

Это динамический интерфейс, который дает конечному пользователю быстрый доступ к графику. Однако проницательный наблюдатель заметит, что чего-то не хватает. Типы отношений. Шаблоны, которые мы создаем и сопоставляем с графом, заботятся только о связанных узлах, а не о том, как они связаны, и это может быть очень важной особенностью нашего графа, который мы опускаем. Увы, этот маленький проект — не последняя миля, он всего лишь на шаг впереди, и в итоге мы его достигнем.

Помоги мне работать над проблемами такого рода.

Понимание мощностей графиков даст толчок вашим навыкам архитектора данных. Не позволяйте этому сообщению в блоге быть последним разом, когда вы думаете о графиках. Узнайте о графиках на одном из  десятков событий,  уже находящихся в Календаре, и следите за тем, как все больше и больше добавляются каждую неделю. Потратьте некоторое время, чтобы посмотреть эти отличные  графические видео о событиях, которые вы могли пропустить. Прочитайте  книгу «Базы данных графиков» и, конечно… подпишитесь на мой блог и  следуйте за мной  в Twitter.