Class: MiniGauge::Graph

Inherits:
Object
  • Object
show all
Defined in:
lib/mini_gauge.rb

Overview

From Railroad, an object to hold our nodes and edges, also to export them on demand

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts = {}) ⇒ Graph



90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/mini_gauge.rb', line 90

def initialize(opts = {})
  @graph_type = opts[:graph_type] || 'Model'
  @show_label = opts[:show_label] || true
  @title = opts[:title] || "#{@graph_type} diagram"
  @description = opts[:description] || ''
  @nodes = []
  @edges = []
  
  # Designed to work with the other class and instance methods on AR objects
  # to make adding nodes and edges easier.  Allows you to do something like
  # @graph.nodes << Invoice.first
  @nodes.instance_eval(%Q(
    def <<(item)
      if item.respond_to?(:dot_node_definition)
        super(item.dot_node_definition)
      else
        super(item)
      end
    end
  ))
  
  # Makes building graphs easier, allows you to do something like
  # @graph.edges << {:source => Person.first, :destination => Person.first.organization}
  @edges.instance_eval(%Q(
    def <<(item)
      if item.is_a? Hash
        if item[:source] && item[:source].respond_to?(:dot_node_name)
          item[:source] = item[:source].dot_node_name
        end
        
        if item[:destination] && item[:destination].respond_to?(:dot_node_name)
          item[:destination] = item[:destination].dot_node_name
        end
      end
      super(item)
    end
  ))
  
end

Instance Attribute Details

#descriptionObject

Returns the value of attribute description.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def description
  @description
end

#edgesObject

Returns the value of attribute edges.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def edges
  @edges
end

#graph_typeObject

Returns the value of attribute graph_type.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def graph_type
  @graph_type
end

#nodesObject

Returns the value of attribute nodes.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def nodes
  @nodes
end

#show_labelObject

Returns the value of attribute show_label.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def show_label
  @show_label
end

#titleObject

Returns the value of attribute title.



88
89
90
# File 'lib/mini_gauge.rb', line 88

def title
  @title
end

Instance Method Details

#add(opts) ⇒ Object

Add an item to the graph, expects a source node and destination node or a label if there is no destination

Raises:

  • (ArgumentError)


131
132
133
134
135
136
137
138
139
140
141
142
143
144
# File 'lib/mini_gauge.rb', line 131

def add(opts)
  raise ArgumentError.new("Must supply :source and :destination") unless opts.is_a?(Hash) && opts.keys.include?(:source) && opts.keys.include?(:destination)
  raise ArgumentError.new("Cannot supply a nil :destination without a label") if opts[:destination].nil? && opts[:label].nil?
  
  if opts[:destination].nil?
    node_name = "#{opts[:label].underscore}_#{opts.object_id}"
    @nodes << self.nil_node_definition(:label => opts[:label], :name => node_name)
    @edges << {:destination => node_name, :source => opts[:source], :empty_rec => true}
  else
    @nodes<<opts[:source] unless @nodes.any?{|x| opts[:source].dot_node_name == x[:name] }
    @nodes<<opts[:destination] unless @nodes.any?{|x| opts[:destination].dot_node_name == x[:name] }
    @edges<<opts
  end
end

#dot_labelObject

Build diagram label



170
171
172
173
174
175
176
177
178
# File 'lib/mini_gauge.rb', line 170

def dot_label
  return "\t_diagram_info [shape=\"plaintext\", " +
         "label=\"#{@title} \\l" +
         "Date: #{Time.now.strftime "%b %d %Y - %H:%M"}\\l" + 
         "Migration version: " +
         "#{ActiveRecord::Migrator.current_version}\\l" +
         "Description: #{@description}\\l" + 
         "\\l\", fontsize=14]\n"
end

#format_edge(edge) ⇒ Object

Take an edge (hash) and format it into dot notation



190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/mini_gauge.rb', line 190

def format_edge(edge)
  edge[:options] ||= {}
  edge[:options][:label] = edge[:label] unless edge[:label].blank?
  edge[:options].merge!({:style => "dotted", :color => "gray61"}) if edge[:empty_rec]
  case edge[:type]
    when 'one-one'
         edge[:options].merge!({:arrowtail => "odot", :arrowhead => "dot", :dir => "both"})
    when 'one-many'
         edge[:options].merge!({:arrowtail => "crow", :arrowhead => "dot", :dir => "both"})
    when 'many-many'
         edge[:options].merge!({:arrowtail => "crow", :arrowhead => "crow", :dir => "both"})
    when 'is-a'
         edge[:options].merge!({:arrowtail => "onormal", :arrowhead => "none"})
  end
  opts = edge[:options].collect{|k,v| %Q(#{k}="#{v}") }.join(", ")
  return %Q( "#{edge[:source]}" -> "#{edge[:destination]}" [#{opts}]\n )
end

#format_node(node) ⇒ Object

Take a node (hash) and format it into dot notation



181
182
183
184
185
186
187
# File 'lib/mini_gauge.rb', line 181

def format_node(node)
  node[:options] ||= {}
  node[:options][:shape] = "Mrecord"
  node[:options][:label] = %Q({#{(node[:label] || node[:name])} | #{node[:attributes].join('\l')} \\l} )
  opts = node[:options].collect{|k,v| %Q(#{k}="#{v}") }.join(", ")
  return %Q(\t"#{node[:name]}" [#{opts}]\n)
end

#nil_node_definition(opts) ⇒ Object

Returns a node definition for an empty node

Raises:

  • (ArgumentError)


147
148
149
150
151
# File 'lib/mini_gauge.rb', line 147

def nil_node_definition(opts)
  raise ArgumentError.new("Must supply at least :name or :label") unless opts.is_a?(Hash) && (opts.keys.include?(:name) || opts.keys.include?(:label))
  
  {:name => (opts[:name] || opts[:label].underscore), :label => (opts[:label] || opts[:name].humanize.titleize), :attributes => ["none"], :options => {:color => "gray61"}}
end

#to_dot_notationObject

Returns a string representing the DOT graph



154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/mini_gauge.rb', line 154

def to_dot_notation
  header = "digraph #{@graph_type.downcase}_diagram {\n" +
           "\tgraph[overlap=false, splines=true]\n"
  header += dot_label if @show_label
  
  uniq_nodes = @nodes.inject([]) { |result,h| result << h unless result.include?(h); result }
  uniq_edges = @edges.inject([]) { |result,h| result << h unless result.include?(h); result }
  
  return header +
         uniq_nodes.map{|node| format_node(node)}.to_s + "\n" + 
         uniq_edges.map{|edge| format_edge(edge)}.to_s +
         "}\n"
end