Graph PluginsΒΆ

You can write your own custom graph for Understand using the API. The graph layout is done by Graphviz. Your graph plugin defines the subgraphs, nodes, and edges to draw. A sample plugin that draws a call tree is below.

The parameter graph passed to init and draw is an Graph object. It represents the root of the graph. Use the cluster, node, and edge methods to add clusters, nodes, and edges to the graph. Each of those objects has a set method to change the graphviz attributes. Default attributes can be set on the root of the graph with the default method. Nodes and edges can also be synced to entities, references or file locations.

Use options to access an Options object. The options object can be used to define and retrieve custom options.

You can also provide a legend for your graph using legend to obtain a Legend object. Use define to create entries. Use set to update existing entries.

The following sample script is a call tree. It can be run as a project level graph which graphs all functions in a database, or as an entity level graph showing the call tree from the initial entity.

import understand

def name():
  """
  Required, the name of the graph.
  """
  return "Calls"

def style():
  """
  Optional, the name as it appears in the graph variants drop down.

  This defaults to "Custom"
  """
  return "Python Template"

def test_global(db):
  """
  Optional, return True if this graph is a project level graph

  If True, this graph will appear in the top level Graph Menu.
  """
  return True

def test_entity(ent):
  """
  Optional, return True if the graph exists for the given entity

  If True, this graph will appear in the Graphical Views menu for the
  entity.
  """
  return ent.kind().check("function ~unknown ~unresolved");

def test_architecture(arch):
  """
  Optional, return True if the graph exists for the given architecture

  If True, this graph will appear in the Graphical Views menu for the
  architecture.
  """
  return True;

def init(graph, target):
  """
  Initialize the graph

  This function is called once on graph creation. Use it to define the
  available graph options and/or a legend.
  """
  # Define options through the options object
  graph.options().define("Fill", ["On","Off"], "Off");

  # Use isinstance on the target to see if it's an entity, architecture, or
  # project level graph (if your plugin supports multiple). You can use the
  # target to customize available options.
  if isinstance(target, understand.Ent):
    graph.options().define("Depth", ["1", "2", "3"], "3")

  # Defining a legend is optional. You can add multiple entries to the legend.
  graph.legend().define("func", "roundedrect", "Function", "blue", "#FFFFFF")

def grabNode(graph, nodes, ent):
  """
  This is a custom function for this script to get a graphviz node
  """
  if ent in nodes:
    node = nodes[ent]
  else:
    # passing an ent to the node object will automatically sync the entity.
    node = graph.node(ent.name(),ent)

    if ent.kind().check("unresolved"):
      # Set graph, node, and edge attributes with the set function.
      # See Graphviz documentation for available attributes.
      node.set("shape","octagon")
      node.set("color","gray")
      node.set("fillcolor","white")
    nodes[ent] = node

  return node


def draw(graph, target):
  """
  Draw the graph

  The second argument can be a database, architecture, or an entity depending
  which test functions return True.
  """

  fore = "blue"
  back = "#FFFFFF"

  # Use options to lookup the current values
  if graph.options().lookup("Fill") == "On":
    fore = "#000000"
    back = "blue"

  # Use set to update the legend outside of init
  graph.legend().set("func","fore",fore)
  graph.legend().set("func","back",back)

  # Use set to change graph, node, and edge attributes
  graph.set("rankdir", "LR")

  # Use default to set the default attributes for graphs, nodes, and edges
  graph.default("color", fore, "node")
  graph.default("fillcolor", back, "node")
  graph.default("style","filled,rounded","node")
  graph.default("shape","box","node")

  # store the ent->graphviz node so that each entity node appears only
  # once no matter how many calls to it there are.
  nodes = dict()

  # avoid visiting any node more than once
  visited = set()

  # If the graph can be more than one type, use isinstance to determine the type
  depth = 1
  curLevel = []
  if isinstance(target, understand.Db):
    # This sample graphs all functions in the database for a project level
    #graph
    curLevel = target.ents("function ~unknown ~unresolved")
  elif isinstance(target, understand.Arch):
    # Graph all functions in the architecture
    for ent in target.ents(False):
      if ent.kind().check("function ~unknown ~unresolved"):
        curLevel.append(ent)
      elif ent.kind().check("file"):
        for ref in ent.refs("define", "function ~unknown ~unresolved", True):
          curLevel.append(ref.ent())
    # Create clusters with cluster
    cluster = graph.cluster(target.name(), target)
    # add nodes to the cluster
    for ent in curLevel:
      grabNode(cluster, nodes, ent)
  else:
    # For an entity level graph, generate a calls tree
    curLevel.append(target)
    depth = int(graph.options().lookup("Depth"))

  # Loop over the levels of the tree
  while depth > 0:
    depth -= 1
    nextLevel = []
    for ent in curLevel:

      # avoid visiting nodes multiple times
      if ent in visited:
        continue
      visited.add(ent)

      # Get a graphviz node for the entity
      tail = grabNode(graph,nodes,ent)

      # Add edges for each call
      for ref in ent.refs("call",unique=True):
        headEnt = ref.ent()
        nextLevel.append(headEnt)
        head = grabNode(graph,nodes,headEnt)

        # create an edge
        edge = graph.edge(tail,head)
        # Use sync so that clicking on the edge will visit the reference
        edge.sync(ref)
    curLevel = nextLevel