2. The JGraph Component

The implementation of JGraph is based on the design of the JTree [bib-JTree] class, which is Swing's component for displaying trees. Rather than explaining JGraph from scratch, this description focuses on the differences between the two classes. Swing features such as serialization, datatransfer, and the Swing MVC pattern are explained in the appendix.

2.1. Foundation

JGraph is a design extension of JTree. In the following, these design changes are briefly outlined, pointing to the classes and methods that were adapted or introduced. The design modifications are grouped into:

  • Differences between trees and graphs

  • JGraph requirements (features)

The following important differences between trees and graphs lay the foundation of the JGraph component:

2.1.1. Overlapping Cells

In a tree, the position and visibility of a cell depends on the expansion state. In a graph, instead, the position and size of a cell is user-defined, possibly overlapping other cells. Consequently, a way to enumerate the cells that intersect the same position, and to change the order in which they are returned is provided.

2.1.2. Multiple Cell Types

A tree consists of nodes, whereas a graph possibly consists of multiple cell types. Consequently, JGraph provides a view for each cell, which specifies the renderer, editor and cell handle. Additionally, a graph view is provided, which has its own internal representation of the graph. The idea of views has been adopted from Swing's text components [bib-View].

2.1.3. Separate Geometry

The mathematical definition of a graph does not include the geometric pattern or layout. Consequently this pattern is not stored in the model, it is stored in the view. The view provides a notification mechanism, and undo-support, which allows changing the geometric pattern independently of the model, and of other views. Since Swing's undo manager is not suitable for this setup, JGraph provides an extension of this Swing's default undo mechanism in the form of the GraphUndoManager class.

2.2. Features

The changes to meet the requirements, or features, may be grouped into:

Inheritance

JTree's implementation of pluggable look and feel support and serialization is used without changes.

Modification

The existing implementation of in-place editing and rendering was modified to work with views, and history.

Extension

JGraph's marquee selection and stepping-into groups extend JTree's selection model.

Enhancement

JGraph is enhanced with datatransfer, attributes and history, which are Swing standards not used in JTree. (A special history must be used in the context of separate geometric patterns.)

Implementation

The layering, grouping, handles, cloning, zoom, ports and grid are new features, which are standards-compliant with respect to architecture, and coding conventions.

2.2.1. Inheritance

The pluggable look and feel and serialization are used without modifications. As in the case of JTree, the UI-delegate implements the current look and feel, and serialization is based on the Serializable interface, and XMLEncoder and XMLDecoder classes for long-term serialization.

2.2.2. Modification

The multiple cell types property affects the rendering and the in-place editing of JGraph. In contrast to JTree, where the renderer and editor for the single node type is referenced from the tree object, the editors and renderers in JGraph are referenced from the cell view, to avoid a look-up of the renderer via the cell's type. The renderer is statically referenced so it can be shared among all instances of a class. Additionally, the cell view specifies a handle that allows extended editing. The idea of handles closely follows the design used for in-place editing.

2.2.3. Extension

Like the JTree selection model, JGraph's selection model allows single and multiple cell selection. JGraph's selection model additionally allows to step-into groups. Marquee selection is also provided using a marquee handler, which is based on the design of Swing's transfer handler.

2.2.4. Enhancement

JGraph allows transferring the cells of a model, together with a description of their group and graph structure, and their geometric pattern either by using drag-and-drop, or via the clipboard.

Based on an idea of Swing's text components, JGraph provides maps to describe and change the attributes of a cell. These maps encapsulate the state of the cell, and may be accessed in a type-safe way using the GraphConstants class.

JGraph provides command history, or history, which is the ability to undo or redo changes. The design follows the design of Swing's text components; however, a special undo manager must be used in the context of separate geometric patterns.

2.2.5. Implementation

There are two groups of features in the implementation chapter:

  • Features that only affect the JGraph class

  • Features that require new classes

The first group consists of the cloning, zoom, and grid, which are implemented by methods in the JGraph class. The rest of the framework does not offer classes or methods to implement these features, however, it is feature-aware, which means it relies on the respective methods of the JGraph class.

The second group consists of the layering, handles, grouping, and ports, which are not used elsewhere in Swing, and require new classes and methods. Special care has been taken to base these features on existing Swing functionalities, and make them analogous with regard to design and implementation. For example, the handles feature closely follows the design and implementation of Swing's in-place editing, so that it is easy for the programmer to adopt this new feature based on his or her understanding of in-place editing.

Because these features are new, some of them are briefly defined below:

Layering

Since cells may overlap, the order in which they are returned is significant. This order is referred to as Layering, and may be changed using the toBack and toFront method of the GraphLayoutCache object. The layering is part of the view, and is explained in the view part of this document.

Handles

Handles are, like editors, objects that are used to change the appearance of a cell. In contrast to in-place editing, which uses a text component to change the value of a cell, handles use other means to provide the user with a visual feedback of how the graph will look after the successful execution of the change (live-preview). Handles and in-place editing are explained in the control part, because the UI-delegate provides this functionality.

Grouping and Ports

Ports and grouping are related because ports are implemented on top of the group structure in the graph model. The grouping is therefore explained in the model part of this document.

2.3. JGraph MVC

Figure 3. JGraph MVC

JGraph MVC

JGraph extends JComponent, and has a reference to its GraphUI. JGraph has a reference to a GraphModel and a GraphLayoutCache, and instantiates BasicGraphUI, which extends GraphUI, which in turn extends ComponentUI.

The basic structure of the component, namely the Swing MVC architecture, is inherited from JTree. However, JGraph has an additional reference to a graph view, which is not typically used in Swing MVC. The graph view is analogous to the root view in Swing's text components, but it is not referenced by the UI-delegate. Instead, it is referenced by the JGraph object such that it preserves the state when the look-and-feel is changed. (The appendix provides an in-depth discussion of MVC, Swing MVC, and how it is applied to JTree and JGraph.)

2.4. Startup

When working with attributes (see Section 2.5, “Attributes”), the startup-sequence is significant. The fact that the default model does not store attributes must be taken into account when inserting cells, because the attributes of such cells are passed to the attached views. If no views are attached, the attributes are ignored!

In the case where a view is added later, the view uses default values for the cell's positions and sizes, resulting in the fact that each cell is located at the same point, and has the same size.

Therefore, when creating a graph with a custom model, first the JGraph instance should be created, using the model as an argument, and then, cells should be inserted into the model (not vice versa). By constructing the JGraph instance, a view is automatically registered with the model.

Figure 4. JGraph's default constructor

JGraph's default constructor

The same holds for setting the model on a JGraph object, in which case the view is notified, and holds a reference to the new model. Anyway, because the model does not store the attributes, the view will use default values as in the case where it is registered with the model after an insert call.

The above does not hold if the model's isAttributeStore returns true, in which case all attributes are stored in the model instead of the view, making the timing issues irrelevant.

2.5. Attributes

JGraph's attributes are only conceptually based on those of Swing, some dynamic aspects, and the class to access these attributes are different from Swing, and must therefore be explained.

Figure 5. Symbol used for attributes

Symbol used for attributes

Attributes are implemented using maps, from keys to values, and may be accessed by use of the GraphConstants class.

2.5.1. GraphConstants

The GraphConstants class is used to create maps of attributes, and to access the values in these maps in a type-safe way. Aside from the creation of, and the access to maps, the class also provides a method to clone maps, and a method to apply a change of more than one value on a target map.

Figure 6. Changing attributes with the GraphConstants class

Changing attributes with the GraphConstants class

The applyMap method combines common entries from the target map and the change map by overriding the target values. Entries that are only present in the change map are inserted into the target map, and entries that are only present in the target map are left unchanged.

To remove entries from the target map, the setRemoveAttributes method is used, providing the keys that should be removed as an argument. The keys are stored as an entry in the change map, and handled by the applyMap method. If the change map replaces the target map completely, then the setRemoveAll method must be used on the change map to indicate that all keys of the target map should be removed.

Attributes may be used in the model and in the view. In both cases, the attribute maps are created and accessed by use of the GraphConstants class. The relation between a cell's attributes and its corresponding view's attributes is as follows:

Figure 7. Relation between a cell's and its view's attributes

Relation between a cell's and its view's attributes

A cell's attributes have precedence over its view's attributes, such that the cell can override the view's values for a specific key with its own value. This means, if a view specifies a value for a key that is also specified by the cell, then the cell's value is used instead of the view's value. (Two attributes are equal if the equals method on their respective keys returns true.)

In other words, the blue attributes have precedence over the yellow ones, and if a blue attribute is not present in the yellow map, then it will be inserted. Yellow entries that do not exist as blue entries are left unchanged. (Since this mechanism is based on the applyMap method, the behavior is exactly the same.)

The GraphConstants class does not distinguish between the attributes for cells and views, because they are based on the same underlying structures. However, in contrast to cells, which accept all attributes, the view performs an additional test on each key. The view's renderer is used to determine if the key is supported, and if not, the entry is removed from the corresponding view's attribute map in order to reduce redundancy. (Both setAttributes methods, for cells and views, are based on the applyMap method.)

Figure 8. Implicit use of the GraphConstants class

Implicit use of the GraphConstants class

In JGraph's default implementation, the UI changes the view's attributes upon interactive changes. By overriding the model's isAttributeStore method, the model can gain control. If the method returns true, then the cell's attributes will be changed instead of the view's, resulting in an immediate update of all attached views. In the default case, only the local view is updated (unless the change constitutes a change to the model).

This is because all views are updated upon a change of the model by the model's notification mechanism, and the cell's attributes are used in all views. An exception is the value attribute, which is in sync with a cell's user object. The value attribute is stored in the cell regardless of the model's isAttributeStore method.

2.5.2. The Value Attribute

Figure 9. Synchronization upon change of a cell's user object

Synchronization upon change of a cell's user object

The setUserObject method of the DefaultMutableTreeNode class is overridden by the DefaultGraphCell class such that the passed-in user object is stored under the value-key in the cell's attribute map. Vice versa, the setAttributes method overwrites the previous user object if a new object for the value-key is specified. Thus, the value attribute points to the user object and vice versa.

The value attribute is used in the context of history and in-place editing. By introducing the value attribute, the complete state of the cell may be represented by its attributes; the user object does not require special handling. A change to the state (and equally to the user object) may be undone using the applyMap method, providing the previous and current states as arguments.

By default, in-place editing replaces the user object with the new String, which is not always desirable. Therefore, the user object may implement the ValueChangeHandler interface, which changes this default behavior.

2.5.3. ValueChangeHandler Interface

The ValueChangeHandler interface, which is an inner interface of the DefaultGraphCell class, may be used to prevent the user object from being overwritten upon in-place editing. If a user object implements this interface, then the DefaultGraphCell informs the user object of a change, the latter of which is responsible for storing the new, and returning the old value. The user object reflects the change through its toString method. (JGraph's convertValuetoString method is used to convert a cell to a String.)

2.5.4. Attributes and Instance Fields

Should new properties be implemented as attributes or as instance fields? In the first case, the cloning of the cell and the undo mechanism are already in place, but the attribute must be accessed through a hash table. In the second case, the cloning of the cell requires special handling, but the variable may be accessed as an instance field, which might be required for inheritance. The combination of the two leads to an increase of redundancy, and complexity. (An example is the value attribute from above.)

Basically, attributes should only be used for rendering, even if there are no technical restrictions for storing custom attributes in a cell. Since only supported attributes are propagated to the view, such custom attributes add no redundancy with respect to the view's attributes. (The value attribute is an exception that is an instance field, and is supported by all renderers.)

Instead of extending the DefaultGraphCell class or using the attributes to store additional information, the class, which inherits from JTree's DefaultMutableTreeNode also provides another mechanism to store user-defined data, namely through the user object. The user object is of type Object, and therefore provides a way to associate any object with a GraphCell.

The user object may be in an arbitrary class hierarchy, for example extending the Hashtable class. Since the user object's toString method is used to provide the label, this method should probably be overridden.

2.6. Cloning

A new feature in JGraph is the possibility to clone cells automatically. This feature is built into the default implementation's clipboard and cell handles, and is based on the clone method of the Object class, and on JGraph's cloneCells method. The feature may be disabled using the setCloneable method on the JGraph object. (By disabling this feature, a Control-Drag will be interpreted as a normal move.)

The process of cloning is split into a local and a global phase: In the local phase, each cell is cloned using its clone method, returning an object that does not reference other cells. The cell and its clone are stored in a hash table, using the cell as a key and the clone as a value.

In the global phase, all cell references from a clone's original cell are replaced by references to the corresponding clones (using the before mentioned hash table). Therefore, in the process of cloning cells, first all cells are cloned using the clone method, and then all cell references are consistently replaced by references to the respective clone.

2.7. Zoom and Grid

JGraph uses the Graphics2D class to implement its zoom. The framework is feature-aware, which means that it relies on the methods to scale a point or rectangle to screen or to model coordinates, which in turn are provided by the JGraph object. This way, the client code is independent of the actual zoom factor.

Because JGraph's zoom is implemented on top of the Graphics2D class, the painting on the graphics object uses non-scaled coordinates (the actual scaling is done by the graphics object itself). For this reason, JGraph always returns and expects non-scaled coordinates.

For example, when implementing a MouseListener to respond to mouse clicks, the event's point will have to be downscaled to model coordinates using the fromScreen method in order to find the correct cell through the getFirstCellForLocation method.

On the other hand, the original point is typically used in the component's context, for example to pop-up a menu under the mouse pointer. Make sure to clone the point that will be changed, because fromScreenmodifies the argument in-place, without creating a clone of the object. To scale from the model to screen, for example to find the position of a vertex on the component, the toScreen method is used.

To support the grid, each point that is used in the graph must be applied to the grid using the snap method. As in the case of zooming, the snap method changes the argument in-place instead of cloning the point before changing it. This is because instantiation in Java is expensive, and it is not always required that the point is being cloned before it is changed. Thus, the cloning of the argument is left to the client code.

JGraph provides two additional bound properties that belong to the grid: one to make the grid visible, and the other to enable the grid. Thus, the grid can be made visible, but still be disabled, or it can be enabled and not visible.