Annealing label layout
Describes the
Annealing Label Layout algorithm (class
IlvAnnealingLabelLayout from the package
ilog.views.graphlayout.labellayout.annealing).
Gives samples of the Annealing Label Layout.
Lists the features of the Annealing Label Layout.
Lists the limitations of the Annealing Label Layout.
Describes the simulated annealing algorithm used by the Annealing Label Layout.
Lists the generic features and parameters supported by the Annealing Label Layout.
Describes the use of label descriptors to specify placement.
Describes the point label descriptor used by the Annealing Label Layout to place labels.
Describes the polyline label descriptor used by the Annealing Label Layout to place labels.
Describes the rotated labels used by the Annealing Label Layout.
Describes the global parameters used by the Annealing Label Layout.
Describes how to create a label descriptor for the Annealing Label Layout.
General information
The Annealing Label layout is used by the label layout renderer of a diagram component or can be used in Java™ code.
The following sample drawings are produced with the Annealing Label Layout.
Label placement at nodes with the Annealing Label Layout
Label placement at links with the Annealing Label Layout
Label placement at cities in a map of Germany with Annealing Label Layout
Features
Places only labels. Does not move any obstacles around.
Quality-controlled, randomized iterative heuristic.
Can place labels at points, rectangles, ellipses, and polylines when used inside Java™ code.
Can be used to place labels at any nodes and links when used inside Java code.
Can be used to place labels at cities on a geographic map when used inside Java code.
Can place multiple labels at the same object (point, node, link, city, and so on).
Can handle upright rectangular labels and rectangular labels that have a rotation that depends on their position
Tries to avoid overlaps among labels, and between labels and obstacles, by using the available free space.
Provides several anchor and preference options.
Easily extensible by subclassing label descriptors.
Efficient, scalable algorithm. Produces nice label placements even with a large number of labels.
Extra feature for JViews Diagrammer:
Limitations
The Annealing Label Layout algorithm, as a randomized iterative heuristic, does not guarantee that labels are placed without overlaps whenever possible. However, it produces a high quality layout with a high probability of minimum overlap. The more iterations, the higher the probability of high quality.
The algorithm is not able to create free space for labels by moving obstacles around. It is recommended that you perform a graph layout algorithm with large spacing parameters to create the necessary free space before placing the labels.
The algorithm
The algorithm uses simulated annealing. This is a general, randomized optimization technique that simulates a thermodynamic particle system. Each label is moved to a new random position within the limits given by its label descriptor. The quality of the new position is calculated and compared to the quality of the old position. If the quality has not improved, the label is moved back to the old position. The amount of movement is controlled by a conceptual temperature: when the system is hot, the labels can move long distances, producing potentially large global quality improvements. When the system cools down, the move distances become smaller and hence focus on local fine-tuning of the position.
Each label has its own label descriptor. The label descriptor describes the path on which the label can move. If a label must be placed at a city in a geographic map, then its label descriptor makes sure that the label is always placed close to the graphics that represent the city. If a label must be placed at a specific point, the IlvAnnealingPointLabelDescriptor can be used and describes an approximately elliptical path around the point. If a label must be placed at a polyline, the IlvAnnealingPolylineLabelDescriptor can be used and describes a boundary path at both sides of the polyline.
Annealing Label Layout algorithm Example
In CSS
The sample below shows how to use CSS syntax to declare labels at nodes of type
IlvGeneralNode. The specification can be loaded as a style file into an application that uses the
IlvDiagrammer class (see
Graph layout in Rogue Wave JViews Diagrammer).
SDM {
LabelLayout: "true";
}
LabelLayout {
enabled: "true";
labelOffset: “5”;
obstacleOffset: “10”;
}
node {
class: "ilog.views.sdm.graphic.IlvGeneralNode";
label: "@name";
labelPosition: "Left";
labelSpacing: "2.0";
}
node:labelLayout {
maxDistFromPath: "5";
}
In Java™
The following code sample uses the IlvAnnealingLabelLayout class. This code sample shows how to perform an Annealing Label Layout on a manager directly.:
...
import ilog.views.*;
import ilog.views.graphlayout.labellayout.*;
import ilog.views.graphlayout.labellayout.annealing.*;
...
IlvManager manager = new IlvManager();
IlvManagerView view = new IlvManagerView(manager);
... /* Fill in here code that fills the manager with labels and obstacles */
IlvAnnealingLabelLayout layout = new IlvAnnealingLabelLayout();
layout.attach(manager);
/* Assume: label1 should be placed at the right side of rectangular node1 */
layout.setLabelDescriptor(
label1,
new IlvAnnealingPointLabelDescriptor(
label1, node1, IlvAnnealingPointLabelDescriptor.RECTANGULAR,
IlvDirection.Right));
... /* Fill in here code that sets descriptors for all other labels */
IlvLabelLayoutReport layoutReport = layout.performLayout();
if (layoutReport.getCode() == IlvLabelLayoutReport.LAYOUT_DONE)
System.out.println("Layout done.");
else
System.out.println("Layout not done, code = " +
layoutReport.getCode());
Generic features and parameters
The
IlvAnnealingLabelLayout class supports generic parameters defined in the
IlvLabelLayout class. The following sections describe the particular way in which these parameters are used by the subclass
IlvAnnealingLabelLayout.
Allowed time
The label layout algorithm stops if the allowed time setting has elapsed. This feature works similarly to the same feature in
IlvGraphLayout; see
Allowed time. If the layout stops early because the allowed time has elapsed, the result code in the layout report is:
IlvLabelLayoutReport.STOPPED_AND_VALID if the labels were moved to some better (but not yet optimal) positions.
IlvLabelLayoutReport.STOPPED_AND_INVALID if the time elapsed even before that.
Percentage of completion calculation
The label layout algorithm calculates the estimated percentage of completion. This value can be obtained from the label layout report during the run of the layout. (For a detailed description of this feature, see
Percentage of completion calculation and
Layout events and listeners.)
Random generator seed value
The Annealing Label Layout is a randomized heuristic. It uses a random number generator to control the movements. For the default behavior, the random generator is initialized using the current system clock. Therefore, different layouts are obtained if you perform the layout repeatedly on the same graph. You can specify the particular value to be used as a
seed value.
Example of specifying seed value
To specify the seed value 10:
In CSS
Add to the LabelLayout section:
useSeedValueForRandomGenerator: "true";
seedValueForRandomGenerator: "10";
In Java™
Call:
layout.setUseSeedValueForRandomGenerator(true);
layout.setSeedValueForRandomGenerator(10);
Save parameters to named properties
The label layout algorithm can save its layout parameters into named properties. This can be used to save layout parameters to
.ivl files. (For a detailed description of this feature, see
Using named properties to save layout parameters.)
Stop immediately
The label layout algorithm stops after cleanup if the method
stopImmediately is called. This method works for the
IlvLabelLayout class similarly to the corresponding method in the
IlvGraphLayout class. For a description of this method in the
IlvGraphLayout class, see
Stop immediately. If the layout stops early in this way, the result code in the layout report is:
IlvLabelLayoutReport.STOPPED_AND_VALID if the labels were moved to some better (but not yet optimal) positions.
IlvLabelLayoutReport.STOPPED_AND_INVALID if the layout stopped even before that.
Use default parameters
After modifying any label layout parameter, you may want the layout algorithm to use the default values. You select the default values for all global parameters by:
layout.setUseDefaultParameters(true);
Rogue Wave JViews Diagrammer keeps the previous settings when selecting the default values mode. You can switch back to your own settings by:
layout.setUseDefaultParameters(false);
This setting affects only the global layout parameters. The label descriptors have no default values, so parameters of the label descriptors do not change depending on this flag.
Label descriptors
To define where a label must be placed, you must specify a label descriptor for each label. The algorithm places only those labels that have a label descriptor.
A label descriptor describes the locations that are allowed for the label. For instance, if you place a city name label in a geographic map, you want the label to be positioned close to the graphic objects that represent the city. Positions far away from the city are not reasonable for the label.
Example of label descriptors
To specify these parameters:
In CSS
Label descriptors are automatically created for all labels. You only need to specify the details of the label descriptors by using the pseudo classes
labelLayout and
labelLayout_ i where
i is the child index of the label in
IlvSDMCompositeNode or
IlvSDMCompositeNode. For instance, the descriptor of a label at an
IlvGeneralNode object is specified in the following way:
node:labelLayout {
... any bean property of the point label descriptor ...
}
The descriptor of a label which is the 3rd child of an IlvSDMCompositeLink is specified in the following way:
link:labelLayout_3 {
... any bean property of the polyline label descriptor ...
}
In Java™
You need to allocate a new label descriptor for each label. There are two ways to specify the label descriptors:
To set the descriptor for one label, call:
layout.setLabelDescriptor(label, descriptor);
You can retrieve the current label descriptor using the method:
layout.getLabelDescriptor(label);
You can instead specify a label descriptor provider.
IlvAnnealingLabelDescriptorProvider is an interface that delivers label descriptors for labels on the fly. The provider has the advantage that you don't need to run in advance through all labels to set the label descriptor. Instead, the layout detects the labels and asks the provider to deliver the label descriptor if none was set explicitly. The provider can then allocate the label descriptor on the fly, or can deliver a preallocated descriptor that was stored somewhere else outside layout. Using the label descriptor provider is in particular useful if the number of labels and obstacles change frequently, because you don't need to keep track of the labels that have already a label descriptor, and if you want to have a central place that manages all label descriptors.
The reference manual of the class IlvAnnealingLabelDescriptorProvider contains further information on how to implement this interface. In order to set the provider, call the following method:
layout.setLabelDescriptorProvider(provider);
Subclasses of label descriptors
There are two predefined subclasses of label descriptors:
In a diagram component with CSS styling, the point label descriptor is used for all labels at nodes, and the polyline label descriptor is used for all labels at links.
If you program the label layout directly in Java code, depending on the parameters passed during the construction, these subclasses allow you to place a label:
Close to a specific point.
Close to a specific rectangular or elliptic obstacle (such as a node).
Along an imaginary polyline.
Close to a polyline obstacle (for example,
IlvLine,
IlvPolyline).
Close to a link.
You can also implement your own label descriptors by subclassing
IlvAnnealingLabelDescriptor. This is explained in the section
For experts: implementing your own label descriptors.
Point label descriptor
The IlvAnnealingPointLabelDescriptor can be used to place a label at a specific obstacle or point. This is known as the point labeling problem.
Positioning at an obstacle
The following example shows how to position a label at a specific obstacle in Java™.
layout.setLabelDescriptor(label,
new IlvAnnealingPointLabelDescriptor(label, node,
IlvAnnealingPointLabelDescriptor.ELLIPTIC, IlvDirection.Right));
This specification can be used if the label must be placed at a node that has an elliptical or circular shape. The label is placed in the free area around the node so that the border of the label just touches the border of the node (see the following figure). The preferred position is the right side of the node, but this preferred position is used only if it does not create overlaps. If the node is moved or reshaped, the next call of label layout will update the position of the label automatically so that it follows the node.
Potential label positions around a node
The example uses the following constructor:
IlvAnnealingPointLabelDescriptor(Object label,
Object relatedObstacle,
int shape,
int preferredDirection)
This constructor takes the following parameters:
The
relatedObstacle parameter is the obstacle that gets the label. The label is placed outside but close to this obstacle. The related obstacle can be a node of a graph, a city of a geographic map, a station of a railroad, and so on, whatever needs to have a label. The shape of the related obstacle should be either an ellipse, a circle, or a rectangle.
The
shape argument can take the following values:
IlvAnnealingPointLabelDescriptor.ELLIPTIC for ellipses or circles,
IlvAnnealingPointLabelDescriptor.RECTANGULAR for rectangles.
If the real shape of the related obstacle is neither of these, pass the shape that is the best approximation. For instance, if the obstacle is an
IlvRectangle with rounded corners, it can be considered as a rectangular shape and the
RECTANGULAR option is then the best approximation.
The
preferredDirection parameter is a suggestion of where the label layout algorithm should preferably place the label. If the area at the preferred position is occupied, the label will be placed elsewhere. Options for the preferred position are:
IlvDirection.Left IlvDirection.Right IlvDirection.Top IlvDirection.Bottom Positioning at a point
The following example shows how to position a label at a specific point in Java:
layout.setLabelDescriptor(label,
new IlvAnnealingPointLabelDescriptor(label, null, new IlvPoint(25, 75),
5f, 15f, IlvDirection.Right));
Use this specification if the label must be placed close to specific coordinates, like (in this example 25, 75) regardless of any obstacle. The label must be at least 5 coordinate units and at most 15 coordinate units away from the point (see the following figure). The preferred position is at the right side of the point.
Potential label positions between 5 and 15 units away from a point
The example uses the following constructor:
IlvAnnealingPointLabelDescriptor(Object label,
Object relatedObstacle,
IlvPoint referencePoint,
float minDist,
float maxDist,
int preferredDirection)
This
IlvAnnealingPointLabelDescriptor constructor takes the following parameters:
relatedObstacle and referencePoint
The label is placed close to the reference point. It does not take the position of the related obstacle into account. If the related obstacle is moved, the label does not follow the obstacle on the next call of layout, but stays at the reference point.
If a related obstacle is specified, the label is not pushed away from the related obstacle. Rather, it is pushed away from all other obstacles to avoid overlaps. You can set the relatedObstacle parameter to null if the label is independent of all obstacles.
minDist and maxDist
These are the minimum and maximum distances from the reference point, measured from the border of the label. If you set the minimum and maximum distance to 0, the label will just touch the reference point. To keep the circular area around the reference point free, set the minimum distance accordingly. Most of the time you probably want to keep the label close to the reference point; hence, set the minimum and maximum distances to the same value.
preferredDirection
This parameter indicates whether the label should be placed to the left, right, top, or bottom of the reference point. This is a suggestion for the labeling algorithm, as described for
Positioning at an obstacle.
Positioning on multiple criteria
The most powerful constructor combines all the possibilities described in
Positioning at an obstacle and
Positioning at a point:
IlvAnnealingPointLabelDescriptor(Object label,
Object relatedObstacle,
IlvPoint referencePoint,
int shape,
float halfWidth,
float halfHeight,
float maxDistFromPath,
float preferredDistFromPath,
int preferredDirection)
This
IlvAnnealingPointLabelDescriptor constructor takes the following parameters:
relatedObstacle and referencePoint
If a related obstacle is specified and the reference point is null, the label is placed close to the related obstacle. If a reference point is not null, the label is placed close to the reference point independently of the related obstacle position.
shape
The shape of the free area around the point can be rectangular or elliptic.
halfWidth and halfHeight
The parameter halfWidth is the minimum distance of the label to the reference point in the horizontal direction. The parameter halfHeight is the minimum distance of the label to the reference point in the vertical direction. If the reference point is null, the parameters halfWidth and halfHeight are calculated from the bounding box of the related obstacle.
maxDistFromPath
This parameter specifies the maximum additional distance allowed for the label (shown in
Potential label positions with rectangular shape at a point).
preferredDistFromPath
This parameter specifies the preferred additional distance for the label. Its value should be between 0 and maxDistFromPath.
preferredDirection
This parameter indicates whether the label should be placed to the left, right, top, or bottom of the reference point or related obstacle. This is a suggestion for the labeling algorithm, as described in
Positioning at an obstacleStarting from an empty descriptor (point)
An alternative way to create a point label descriptor is to start from the empty descriptor:
descriptor = new IlvAnnealingPointLabelDescriptor();
Before using the empty descriptor, you must fill it with information on how the label should be placed. As a minimum, you need to specify a related obstacle or a reference point. For example:
descriptor.setRelatedObstacle(obstacle);
descriptor.setShape(IlvAnnealingPointLabelDescriptor.ELLIPTIC);
descriptor.setPreferredDirection(IlvDirection.Left);
At the end of all changes, the descriptor must be passed to the layout instance:
layout.setLabelDescriptor(label, descriptor);
Once the descriptor is passed to the layout instance, it should normally not be changed. If you need to change it later, you must pass it to the layout instance again.
Potential label positions with rectangular shape at a point
Example of specifying point label descriptor
Example of an IlvGeneralNode specification with label:
In CSS
You can specify all multiple criteria of the point labeling descriptor except the related obstacle. In CSS, the related obstacle is always the node that owns the label. The reference point and the shape ( ELLIPTIC or RECTANGULAR ) are automatically derived from the node that owns the label, but can be overridden in the CSS specification.
Example of an IlvGeneralNode specification with label:
node {
class: "ilog.views.sdm.graphic.IlvGeneralNode";
label: "@name";
shapeType: "Rectangle"; // shape of path for label descriptor
shapeWidth: "30"; // twice the halfWidth
shapeHeight: "30"; // twice the HalfHeight
labelPosition: "Left"; // preferredDirection
labelSpacing: "2.0"; // preferredDistFromPath
}
In this example, shapeType, shapeWidth and shapeHeight specify the basic shape of the node and are taken into account by the label layout, the labelPosition specifies the preferred direction of the label for the label layout, and the labelSpacing specifies the preferred distance of the label from the path around the border of the basic shape of the node.
These parameters can be overridden by specifying the details of the point label descriptor in the following way:
node:labelLayout {
shape: "ELLIPTIC";
halfWidth: "16";
halfHeight: "16";
maxDistFromPath: "5";
preferredDistFromPath: "3";
preferredDirection: "Top";
}
If the node is an instance of
IlvSDMCompositeNode and its fourth child is a label, the details of the label descriptor for that label can be specified in the following way:
node:labelLayout_4 {
shape: "ELLIPTIC";
halfWidth: "16";
halfHeight: "16";
maxDistFromPath: "5";
preferredDistFromPath: "3";
preferredDirection: "Top";
}
The meaning of these parameters is explained in
Positioning on multiple criteria.
Polyline label descriptor
If you want to place labels at straight lines, polylines, or links, you should use the class
IlvAnnealingPolylineLabelDescriptor. The allowed area for labels at a polyline is different from the rectangular or elliptic area considered for placing labels at a reference point (see
Positioning at a point). A polyline has two sides where the label can be placed along a path. This is known as the
polyline labeling problem.
This section first explains how to code a polyline label descriptor in Java™, then gives a CSS sample.
NOTE The polyline label descriptor is not suitable for placing labels at splines or spline links. Because splines have a complex geometric shape, the automatic placement of labels at splines is currently not supported.
Simple positioning at a link
This specification can be used if the label must be placed at a straight or polyline link. Here is an example:
layout.setLabelDescriptor(label,new IlvAnnealingPolylineLabelDescriptor(label,
link,IlvAnnealingPolylineLabelDescriptor.CENTER));
The label is placed near the center of the link such that one border of the label just touches the link. If the link is moved or reshaped, the next label layout call will update the position of the label automatically to follow the link.
The example uses the following
IlvAnnealingPolylineLabelDescriptor constructor:
IlvAnnealingPolylineLabelDescriptor (Object label,
IlvLinkImage link,
int anchor)
The options for the anchor parameter are:
IlvAnnealingPolylineLabelDescriptor.CENTER: places the label near the center of the link (that is, in the middle third of the link path).
IlvAnnealingPolylineLabelDescriptor.START: places the label near the source node of the link (that is, in the first third of the link path).
IlvAnnealingPolylineLabelDescriptor.END: places the label near the target node of the link (that is, in the last third of the link path).
IlvAnnealingPolylineLabelDescriptor.FREE: places the label anywhere on the link.
Anchors for label positions at a link
Simple positioning at a polyline obstacle
Use this specification if the label must be placed at a polyline obstacle (an instance of
IlvLine or
IlvPolyline in the default labeling model). The polyline obstacle does not need to be a link of a grapher.
Here is an example:
layout.setLabelDescriptor(label,
new IlvAnnealingPolylineLabelDescriptor(label, polyline,
IlvAnnealingPolylineLabelDescriptor.FREE,
IlvDirection.Left, IlvDirection.TopLeft,
IlvAnnealingPolylineLabelDescriptor.GLOBAL));
The label is placed anywhere at the left or top side of the polyline obstacle, with preference given to the left side.
The example uses the following constructor:
IlvAnnealingPolylineLabelDescriptor
(Object label,
Object relatedPolylineObstacle,
int anchor,
int preferredSide,
int allowedSide,
int sideAssociation)
Even though a polyline does not have a source node or a target node, the anchor parameter can be used in the same way as for links ( CENTER, START, END, and FREE ). The first control point of the polyline is the start point. Labels with anchor START are placed closer to the first control point, and labels with anchor END are placed closer to the last control point of the polyline.
The value of the preferredSide parameter is a suggestion of where the label layout algorithm should preferably place the label. If the area at the preferred side is occupied, the label is placed elsewhere.
In contrast, the allowedSide parameter is a strict constraint: it is always obeyed, even if the entire area at the allowed side is occupied and the label must overlap the obstacles in that area.
Side association
The orientation of the preferred and allowed sides depend on the sideAssociation parameter. This parameter can take the following values (see the following figure):
IlvAnnealingPolylineLabelDescriptor.LOCAL IlvAnnealingPolylineLabelDescriptor.GLOBAL Side associations
Local side association
If the side association is local, each polyline has two sides: left and right. The sides can be determined from the flow direction of the polyline from start point to end point. Consider yourself standing on the polyline looking in the direction where the polyline continues towards the end point, and then determine which is the left and which is the right side. Hence, the meaning of left and right in local side association is relative to the polyline. The options for the preferredSide and allowedSide parameters are in this case:
IlvDirection.Left IlvDirection.Right You can also specify the value 0 for the allowed side, which indicates that you do not want to restrict the side: both sides are allowed.
Global side association
If the side association is global, the side specification is independent of the flow direction of the polyline and more like a compass direction: north is top, south is bottom, west is left, and east is right. Here more options are possible: in addition to the basic top, bottom, left, right, all meaningful combinations of these are allowed. You specify the sides in the following way:
IlvDirection.Left IlvDirection.Right IlvDirection.Top IlvDirection.Bottom You can also use combinations of these, such as:
IlvDirection.Left | IlvDirection.Right (left or right but not top or bottom).
IlvDirection.Left | IlvDirection.Top (which is the same as
IlvDirection.TopLeft, meaning the left or the top side).
You can specify the value 0 for the allowed side if all sides should be allowed.
Full positioning at a link
The most powerful constructor of a descriptor for a label that should be placed at a link is the following
IlvAnnealingPolylineLabelDescriptor one:
IlvAnnealingPolylineLabelDescriptor
(Object label,
Object link,
Object source,
Object target,
int anchor,
float maxDistFromLink,
float preferredDistFromLink,
int preferredSide,
int allowedSide,
int sideAssociation,
float topOverlap,
float bottomOverlap,
float leftOverlap,
float rightOverlap)
It combines all the possibilities described in
Simple positioning at a link: in addition to the anchor, the side association, and the allowed and preferred sides, you can specify overlap options.
You must pass the link as well as the source node and the target node of the link. The label does not need to touch the link. If you want to allow the label to be placed at a short distance from the link, then specify the maximum distance by maxDistFromLink and the preferred distance by preferredDistFromLink. Conversely, you may want to allow the link to partially overlap the label. You specify this by setting topOverlap, bottomOverlap, leftOverlap, or rightOverlap to a value larger than 0. This is illustrated in the following figure.
Distance and overlap at link
Full positioning at a polyline obstacle
If the label should be placed at a polyline that is not a link, then the most powerful
IlvAnnealingPolylineLabelDescriptor constructor is the following one:
IlvAnnealingPolylineLabelDescriptor
(Object label,
Object relatedObstacle,
IlvPoint[] referencePoints,
float lineWidth,
float minPercentageFromStart,
float maxPercentageFromStart,
float prefPercentageFromStart,
float maxDistFromPath,
float preferredDistFromPath,
int preferredSide,
int allowedSide,
int sideAssociation,
float topOverlap,
float bottomOverlap,
float leftOverlap,
float rightOverlap)
It combines all previously mentioned possibilities. If the label should be placed at a polyline obstacle, then pass this object as the related obstacle. If the label should be placed at an imaginary polyline, then pass the polyline with the points parameter and the width of the polyline with the lineWidth parameter. Instead of an anchor, you can pass the area where the label is placed by the minimum, maximum, and preferred percentage values relative to the polyline length. The minimum and maximum percentages are strictly obeyed, while the preferred percentage is only a recommendation for the layout.
For example, if you want to specify that the label can be placed anywhere but you prefer the center of the polyline, specify 0 and 100 for the minimum and maximum percentages and 50 for the preferred percentage. If there is not enough free space at the center, the label will be placed elsewhere.
If you want to specify that the label must be placed close to the center even if there is not enough space, then specify, for example, 40 and 60 for the minimum and maximum percentages instead.
Starting from an empty descriptor (polyline)
An alternative way to create a polyline label descriptor is to start with the empty descriptor:
descriptor = new IlvAnnealingPolylineLabelDescriptor();
Before using the empty descriptor, you must fill it with information on how the label should be placed. As a minimum, you need to specify a related obstacle or reference points and line width. For instance:
descriptor.setRelatedObstacle(polyline);
descriptor.setMinPercentageFromStart(10f);
descriptor.setMaxPercentageFromStart(90f);
descriptor.setPreferredPercentageFromStart(50f);
At the end of all changes, the descriptor must be passed to the layout instance:
layout.setLabelDescriptor(label, descriptor);
Once the descriptor is passed to the layout instance, it should normally not be changed. If you need to change it later, you must pass it to the layout instance again.
Example of specifying polyline label descriptor
Example of an
IlvGeneralLink instance with a label:
In CSS
You can specify all multiple criteria of the polyline labeling descriptor except the related obstacles. In CSS, the related obstacle is always the link that owns the label. The related source and target object of the link label descriptor are calculated from the source and target of the link. The reference points and line width are automatically derived from the link that owns the label, but can be overridden in the CSS specification.
link {
class: "ilog.views.sdm.graphic.IlvGeneralLink";
lineWidth: "2";
label: "@name";
decorationPositions[0]: "0.8"; // prefPercentageFromStart
}
In this example, the lineWidth value of the link automatically specifies the line width for the label descriptor, and the decorationPositions[0] affects where the label is anchored at the link. These parameters can be overridden by specifying the details of the polyline label descriptor in the following way:
link:labelLayout {
lineWidth: "3";
minPercentageFromStart: "30";
maxPercentageFromStart: "70";
preferredPercentageFromStart: "50";
maxDistFromPath: "4";
preferredDistFromPath: "2";
preferredSide: "Left";
allowedSide: "Left";
sideAssociation: "GLOBAL";
topOverlap: "1";
bottomOverlap: "1";
leftOverlap: "1";
rightOverlap: "1";
}
If the link is an instance of
IlvSDMCompositeLink and its fourth child is a label, the details of the descriptor for this label can be specified in the following way:
link:labelLayout_4 {
lineWidth: "3";
minPercentageFromStart: "30";
maxPercentageFromStart: "70";
preferredPercentageFromStart: "50";
maxDistFromPath: "4";
preferredDistFromPath: "2";
preferredSide: "Left";
allowedSide: "Left";
sideAssociation: "GLOBAL"”;
topOverlap: "1";
bottomOverlap: "1";
leftOverlap: "1";
rightOverlap: "1";
}
Rotated labels
Both the point label descriptor and the polyline label descriptor can be used if the rectangular label has a rotation that depends on its position.
These descriptors work only if the labeling model attached to the Annealing Label Layout implements the interface
IlvLabelingModelWithRotation. (The labeling model
IlvDefaultLabelingModel implements this interface.)
Rectangular label rotation dependent on position
In Java™ code, the label descriptor must describe how the rotation of a label depends on its position. For example, a label associated with a line can always have the rotation of the line. You must create a label descriptor whose method
getRotation returns the corresponding rotation. See
Label descriptor that returns the corresponding rotation.
Label descriptor that returns the corresponding rotation
layout.setLabelDescriptor(label,
new IlvAnnealingPolylineLabelDescriptor(label, line,
IlvAnnealingPolylineLabelDescriptor.FREE,
IlvDirection.Left, IlvDirection.TopLeft,
IlvAnnealingPolylineLabelDescriptor.GLOBAL) {
public double getRotation(IlvLabelingModel model,
IlvRect labelRect) {
IlvLine line = (IlvLine)getRelatedObstacle();
IlvPoint p1 = line.getFrom();
IlvPoint p2 = line.getTo();
double angle = Math.atan2(p2.y-p1.y, p2.x-p1.x);
return Math.toDegrees(angle);
}
});
If the method
getRotation is not overridden, it will query the labeling model for the rotation. Therefore, as an alternative to overriding the method
getRotation at the label descriptor, it is also possible to override the method
getRotation of the default labeling model (more specifically of the
IlvLabelingModelWithRotation interface).
In CSS, it is not necessary to specify the rotation of the label. Rotated labels are only supported for
IlvGeneralLink. CSS automatically uses polyline label descriptors that return the appropriate rotation for labels of
IlvGeneralLink.
Specific global parameters
The following global parameters are specific to the
IlvAnnealingLabelLayout class:
Label offset
The label offset controls the desired minimum distance between two neighboring labels (see
Label and obstacle offsets, left). To avoid labels being placed too close to each other, you can increase the label offset. This conceptually pushes the labels farther apart. However, depending on the available space, the minimum distance between labels cannot always be maintained.
Example of specifying label offset
To set the label offset:
In CSS
Add to the LabelLayout section:
labelOffset: "25";
In Java™
Call:
layout.setLabelOffset(25f);
Obstacle offset
The obstacle offset controls the desired minimum distance between a label and an unrelated obstacle. The obstacle offset is usually more important than the label offset because, if the obstacle offset is too small, the label may be placed so close to an unrelated obstacle that it incorrectly appears to be assigned to that obstacle (see
Label and obstacle offsets, right: does, for example, the green label belong to the upper yellow node or to the green node?). Increasing the obstacle offset conceptually pushes the label away from the obstacle. However, depending on the available space, the minimum distance between label and obstacle cannot always be maintained.
The obstacle offset should not be set to an unreasonably large value (such as Float.MAX_VALUE ) because this can cause computational problems inside quadtree operations.
Example of specifying node placement iterations and allowed time (GL algorithm)
To set the obstacle offset:
In CSS
Add to the LabelLayout section:
obstacleOffset: "25";
In Java
Call:
layout.setObstacleOffset(25f);
The specified obstacle offset works globally for all labels.
In Java, it is also possible to specify a smaller obstacle offset for specific label/obstacle pairs. You need to install an obstacle offset interface that returns the obstacle offset for a specified pair:
layout.setObstacleOffsetInterface(new IlvObstacleOffsetInterface() {
public float getOffset(IlvLabelingModel m, Object label, Object obstacle)
{
if (label instanceof IlvZoomableLabel &&
obstacle instanceof IlvIcon)
return 3f;
else
// use the global obstacle offset
return Float.MAX_VALUE;
}
});
The effective offset is the lower of the values returned by the obstacle offset interface and the globally specified offset respectively. Hence, the obstacle offset interface in the previous example means that
IlvZoomableLabel labels can be placed up to 3 units near
IlvIcon obstacles, while they are placed away from all other obstacles by at least the amount specified by the call
layout.setObstacleOffset.
Label and obstacle offsets
Label movement policy
The label movement policy is an easy way to define which labels should be moved by the label layout algorithm.
Example of specifying label movement policy
In CSS
It is not possible to specify the label movement policy in CSS, but you can integrate a label movement policy into the label layout renderer by subclassing the renderer. An example showing how to subclass renderers is provided in
Writing a new layout renderer to clip links.
In Java
The following code installs a label movement policy such that the layout moves only labels with a height greater than 100:
layout.setLabelMovementPolicy(new IlvLabelMovementPolicy() {
public boolean allowMove(IlvLabelingModel labelingModel, Object label)
{
return (labelingModel.boundingBox(label).height > 100);
}
});
Labels with smaller heights are not moved. However, they are also not completely ignored, because the layout tries to position the movable labels so that they do not overlap the immovable labels, and the label offset is respected between movable and immovable labels.
A more general useful example is a movement policy that prohibits moving labels that initially do not overlap anything. This predefined movement policy is available through the class
IlvOverlappingLabelMovementPolicy. You can use this class in applications that have their own label positioning mechanism and use the Annealing Label layout only as a postprocessing step to improve the positions of overlapping labels. To install this policy, call:
layout.setLabelMovementPolicy(new IlvOverlappingLabelMovementPolicy());
Automatic update
After layout, the labels are placed close to the related obstacle according to the label descriptor. For instance, a link label is placed close to its link. However, if you move the link interactively, the label normally stays at the old position, which may be far away from the link after the movement. The label loses the connection to the link, and a new layout is necessary.
Because it is too time-consuming to redo the layout after each single interactive move, the Annealing Layout algorithm has a feature that automatically updates the labels on geometric changes, that is, the label follows the link when the link moves.
Example of specifying automatic update
To enable this feature:
In CSS
Add to the LabelLayout section:
autoUpdate: "true";
In Java
Call:
layout.setAutoUpdate(true);
If automatic update is enabled, the algorithm does not perform a full layout of all labels during each interactive change. It repositions only the label whose related obstacle has moved in one step. Thus it may produce more overlaps than a full layout. The automatic update mechanism is much faster, however, and hence better suitable for interactions.
NOTE The automatic update feature works only if the labeling model provides
LabelingModelEvent objects on each obstacle movement. The
IlvDefaultLabelingModel provides these events. If you implement your own labeling model, you must provide these events in order to use the automatic update feature.
Expert parameters
A few parameters are available for an advanced use of the Annealing Label Layout.
Quadtree
To speed up the layout, the Annealing Label Layout algorithm uses a quadtree data structure. The quadtree enables a very efficient check for overlaps. The layout algorithm automatically detects from the graph model whether the quadtree can be used. You can switch it off explicitly by calling:
layout.setUseQuadtree(false);
Normally, it is not useful to switch the quadtree off because it slows down label positioning. However, if you implement your own labeling model, you may want to use this flag to verify that the labeling model is correct.
Simulated annealing
Simulated annealing is an iterative mechanism. In each iteration step, all labels are tested for better positions. Usually, the algorithm is capable of detecting automatically when to stop. The algorithm stops if:
The maximum number of iterations is reached.
After several iterations, no better position was found for any label.
After several iterations, the quality did not improve by a specified percentage.
In a few cases, it may be necessary to limit the number of iterations, which can be done by calling:
layout.setAllowedNumberOfIterations(100);
As a general hint, to obtain a reasonable layout, the allowed number of iterations should not be considerably lower than the number of labels.
Simulated Annealing stops if, after several iterations, no better position was found for any label. Because the search is randomized, this does not necessarily mean that the best position was already found; however, it indicates that finding the best position would require too much layout time. The number of ineffective iterations before stopping can be changed by calling:
layout.setMaxNumberOfFailIterations(maxNumber);
The default value is 20. If you set it to a higher value, the layout slows down but may find better positions for the labels. If you set it to a lower value, the layout stops sooner, but the label positions may be far from optimal.
In some cases, the algorithm improves the quality in each step, but the amount of improvement gets smaller in each step. In this situation, the previous fail-iteration criteria does not work well because there is an improvement in each step, but the amount of the improvement is so negligibly small that we want to stop. Therefore, it is also possible to require that the quality must improve in each step by a minimum percentage.
For example, to specify that the algorithm must improve over ten rounds by at least 2%, call:
layout.setNumberIterationsForMinImprovement(10);
layout.setMinImprovementPercentageToContinue(2);
By default, the layout stops if the quality did not improve by 0.5% over five iterations. If you set the required improvement percentage higher or the number of iterations lower, the layout stops sooner, but the label positions may be far from optimal. If you set the required percentage to 0%, this stop criterion is disabled and will no longer have any effect.
For experts: implementing your own label descriptors
The Annealing Label Layout is extensible. The point label descriptor and the polyline label descriptor are designed to cover the majority of the cases. In rare situations, you may want to implement your own label descriptor by subclassing the base class
IlvAnnealingLabelDescriptor. This section describes the necessary steps.
A label descriptor basically specifies the path where the top-left corner of a label can be placed. For simplification, it considers the path rolled out such that the path has only one dimension. If the path is known, the precise label position can be specified by just one value: the path location. The Annealing Label Layout proposes different path locations during the layout; however, it does not know what the path looks like. The task of the label descriptor is to translate the path location into concrete (x, y) coordinates of the label.
As an example, we want to create a label descriptor that can place labels precisely at a triangular obstacle. We could use the point label descriptor as an approximation, but it does not place the labels precisely at a triangular shape.
In the following figure, the upper diagram shows the path around the triangle (the dashed red and blue line). Below, you can see the same path rolled out in one dimension. The Annealing Label Layout may ask the label descriptor to place the label at position 1 to 8. For the Annealing Label Layout, these positions are just numbers between 0 and maxPathLocation. The task of the label descriptor is to translate these numbers into the correct positions as shown in the upper part of the figure.
Path locations at a triangle label descriptor
The base class
IlvAnnealingLabelDescriptor. has two protected data members:
actPathLocation is the current path location of the label.
maxPathLocation is the maximum value of the path location.
To create a new label descriptor, you need to implement a method that initializes the path constructor at the beginning of layout. You should calculate the maximum path location maxPathLocation and initialize the actPathLocation here. The method is called only once during layout:
void initialize(IlvLabelingModel labelingModel)
In the previous figure, the maximum path location for an equilateral triangle is:
3 * sidelength + 2 * labelwidth + 2 * labelheight
At each iteration step, the layout calls the method setPosition and provides an actual value for the path location. The method setPosition should store the value into actPathLocation and translate the path location into appropriate (x, y) coordinates. Then it should call the predefined method updatePosition(x, y) with these coordinates:
public void setPosition(double pathLocation, float distFromPath)
{
float x, y;
// make sure the position is between 0 and max
while (pathLocation > maxPathLocation)
pathLocation -= maxPathLocation;
while (pathLocation < 0)
pathLocation += maxPathLocation;
// store the actual position
actPathLocation = pathLocation;
// translate the path location into (x, y)
if (pathLocation < labelwidth + sidelength) {
x = (float)pathLocation;
y = triangleBottom;
} else if (pathLocation < labelwidth + labelheight + sidelength) {
x = labelwidth + sidelength;
y = triangleBottom - (float)pathLocation + labelwidth + sidelength;
} else if ... (other cases) ...
...
// finally, update the internal data structures
updatePosition(x, y);
}
The label may have a preferred position at the triangle. The Annealing Layout checks a location close to the preferred position from time to time. You should implement the following method to return the preferred path location:
double getPreferredPathLocation()
Furthermore, you should implement a strategy on how to come close to the preferred location. Towards the end of layout, the algorithm calls the method:
setTowardsPreferredPosition(pathLocation, dist, i, maxI)
to perform a sequence of steps that shift the label from the current position closer to the preferred position.
with i from 1 to maxI. Implement the method so that at each step you calculate a path location closer to the preferred location. When i is maxI, it should be exactly at the preferred location. You can call setPosition to move the label to the preferred (x, y) position. For instance:
public void setTowardsPreferredPosition(
double pathLocation, float dist, int i, int maxI)
{
double offset = pathLocation - getPreferredPathLocation();
double newLocation = pathLocation - i * offset / maxI;
setPosition(newLocation, dist);
}
These methods take the distance parameter in addition to the path location. This is the distance from the path. If the label must always be on the path, you can assume this distance is 0. Set it to a different value only if your label descriptor allows the label to have a variable offset from the path.
Copyright © 2018, Rogue Wave Software, Inc. All Rights Reserved.