The predefined node realizer and edge realizer implementations of package
y.view already
provide advanced out-of-the-box functionality (please see the sections on
node realizers and
edge realizers, respectively).
To modify specific aspects of either visual representation or behavior of a
realizer, an available realizer implementation can be used as the starting
point for customization.
Both node realizer and edge realizer implementations support two different customization schemes:
The latter customization scheme is applied by classes
GenericNodeRealizer and
GenericEdgeRealizer
, while the former is
applied by all remaining node realizer and edge realizer implementations.
Creating a custom variant of either node realizer or edge realizer is easily accomplished by extending one of:
(for a node realizer specialization) or one of:
(for an edge realizer specialization).
A specialized node realizer, for example, defines a new
paintNode
implementation, maybe also a new
calcUnionRect
implementation.
In case there is a new shape created in a ShapeNodeRealizer descendant, the
methods
setSize
and
setLocation
eventually would have to be changed, too.
Generally, to have a customized realizer behave properly, its responsibilities have to be retained. These include:
Replication means the mechanism that is utilized when a new realizer instance for a graph element in a Graph2D object is created. The default realizer types registered with the Graph2D object serve as factories for realizer creation, i.e., they create copies of themselves via the methods shown below:
NodeRealizer createCopy() |
|
Description | NodeRealizer replication methods |
EdgeRealizer createCopy() |
|
Description | EdgeRealizer replication methods |
Proper replication behavior for a realizer implementation can easily be achieved using, e.g., a copy constructor as demonstrated in Example 6.22, “Sample replication code”.
Example 6.22. Sample replication code
public class MyCustomNodeRealizer extends ShapeNodeRealizer { // Custom attribute. private int myFancyAttribute; // Copy constructor. public MyCustomNodeRealizer(NodeRealizer nr) { super(nr); // If the given node realizer is of this type, then apply copy semantics. if (nr instanceof MyCustomNodeRealizer) { MyCustomNodeRealizer mcnr = (MyCustomNodeRealizer)nr; // Copy the values of custom attributes. myFancyAttribute = mcnr.myFancyAttribute; } } public NodeRealizer createCopy(NodeRealizer nr) { // Return a new node realizer that has specific type. return new MyCustomNodeRealizer(nr); } }
The bounds calculation of a realizer plays an important role for the graphical user interface. Among other things, it is necessary for:
Hit-testing a node, for example, also relates to determining the intersection
points of all its connecting edges.
The methods from class EdgeRealizer that return an edge's intersection points
in turn invoke the
contains
method of both its source and target node's NodeRealizer.
The correct edge clipping calculation thus relies on the proper implementation
of the node realizer's hit-testing method.
Example 6.23, “Customized ShapeNodeRealizer” presents a customized node realizer that
draws an additional shadow underneath the used shape.
Since the shadow leads to an increase in both width and height of the resulting
graphic, the bounds calculation as performed by method
calcUnionRect
is modified to accommodate that fact.
Also, the node's resize knobs (a.k.a. "hot spots") are relocated, and their
hit-testing logic is changed, too.
Note, however, that this implementation does not change the general hit-testing logic from its superclass, i.e., the shadow is not relevant for any mouse events.
Example 6.23. Customized ShapeNodeRealizer
public class DropShadowedShapeNodeRealizer extends ShapeNodeRealizer { public void paintNode(Graphics2D gfx) { gfx.translate(4, 4); gfx.setColor(Color.darkGray); gfx.fill(shape); gfx.translate(-4, -4); // Invoke the superclass's paint method. super.paintNode(gfx); } // Enlarge the realizer's dimensions to include the shadow. public void calcUnionRect(Rectangle2D rect) { super.calcUnionRect(rect); rect.add(x + width + 4, y + height + 4); } // Paint the resize knobs outside the shadowed area. public void paintHotSpots(Graphics2D gfx) { width += 4; height += 4; super.paintHotSpots(gfx); width -= 4; height -= 4; } public byte hotSpotHit(double hx, double hy) { width += 4; height += 4; byte hit = super.hotSpotHit(hx, hy); width -= 4; height -= 4; return hit; } }
Realizer (de)serialization is important when the respective class should be written to/read from a file in Y Graph Format (YGF). The following methods provide the (de)serialization support:
void read(ObjectInputStream in) |
|
Description | Realizer methods for YGF (de)serialization |
Links to tutorial demo code that shows customized realizer implementations and examples for proper realizer (de)serialization can be found in the section called “Tutorial Demo Code”.
Classes GenericNodeRealizer and
GenericEdgeRealizer
each define an
appropriate set of static inner interfaces which allow fine-grained control
over all aspects of a node's or an edge's visual representation and behavior.
Custom logic for a specialized realizer can be conveniently expressed by
providing implementations for only a selection or for all of these interfaces.
Together, a set of actual implementations for these interfaces forms a so-called "configuration" that defines the look and feel of one node type or edge type, respectively. Different configurations can be used to define a variety of types. Within a configuration, different settings for GenericNodeRealizer instances can be easily achieved using so-called style properties.
Management of realizer configurations is done by static inner classes
GenericNodeRealizer.Factory and
GenericEdgeRealizer.Factory
.
A reference to either of these classes can be get using the class methods
listed below:
static GenericNodeRealizer.Factory getFactory() |
|
Description | Getter method of class GenericNodeRealizer |
static GenericEdgeRealizer.Factory getFactory() |
|
Description | Getter method of class GenericEdgeRealizer |
In contrast to realizer customization using subclassing and overriding, both GenericNodeRealizer and GenericEdgeRealizer already support proper realizer replication and YGF (de)serialization. In addition, support for (de)serialization from/to GML and XGML file formats is also supported.
To handle user-defined data, the generic realizer classes define static inner
interfaces
GenericNodeRealizer.UserDataHandler and
GenericEdgeRealizer.UserDataHandler
.
These interfaces are used to delegate all work that relates to copying or
(de)serializing of all user-defined data of a realizer object.
Class SimpleUserDataHandler
can be used
as a default implementation for both these interfaces.
It is capable of dealing with arbitrary user-defined data objects that
implement interfaces java.lang.Cloneable (for copying)
and java.io.Serializable (for serialization and
deserialization).
Static inner interfaces
GenericNodeRealizer.GenericMouseInputEditorProvider
and
GenericEdgeRealizer.GenericMouseInputEditorProvider
serve as entry points for custom MouseInputEditor implementations that can be
associated with configurations for either kind of generic realizer.
See also the section called “Interface MouseInputEditor”.
GenericNodeRealizer offers all methods from its superclass NodeRealizer, the actual work, though, is delegated to a number of static inner interfaces, namely:
Abstract classes
AbstractCustomNodePainter and
AbstractCustomHotSpotPainter
serve as
convenient base implementations for interfaces GenericNodeRealizer.Painter and
GenericNodeRealizer.HotSpotPainter/GenericNodeRealizer.HotSpotHitTest,
respectively.
A variety of predefined implementations for interface GenericNodeRealizer.Painter provides support for using, e.g., geometric shapes, images, GeneralPath objects, or Swing UI components for the visual representation of nodes.
Example 6.24, “Creating a GenericNodeRealizer configuration” shows how to create a configuration for a node type that renders an ellipse.
Example 6.24. Creating a GenericNodeRealizer configuration
public String createEllipseConfiguration(Map implementationsMap) { // Create and register an implementation for interfaces // GenericNodeRealizer.Painter and GenericNodeRealizer.ContainsTest. // (This implementation renders and hit-tests an ellipse.) MyCustomPainter painter = new MyCustomPainter(new Ellipse2D.Double()); implementationsMap.put(GenericNodeRealizer.Painter.class, painter); implementationsMap.put(GenericNodeRealizer.ContainsTest.class, painter); // Create and register an implementation for interfaces // GenericNodeRealizer.HotSpotPainter and GenericNodeRealizer.HotSpotHitTest. // ('165' is the decimal value for a bit mask that yields hot spots at the // corners of the bounding box of the ellipse.) MyCustomHotSpotPainter chsp = new MyCustomHotSpotPainter(165, new Ellipse2D.Double(), null); implementationsMap.put(GenericNodeRealizer.HotSpotPainter.class, chsp); implementationsMap.put(GenericNodeRealizer.HotSpotHitTest.class, chsp); return "Ellipse"; }
If a configuration lacks implementations for any of the aforementioned interfaces, then appropriate default implementations from class NodeRealizer are invoked.
Example 6.25, “Adding a GenericNodeRealizer configuration” demonstrates the steps to add a new node realizer configuration to the factory that manages the available configurations. Note that the factory can be conveniently used to create a map holding the default configuration for GenericNodeRealizer.
Example 6.25. Adding a GenericNodeRealizer configuration
public void addConfiguration() { // Get the factory to register different configurations/styles. GenericNodeRealizer.Factory factory = GenericNodeRealizer.getFactory(); // Retrieve a map that holds the default GenericNodeRealizer configuration. // The implementations contained therein can be replaced one by one in order // to create custom configurations... Map implementationsMap = factory.createDefaultConfigurationMap(); // Create and store a configuration in the map. String desc = createEllipseConfiguration(implementationsMap); // Add the configuration to the factory. factory.addConfiguration(desc, implementationsMap); }
Finally, Example 6.26, “Using a specific GenericNodeRealizer configuration” shows how a specific configuration is used with GenericNodeRealizer.
Example 6.26. Using a specific GenericNodeRealizer configuration
public void setDefaultNodeRealizerForView(Graph2DView view) { // Add a configuration to the pool of GenericNodeRealizer configurations. addConfiguration(); // Create a node realizer. GenericNodeRealizer gnr = new GenericNodeRealizer(); // Initialize this node realizer to apply the configuration just added... gnr.setConfiguration("Ellipse"); // Set the node realizer as the default node realizer for the given view. view.getGraph2D().setDefaultNodeRealizer(gnr); }
If GenericNodeRealizer is used with an empty configuration, then appropriate methods from class NodeRealizer are invoked, which results in a simple rectangular node representation.
Instance specific states can be stored as so-called style properties. Style properties allow to apply different settings to instances of the same GenericNodeRealizer configuration. Arbitrary objects can be stored mapped to an arbitrary String as key.
Object getStyleProperty(String propertyKey) |
|
Description | Getters and setters for style properties. |
Custom implementations of GenericNodeRealizer's inner interfaces can access these properties. A custom GenericNodeRealizer.Painter is shown in Example 6.27, “Using style properties from within a custom Painter implementation”. The full source code can be viewed in the GenericNodeRealizerDemo.java.
Example 6.27. Using style properties from within a custom Painter implementation
public class FloatingPainter extends AbstractCustomNodePainter { protected void paintNode(NodeRealizer context, Graphics2D graphics, boolean sloppy) { // set a default in case there is no property double inset = 4; // retrieve the property as Object from the context Object o = ((GenericNodeRealizer)context) .getStyleProperty("FloatingPainter.Inset"); // convert the Object to the actual type // here: double if (o instanceof Number) { inset = ((Number)o).doubleValue(); } // draw the node using the insets // provided by the "inset" value } }
An example which shows how to configure node realizers with instances of the same GenericNodeRealizer configuration but different style properties is shown in Example 6.28, “Setting style properties to different realizer instances”.
Example 6.28. Setting style properties to different realizer instances
// 'graph2D' is of type y.view.Graph2D. GenericNodeRealizer gnr1 = new GenericNodeRealizer("Floating"); gnr1.setStyleProperty("FloatingPainter.Inset", new Double(4)); gnr1.setFrame(-50, 0, 80, 30); Node n1 = graph2D.createNode(gnr1); GenericNodeRealizer gnr2 = new GenericNodeRealizer("Floating"); gnr2.setStyleProperty("FloatingPainter.Inset", new Double(8)); gnr2.setFrame(50, 0, 80, 30); Node n2 = graph2D.createNode(gnr2);
The two node realizers in the above example will be drawn differently according to their style properties as shown in Figure 6.36, “GenericNodeRealizer with different style properties”.
The tutorial demo application GenericNodeRealizerDemo.java presents the customization of GenericNodeRealizer in detail.
GenericEdgeRealizer offers all methods from its superclass EdgeRealizer, the actual work, though, is delegated to a number of static inner interfaces, namely:
Classes SimpleBendHandler and
PolyLinePathCalculator
serve as default
implementations for interfaces GenericEdgeRealizer.BendHandler and
GenericEdgeRealizer.PathCalculator, respectively.
They are also part of the default configuration for GenericEdgeRealizer which
renders a simple poly-line edge path.
Table 6.5, “Predefined GenericEdgeRealizer.PathCalculator implementations” lists all predefined
implementations for interface GenericEdgeRealizer.PathCalculator.
Additionally, class GenericEdgePainter
serves as a convenient base implementation for interface
GenericEdgeRealizer.Painter that can be used for further customization.
An actual edge type configuration must always provide implementations for static inner interfaces GenericEdgeRealizer.BendHandler and GenericEdgeRealizer.PathCalculator to form a valid configuration.
The tutorial demo application GenericEdgeRealizerDemo.java presents the customization of GenericEdgeRealizer in detail.
Copyright ©2004-2012, yWorks GmbH. All rights reserved. |