ViewKit ObjectPak Graph Class

This section describes how to build and manipulate graphs using the VkGraph class.

Building and displaying a graph

Minimally, you must perform the following actions to build and display an ObjectPak graph:

  1. Create the VkGraph object
  2. Create the nodes as instances of VkNode or a subclass
  3. Add the nodes to the graph and specify the node connectivity
  4. Indicate which nodes to display
  5. Lay out the graph

Graph Constructor and Destructor

The VkGraph constructor is simple with few arguments. You must provide a name and the parent widget for the graph:

VkGraph(char *name, Widget parent)

The VkGraph destructor destroys the graph. It does not destroy any VkNode objects that are part of the graph.

Adding Nodes and Specifying Node Connectivity

After you create nodes, you must add them to the graph object you created. Also, if you didn't specify the parent-child relationship for the nodes when you created them, you should supply the remaining connectivity information when adding the nodes to the graph. (See "ViewKit Node Class" for information on creating nodes.)

The VkGraph::add() function adds nodes to a graph object:

virtual int add(VkNode *node)

virtual void add(VkNode *parent, VkNode *child,

char *attribute = NULL)

If you supply only one node pointer as an argument, add() simply adds the node to the graph. If you have already added the node to the graph, add() does nothing.

If you supply two node pointers as arguments, add() adds both nodes to the graph and establishes the first node as the parent of the second node. If you have already added either node to the graph, add() does not add the node again, but it does establish the parent-child relationship between the nodes.


Note: The second form of add() establishes the parent-child relationship between nodes even if one already exists. Thus, it is possible to have more than one connection between nodes. By default, the graph displays only a single arc between connected nodes, even if you define multiple connections between the nodes. However, as described in "Interactive Viewing Features Provided by VkGraph" on page 256, by clicking on the graph's Multiple Arcs button the user can force the graph to an arc for each connection you defined. To turn off multiple-arc display, the user can click again on the Multiple Arcs button.


When specifying a parent/child connection using add(), you can specify an attribute for that connection. An attribute is an arbitrary name that you can use to control the appearance of the arc widget that connects the two nodes. For example, assume that you add two nodes to a graph as follows:

graph->add(parent, child, "primary");

graph->add(parent, child, "secondary");

The resulting graph displays two connecting arcs between the two nodes.

Specifying X resources to control the arc

You can now specify X resources to control various aspects of the arc. For example:

*primary*foreground: red

*primary*arcDirection: bidirected

*secondary*foreground: blue

*secondary*arcDirection: undirected

*secondary*style: LineOnOffDash

You can use this method to set many of the resources supported by the SgArc widget. The resources you can specify are: XmNforeground, XmNtoSide, XmNfromSide, XmNfromPosition, XmNtoPosition, XmNarcDirection, XmNfontList, XmNarcWidth, XmNstyle, and XmNdashes. See the SgArc(3) man page for details on these resources.

The following code fragment creates a graph, creates two nodes, establishes a parent-child relationship between the nodes, and adds the nodes to the graph:

graph = new VkGraph("graph", parent);

p_node = new VkNode("parentNode", "Parent");

c1_node = new VkNode("childNode1", p_node, "Child 1");

graph->add(p_node);

graph->add(c1_node);


Note: In this example, the connection between the two nodes is established when you create c1_node. Therefore, you must add the nodes to the graph using separate calls to add().


If, instead of the two separate calls, you execute:

graph->add(p_node, c1_node);

then you not only add the two nodes to the graph, but you establish a second connection between the nodes.

You can accomplish the same result as above by creating the nodes without providing the parent-child relationship, and then specifying the connection when you add the nodes to the graph. The following code fragment is functionally equivalent to that shown above:

graph = new VkGraph("graph", parent);

p_node = new VkNode("parentNode", "Parent");

c1_node = new VkNode("childNode1", "Child 1");

graph->add(p_node, c1_node);

Removing Nodes

You can remove nodes from a graph using VkGraph::remove():

virtual void remove(VkNode *node, Boolean deleteNode = FALSE)

By default, remove() removes the node from the graph but does not delete it. If you set the deleteNode argument to TRUE, remove() deletes the node when it removes it.

Indicating Display Nodes

Once you have added all nodes to a graph and specified their connectivity, you must indicate which nodes the graph should display. VkGraph provides many functions that allow you to display or hide all of the graph, individual nodes, and portions of node subtrees.

After displaying nodes, you should call one of the graph layout member functions as described in "Laying Out the Graph" . Otherwise, the nodes might not display in desired locations.

The basic display functions are VkGraph::displayAll() and VkGraph::clearAll():

virtual void displayAll()
void clearAll()

displayAll() displays all nodes and clearAll() hides all nodes. Typically, after creating your graph, you execute displayAll() to display all of the nodes. For example:

graph->displayAll();

Sometimes you might want to display only portions of your graph. VkGraph provides functions to operate on either single nodes or subtrees of nodes.

The VkGraph::display() function displays a single node:

virtual void display(VkNode *child)

virtual VkNode *display(char *name)

You can provide display() with either a pointer to the node or the component name of the node. If you provide the node's name, this function returns a pointer to the node.

VkGraph::undisplay() hides a single node:

virtual void undisplay(VkNode *node)

virtual void hideNode(VkNode *node)

VkGraph::hideNode() is equivalent to undisplay().

Displaying or hiding portions of the graph

VkGraph also provides a large number of functions that display or hide portions of the graph:

  • displayWithChildren() displays a node and all of its immediate child nodes (not all descendent nodes). If you provide the node's name, this function returns a pointer to the node.
    virtual void displayWithChildren(VkNode *node)
    virtual VkNode *displayWithChildren(char *name)

  • expandNode() is functionally equivalent to displayWithChildren() except that it also calls VkGraph::doSubtreeLayout() to lay out the child nodes according to the graph's layout algorithm. See "Laying Out the Graph" for more information on doSubtreeLayout().
    virtual void expandNode(VkNode *node)

  • displayWithAllChildren() displays a node and all of its descendent nodes. If you provide the node's name, this function returns a pointer to the node.
    virtual void displayWithAllChildren(VkNode *node)
    virtual VkNode *displayWithAllChildren(char *name)

  • expandSubgraph() is functionally equivalent to displayWithAllChildren() except that it also calls VkGraph::doSubtreeLayout() to lay out the child nodes according to the graph's layout algorithm. See "Laying Out the Graph" for more information on doSubtreeLayout().
    virtual void expandSubgraph(VkNode *node)

  • hideAllChildren() hides all of a node's descendent nodes. Note that this function does not hide node itself.
    virtual void hideAllChildren(VkNode *node)

  • hideWithAllChildren() hides a node and all of its descendent nodes.
    virtual void hideWithAllChildren(VkNode *node)

  • displayWithParents() displays a node and all of its immediate parent nodes (not all ancestor nodes). If you provide the node's name, this function returns a pointer to the node.
    virtual void displayWithParents(VkNode *node)

    virtual VkNode *displayWithParents(char *name)

  • displayWithAllParents() displays a node and all of its ancestor nodes. If you provide the node's name, this function returns a pointer to the node.
    virtual void displayWithAllParents(VkNode *node)

    virtual VkNode *displayWithAllParents(char *name)

  • hideParents() hides all of a node's immediate parent nodes (not all ancestor nodes). Note that this function does not hide node itself.
    virtual void hideParents(VkNode *node)

  • displayParentsAndChildren() displays a node and all of its immediate parent and child nodes (not all ancestor and descendent nodes). If you provide the node's name, this function returns a pointer to the node. Note that this function does display node itself.
    virtual void displayParentsAndChildren(VkNode *node)

    virtual VkNode *displayParentsAndChildren(char *name)

  • hideParentsAndChildren() hides all of a node's immediate parent and child nodes (not all ancestor and descendent nodes). Note that this function does not hide node itself.
    virtual void hideParentsAndChildren(VkNode *node)

You can also create your own functions for determining whether or not nodes are displayed and then use the VkGraph::displayIf() function to apply those functions:

virtual void displayIf(VkGraphFilterProc)

The type definition of VkGraphFilterProc is:

typedef Boolean (*VkGraphFilterProc) (VkNode *)

The function you provide must be a static function that accepts a node as an arguments and returns TRUE if the node should be displayed.


Note: displayIf() does not hide (that is, call undisplay()) if the filter function returns FALSE for a node. Therefore, to display only those nodes for which the filter function returns TRUE, you must first call clearAll().


For example, the following function displays only those nodes whose names begin with the string "state":

static Boolean displayState(VkNode *node)
{
if ( strcmp("state", node->name(), 5)
return TRUE;
else
return FALSE;
}

Laying Out the Graph

The final step in displaying a graph is to lay it out. Laying out the graph arranges the widgets in a logical manner and then manages the widgets.

To lay out the entire graph, call the VkGraph::doLayout() function, which applies the layout algorithm to the entire graph and then manages all widgets associated with the graph:

void doLayout()

If you modify the graph after displaying it, or if you allow the user to edit the graph interactively, the graph might become cluttered and you might want to lay out the graph again. To do so you can call doLayout() again to force the graph to reapply the layout algorithm to the graph to clean up the display. As an example, the Realign button provided on the graph command panel simply calls doLayout() whenever the user clicks on the button.

If, after displaying the graph, you display any additional nodes (for example, using the VkGraph::display() function), you must force a layout of the graph to manage all the widgets you created. You can call doLayout() again to do so, but this applies the layout algorithm to the entire graph. Doing so could produce major changes in the layout of the entire graph, which could be disruptive and undesired if the user has previously moved nodes. Also, it could take considerable time if the graph is large. In this case, you can instead call the VkGraph::doSubtreeLayout() function which, given a root node, applies the layout algorithm to just a subtree of the graph:

void doSubtreeLayout(VkNode *node)

For example, the following code fragment illustrates displaying a graph, graph, and then displaying another node, newNode:

// At this point, all nodes are created, the connectivity is
// specified, and certain nodes selected to be displayed
// Lay out and display the graph
graph->doLayout();
// Mark newNode to be displayed
graph->display(newNode);
// Display newNode, re-laying out only the subtree
// under newNode
graph->doSubtreeLayout(newNode);

VkGraph::doSparseLayout() is a special-purpose build and layout function that displays the relationship between a node and its grandparent nodes even if the node's parents are not displayed:

void doSparseLayout()

doSparseLayout() performs a special build of the graph and whenever it finds a node with an undisplayed parent node, it checks to see whether there are any displayed grandparent nodes. If doSparseLayout() finds such grandparent nodes, it creates a dashed-line arc (instead of a solid-line arc) to connect the node and its grandparent nodes. After finishing the build process, doSparseLayout() performs a layout of the entire graph and manages all widgets associated with the graph.

Butterfly Graphs

So far, this chapter discussed creating tree graphs using the VkGraph class. However, VkGraph also supports butterfly graphs, which display only a central node and its immediate parent and child nodes. The central node of a butterfly graph is called the butterfly node.

VkGraph can construct a butterfly graph from any graph specification. Call VkGraph::displayButterfly() to specify one node as the butterfly node; VkGraph automatically determines which nodes to display:

virtual void displayButterfly(VkNode *node)
virtual VkNode *displayButterfly(char *name)

Then call VkGraph::doLayout() to lay out the graph as you normally would. For example, assuming that you have already defined a graph specification for a graph called graph, the following code fragment would instruct the graph object to display a butterfly graph centered on the node centerNode:

graph->displayButterfly( centerNode );
graph->doLayout();

After displaying a butterfly graph, you can use displayButterfly() to specify a new butterfly node and display a different butterfly graph given the same graph specification. For example, the following code fragment illustrates setting a new butterfly node, newCenter, after displaying the butterfly graph in the example above:

graph->displayButterfly( newCenter );
graph->doLayout();

After displaying a butterfly graph, you can return to displaying a normal tree graph by setting the layout style to XmGRAPH using the VkGraph::setLayoutStyle() function:

virtual void setLayoutStyle(char type)

For example, the following code fragment illustrates displaying the entire graph specified by graph after displaying the butterfly graphs above:

graph->setLayoutStyle( XmGRAPH );
graph->displayAll();
graph->doLayout();

Displaying a Graph Overview

As discussed in "Interactive Viewing Features Provided by VkGraph" , by clicking on the Graph Overview button in the graph command panel, a user can display an overview of all a graph's visible nodes.

You can also display the overview window programmatically using VkGraph::showOverview():

void showOverview()

Call VkGraph::hideOverview() to programmatically hide the overview window:

void hideOverview()

You can obtain a pointer to the overview window's VkWindow object using VkGraph::overviewWindow():

VkWindow *overviewWindow()

Graph Utility Functions

VkGraph provides the following utility functions:

  • VkGraph::setZoomOption() sets the zoom value for the graph. Pass to this function the integer index corresponding to the index in the Zoom Menu of the magnification that you want. ("Interactive Viewing Features Provided by VkGraph" describes the Zoom Menu and its default values.)
    virtual void setZoomOption(int index)

  • VkGraph::sortAll() sorts all nodes associated with the graph by calling VkNode::sortChildren() on all nodes. ("Basic Node Functionality" describes VkNode::sortChildren().)
    void sortAll()

  • VkGraph::forAllNodesDo() allows you to perform some action on all nodes registered with a graph. The type definition of VkGraphNodeProc is:
    typedef void (*VkGraphNodeProc) (VkNode *)

    The function you provide must be a static function that accepts a node as an arguments and has a void return value.

    virtual void forAllNodesDo(VkGraphNodeProc function)

  • VkGraph::makeNodeVisible() ensures that a particular node is in the visible portion of the graph's window. If the node you specify is not currently visible, makeNodeVisible() scrolls the graph until the specified node appears in the visible portion of the window.
    void makeNodeVisible(VkNode *node)

  • VkGraph::saveToFile() prompts the users for a file name and saves a PostScript® version of the graph to that file.
    void saveToFile()

  • VkGraph::setSize() allows you to pre-allocate space in your graph's internal tables for the number of nodes you specify. If you know how many nodes you plan to add to your graph, calling setSize() before adding nodes to your graph can save time because the graph can allocate all memory needed in one operation instead of expanding the tables dynamically as you add nodes. Your graph can still allocate additional space if you actually add more nodes than you reserved space for using setSize().
    void setSize(int entries)

Graph Access Functions

VkGraph provides the following access functions for obtaining values associated with the graph:

  • VkGraph::numNodes() returns the number of nodes in the graph.
    int numNodes()

  • VkGraph::find() returns the first VkNode object registered with the VkGraph object that has the given name.
    VkNode *find(char *name)

  • VkGraph::graphWidget() returns the SgGraph widget instantiated by the VkGraph component. Not all the functionality of the SgGraph widget is encapsulated in the VkGraph class, and it is sometimes useful to set various resources directly on the graph widget.
    Widget graphWidget()

  • VkGraph::workArea() returns the XmForm widget at the bottom of the VkGraph component, which contains the graph controls. You can use this area to add additional controls.
    Widget workArea()

  • VkGraph::twinsButton() returns the Multiple Arcs button widget used to control whether sibling arcs are shown.
    Widget twinsButton()

  • VkGraph::relayButton() returns the Realign button widget used to relay the graph.
    Widget relayButton()

  • VkGraph::reorientButton() returns the Rotate button widget used to reorient the graph.
    Widget reorientButton()

Reusing a Graph Object

Occasionally, after displaying one graph, you might want to display an entirely different graph. The simplest method of accomplishing this is to create another VkGraph object for the new graph.

However, creating a new graph object entails the overhead of creating many new widgets and data structures. Sometimes it is simpler, faster, and more appropriate to re-use the existing graph object. For example, consider a window in which you are displaying a graph of C++ class hierarchies associated with a program. The window might contain controls that allow the user to select other programs to examine. If the user selects a new program to examine, the most convenient thing to do would be to keep the existing graph object but "clear it" of all existing information.

The VkGraph::tearDownGraph() function provides this ability:

virtual void tearDownGraph()

It tears down the graph by destroying all arc and node widgets and deleting all VkNode objects associated with the graph. This function is equivalent to deleting all VkNode objects associated with the graph, deleting the graph object, and creating a new graph object with the same name, but entails less overhead processing than if you were to explicitly perform these actions separately.

ObjectPak Callbacks Associated with VkGraph

The VkGraph class declares two ObjectPak member function callbacks and activates the VkGraph::arcCreatedCallback whenever the graph creates a SgArc widget to connect two nodes. The arcCreatedCallback callback includes as call data the newly created SgArc widget. See the SgArc(3) reference pages for information on the SgArc widget.

VkGraph activates the VkGraph::arcDestroyedCallback whenever the graph destroys all arc widgets as a result of a call to VkGraph::clearAll() (see "Indicating Display Nodes" ). VkGraph activates the arcDestroyedCallback callback once for every arc destroyed, including as call data the SgArc widget destroyed. See the SgArc(3) reference pages for information on the SgArc widget.

X Resources Associated with VkGraph

VkGraph sets several X resources that specify the labels of its popup menus. You can override these values in an app-defaults file if you want to provide your own labels. The resources and their default values are:

*graph*popupMenu*hideNode*labelString: Hide Node
*graph*popupMenu*collapseSubgraph*labelString: Collapse Subgraph
*graph*popupMenu*expandOneLevel*labelString: Show Immediate Children
*graph*popupMenu*expandSubgraph*labelString: Expand Subgraph
*graph*popupMenu*hideParents.labelString: Hide Parents
*graph*popupMenu*expandParents.labelString: Show Parents
*graph*popupMenu*selectedNodes.labelString: Selected Nodes
*graph*popupMenu*hideSelectedNodes.labelString: Hide
*graph*popupMenu*collapseSelectedNodes.labelString: Collapse
*graph*popupMenu*expandSelectedNodes.labelString: Expand

Subclassing VkGraph

VkGraph provides much of the functionality required for displaying and manipulating graphs. In most other cases, you can use the graphWidget() access function to obtain a pointer to the SgGraph widget for direct operations on the widget.

However, you might want to perform additional processing when certain actions occur. You can create a subclass of VkGraph which provides several hook" functions that you can override to implement additional functionality:

  • VkGraph::buildCmdPanel() builds the command panel at the bottom of the graph. You can override this function to create your own custom command panel for your graph.
    virtual void buildCmdPanel(Widget parent)

  • VkGraph::buildZoomMenu() builds the Zoom menu, the Zoom Out button, and the Zoom In button as part of the command panel. You can override this function to provide your own custom zoom controls for your graph.
    virtual void buildZoomMenu(Widget parent)

  • VkGraph::addMenuItems() allows you to modify the Node popup menu described in "Interactive Viewing Features Provided by VkGraph" . You can override this function and use the various functions provided by the VkMenu class to add new menu item or delete default menu items. "ViewKit Menu Base Class" describes the functions provided by VkMenu.
    virtual void addMenuItems(VkPopupMenu *menu)

  • VkGraph::popupMenu() posts the Node popup menu described in "Interactive Viewing Features Provided by VkGraph." The function receives two arguments: a pointer to the node on which the user clicked the right mouse button, and the X ButtonPress event. By default, the function: 1) activates and deactivates menu items to reflect the valid options for the node; 2) sets the label of the popup menu to be the same as the label of the node; and 3) calls the popup menu's show() function, passing event as an argument.

    You can override this function if you want to change its behavior or support any additional menu items that you added by overriding addMenuItems().

    virtual void popupMenu(VkNode *node, XEvent *event)

  • VkGraph::addDesktopMenuItems() allows you to modify the Selected Nodes popup menu described in "Interactive Viewing Features Provided by VkGraph" . You can override this function and use the various functions provided by the VkMenu class to add new menu items or delete default menu items. "ViewKit Menu Base Class" describes the functions provided by VkMenu.
    virtual void addDesktopMenuItems(VkPopupMenu *menu)

  • VkGraph::twinsVisibleHook() is called when the user toggles the Multiple Arcs or "twins" button. The new state of the twins buttons is passed as an argument to this function. By default, the function is empty. You can override this function to perform additional operations when the graph changes its display mode.
    virtual void twinsVisibleHook(Boolean state)