Chapter 12. The ViewKit Graph Component

ViewKit provides a high-level component, VkGraph, for displaying and manipulating complex arc-and-node graphs. Figure 12-1 shows the inheritance graph for VkGraph and an auxiliary class, VkNode.

Figure 12-1. Inheritance Graph for the ViewKit Graph Classes

Figure 12-1 Inheritance Graph for the ViewKit Graph Classes

Overview of ViewKit Graphs

VkGraph is a self-contained ViewKit component for displaying and manipulating complex arc-and-node graphs. The graph can be disconnected and can contain cycles. VkGraph can arrange the nodes horizontally or vertically and change the orientation interactively. VkGraph also provides controls for interactive zooming, node repositioning, and node alignment. Figure 12-2 shows an example of a graph created using the VkGraph component.

Figure 12-2. Graph Created With VkGraph

Figure 12-2 Graph Created With VkGraph

All nodes displayed by a VkGraph component must be instances of the VkNode class or subclasses that you derive from it. By default, VkNode creates an SgIconGadget, but if you create a subclass VkNode, you can use any widget for a node.

Graph Widget

The basis of the VkGraph class is the SgGraph widget, which manages and displays the graph. This section provides an overview of the SgGraph widget. For in-depth information on interacting with the graph widget, consult the SgGraph(3x) reference page.

A primary responsibility of the SgGraph widget is to clearly and systematically lay out the nodes. The graph layout algorithm is a simple and efficient tree layout algorithm designed to handle forests of nodes. It lays out nodes as a multi-rooted tree.

By default, the graph widget created by the VkGraph class operates in a read-only mode in which the graph widget is used primarily as a layout manager for arranging the node widgets. By modifying certain SgGraph resources, you can also interactively edit the displayed graph, creating and moving arcs and nodes. However, to support most of the functionality of the edit mode, you must provide callback functions and other information to the graph widget so that you can interpret the edit operations and use them in your program.

Refer to the SgGraph(3x) reference page for details on the resources and callbacks used for edit mode. Also refer to the example in /usr/share/src/ViewKit/ComponentDemos/graph.

Building a Graph

The process of building and displaying a graph using the VkGraph component consists of the following steps:

  1. Creating the nodes.

  2. Specifying node connectivity.

  3. Indicating which nodes to display.

  4. Laying out the graph.

Example 12-1 illustrates this process by showing the code used to create the graph shown in Figure 12-2.

Example 12-1. Creating a Graph Using VkGraph

#include <Vk/VkApp.h>
#include <Vk/VkWindow.h>
#include <Vk/VkNode.h>
#include <Vk/VkGraph.h>
#include <Vk/VkMenu.h>
class GraphWindow: public VkWindow {
 
  public:
    GraphWindow( const char *);
    ~GraphWindow();
    virtual const char* className();
  protected:
    VkGraph *graph;
    VkNode *p_node, *c1_node, *c2_node, *gc1_node, *gc2_node;
 
  private:
    static void quitCallback (Widget, XtPointer, XtPointer);
    static VkMenuDesc appMenuPane[];
};
 
 
VkMenuDesc GraphWindow::appMenuPane[] = {
  { ACTION,   "Quit",      &GraphWindow::quitCallback },
  { END }
};
 
GraphWindow::GraphWindow(const char *name) : VkWindow( name )
{
    // Create nodes
 
    p_node   = new VkNode("parentNode", "Parent");
    c1_node  = new VkNode("childNode1", "Child 1");
    c2_node  = new VkNode("childNode2", "Child 2");
    gc1_node = new VkNode("grandChildNode1", "Grandchild 1");
    gc2_node = new VkNode("grandChildNode2", "Grandchild 2");
    
    // Create graph
 
    graph   = new VkGraph( "graph", mainWindowWidget() );
 
    // Add nodes to graph
 
    graph->add(p_node, c1_node);     // p_node is parent to c1_node
    graph->add(p_node, c2_node);     // p_node is parent to c2_node
    graph->add(c1_node, gc1_node);   // c1_node is parent to gc1_node
    graph->add(c1_node, gc2_node);   // c1_node is parent to gc2_node
    graph->displayAll();             // Display all nodes in graph
    graph->doLayout();               // Layout the graph
    
    addView(graph);                  // Set graph to be window's view
    addMenuPane("Application", appMenuPane);         // Create menu bar
}
GraphWindow::~GraphWindow()
{
    delete graph;
    delete p_node;
    delete c1_node;
    delete c2_node;
    delete gc1_node;
    delete gc2_node;
}
 
const char* GraphWindow::className()
{
    return "GraphWindow";
}
 
void GraphWindow::quitCallback ( Widget, XtPointer, XtPointer )
{
    theApplication->quitYourself();
}
 
void main(int argc, char **argv)
{
  VkApp       *myApp    = new VkApp("GraphViewer", &argc, argv);
  GraphWindow *graphWin = new GraphWindow("GraphViewer");
 
  graphWin->show();
  myApp->run();
}

This example creates a VkWindow subclass to contain the graph. The graph itself is created in the GraphWindow constructor:

  1. The program creates five nodes. These nodes are instances of the VkNode class, which is described in “ViewKit Node Class”. The version of the VkNode constructor used in this example accepts a name that is used for internal reference and a label that is displayed.

  2. The program creates a VkGraph object. The VkGraph constructor accepts as arguments a name and a parent widget, in this case, the main window widget obtained by mainWindowWidget().

  3. The program adds the nodes to the graph using VkGraph::add(). When called with pointers to two nodes, this function associates the nodes with the graph, and marks the first node as being the parent of the second node. In this way, the program specifies the structure of the graph.

  4. The program calls VkGraph::displayAll(), which indicates that the graph should display all nodes.

  5. The program calls VkGraph::doLayout(), which lays out the graph according to the layout algorithm and manages all widgets associated with the graph.

Interactive Viewing Features Provided by VkGraph

In addition to displaying a graph, VkGraph automatically provides controls for interactively manipulating the graph. One set of controls is contained in the control panel, shown in Figure 12-3, which appears along the bottom of the graph.

Figure 12-3. Graph Command Panel

Figure 12-3 Graph Command Panel

The control panel contains buttons and a menu that allow the user to interactively control various characteristics of the graph's display. Using the control panel, the user can

  • zoom in or out

  • display a graph overview

  • toggle between displaying and hiding duplicate arcs connecting nodes

  • align nodes

  • toggle between horizontal and vertical orientation

Additionally, VkGraph automatically creates popup menus that contain commands that allow the user to hide and display nodes in the graph.

Zooming

VkGraph provides eight preset zoom settings that allow the user to shrink or enlarge the size of the graph. The user can directly set the zoom value using the Zoom menu shown in Figure 12-4.

Figure 12-4. Interactively Changing the Graph Zoom Value

Figure 12-4 Interactively Changing the Graph Zoom Value

Clicking the Zoom Out button (the down-arrow button immediately to the right of the Zoom menu) changes the zoom setting to the next lower value, and clicking the Zoom In button (the up-arrow button to the right of the Zoom Out button) changes the zoom setting to the next higher value.

Graph Overview

The user can display an overview of all a graph's visible nodes by clicking the Graph Overview button.

Within the overview window is a viewport that represents the boundaries of the graph visible in the main graph window. The user can click the viewport and drag it to a new location to change the area visible in the main graph window. As the user drags the viewport, the main graph window scrolls to match the viewport's location in the overview.

The overview window also contains an Admin menu with these commands:

“Scale to Fit” 

Scales the graph in the window to match the aspect ratio of the window.

“Show Arcs” 

Shows the arcs between nodes. This option is turned on by default; if the arcs clutter the window, the user can turn off the option, which removes the arcs from the window.

“Close” 

Closes the overview window.

Displaying Duplicate Arcs

By default, the graph displays only a single arc between nodes, even if you define multiple connections between the nodes. The user can click the Multiple Arcs button to display multiple arcs between nodes; the graph displays an arc for each connection you defined. The user can turn off the multiple-arc display by clicking the Multiple Arcs button again.

Realigning Nodes

Occasionally, as a result of moving or displaying nodes, your graph display might become cluttered. The Realign button “cleans up” the graph display by laying out all visible nodes again.

Toggling Between Horizontal and Vertical Orientation

The default graph orientation is horizontal. The user can change to a vertical orientation by clicking the Rotate Graph button. The user can return to the horizontal orientation by clicking the Rotate Graph button again.

Hiding and Displaying Nodes

VkGraph provides controls that allow the user to hide a single node, reveal a node's parents or children, or collapse the part of the graph that branches from a node. To perform any of these actions, the user moves the pointer onto the node and presses the right mouse button to open the popup Node menu. The Node menu contains four commands; only commands applicable to that node are made available. Nonapplicable commands are grayed. The commands are as follows:

“Hide Node” 

Hides the node and connecting arcs from the graph.

“Collapse Subgraph” 


Hides all descendent nodes and connecting arcs.

“Show Immediate Children” 


Displays the node's immediate child nodes and connecting arcs. This command does not display more than the first subordinate level of nodes.

“Show Parents” 


Displays the node's immediate parent nodes and connecting arcs.

Edit Mode Operations

There are additional operations that a user can perform if you set the graph to edit mode, as described in “Graph Widget”. By default, the graph widget created by the VkGraph class operates in read-only mode. You can set the graph widget to edit mode in a VkGraph subclass.


Note: To support much of the functionality of the edit mode, you must provide callback functions and other information to the graph widget so that you can interpret the edit operations and use them in your program. Refer to the SgGraph(3x) reference page for details on the resources and callbacks used for edit mode.

You must select one or more nodes before you can perform an operation on it. You can select nodes only if the graph is in edit mode. By default, the graph is created in display-only mode.

To perform most operations in edit mode, the user must first select one or more nodes. The user can select a single node by clicking it with the left mouse button. The graph highlights the selected node. The user can select additional nodes by holding down the Control key and clicking additional nodes with the left mouse button. The user can also select multiple nodes with a bounding box by moving the pointer to a spot on the graph where there is no node or arc, then holding down the left mouse button and dragging out a bounding box. When the button is released, all nodes fully enclosed by the box are selected. (Partially enclosed nodes aren't selected.)

The user can deselect nodes by clicking the left mouse button on a blank section of the graph.

The user can move a node by clicking that node with the middle mouse button and then dragging the node anywhere in the graph window. The user can move several nodes at once by first selecting the nodes and then clicking any one of the nodes with the middle mouse button and dragging the nodes to their new position.

The popup Selected Nodes menu allows the user to perform an operation on all selected nodes. To open the Selected Nodes menu, the user moves the pointer to any blank area of the graph, and then presses the right mouse button. The menu has three commands:

“Hide Selected Nodes” 


Hides all selected nodes and their connecting arcs.

“Collapse Selected Nodes” 


Hides all descendent nodes and connecting arcs of the selected nodes.

“Expand Selected Nodes” 


Displays the immediate children of all the selected nodes.

ViewKit Node Class

VkGraph requires that all nodes that it contains be instances of either the VkNode class or a subclass of VkNode. The VkNode class is responsible for tracking the connectivity, display characteristics, and other features of the nodes. VkNode is a subclass of VkComponent.

The VkNode class provides only basic support for interacting with the node widget. In particular, you can set the string displayed as a label through the VkNode constructor; however, you can create subclasses of VkNode that support any widget type, as discussed in “Creating Node Subclasses”.

Basic Node Functionality

This section describes the basic functionality provided by the VkNode class. Most VkNode functions other than the constructor are for use by VkGraph; however, you might occasionally find some of the utility and access functions useful.

Node Constructor and Destructor

The VkNode constructor has two forms:

VkNode(const char *name, const char *label = NULL)
 
VkNode(const char *name, VkNode *parent,
       const char *label = NULL)

name is the node's component name. You should provide unique names for all nodes. label is the label that the node displays when visible in a graph. If you do not provide a label, the node uses the component name as the label. You can optionally provide a pointer to an existing node, which the constructor uses as a parent node for the new node.

As an example, the following line of code creates the node state19 with the internal name “state19” and the label “Indiana”:

VkNode state19 = new VkNode("state19", "Indiana");

The following line of code creates a new node, city41, as a child of state19. The name of the new node is “city41” and the label is “Terre Haute”:

VkNode city41 = new VkNode("city41", state19, "Terre Haute");


Note: The VkNode constructor merely initializes internal variables; it does not create any widgets. The VkGraph object of which a VkNode object is a member can create and destroy node widgets as needed. The VkGraph object calls a protected member function, VkNode::build(), whenever it needs to create a node's widget. “Creating Node Subclasses” discusses build() in more detail.

The VkNode destructor destroys the node's widget if it exists and deallocates all other internal storage.

Node Utility Functions

VkNode maintains a list of child nodes that you can access using the access functions described in “Node Access Functions”. By default, the order of the child nodes in this list depends on the order in which you specified the child relationships. The first child node you specify has an index of 0, the second 1, and so on.You can use the VkNode::sortChildren() to sort the immediate child nodes of a node:

void sortChildren()

The default algorithm used by sortChildren() sorts nodes alphabetically by their internal node names (not their labels).

You can direct VkNode to use a different sort comparison function with VkNode::setSortFunction():

static void setSortFunction(VkNodeSortFunction func)

The type definition of VkNodeSortFunction is as follows:

typedef int (*VkNodeSortFunction)(VkNode *, VkNode *)

The function you provide must be a static function that accepts as arguments two nodes, and returns an integer value less than zero if the first node comes before the second node, zero if the two nodes are equal, and greater than zero if the second node comes before the first node. For example, the following function sorts nodes by their label strings:

static int sortNodesByLabel(VkNode *one, VkNode *two)
{
    int value = strcmp(one->label(), two->label());
    return value;
}     

(“Node Access Functions” describes VkNode::label().)

Node Access Functions

VkNode provides a number of access functions for obtaining values associated with a node.

You can retrieve the node's component name using VkNode::name():

char *name() const

You can retrieve the node's label string with VkNode::label():

virtual char *label()

If you did not provide a label string in the node constructor, the value of the label string is the same as the component's name.

You can determine the number of parent and child nodes with VkNode::nParents() and VkNode::nChildren(), respectively:

int nParents() const
int nChildren() const

You can retrieve a specific parent or child node using VkNode::parent() and VkNode::child() respectively:

VkNode *parent(int index) const
VkNode *child(int index) const

By default, the order of the parent and child nodes depends on the order in which you specified the parent or child relationships. The first parent node you specify has an index of 0, the second 1, and so on. Initially, the child nodes are numbered similarly; however, if you sort the child nodes using the sortChildren() function, the nodes are reordered according to the sort function you used. For example, if you sorted the child nodes alphabetically by component name, the first child node alphabetically has an index of 0, the second 1, and so on.

You can find a particular parent or child node by component name using VkNode::findParent() and VkNode::findChild(), respectively:

VkNode *findParent(char *name)
VkNode *findChild(char *name)

These functions return a pointer to the node if found, and NULL if they do not find the node. These functions search only immediate parent or child nodes, not all ancestor or descendent nodes.

Creating Node Subclasses

You can create subclasses of VkNode to extend its features in a variety of ways to maintain additional data or to change the way the node displays itself in a graph. Some possibilities include the following:

  • providing access functions for setting and retrieving resources of the default SgIconGadget(3x) widget provided by the VkNode base class

  • using widgets other than the default SgIconGadget(3x) widget

  • creating additional data members and member functions to store application-specific node information

You have a great deal of flexibility in deciding how to extend the VkNode class. The important restriction that you must keep in mind is that the VkGraph object of which a VkNode object is a member can create and destroy node widgets as needed. Therefore, in your subclass function definitions you cannot assume that your node's widget exists.

The VkGraph object calls a protected member function, VkNode::build(), whenever it needs to create a node's widget. If you want to use the additional features of the default SgIconGadget widget or if you want to use a different widget in you subclass, you must override build():

virtual void build(Widget parent)

If you simply want to use the additional features of the default SgIconGadget widget, you can call VkNode::build() from within your subclass's build() function to create the SgIconGadget widget and set the widget's label. Then, you can perform any additional operations you want. (Consult the SgIconGadget(3x) reference page for more information on using this widget.) For example:

void MyNode::build(Widget parent)
{
    VkNode::build(parent);
    // Additional setup...
}

If you want to use your own widget or widget hierarchy, create the widget(s) using parent as the parent widget, and assign the widget or root of a widget hierarchy to the _baseWidget data member. After creating the _baseWidget, call installDestroyHandler(), as described in “Handling Component Widget Destruction”.

From within a VkNode subclass you can also access the _label data member:

char *_label

_label contains the node's label string as set by the VkNode constructor.

ViewKit Graph Class

This section describes how to build and manipulate graphs using the VkGraph class. Minimally, you must perform the following actions to build and display a ViewKit 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.

VkGraph 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 “Displaying Duplicate Arcs”, by clicking the graph's Multiple Arcs button the user can force the graph to display an arc for each connection you defined. To turn off the multiple-arc display, the user can click the Multiple Arcs button again.

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. 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(3x) reference 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 that 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(). Suppose that, instead of the two separate calls, you execute this:

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 Which Nodes to Display

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().

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):

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

    If you provide the node's name, this function returns a pointer to the node.

  • 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:

    virtual void expandNode(VkNode *node)
    

    See “Laying Out the Graph” for more information on doSubtreeLayout().

  • displayWithAllChildren() displays a node and all of its descendent nodes:

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

    If you provide the node's name, this function returns a pointer to the node.

  • 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:

    virtual void expandSubgraph(VkNode *node)
    

    See “Laying Out the Graph” for more information on doSubtreeLayout().

  • hideAllChildren() hides all of a node's descendent nodes:

    virtual void hideAllChildren(VkNode *node)
    

    Note that this function does not hide node itself.

  • 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):

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

    If you provide the node's name, this function returns a pointer to the node.

  • displayWithAllParents() displays a node and all of its ancestor nodes:

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

    If you provide the node's name, this function returns a pointer to the node.

  • hideParents() hides all of a node's immediate parent nodes (not all ancestor nodes):

    virtual void hideParents(VkNode *node)
    

    Note that this function does not hide node itself.

  • displayParentsAndChildren() displays a node and all of its immediate parent and child nodes (not all ancestor and descendent nodes):

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

    If you provide the node's name, this function returns a pointer to the node. Note that this function does display node itself.

  • hideParentsAndChildren() hides all of a node's immediate parent and child nodes (not all ancestor and descendent nodes):

    virtual void hideParentsAndChildren(VkNode *node)
    

    Note that this function does not hide node itself.

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 as follows:

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, if you want 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 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 has 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. All you need to do is 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 “Graph Overview”, by clicking 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:

    virtual void setZoomOption(int index)
    

    Pass to this function the integer index corresponding to the index in the Zoom Menu of the magnification that you want. (“Zooming” describes the Zoom Menu and its default values.)

  • VkGraph::sortAll() sorts all nodes associated with the graph by calling VkNode::sortChildren() on all nodes:

    void sortAll()
    

    “Node Utility Functions” describes VkNode::sortChildren().

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

    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:

    void makeNodeVisible(VkNode *node)
    

    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.

  • VkGraph::saveToFile() prompts the users for a filename 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:

    void setSize(int entries)
    

    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().

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:

    Widget graphWidget()   
    

    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.

  • VkGraph::workArea() returns the XmForm widget at the bottom of the VkGraph component, which contains the graph controls:

    Widget workArea()   
    

    You can use this area to add additional controls.

  • 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.

ViewKit Callbacks Associated With VkGraph

The VkGraph class declares two ViewKit member function callbacks.

VkGraph 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(3x) 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 Which Nodes to Display”). VkGraph activates the arcDestroyedCallback callback once for every arc destroyed, including as call data the SgArc widget destroyed. See the SgArc(3x) 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 as follows:

*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 that you should require for displaying and manipulating graphs. In most other cases, you can obtain a pointer to the SgGraph widget using the graphWidget() access function and operate directly on the widget.

However, sometimes you might want to perform additional processing when certain actions occur. In a case like this, you can create a subclass of VkGraph. VkGraph provides a number of virtual “hook” functions that you can override and implement additional functionality:

  • VkGraph::buildCmdPanel() builds the command panel at the bottom of the graph:

    virtual void buildCmdPanel(Widget parent)
    

    You can override this function to create your own custom command panel for your graph.

  • VkGraph::buildZoomMenu() builds the Zoom menu, the Zoom Out button, and the Zoom In button as part of the command panel:

    virtual void buildZoomMenu(Widget parent)
    

  • VkGraph::addMenuItems() allows you to modify the Node popup menu described in “Hiding and Displaying Nodes”:

    virtual void addMenuItems(VkPopupMenu *menu)
    

    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.

  • VkGraph::popupMenu() posts the Node popup menu (described in “Hiding and Displaying Nodes”):

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

    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 does the following:

    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.

    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().

  • VkGraph::addDesktopMenuItems() allows you to modify the Selected Nodes popup menu (described in “Edit Mode Operations”):

    virtual void addDesktopMenuItems(VkPopupMenu *menu)
    

    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.

  • VkGraph::twinsVisibleHook() is called when the user toggles the Multiple Arcs or “twins” button:

    virtual void twinsVisibleHook(Boolean state)
    

    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.