How to implement a custom label model
Tips & TricksSummary
Description
In some cases the built in label models are not sufficient to produce the desired result, e.g. when a node label should fit into an irregular shape. This article describes how to create a custom label model.
The following example shows how to create a label model which places the label in the center of the node and sizes the label to 2/3 of the node bounds.
A custom label model has to implement the interfaceILabelModel. This interface has four methods to implement:
public function lookup(type:Class):Object: This method may returnnullpublic function getGeometry(modelParameter:ILabelModelParameter, label:ILabel):IOrientedRectangle: This is the most important method: it calculates the label's bounds based on the model parameterpublic function createDefaultParameter():ILabelModelParameter: Returns the default model parameterpublic function getContext(label:ILabel, parameter:ILabelModelParameter):ILookup: Provides a lookup context for the given combination of label and parameter. This method was introduced with yFiles FLEX 1.6 and may be ignored using earlier versions.
ExteriorLabelModel.south is a label model parameter for the model
ExteriorLabelModel with the label positioned below the node.
The label model parameter has to implement the interface ILabelModelParameter. This interface
has three methods to implement:
public function clone():Object: creates a copy of the parameter.public function get model():ILabelModel: returns the label model.public function supports(label:ILabel):Boolean: returnstrueif the label is supported by the label model (e.g. node label models support only node labels.
Implementation of the model parameter
- The model paramter needs to know its model. Thus, we pass the model in the constructor and store
it in a field. The
modelgetter returns this field. - The model is the only parameter which we need to clone.
- This parameter supports all node labels, so our
supportsmethod returnstrueif the label's owner is a node.
package demo.test
{
import com.yworks.graph.model.ILabel;
import com.yworks.graph.model.ILabelModel;
import com.yworks.graph.model.ILabelModelParameter;
import com.yworks.graph.model.INode;
public class ExampleLabelModelParameter implements ILabelModelParameter
{
private var _model:ExampleLabelModel;
public function ExampleLabelModelParameter(model:ExampleLabelModel)
{
this._model = model;
}
public function clone():Object
{
return new ExampleLabelModelParameter(_model);
}
public function get model():ILabelModel
{
return this._model;
}
public function supports(label:ILabel):Boolean
{
return label != null && label.owner is INode;
}
}
}
Implementation of the model
createDefaultParameter()returns an instance of the only parametergetGeometry()uses the label owner's layout to calculate the bounding rectangle for the label which should be centered and have 2/3 of the node's size. If the calculation is not possible (there is no label or the label's owner is not a node), an empty rectangle will be returned.- We don't need the lookup at the moment, so it returns
null - We don't need the context either, so it returns
com.yworks.support.Lookups.EMPTY.
package demo.test
{
import com.yworks.canvas.geom.IOrientedRectangle;
import com.yworks.canvas.geom.IRectangle;
import com.yworks.canvas.geom.OrientedRectangle;
import com.yworks.graph.model.ILabel;
import com.yworks.graph.model.ILabelModel;
import com.yworks.graph.model.ILabelModelParameter;
import com.yworks.graph.model.INode;
import com.yworks.support.ILookup;
import com.yworks.support.Lookups;
public class ExampleLabelModel implements ILabelModel
{
public function lookup(type:Class):Object
{
return null;
}
public function getGeometry(modelParameter:ILabelModelParameter, label:ILabel):IOrientedRectangle
{
if (label != null) {
var node:INode = label.owner as INode;
if (node != null) {
var layout:IRectangle = node.layout;
// size is 2/3 of the node size
var width:Number = layout.width * 0.6;
var height:Number = layout.height * 0.6;
// place the rectangle centered
var x:Number = layout.x + (layout.width - width)/2;
// note that in an oriented rectangle
// x,y represent its *lower* left corner
var y:Number = layout.y + (layout.height + height)/2;
return OrientedRectangle.create(x, y, width, height, 0, -1);
}
}
return OrientedRectangle.EMPTY;
}
public function createDefaultParameter():ILabelModelParameter
{
return new ExampleLabelModelParameter(this);
}
public function getContext(label:ILabel, parameter:ILabelModelParameter):ILookup
{
return Lookups.EMPTY;
}
}
}
Advanced features
The lookup() and getContext() methods of a label model are queried by the yFiles Flex library to provide additional
functionality.
Currently lookup() is queried for the following interfaces (for details see the API docs):
| Interface | Needed for |
|---|---|
ILabelModelParameterProvider |
Asked for possible label positions when a label is moved by the user. |
ILabelModelParameterFinder |
Asked for the best label model parameter for a given label layout (lets the label "snap" to a possible candidate). |
ISerializer |
A serializer which can handle this label model parameter (note that the parameters are serialized, not the model. Also note that a matching deserializer has to be registered with the IOHandler). |
INodeInsetsProvider |
Takes the label into account for node insets. |
getContext() is queried to get a lookup for the following interface (for details see the API docs):
| Interface | Needed for |
|---|---|
ILabelCandidateDescriptor |
Describes some properties of a candidate label model parameter that may be used by automatic labeling algorithms. |
Example: A label model which places the label centered inside the node
The example label model VerticalCenterStretchLabelModel, which can be downloaded as resource, can be used to place a label centered inside a node. It overcomes the following shortcomings of the build in label models:
InteriorLabelModel.centerplaces the label at the center of the node. A long label text, however, will exceed the node's bounds.InteriorStretchLabelModel.centeruses the node's bounds as label bounds. Thus, the label will not exceed the node's bounds. The text, however, will start at the top left corner of the node. Thus, the label text does not appear centered.
The VerticalCenterStretchLabelModel uses the node's bounds as maximum width and height. If necessary, it breaks the lines and word wraps the text. The label's actual bounds are not larger than necessary. Thus, the label doesn't exceed the node's bounds and appears centered within the node.
The label model parameter can be obtained using the createDefaultParameter() method. To center the text within the label's bounds one has to set the text format's align parameter to "center". Example usage:
// get a model parameter
graphCanvas.graph.defaultNodeLabelModelParameter = new VerticalCenterStretchLabelModel().createDefaultParameter();
// center the text within the label
var style:SimpleLabelStyle = new SimpleLabelStyle();
style.textFormat = FontManager.INSTANCE.getDefaultTextFormat();
style.textFormat.align = TextFormatAlign.CENTER;
graphCanvas.graph.defaultNodeLabelStyle = style;