phenomenal/phenomenal

View on GitHub
lib/phenomenal/viewer/graphical.rb

Summary

Maintainability
A
1 hr
Test Coverage
# Define the way to generate a representation of the system
# using the graphical library graphviz
class Phenomenal::Viewer::Graphical
  begin
    require "graphviz"
    @@graphviz=true
  rescue LoadError
    @@graphviz=false
  end  
  
  attr_reader :manager, :rmanager
  attr_accessor :main_graph, :feature_nodes, :r_feature_nodes, :context_nodes, :destination_file
  
  def initialize(destination_file)
    if !@@graphviz
      raise(Phenomenal::Error,
        "The 'ruby-graphviz' gem isn't available. Please install it to generate graphic visualisations\n"+
        " Otherwise use the text version: phen_textual_view"
      )
    end
    
    @manager=Phenomenal::Manager.instance
    @rmanager=Phenomenal::RelationshipManager.instance
    @destination_file=destination_file
    @main_graph=nil
    @feature_nodes={}
    @r_feature_nodes={}
    @context_nodes={}
  end
  
  def generate()
    set_options()
    # Add nodes to the graph
    self.manager.contexts.each do |key,context|
      if !feature_nodes.include?(context) && !context_nodes.include?(context)
        add_node_for(context)
      end
    end
    # Create a relationship links
    self.manager.contexts.each do |key,context|
      add_edges_for(context)
    end
    self.main_graph.output(:png => destination_file)
    nil
  end
  
  private
  def set_options
    # Create main graph
    self.main_graph = GraphViz::new("")
    # Default options
    self.main_graph[:compound] = "true"
    self.main_graph[:rankdir]="LR"
    self.main_graph.edge[:lhead] = ""
    self.main_graph.edge[:ltail] = ""
    self.main_graph.edge[:fontsize] = 10.0
  end
  
  def add_edges_for(context)
    if context.is_a?(Phenomenal::Feature)
      context.relationships.each do |relationship|
        # Get source and destionation node
        relationship.refresh
        source_node,ltail = node(relationship.source)
        target_node,lhead = node(relationship.target)
        # Get graph container
        graph = graph_container(relationship)
        # Add edge
        edge = graph.add_edges(source_node,target_node,:ltail=>ltail,:lhead=>lhead)   
        # Define edge type
        set_edge(context,edge,relationship)
      end
    end
  end
  
  def node(feature)
    if feature_nodes.include?(feature)
      [r_feature_nodes[feature],"cluster_#{feature.to_s}"]
    else 
      [context_nodes[feature],""]
    end
  end
  
  def graph_container(relationship)
    s_parent_feature=relationship.source.parent_feature
    t_parent_feature=relationship.target.parent_feature
    if s_parent_feature==t_parent_feature && s_parent_feature!=manager.default_feature
      feature_nodes[relationship.source.parent_feature]
    else
      main_graph
    end
  end
  
  def set_edge(context,edge,relationship)
    # Define edge label
    if context!=manager.default_feature
      edge[:label]=context.to_s
     end
    # Define edge color
    if rmanager.relationships.include?(relationship)
      edge[:color]="red"
    end
    # Define arrow type
    if relationship.is_a?(Phenomenal::Implication)
      edge[:arrowhead]="normal"
    elsif relationship.is_a?(Phenomenal::Suggestion)
      edge[:arrowhead]="empty"
    elsif relationship.is_a?(Phenomenal::Requirement)
      edge[:arrowhead]="inv"
    else
      raise(Phenomenal::Error,
        "This relationship hasn't been defined yet in the graphical viewer"
      )
    end
  end
  
  def add_node_for(context)
    # The default feature is the first to be added to the main graph
    if feature_nodes[context.parent_feature].nil? && context==manager.default_feature 
      current_graph=main_graph
    # Always add the parent_feature before the contexts inside
    elsif feature_nodes[context.parent_feature].nil?
      add_node_for(context.parent_feature)
    else
      current_graph=feature_nodes[context.parent_feature]
    end
    # Add node
    node  = new_node_for(context,current_graph)
    # Define node color
    if context.active?
      node[:color]="red"
    else
      node[:color]="black"
    end
  end
  
  def new_node_for(context,current_graph)
    if context.is_a?(Phenomenal::Feature)
      node=current_graph.add_graph("cluster_#{context.to_s}")
      node[:label]="#{context.to_s}"
      # Add hidden node for feature relationship
      fr=node.add_nodes("#{context.to_s}_relationship")
      fr[:style]="invis"
      fr[:height]=0.02
      fr[:width]=0.02
      fr[:fixedsize]=true
      self.feature_nodes[context]=node
      self.r_feature_nodes[context]=fr
      node
    else
      node=current_graph.add_nodes(context.to_s) 
      self.context_nodes[context]=node
    end
    node
  end
end