Where to Find Up-to-date yFiles Information

This page is from the outdated yFiles for Java 2.13 documentation. You can find the most up-to-date documentation for all yFiles products on the yFiles documentation overview page.

Please see the following links for more information about the yFiles product family of diagramming programming libraries and corresponding yFiles products for modern web apps, for cross-platform Java(FX) applications, and for applications for the Microsoft .NET environment.

More about the yFiles product family Close X

Writing Customized Realizers

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.

Subclassing and Overriding

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:

Realizer Replication

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:

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);
  }
}

Bounds Calculation

The bounds calculation of a realizer plays an important role for the graphical user interface. Among other things, it is necessary for:

  • determining those parts of a view that have to be repainted
  • calculating the correct bounding box of an entire graph so that all elements are included
  • hit-testing a graph element

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;
  }
}

YGF Serialization and Deserialization

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)
void write(ObjectOutputStream out)
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”.

Providing Interface Implementations

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

Class GenericNodeRealizer

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";
}

Note

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);
}

Note

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.

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

Figure 6.36. GenericNodeRealizer with different style properties

GenericNodeRealizer with different insets passed as style properties.

The tutorial demo application GenericNodeRealizerDemo.java presents the customization of GenericNodeRealizer in detail.

Class GenericEdgeRealizer

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.

Important

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.