As simple as it is, the editor type application that we have built can be perfectly used to create and edit "flat" graphs. Now, if we want to use it to also work with grouped graphs, where we have nodes that can contain other nodes, then we will need to add grouping support.
Grouping support means the ability to put nodes into other, so-called group nodes, which allows to create a hierarchical inclusion tree of (sub)graphs. This is also referred to as a graph hierarchy, or a grouped graph structure.
Group nodes can have one of two states: they are either open or closed:
A closed group node is also called "folder node."
Figure 1.1, “Group node and folder node side by side” shows the SimpleGroupingEditor1 demo application with both group node and folder node in the view.
Our current editor application can only handle "flat" graphs, i.e., graphs that do not contain group nodes or folder nodes. However, we will change that right away.
For that, we need to create a HierarchyManager
for our graph as shown in Example 1.6, “SimpleGroupingEditor1 constructor (where the HierarchyManager is created)”.
Naturally, the HierarchyManager is only the beginning, but a very important one:
it maintains the model that is behind a graph hierarchy, and it is responsible for
quite everything related to that model.
Example 1.5. Creating the HierarchyManager to handle the graph hierarchy
// Instantiate a HierarchyManager for the graph. hierarchy = new HierarchyManager(graph);
In the constructor of SimpleGroupingEditor1, where we actually create the HierarchyManager, we immediately hand it over in the call to the new configureDefaultGroupNodeRealizers method that we use to configure the visual representation of our group nodes.
Example 1.6. SimpleGroupingEditor1 constructor (where the HierarchyManager is created)
public SimpleGroupingEditor1(Dimension size, String title) {
view = createGraph2DView();
graph = view.getGraph2D();
// Instantiate a HierarchyManager for the graph.
hierarchy = new HierarchyManager(graph);
frame = createApplicationFrame(size, title, view);
configureDefaultRealizers(graph);
// Configure group node type and visual representation.
configureDefaultGroupNodeRealizers(hierarchy);
}
The configureDefaultGroupNodeRealizers method is shown in full length
in Example 1.7, “configureDefaultGroupNodeRealizers method in SimpleGroupingEditor1”.
It uses the given HierarchyManager to get the DefaultHierarchyGraphFactory
which is responsible for creating both group nodes and folder nodes in a graph hierarchy.
Similar to the default realizer mechanism of the Graph2D (which we have learned
about in the Creating a Simple Graph Viewer
trail), this factory maintains default node realizers for both group nodes and folder
nodes.
In the configureDefaultGroupNodeRealizers method, we set up and create
a GenericGroupNodeRealizer
to render these node types.
GenericGroupNodeRealizer is a subclass of GenericNodeRealizer, and as such uses
a so-called configuration that defines all aspects of the node realizer's visual
representation and behavior.
For our GenericGroupNodeRealizer we will be happy with the default configuration
which we can retrieve using the static createDefaultConfigurationMap()
method.
Basically, this gives us already a group node representation:
As our only customization to this, we set other fill color and line color for the group node. Finally, at the end of the method, we assign copies of our node realizer as the default realizer for group node and folder node representation. Note that GenericGroupNodeRealizer is used for both representations: group node as well as folder node.
With the default group node/folder node realizers set in the graph factory, all group nodes and folder nodes that are created subsequently will have the specified rendering and behavior.
Example 1.7. configureDefaultGroupNodeRealizers method in SimpleGroupingEditor1
protected void configureDefaultGroupNodeRealizers(HierarchyManager hierarchy) {
// Get the graph factory that maintains the default group node/folder node realizer.
DefaultHierarchyGraphFactory hgf = (DefaultHierarchyGraphFactory)hierarchy.getGraphFactory();
GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory();
// Get the default configuration for a GenericGroupNodeRealizer and add it to
// the factory that holds all configurations for generic node realizers.
factory.addConfiguration("DefaultGroupNodeConfig",
GenericGroupNodeRealizer.createDefaultConfigurationMap());
GenericGroupNodeRealizer gnr = new GenericGroupNodeRealizer();
// Use the default configuration.
gnr.setConfiguration("DefaultGroupNodeConfig");
gnr.setFillColor(new Color(202, 236, 255, 132));
gnr.setLineColor(new Color(102, 102, 153, 255));
// Set the default group node/folder node realizer.
hgf.setDefaultGroupNodeRealizer(gnr.createCopy());
hgf.setDefaultFolderNodeRealizer(gnr.createCopy());
}
One of the nice things about GenericGroupNodeRealizer is that it brings convenient support for collapsing/expanding a group node when clicking on the state icon in its "title bar." This support is provided by a MouseInputEditor implementation which is part of the configuration. The MouseInputEditor implementation is used, if node-specific mouse interaction in the view is enabled, which we can easily achieve by the following line:
Example 1.8. Enabling GenericGroupNodeRealizer's support for collapsing/expanding group nodes
// To enable convenient switching between group node and folder node presentation. editMode.getMouseInputMode().setNodeSearchingEnabled(true);
In the bigger context, this line is part of the EditMode setup when creating the application's view:
Example 1.9. createGraph2DView method with updated EditMode setup to enable node-specific mouse interaction
private Graph2DView createGraph2DView() {
Graph2DView view = new Graph2DView();
// Add a mouse wheel listener to zoom in and out of the view.
new Graph2DViewMouseWheelZoomListener().addToCanvas(view);
// Add the central view mode for an editor type application.
editMode = new EditMode();
// To enable convenient switching between group node and folder node presentation.
editMode.getMouseInputMode().setNodeSearchingEnabled(true);
view.addViewMode(editMode);
// "Install" keyboard support and many predefined hierarchy-related actions.
new Graph2DViewActions(view).install();
return view;
}
In the method where we create our application's view, we also use the Graph2DViewActions class. In the previous lesson we have added the corresponding line to enable keyboard support in the view. However, the Graph2DViewActions class also adds quite a number of predefined Action implementations to the ActionMap of the Graph2DView to handle hierarchy-related user interaction.
In the tradition of our simple applications, we will make two of these actions available in the toolbar: the Group Selection action and its counterpart, the Ungroup Selection action. The code that does this is shown in Example 1.10, “createToolBar method of SimpleGroupingEditor1”. We retrieve the predefined actions from the ActionMap of the view's actual canvas component and add buttons to the toolbar that use these actions:
Example 1.10. createToolBar method of SimpleGroupingEditor1
protected JToolBar createToolBar() {
JToolBar toolbar = new JToolBar();
toolbar.add(new FitContent(getView()));
toolbar.add(new Zoom(getView(), 1.25));
toolbar.add(new Zoom(getView(), 0.8));
toolbar.addSeparator();
// Get the action map from the Graph2DView's canvas component.
ActionMap aMap = view.getCanvasComponent().getActionMap();
// Add some of the hierarchy-related actions as toolbar buttons.
toolbar.add(aMap.get(Graph2DViewActions.GROUP_SELECTION)).setText("Group Selection");
toolbar.add(aMap.get(Graph2DViewActions.UNGROUP_SELECTION)).setText("Ungroup Selection");
return toolbar;
}
Now that we have the buttons and all logic properly in place, let's run the application and create some group nodes at last!
We can easily do so by first creating some "normal" nodes, selecting them, and then using the "Group Selection" toolbar button. Immediately, the selected nodes will be contained in a newly created group node. Good. Let's try the "Ungroup Selection" toolbar button also. Umm, nothing happens. What's wrong?
With the Ungroup Selection action, "taking selected nodes out" (of their directly containing group node) as described above, does not mean that they are moved in any way. They are logically taken out of the group node, i.e., they are no longer "children" of the group node in the graph hierarchy. At first, this might be irritating since there doesn't seem to change anything when pressing the toolbar button for that action. However, when you try moving around selected nodes, you will quickly see the difference between nodes being grouped vs. nodes not being grouped.
When you move grouped nodes, i.e., nodes that have a group node as their parent in the graph hierarchy, the borders of any containing group node(s) automatically adjust as you move. In other words, you won't be able to move the nodes out of the group node(s). In contrast, with ungrouped nodes you will not see anything like that, they can be freely moved.
OK, that out of the way, what else is new when we have a grouped graph with group nodes and that all? And where are the folder nodes, you ask. Good questions.
"Folder node" is just another name for a closed group node. Now, closing a group node is easily done by clicking on the state icon, the little minus sign in the top-left corner of the group node. Voilà, we have a folder node. By clicking on the state icon again (which has turned into a plus sign), you will get the group node back. Note how the contained nodes of the group node disappear from the view when you close the group node, and re-appear when you open the folder node.
The other, less obvious mouse gesture that is new in a grouped graph is related to moving diagram elements into and out of group nodes.
Upon creation, the label of a group node shows the following text: "Group" But where does this come from? We surely haven't specified that anywhere! Is this part of the default group node magic, or maybe provided by some logic in the GenericGroupNodeRealizer's configuration?
The answer is that the label text is set when the group node is being created by
the Group Selection action of class Graph2DViewActions.
It is the default text for a group node, and if we would need to, we could use the
createGroupName
callback method which is part of the Group Selection action to conveniently customize
it.
You will find related information in the yFiles for Java Developer's Guide:
In the yFiles for Java API:
In the yFiles for Java source code demos:
|
Copyright ©2008-2009, yWorks GmbH. All rights reserved. |