SpeciesFileGroup/taxonworks

View on GitHub
lib/export/graph.rb

Summary

Maintainability
A
0 mins
Test Coverage
class Export::Graph

  NODE_COLORS = {
    'Person' => '#009688',
    'CollectionObject' => '#2196F3',
    'TaxonName' => '#E91E63',
    'CollectingEvent' => '#7E57C2',
    'TaxonDetermination' => '#FF9800',
    'Identifier' => '#EF6C00',
    'Otu' =>'#4CAF50',
    'User' => '#F44336',
    'ControlledVocabularyTerm' => '#CDDC39',
    'BiologicalRelationship' => '#9C27B0',
    'Repository' => '#009688',
    'Observation' => '#CDDC39',
    'Citation' => '#CCCCCC',
    'Georeference' => '#CDDC39',
    'Source' => '#2196F3',
    'AssertedDistribution' => '#FF9800',
    'Image' => '#CDDC39',
  }.freeze

  NODE_SHAPES = {
    'Person' => 'person',
    'CollectionObject' => 'circle',
    'TaxonName' => 'square',
    'CollectingEvent' => 'circle',
    'TaxonDetermination' => nil,
    'Identifier' => 'triangle',
    'Otu' => 'hexagon',
    'User' => 'person',
    'ControlledVocabularyTerm' => nil,
    'BiologicalRelationship' => 'square' ,
    'Observation' => 'square',
    'Repository' => 'circle',
    'Georeference' => 'square',
    'Source' => 'pentagon',
    'Citation' => 'octagon',
    'AssertedDistribution' => 'pentagon',
    'Image' => 'square',
    'GeographicArea' => 'square'
  }.freeze

  RENDERABLE_ANNOTATIONS = []

  attr_accessor :nodes
  attr_accessor :edges

  attr_accessor :draw_annotations

  # @params object [An AR record, nil]
  #   the "base" of the object, if rooted
  def initialize(object: nil, annotations_to_draw: RENDERABLE_ANNOTATIONS )
    @draw_annotations = annotations_to_draw
    @nodes = []
    @edges = []

    add(object) if !object.nil?
  end

  def nodes
    @nodes.compact.uniq
  end

  def edges
    @edges.compact.uniq
  end

  def to_json(include_stats: true)
    h =  {
      nodes:,
      edges:
    }

    h[:stats] = stats if include_stats
    return h
  end

  def add(object, object_origin = nil, edge_label: nil, edge_link: nil)
    add_node(object)
    add_edge(object_origin, object, edge_label:, edge_link:) if !object_origin.nil?
  end

  # TODO factor view options out
  def add_node(object, citations: true, identifiers: true)
    @nodes.push graph_node(object)

    if citations && object.respond_to?(:citations)
      object.citations.each do |c|
        add_node(c, citations: false, identifiers: false)
        add_node(c.source, citations: false, identifiers: true)

        add_edge(c, object)
        add_edge(c.source, c)

        if c.source.is_bibtex?
          c.source.authors.each do |p|
            add_node(p, citations: false, identifiers: true)
            add_edge(p, c.source)
          end
        end
      end
    end

    if identifiers && object.respond_to?(:identifiers)
      object.identifiers.each do |i|
        add_node(i, citations: false, identifiers: false)
        add_edge(i, object)
      end
    end
  end

  def add_edge(object, object_origin = nil, edge_label: nil, edge_link: nil)
    @edges.push graph_edge(object_origin, object, edge_label:, edge_link:)
  end

  def graph_node(object, node_link: nil)
    return nil if object.nil?
    b = object.class.base_class.name
    h = {
      id: object.to_global_id.to_s,
      name:  ApplicationController.helpers.label_for(object) || object.class.base_class.name,
    }

    if b == 'ControlledVocabularyTerm'
      object.css_color || '#000000'
    else
      h[:color] = NODE_COLORS[b] || '#000000'
    end

    h[:shape] = NODE_SHAPES[b] if !NODE_SHAPES[b].nil?
    h[:link] = node_link || Rails.application.routes.url_helpers.object_graph_task_path(global_id: object.to_global_id.to_s)
    h
  end

  def graph_edge(start_object, end_object, edge_label: nil, edge_link: nil)
    return nil if start_object.nil? || end_object.nil?
    h = {
      start_id: start_object.to_global_id.to_s,
      end_id: end_object.to_global_id.to_s
    }

    # TODO: change label to name
    h[:label] = edge_label if edge_label.present?
    h[:link] = edge_link if edge_link.present?
    h
  end

  # @return True
  # !param graph [Export::Graph]
  # Merges the edges and nodes from another graph to this one
  def merge(graph)
    @nodes += graph.nodes
    @edges += graph.edges
    @nodes.uniq!
    @edges.uniq!
    true
  end

  def stats
    count_nodes = nodes.count
    count_peopled_nodes = annotated_nodes('Person').count
    count_identified_nodes = annotated_nodes('Identifier').count
    count_identifiers = count_nodes('Identifier')

    return {
      nodes: count_nodes,
      edges: edges.count,
      people: count_nodes('Person'),
      identifiers: count_identifiers,
      peopled_nodes: count_peopled_nodes,
      identified_nodes: count_identified_nodes,
      node_personability:  ( count_peopled_nodes.to_f / count_nodes.to_f * 100.0 ).round(2).to_s + '%',
      node_identifiability:  ( count_identified_nodes.to_f / count_nodes.to_f * 100.0).round(2).to_s + '%',
      identifier_saturation:  (count_identifiers.to_f / count_identified_nodes.to_f * 100.0).round(2).to_s + '% (total identifiers / identified nodes * 100)',
    }
  end

  def count_nodes(node_type)
    nodes.select{|n| n[:id] =~ /#{node_type}/}.count
  end

  def annotated_nodes(node_type)
    n = []
    edges.each do |e|
      if e[:start_id] =~ /#{node_type}/
        n.push e[:end_id]
      elsif e[:end_id] =~ /#{node_type}/
        n.push e[:start_id]
      end
    end
    n.uniq
  end

end