lib/rails_erd/diagram/graphviz.rb in rails-erd-0.4.3 vs lib/rails_erd/diagram/graphviz.rb in rails-erd-0.4.4

- old
+ new

@@ -38,17 +38,17 @@ # homepage for more information and examples. # orientation:: The direction of the hierarchy of entities. Either +:horizontal+ # or +:vertical+. Defaults to +horizontal+. The orientation of the # PDF that is generated depends on the amount of hierarchy # in your models. - # title:: The title to add at the top of the diagram. Defaults to + # title:: The title to add at the top of the diagram. Defaults to # <tt>"YourApplication domain model"</tt>. class Graphviz < Diagram - NODE_LABEL_TEMPLATE = ERB.new(File.read(File.expand_path("templates/node.erb", File.dirname(__FILE__))), nil, "<>") # @private :nodoc: + NODE_LABEL_TEMPLATES = { :html => "node.html.erb", :record => "node.record.erb" } # @private :nodoc: NODE_WIDTH = 130 # @private :nodoc: - + # Default graph attributes. GRAPH_ATTRIBUTES = { :rankdir => :LR, :ranksep => 0.5, :nodesep => 0.4, @@ -76,20 +76,20 @@ :dir => :both, :arrowsize => 0.9, :penwidth => 1.0, :labelangle => 32, :labeldistance => 1.8, - :fontsize => 7 + :fontsize => 7 } - + module Simple def entity_style(entity, attributes) {}.tap do |options| options[:fontcolor] = options[:color] = :grey60 if entity.abstract? end end - + def relationship_style(relationship) {}.tap do |options| options[:style] = :dotted if relationship.indirect? # Closed arrows for to/from many. @@ -100,11 +100,11 @@ def specialization_style(specialization) { :color => :grey60, :arrowtail => :onormal, :arrowhead => :none, :arrowsize => 1.2 } end end - + module Bachman include Simple def relationship_style(relationship) {}.tap do |options| options[:style] = :dotted if relationship.indirect? @@ -119,11 +119,11 @@ options[:arrowsize] = 0.6 options[:arrowhead], options[:arrowtail] = dst, src end end end - + module Uml include Simple def relationship_style(relationship) {}.tap do |options| options[:style] = :dotted if relationship.indirect? @@ -141,11 +141,11 @@ end options[:headlabel], options[:taillabel] = *ranges end end end - + attr_accessor :graph setup do self.graph = GraphViz.digraph(domain.name) @@ -154,97 +154,109 @@ NODE_ATTRIBUTES.each { |attribute, value| graph.node[attribute] = value } EDGE_ATTRIBUTES.each { |attribute, value| graph.edge[attribute] = value } # Switch rank direction if we're creating a vertically oriented graph. graph[:rankdir] = :TB if options.orientation == :vertical - + # Title of the graph itself. graph[:label] = "#{title}\\n\\n" if title - + # Setup notation options. extend self.class.const_get(options.notation.to_s.capitalize.to_sym) end - + save do - raise "Saving diagram failed. Output directory '#{File.dirname(filename)}' does not exist." unless File.directory?(File.dirname(filename)) + raise "Saving diagram failed!\nOutput directory '#{File.dirname(filename)}' does not exist." unless File.directory?(File.dirname(filename)) begin graph.output(filetype => filename) filename + rescue RuntimeError => e + raise "Saving diagram failed!\nGraphviz produced errors. Verify it has support for filetype=#{options.filetype}, or use filetype=dot." << + "\nOriginal error: #{e.message.split("\n").last}" rescue StandardError => e - raise "Saving diagram failed. Verify that Graphviz is installed or select filetype=dot." + raise "Saving diagram failed!\nVerify that Graphviz is installed and in your path, or use filetype=dot." end end each_entity do |entity, attributes| draw_node entity.name, entity_options(entity, attributes) end - + each_specialization do |specialization| from, to = specialization.generalized, specialization.specialized draw_edge from.name, to.name, specialization_options(specialization) end - + each_relationship do |relationship| from, to = relationship.source, relationship.destination unless draw_edge from.name, to.name, relationship_options(relationship) if from.children.any? from.children.each do |child| draw_edge child.name, to.name, relationship_options(relationship) end end end end - + private - + def node_exists?(name) - !!graph.get_node(name) + !!graph.get_node(escape_name(name)) end - + def draw_node(name, options) - graph.add_node name, options + graph.add_node escape_name(name), options end - + def draw_edge(from, to, options) - graph.add_edge graph.get_node(from), graph.get_node(to), options if node_exists?(from) and node_exists?(to) + graph.add_edge graph.get_node(escape_name(from)), graph.get_node(escape_name(to)), options if node_exists?(from) and node_exists?(to) end + def escape_name(name) + "m_#{name}" + end + # Returns the title to be used for the graph. def title case options.title when false then nil when true if domain.name then "#{domain.name} domain model" else "Domain model" end else options.title end end - + # Returns the file name that will be used when saving the diagram. def filename "#{options.filename}.#{options.filetype}" end - + # Returns the default file extension to be used when saving the diagram. def filetype if options.filetype.to_sym == :dot then :none else options.filetype.to_sym end end def entity_options(entity, attributes) - entity_style(entity, attributes).merge :label => "<#{NODE_LABEL_TEMPLATE.result(binding)}>" + label = options[:markup] ? "<#{read_template(:html).result(binding)}>" : "#{read_template(:record).result(binding)}" + entity_style(entity, attributes).merge :label => label end - + def relationship_options(relationship) relationship_style(relationship).tap do |options| # Edges with a higher weight are optimised to be shorter and straighter. options[:weight] = relationship.strength - + # Indirect relationships should not influence node ranks. options[:constraint] = false if relationship.indirect? end end def specialization_options(specialization) specialization_style(specialization) + end + + def read_template(type) + ERB.new(File.read(File.expand_path("templates/#{NODE_LABEL_TEMPLATES[type]}", File.dirname(__FILE__))), nil, "<>") end end end end