Realizer-Related Features

Label Support

The yFiles label capabilities cover a variety of standard features common with today's word processing systems. A label can have multiple lines, the label's text can be aligned in several ways, and font attributes like type, size, and color can be controlled. Also, using an instance of type java.awt.Insets, a label's text can be placed with custom margins within its bounding box. Other graphical attributes, including background and border color, can be set, too.

Figure 6.16. Hierarchy of the yFiles label classes

Features of the yFiles labels.

Look and feel of both node labels and edge labels are defined by so-called "configurations," i.e., sets of interface implementations that cover all aspects of label functionality. Classes NodeLabel and EdgeLabel each make available two predefined configurations, the default label configuration, which is encapsulated in class DefaultLabelConfiguration, and a second configuration that provides automatic line-wrapping of a label's text. The latter one is accessible with these classes using the name "CroppingLabel".

In addition to text alignment and font attributes, the default label configuration provides further useful features. On the basis of the used font the label's size is automatically determined each time a new text is set, respectively one of the label's properties changes. Instead of text, or in addition to its text, a label can also show an icon, and for complete freedom in design it can even render HTML code. Furthermore, the entire label can be rotated about its center. Figure 6.17, “Some features of the default label configuration” depicts some label variations.

Figure 6.17. Some features of the default label configuration

Some features of the default label configuration.

HTML rendering is enabled by default and is triggered as soon as the label's text starts with <html>. Note that the rendering capabilities are constrained to the basic HTML support provided by Swing. Example 6.2, “Creating some labels” shows code to generate various kinds of labels.

Example 6.2. Creating some labels

// 'nr1' to 'nr3' are of type y.view.NodeRealizer.

NodeLabel nl;
// First node. 
nl = nr1.createNodeLabel();
nr1.setLabel(nl);

// Set the label model and position. 
nl.setModel(NodeLabel.CORNERS);
nl.setPosition(NodeLabel.NW);

// Note the line breaks. 
nl.setText("Label with \nbackground \nand border \ncolor.");
nl.setBackgroundColor(new Color(204, 255, 204));
nl.setLineColor(new Color(51, 153, 102));

// Second node. 
nl = nr2.createNodeLabel();
nr2.setLabel(nl);

nl.setText("Rotated label.");
nl.setRotationAngle(310);

// Third node. 
nl = nr3.createNodeLabel();
nr3.setLabel(nl);

// Set the label model and position. 
nl.setModel(NodeLabel.SANDWICH);
nl.setPosition(NodeLabel.S);

// HTML rendering is on by default. 
nl.setText("<html><hr>" + 
  "<em><b>HTML</b> label</em><br>" + 
  "with multiple lines<br>" + 
  "and rules." + 
  "<hr></html>");

Figure 6.18, “Label with icon instead of text” is created by the code shown in Example 6.3, “Creating a label with an icon”.

Figure 6.18. Label with icon instead of text

Label with icon instead of text.

Example 6.3. Creating a label with an icon

// 'nr' is of type y.view.NodeRealizer.

NodeLabel nl = nr.createNodeLabel();
nr.setLabel(nl);

// Set the label model and position. 
nl.setModel(NodeLabel.SANDWICH);
nl.setPosition(NodeLabel.N);

// Well-known icon that comes in handy as an example... 
nl.setIcon(MetalIconFactory.getFileChooserHomeFolderIcon());

Tip

Using an image icon as the content of a node label permits a node that is rendered by a ShapeNodeRealizer to show both shape and image. In contrast to using an ImageNodeRealizer, however, the image will retain its size, i.e., it will not scale with the node.

Using the additional line of code from Example 6.4, “Setting custom margins”, the label's bounding box is effectively enlarged to create the specified margins around the text. See Figure 6.19, “Label text with custom margins” for the result.

Example 6.4. Setting custom margins

// Increase the left margin. 
nl.setInsets(new Insets(10, 70, 10, 10));

Figure 6.19. Label text with custom margins

Label text with custom margins.

Using the "CroppingLabel" configuration, which enables a label's text to be automatically line-wrapped, it is easily possible to have a label's visual presentation truncated to fit into the node's size, for example. Figure 6.20, “Label text line-wrapping and cropping” shows both the default behavior when a node label has a long text, and the results when the text is automatically line-wrapped to fit either the width or the width and height of its node. In the latter case, when the line-wrapped label text does not fit, the displayed text is truncated and an ellipsis character replaces the part that is not shown.

Figure 6.20. Label text line-wrapping and cropping

Label text line-wrapping and cropping.

Example 6.5, “Different sizing policies for the label text” shows the code that sets up the auto-sizing policies depicted in Figure 6.20, “Label text line-wrapping and cropping”.

Note

The CroppingLabel configuration currently has no support for HTML rendering or setting an icon with a label.

Example 6.5. Different sizing policies for the label text

// 'graph' is of type y.view.Graph2D.

Set configurations = NodeLabel.getFactory().getAvailableConfigurations();
for (int i = 0; i < 3; i++) {
  Node n = graph.createNode(200 * (i + 1), 100);
  NodeLabel nl = graph.getRealizer(n).getLabel();

  nl.setFontName("Tahoma");
  nl.setFontSize(14);
  nl.setAlignment(YLabel.ALIGN_LEFT);
  nl.setText("The quick brown fox jumps over the lazy dog.");

  switch (i) {
    case 1:
      if (configurations.contains("CroppingLabel")) {
        nl.setConfiguration("CroppingLabel");
        nl.setAutoSizePolicy(NodeLabel.AUTOSIZE_NODE_WIDTH);
      }
      break;
    case 2:
      if (configurations.contains("CroppingLabel")) {
        nl.setConfiguration("CroppingLabel");
        nl.setAutoSizePolicy(YLabel.AUTOSIZE_NONE);
        nl.setContentSize(60, 60);
      }
      break;
    default:
      nl.setAutoSizePolicy(YLabel.AUTOSIZE_CONTENT);
  }
}

Customizing Label Behavior

Abstract class YLabel defines a set of inner interfaces which allow fine-grained control over all aspects of a label's visual representation and behavior. Custom label logic can be conveniently expressed by providing implementations for only a selection or for all of these interfaces:

  • YLabel.Painter renders the label's visual representation and provides the locations for the label's icon and text
  • YLabel.Layout calculates the actual size of a label's content and provides hit-testing support
  • YLabel.BoundsProvider is used to enlarge a given rectangle so that the label's bounds is fully contained
  • YLabel.UserDataHandler governs both copying and serializing of user-defined data

Together, a set of actual implementations for these interfaces forms a so-called "configuration" that defines the look and feel of a label. Different configurations can be used to define a variety of label types.

Management of label configurations is done by static inner class YLabel.Factory. A reference to this class can be get using the NodeLabel and EdgeLabel class method shown in API Excerpt 6.23, “Static method to get the factory for configuration management”.

API Excerpt 6.23. Static method to get the factory for configuration management

// Getter method in classes NodeLabel and EdgeLabel.
static YLabel.Factory getFactory()

To handle user-defined data, YLabel defines the YLabel.UserDataHandler inner interface. This interface is used to delegate all work that relates to copying or (de)serializing of all user-defined data of a label. Class SimpleUserDataHandler can be used as a default implementation for this interface. 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).

Class DefaultLabelConfiguration serves as a default implementation for all YLabel interfaces. It provides the default label behavior and can be used as a convenient base for customized configurations. Example 6.6, “Customizing the default node label configuration” shows how to create a specialized node label configuration using another YLabel.Painter implementation.

Example 6.6. Customizing the default node label configuration

// Get the factory to register custom styles/configurations.
YLabel.Factory factory = NodeLabel.getFactory();

// Retrieve a map that holds the default NodeLabel configuration.
// The implementations contained therein can be replaced one by one in order to 
// create custom configurations...
Map implementationsMap = factory.createDefaultConfigurationMap();

// Customize the painting with a custom YLabel.Painter implementation.
implementationsMap.put(YLabel.Painter.class, new MyPainter());

// Add this configuration to the factory.
factory.addConfiguration("Bubble", implementationsMap);

Tutorial demo application YLabelConfigurationDemo.java presents in detail how a custom node label configuration is created.

Label Models

The aforementioned general features are offered by abstract class YLabel. Classes NodeLabel and EdgeLabel provide further label functionality that is specific to the respective type of graph element. Figure 6.21, “Label classes hierarchy” presents the class hierarchy for the label functionality from package y.view.

Figure 6.21. Label classes hierarchy

Label classes hierarchy.

A label naturally belongs to a specific graph element, and accordingly should be presented near this element. To control the label's position relative to its element, so-called "label models" are used.

Class NodeLabel provides a number of label models with predefined positions and additionally a "free" model, where the label's position is solely defined by an offset. Figure 6.22, “Possible node label positions” depicts all predefined positions for a node label.

Figure 6.22. Possible node label positions

Possible node label positions.

It is important to note that the set of valid positions varies with the chosen model, i.e., not all possible positions are valid for each model. Table 6.4, “Valid label positions for each model” lists the sets of valid positions for each node label model.

Table 6.4. Valid label positions for each model

Model Name Valid Positions
INTERNAL TOP_LEFT, TOP, TOP_RIGHT, LEFT, CENTER, RIGHT, BOTTOM_LEFT, BOTTOM, BOTTOM_RIGHT
CORNERS NW, NE, SW, SE
SIDES N, W, E, S
EIGHT_POS All positions from CORNERS and also all from SIDES.
SANDWICH N, S
FREE ANYWHERE

The offset with the "free" label model is the distance vector between the upper left corners of node and label. Setting a label model and choosing one of the predefined label positions is demonstrated in Example 6.7, “Setting a node label model”. If the chosen position is not in the set of valid positions for a label model, then a default position is taken instead.

Example 6.7. Setting a node label model

// 'nr' is of type y.view.NodeRealizer. 

// Create the label and add it to the node realizer. 
NodeLabel nl = nr.createNodeLabel();
nr.setLabel(nl);

// Set a node label model and choose one of the predefined label positions. 
// 'CORNERS' has four positions at the corners of a node, outside its bounds. 
nl.setModel(NodeLabel.CORNERS);
// 'SE' means the lower right corner. 
nl.setPosition(NodeLabel.SE);

Observe that the sets of valid positions with label models CORNERS, SANDWICH, and SIDES are proper subsets of those allowed with label model EIGHT_POS. Choosing one of these subsets explicitly restricts the number of available label positions, i.e., for the most flexibility EIGHT_POS is the best choice.

Class EdgeLabel provides several label models with predefined positions, and some "slider" models where the set of valid positions is dynamic and varies with the length of the edge path. Additionally, there is also a "free" model available, where the label's position is defined by an offset. Table 6.5, “Edge label models” lists all edge label models and shows the predefined label positions, the dynamic positions of the "slider" models, and also examples for offset positions with the "free" model.

Table 6.5. Edge label models

Model Name Valid Positions
CENTERED CENTER
TWO_POS HEAD, TAIL
THREE_CENTER SCENTR, CENTER, TCENTR
SIX_POS SHEAD, HEAD, THEAD, STAIL, TAIL, TTAIL
CENTER_SLIDER DYNAMIC
SIDE_SLIDER DYNAMIC
FREE ANYWHERE

The dynamic positions of the "slider" models are specified by a ratio value that ranges from 0.0 to 1.0. It determines the label's placement along the edge path, with increasing value meaning positions farther from the source node. The offset with the "free" label model is the distance vector between the intersection point of the first edge segment with the source node and the upper left corner of the label. Setting a label model and choosing a label position is demonstrated in Example 6.8, “Setting an edge label model”. If the chosen position is not in the set of valid positions for a label model, then a default position is taken instead.

Example 6.8. Setting an edge label model

// 'er' is of type y.view.EdgeRealizer. 

// Create the label and add it to the edge realizer. 
EdgeLabel el = er.createEdgeLabel();
er.addLabel(el);

// Set an edge label model. 'CENTER_SLIDER' has a dynamic number of label 
// positions that lie on the edge's path. 
el.setModel(EdgeLabel.CENTER_SLIDER);
el.setPosition(EdgeLabel.DYNAMIC);
// Choose a label position near the source node. 
el.setRatio(0.3);

Related Classes

In addition to the EdgeRealizer object associated there are further classes involved in rendering an edge's visual representation. For each of the edge's bends the EdgeRealizer object holds a corresponding object of type Bend. The special graphical decoration that can be placed at either end of an edge is governed by class Arrow. And the line graphic of the edge itself can be controlled using class LineType.

Figure 6.23. EdgeRealizer-related classes

EdgeRealizer-related classes.

API Excerpt 6.24, “Arrow-related methods from class EdgeRealizer” shows the methods provided by class EdgeRealizer to handle arrows.

API Excerpt 6.24. Arrow-related methods from class EdgeRealizer

// Getter methods. 
Arrow getArrow()
Arrow getSourceArrow()
Arrow getTargetArrow()

// Setter methods. 
void setArrow(Arrow arrow)
void setSourceArrow(Arrow arrow)
void setTargetArrow(Arrow arrow)

Class Arrow

Class Arrow renders the graphical decorations that can be placed at an edge's ends. Normally, this decoration is some kind of arrowhead that is used to indicate the edge's direction. Table 6.6, “Arrow shapes of class Arrow” depicts the predefined arrow shapes.

Note that omitting the decoration is done using another arrow shape. Arrow type NONE, which is not shown below, "renders" an empty arrow shape.

Table 6.6. Arrow shapes of class Arrow

Arrow Type Arrow Shape
DELTA
DIAMOND
SHORT
STANDARD
WHITE_DELTA
WHITE_DIAMOND

Class Arrow supports fine-grained control of arrowhead rendering. It is possible to specify an arrowhead's distance to the border of a node, and also the position of the edge path's end relative to an arrowhead's tip.

In addition to the predefined arrow shapes it is also possible to register custom ones using the methods listed in API Excerpt 6.25, “Support for custom arrow shapes”. The most freedom defining a customized arrow(head) is provided when using graphical objects that implement interface Drawable.

API Excerpt 6.25. Support for custom arrow shapes

// Getter and setter methods for custom arrow shapes. 
static Arrow addCustomArrow(String name, Drawable drawable)
static Arrow addCustomArrow(String name, Shape shape, Color fillColor)
static Arrow addCustomArrow(String name, Shape shape, Color fillColor, 
                            Stroke lineStroke, Color lineColor)
static Arrow getCustomArrow(String name)

// Arrow shapes using customized distance and arrow length. 
static Arrow addCustomArrow(String name, Drawable drawable, 
                            double arrowLength, double clipLength)
static Arrow addCustomArrow(String name, Shape shape, Color fillColor, 
                            Stroke lineStroke, Color lineColor, 
                            double arrowLength, double clipLength)

String getCustomName()

Class Bend

Class Bend is to bends (also known as control points) what classes NodeRealizer and EdgeRealizer are to nodes and edges. It handles all user interface (UI)-related aspects, i.e., it renders a bend's graphical representation, and also provides a number of features including, e.g., control of the selection state and support for hit tests.

Note that bends, i.e., control points, determine the path of an edge, but are not necessarily part of the path itself. Especially the classes BezierEdgeRealizer and SplineEdgeRealizer exhibit this difference.

The most defining characteristic of a bend is its location. API Excerpt 6.26, “Positional methods from class Bend” lists the methods that can be used to control it.

API Excerpt 6.26. Positional methods from class Bend

// Getter methods. 
double getX()
double getY()

// Setter methods. 
void setLocation(double newX, double newY)
void moveBy(double dx, double dy)

Class LineType

Class LineType is responsible for rendering the line graphic of an edge. It is used for both poly-line edges as well as curves. Table 6.7, “Line types of class LineType” shows the predefined line types. Note that the line types are available in different widths.

Table 6.7. Line types of class LineType

Constant Name Line Type
DASHED_STYLE
DOTTED_STYLE
LINE_STYLE

Different line types can be get directly using their respective names, e.g., DASHED_1, DOTTED_2, LINE_4, etc. Alternatively, LineType also offers the static methods from API Excerpt 6.27, “Static getter methods from class LineType” to get (one of) the available line types.

API Excerpt 6.27. Static getter methods from class LineType

static Vector availableLineTypes()
static LineType getLineType(int width, byte style)

In addition to the predefined line types it is also possible to create custom ones using the method from API Excerpt 6.28, “Support for creation of custom line types”.

API Excerpt 6.28. Support for creation of custom line types

static LineType createLineType(float width, 
                               int cap, int join, 
                               float miterlimit, 
                               float[] dash, float dashPhase)

Selection State

The indication whether a graph element is selected or not contributes a great deal to the user experience and is of central importance to the notion of a user interacting with the graph structure by means of a graphical user interface.

The predefined NodeRealizer and EdgeRealizer implementations define special rendering logic for the selection indication of its associated graph elements. A selected node, e.g., also shows eight small knobs around its outline that can be used to change the node's width and height. Figure 6.24, “Selection indication for nodes, edges, and bends” shows the default results for selected nodes, a selected edge, and selected bends. Note that bends have different appearance depending on the context.

Figure 6.24. Selection indication for nodes, edges, and bends

Selected nodes.
Selected edge.
Selected control points (bends).
Selected nodes. Selected edge. Selected control points (bends).

Both realizer types offer getter and setter methods to control the selection state of their associated graph element. Likewise, similar methods in class Bend can be used to control the selection state of an edge's control points. Class Graph2D has a variety of additional convenience methods to change, respectively query, the selection state of arbitrary collections of graph elements. Figure 6.25, “Methods for modifying the selection state” presents an overview of the classes and their methods dealing with selection state. Further, more sophisticated methods to alter the selection state are offered by abstract class Selections.

Figure 6.25. Methods for modifying the selection state

Methods for modifying the selection state.

Changes in the selection state of graph elements can easily be listened to using the familiar events and listener concept. Interface Graph2DSelectionListener conveys an event of type Graph2DSelectionEvent which can be queried for specific details. See the section called “Events and Listeners” for further description.