require 'graphviz' require 'set' module DependencyGrapher class Grapher def initialize(dependencies) @dependencies = dependencies generate_graph_from_dependencies end def output(format) @graph.output(format) end def generate_graph_from_dependencies # Initialize a directional graph @graph = GraphViz.digraph( :G, type: :digraph, tooltip: " ") # Iterate through dependency set to get edges and methods @dependencies.each do |dependency| # Methods from the dependencies kaller = dependency.kaller receiver = dependency.receiver # Add edges to set #@graph.add_edges(kaller.method_id, receiver.method_id, color: :red) add_node(kaller) add_node(receiver) add_edge(dependency) end end def add_edge(dependency) options = {} normal_color = get_normal_color(dependency.count) options[:color] = dependency.flags.include?(:violation) ? :red : normal_color options[:label] = dependency.count > 1 ? dependency.count : "" @graph.add_edges(dependency.kaller.id, dependency.receiver.id, options) end def get_normal_color(count) code = 80 - count * 5 binding.pry if code < 0 code = 0 if code < 0 "gray#{code}" end # Add a method to the structure of stored graph. Defined classes are stored # as clusters (subgraphs) and method id is used to identify the node def add_node(method) graph = create_clusters(method) # Node options = {} options[:label] = method.method_id options[:tooltip] = method.full_path graph.add_nodes(method.id, options) end def create_clusters(method) cluster_options = {tooltip: " "} if method.types.include?(:service) cluster_options[:bgcolor] = :azure3 elsif method.types.include?(:framework) cluster_options[:bgcolor] = :brown end # Iterate over ancestors (eg. Minitest::Unit yields Minitest, # Unit) # This variable is used to reference the immediate parent of the # current graph. Initializes to the root; updates on each iteration. prev_graph = @graph method.ancestors.each_with_index do |klass, i| # Prepending "cluster_" is mandatory according to ruby-graphviz API graph_id = "cluster_" + method.ancestors[0..i].join("_") # add_graph returns an existing graph if it exists subgraph = prev_graph.add_graph(graph_id, cluster_options) subgraph[:label] = klass # Update parent for next iteration prev_graph = subgraph end prev_graph end end end