Статьи

Визуализация новостей с Vivagraph.js


neo_news

Сегодня я хочу познакомить вас с  VivaGraphJS  — библиотекой JavaScript Graph Graphing, созданной  Андреем Кащей  из  Ясива . Он поддерживает рендеринг графиков с использованием форматов WebGL, SVG или CSS и в настоящее время поддерживает принудительную компоновку. Библиотека предоставляет API, который отслеживает изменения графиков и отражает изменения на поверхности рендеринга, что делает его фантастическим для исследования графиков.

Сегодня мы будем интегрировать его с  Neo4j  и  Alchemy API .

alchemyapi

Alchemy API предоставляет набор инструментов обработки естественного языка, и мы будем использовать их   возможности Entity Extraction .

Мы собираемся взглянуть на новости мира, извлечь упомянутые в них сущности, соединить их все вместе в Neo4j и визуализировать их. Чтобы получать новости, мы будем использовать  Feedzilla .

feedzilla

Задание  sidekiq  будет запускаться каждые шесть часов, чтобы собрать последние 100 новостных статей:

module Job
  class GetNews
    include Sidekiq::Worker
    sidekiq_options :retry => false
 
    def perform
      Job::GetNews.perform_in(6.hours)
 
      feed = HTTPClient.get("http://api.feedzilla.com/v1/categories/19/articles.json?order=date&count=100&title_only=1")
      parsed_feed = Oj.load(feed.body)
      parsed_feed["articles"].each do |article|
        Job::GetArticle.perform_async(article["url"])
      end
    end
 
  end
end

Из каждой статьи мы получим историю, передадим ее в Alchemy API и попросим  найти сущности в статье .

@entities = Oj.load(HTTPClient.post_content("http://access.alchemyapi.com/calls/url/URLGetRankedNamedEntities",
      {:url => article_url, 
       :apikey => ENV['ALCHEMY_API'],
       : outputMode => "json"}),
       {:'Accept-encoding' => "gzip"})["entities"]

Мы создадим узел для каждой статьи и свяжем каждую сущность, найденную с помощью отношения «MENTIONED»:

commands = []
@batch_result.each do |b|
  commands << [:create_relationship, "MENTIONED", @article_node, b["body"]["self"].split("/").last]
end

Некоторые из этих сущностей имеют интересные описания, поэтому мы  запросим dbpedia  для получения дополнительной информации.

dbpedia_logo

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

get '/search' do
  content_type :json
  neo = Neography::Rest.new   
 
  cypher = "START me=node:entities({query}) 
            RETURN ID(me), me.text
            ORDER BY me.text
            LIMIT 15"
 
  neo.execute_query(cypher, {:query => "text:*#{params[:term]}* OR uri:*#{params[:term]}*" })["data"].map{|x| { label: x[1], value: x[0]}}.to_json   
end

Мы также предоставим конечную точку для извлечения узла и всех узлов, подключенных к нему, и возврата объекта JSON с этими данными:

get '/edges/:id' do
    content_type :json
    neo = Neography::Rest.new   
 
    cypher = "START me=node(#{params[:id]}) 
              MATCH me -- related
              RETURN ID(me), me.text, me.description, me.type, ID(related), related.text, related.description, related.type"
 
    connections = neo.execute_query(cypher)["data"]   
    connections.collect{|n| {"source" => n[0], "source_data" => {:label => n[1], 
                                                                 :description => n[2],
                                                                 :type => n[3] },
                             "target" => n[4], "target_data" => {:label => n[5], 
                                                                 :description => n[6],
                                                                 :type => n[7]}} }.to_json
  end

Наш  javascript  будет содержать обращения к Vivagraph:

var graph = Viva.Graph.graph(); 
 
  var layout = Viva.Graph.Layout.forceDirected(graph, {
      springLength:100,
      springCoeff:0.0001,
      dragCoeff:0.02,
      gravity:-1
  });   

Наши узлы будут отображать как текст, найденный в объекте, так и изображение для представления типа объекта.

var ui = Viva.Graph.svg('g'),
        svgText = Viva.Graph.svg('text').attr('y', '-4px').text(node.data.label),
        img = Viva.Graph.svg('image')
           .attr('width', 32)
           .attr('height', 32)
           .link('/img/' + node.data.type + '.png');
 
    ui.append(svgText);
    ui.append(img);

Мы добавим событие mouseover, чтобы обновить боковую панель с описанием нашего узла:

$(ui).hover(function() { // mouse over
                        highlightRelatedNodes(node.id, true);
                        $('#explanation').html(node.data.description);
                    }, function() { // mouse out
                        highlightRelatedNodes(node.id, false);
                    });

… И загрузить узлы, связанные с этой сущностью, по клику:

$(ui).click(function() { 
                        console.log("click", node);
                        if (!node || !node.position) return;
                        renderer.rerender();
                        loadData(graph,node.id);
            }
    );

Как обычно,  код  доступен на  github , и вы можете увидеть, как он  работает  на  Heroku .

Посмотрите еще несколько демонстраций Vivagraph.js:

  • Amazon Visualization — показывает связанные продукты на Amazon.com, использует SVG в качестве вывода графика
  • Визуализация YouTube  — показывает похожие видео с YouTube. На основе SVG.
  • Визуализация Facebook  — визуализация дружбы на Facebook. На основе WebGL.
  • Graph Viewer  — визуализация коллекции разреженных матриц Университета Флориды. На основе WebGL.
  • Вконтакте Визуализация  — визуализация дружбы крупнейшей социальной сети в России  vk.com . На основе WebGL.