This article explains the support that LuciadLightspeed offers for labeling the representations of your domain objects. A label can be anything: a single word, a multi-line text string, an icon, or even an interactive panel. Painting labels is similar to painting domain objects, with the following differences:

  • Labels are usually painted on top of all regular object representations.

  • The labeling API explicitly models multiple labels for an object’s representation.

  • Labels have more advanced positioning options. This allows decluttering of labels to avoid overlap.

The following sections describe a typical labeling scenario, how to paint labels, how to work with label locations, how to interact with labels, and some more advanced labeling topics.

A typical labeling scenario

The following requirements are exemplary for a typical labeling scenario:

  • You want to place a single label next to your domain object’s representation.

  • The label is text-based, its text comes from a certain property of the object.

  • To prevent cluttering, overlapping labels should be dropped.

To determine the number of labels, the label content, and the rendering of the label, you need to configure an ILcdGXYLabelPainterProvider on the layer so that it returns an ILcdGXYLabelPainter2 for all domain objects. All the main implementations of ILcdGXYLabelPainter2 that LuciadLightspeed provides also implement the ILcdGXYLabelPainterProvider interface, and return themselves as label painter for every object. This is convenient when all objects of a layer need the same label painter.

Choosing a label painter details the LuciadLightspeed implementations of the label painters and label painter providers. In the sample scenario, a TLcdGXYDataObjectLabelPainter is used: a text-based label painter which you can configure to paint one of the properties of the domain objects. The following snippet shows how to configure this label painter on the layer.

Program: Configuring an ILcdGXYLabelPainter2 for a TLcdGXYLayer
TLcdGXYLayer layer = TLcdGXYLayer.create(aModel);
//Activate labeling and configure the styling of those labels
TLcdGXYDataObjectLabelPainter labelPainter = new TLcdGXYDataObjectLabelPainter();
labelPainter.setExpressions("identifier");
labelPainter.setForeground(Color.WHITE);
labelPainter.setHaloEnabled(true);
labelPainter.setHaloColor(Color.BLACK);
layer.setGXYLabelPainterProvider(labelPainter);
layer.setLabeled(true);

By default, the layer paints all labels for all objects on the location suggested by the label painter. To add decluttering behavior or more advanced placement behavior, you need to set up and configure an ILcdGXYLabelingAlgorithm. In the sample scenario, we use a TLcdGXYLocationListLabelingAlgorithm. This algorithm places as many labels as possible without introducing overlapping labels. This snippet shows you how:

Program: Configuring the label placement
TLcdGXYLocationListLabelingAlgorithm labelingAlgorithm = new TLcdGXYLocationListLabelingAlgorithm();
layer.setGXYLabelingAlgorithmProvider(l -> labelingAlgorithm);

Painting labels

LuciadLightspeed provides the interface ILcdGXYLabelPainter2 for painting labels. A label painter determines the content and visualization of your labels. It typically paints the label at a position near the domain object.

For more information on how to influence where and when labels are placed, refer to Working with label locations.

Choosing a label painter describes LuciadLightspeed’s default implementations. One particular implementation, TLcdGXYStampLabelPainter, allows you to customize the painting of labels by providing a label stamp as explained in Customizing labels using label stamps. If you want complete control of the painting of labels, you can provide your own implementation of ILcdGXYLabelPainter2 as explained in Implementing your own label painter. Finally, Adding a halo to labels explains how to add a halo effect to your labels.

Choosing a label painter

As described in A typical labeling scenario, you need to install an ILcdGXYLabelPainter2 on your ILcdGXYLayer to add labels for your domain objects. LuciadLightspeed provides several ILcdGXYLabelPainter2 implementations in the packages com.luciad.view.gxy and com.luciad.view.gxy.painter:

  • TLcdGXYLabelPainter is the default label painter for text-based labels and allows full configuration of the appearance of the label (the font, the color, whether or not the text should be in a frame, and more). Refer to the API reference to see the complete list of properties which you can modify to customize the appearance.

    The text for the label is retrieved by using the toString method of the domain object that is labeled, but this can be overridden by overwriting the retrieveLabels method in a subclass. There is an extension of this class: TLcdGXYDataObjectLabelPainter which takes the text from the properties of a domain object that implement ILcdDataObject.

  • TLcdGXYStampLabelPainter delegates the actual painting of the label to a label stamp. This allows you to easily customize the graphical representation of a label without having to worry about its location. This stamp could, for example, paint the label as simple text, or could use a complex AWT/Swing component to paint the label. Refer to Customizing labels using label stamps for more information on label stamps.

    This painter also support interactive labels, which are described in Working with interactive labels.

  • TLcdGXYLabelPainterAdapter wraps around a regular painter so it can be used as a label painter. Painting many domain objects usually results in a cluttered view, making selection more difficult and making it difficult to distinguish one object from another. Painting object representations as a label has two advantages. Firstly, it provides the possibility to paint labels at a certain offset which reduces cluttering as shown in Figure 1, “Offset icons can reduce clutter and improve readability”.

    offset icon
    Figure 1. Offset icons can reduce clutter and improve readability

    Secondly, it provides the possibility to position the labels using the label placers as described in Using label placers. Refer to the labels.offset.* sample on how to use the TLcdGXYLabelPainterAdapter, and Labels as domain object representations and Labeling labels for more information on using labels to represent the domain object.

  • TLcdGXYCompositeLabelPainter allows you to combine different label painters for the different labels of an object. For example, it can hold a TLcdGXYLabelPainterAdapter to render an icon representation of the domain object and a TLcdGXYLabelPainter to render a text label attached to the icon.

Customizing labels using label stamps

The easiest way to implement your own labels is to setup a TLcdGXYStampLabelPainter with a custom ALcdGXYLabelStamp implementation.

Splitting up the label painter in the TLcdGXYStampLabelPainter and an ALcdGXYLabelStamp separates the information on where to draw the label from how to draw the label. The TLcdGXYStampLabelPainter takes care of determining the label location and then passes this position to the ALcdGXYLabelStamp.

In order to calculate the label location, the dimensions of the label must be known in advance. As this is related to how the labels are drawn, the ALcdGXYLabelStamp needs to provide this information. Therefore the ALcdGXYLabelStamp class contains the following three methods:

  • dimensionSFCT determines the size of the label. This needs to be independent of the location, because the size is needed to determine the location.

  • paint paints the label on the specified java/awt Graphics instance. The Graphics is already translated and rotated, so the stamp can start painting at (0,0). Nevertheless, location and rotation information is provided, in case the stamp needs it. The ALcdGXYLabelStamp should make sure not to paint outside of the bounds that it has returned, as the labeling algorithms typically rely on the bounds to declutter the labels.

  • isTouched determines if the label is present at the specified location. For example, when the label is circular, the bounds of that label, which are rectangular by definition, cover a larger area than is actually used by the label. This method then returns false for points that fall outside of the circle and true for those within the circle.

If TLcdGXYStampLabelPainter does not suit your needs, it is possible to make your own implementation of the interface ILcdGXYLabelPainter2 from scratch as explained in Implementing your own label painter.

Adding a halo to labels

Similarly as in Painting halos around objects, you can add a halo effect around a label. TLcdGXYLabelPainter and TLcdGXYStampLabelPainter have built-in support for halos.

You can enable halos on other label painters using the class TLcdGXYHaloLabelPainter. This class wraps around an existing ILcdGXYLabelPainter2, adding a halo to anything drawn by the wrapped painter. The methods to configure the label halo functionality are the same as those mentioned in Painting halos around objects. Make sure to disable the image cache or to call the method clearImageCache on TLcdGXYHaloLabelPainter when the label is modified.

Working with label locations

It is possible to complement a label painter with an ILcdGXYViewLabelPlacer. Whereas ILcdGXYLabelPainter2 determines the content and appearance of the labels, ILcdGXYViewLabelPlacer computes if and where the label is positioned.

A label placer takes multiple labels of a group of domain objects into account. The group can be as large as all the objects visible in the view, including objects of different layers. This allows more advanced placing of labels, to improve readability and avoid overlap.

Using label placers explains the label placer implementations and how they use an algorithm to compute the label positions. Using label algorithms details the label algorithm implementations. Customizing label locations and label dependencies explains where the label locations are stored and how you can customize them.

Using label placers

Because a label placer operates on the labels of all layers, it is configured on the view by calling the setGXYViewLabelPlacer method of the ILcdGXYView implementation. LuciadLightspeed offers two label placer implementations (see the com.luciad.view.gxy.labeling package), which you can configure both with a placement algorithm that encapsulates the actual positioning logic:

  • TLcdGXYLabelPlacer is the default label placer implementation. It allows setting an obstacle provider to prevent the placing of labels at certain positions.

  • TLcdGXYAsynchronousLabelPlacer offers the same functionality, but positions the labels in its own thread. Use this implementation in combination with asynchronously painted layers as described in the the asynchronous painting documentation.

Using label algorithms

The view’s label placer uses one or more ILcdGXYLabelingAlgorithms to determine which labels are placed and at which locations. The easiest way to use these algorithms is to configure them directly on your layer using TLcdGXYLayer#setGXYLabelingAlgorithmProvider.

LuciadLightspeed offers the following labeling algorithms (see the package com.luciad.view.gxy.labeling.algorithm):

One way to customize these algorithms is to wrap them, for example to further remove unwanted labels. To illustrate this, the labels.createalgorithm.* sample shows some possible algorithm wrappers. If you want even more control of label placement, you can consider implementing your own labeling algorithm as described in Implementing your own placement algorithm.

Customizing label locations and label dependencies

Storing and retrieving label locations

Objects that compute label positions, such as label placers or edit controllers (see Graphically editing labels) store these locations in the ALcdLabelLocations of an ILcdGXYEditableLabelsLayer.

With the getLabelLocation method of an ALcdLabelLocations instance you can retrieve the location information for a given label. This information needs to be interpreted by the label painter. The ALcdLabelLocations instance not only stores the label locations, but also which labels are painted. For example, in the case that a certain labeling algorithm (see Using label algorithms) decides that there is not enough place on the screen to paint all labels. In this case you can use the method applyOnPaintedLabelLocations of the ALcdLabelLocations instance to go over all labels that are painted, for example to determine which labels are visible at a certain location on the screen.

Using label locations

All the information regarding the location of a label is contained in a TLcdLabelLocation instance. This instance can specify the location of the label in several ways: as a location index, as a position relative to the labeled domain object, or as an absolute position on the view. The actual placing of the label depends on how the location is specified:

  • If the getLocationIndex method returns a value smaller than zero, the label is freely placed and its position is specified by the getLocationX and getLocationY methods. This type of placement is used by most labeling algorithms. How these methods are interpreted depends on the label edit mode:

    • If the label edit mode indicates that the TLcdLabelLocation contains absolute information, the result of the methods is interpreted as absolute coordinates on the view.

    • If the label edit mode indicates that the TLcdLabelLocation contains a relative position, the result of the methods is interpreted as the horizontal and vertical offset with respect to a certain point of the domain object. What that point exactly is depends on the implementation of ILcdGXYLabelPainter2. It can, for example, be the anchor point of the domain object as specified by the anchorPointSFCT method of the ILcdGXYPainter of the domain object.

  • If the getLocationIndex method of the TLcdLabelLocation instance returns a value larger than or equal to zero, the label location is placed on a discrete location. What the actual position is, depends on how the ILcdGXYLabelPainter2 interprets this location index.

It is possible to subclass TLcdLabelLocation so it can contain more information. Refer to the API reference for more information.

Labels as domain object representations

TLcdLabelLocation allows declaring a label as being a domain object representation instead of a label of the domain object representation with the method setBodyLabel. This information is used by the TLcdGXYEditControllerModel2 to influence editing behavior: moving the label moves the entire domain object. It can also be used to influence the paint order of labels. The main advantage of painting domain objects as labels is that they can reduce clutter because they can take part in the label placement process.

The labels.offset.* sample has an icon representation of its domain objects. These icon labels are modeled as body labels. The sample shows how to make a layer paint these body labels along with the regular domain object representations, and the other labels along with the other labels of the view.

Labeling labels

TLcdLabelLocation allows declaring a dependency on another label with the method setParentLabel. ILcdGXYLabelPainter2 implementations can use this information to paint the label relative to this parent label, instead of the domain object. ILcdGXYViewLabelPlacer implementations can use this information to make sure that the parent labels are positioned first. Hence, you can use this to label a label which is especially useful when you are painting the object representation as a label. TLcdLabelLocations allows setting a dependency provider so that the label locations automatically have their parent labels configured.

The labels.offset.* sample shows how to use dependencies to paint labels relative to an icon representation of the object, which is in turn modeled as a label.

Interacting with labels

Just like with representations of domain objects, it is possible to interact with labels: users can select them and perform certain operations on them. Graphically editing labels explains how to graphically edit labels so users can, for example, move them around. If you want more complex interaction, you can use interactive labels, which allow you to represent your label with any java.awt.Component. This is explained in Working with interactive labels.

Graphically editing labels

Similar to graphically editing domain objects with an ILcdGXYEditor, you can edit the labels of a domain with an ILcdGXYLabelEditor. The following sections provide more details on the main implementations of ILcdGXYLabelEditor and how to install and obtain an ILcdGXYLabelEditor.

Main implementations of ILcdGXYLabelEditor

TLcdGXYLabelPainter and TLcdGXYStampLabelPainter implement ILcdGXYLabelEditor, allowing users to move labels around. When asked to edit the location of a label, it modifies the TLcdLabelLocation by setting the location index to -1 which indicates free placement. It also adjusts the location information so that the offset, specified in the given ILcdGXYContext, is added to the label position.

Installing and obtaining an ILcdGXYLabelEditor

ILcdGXYLabelEditor instances are obtained with the getGXYLabelEditor method of the ILcdGXYEditableLabelsLayer interface. This interface is an extension of ILcdGXYLayer which provides methods to support editable labels. TLcdGXYEditController2 retrieves an ILcdGXYLabelEditor using this interface to offer mouse-based editing.

TLcdGXYLayer implements ILcdGXYEditableLabelsLayer and delegates the retrieval of a label editor to an ILcdGXYLabelEditorProvider. All the main implementations of ILcdGXYLabelEditor provided in LuciadLightspeed also implement this ILcdGXYLabelEditorProvider interface and return themselves as label editor for every object.

Program: Installing an ILcdGXYLabelEditor on an ILcdGXYEditableLabelsLayer shows how to install a label editor on a layer.

Program: Installing an ILcdGXYLabelEditor on an ILcdGXYEditableLabelsLayer (from samples/gxy/labels/interactive/MainPanel)
aCitiesLayer.setGXYLabelEditorProvider(composite);

Working with interactive labels

An interactive label is a label with which the user can interact to modify properties related to the domain object. You can configure interactive labels using an ALcdGXYInteractiveLabelProvider. To display and edit interactive labels you can use a TLcdGXYInteractiveLabelsController.

Interactive labels are only shown when the user moves the mouse cursor over a regular label. This is done for performance reasons. In case the ALcdGXYInteractiveLabelProvider indicates that it can provide an interactive label for a certain label, it is asked to provide the interactive label. The interactive label is then presented to the user.

Implementing an ALcdGXYInteractiveLabelProvider

The most important method when implementing an ALcdGXYInteractiveLabelProvider is the provideInteractiveLabel method. This method returns the java.awt.Component with which the user can interact. This component can be any AWT/Swing component; it can be a single text field or a JPanel containing check boxes, combo boxes, and more. As there can only be one interactive label from the same provider at the same time, the implementation of this method can always return the same instance of the AWT/Swing component.

Additionally an ALcdGXYInteractiveLabelProvider provides the following methods:

  • The canProvideInteractiveLabel method is used to determine if the ALcdGXYInteractiveLabelProvider can provide an interactive label for the given object and indices. This allows fine-grained control over which labels can be interactive and which not. This method is always called before the ALcdGXYInteractiveLabelProvider is asked to actually provide an interactive label.

  • The method canStopInteraction is called to ask if the interactive label can be stopped. This is useful for validation purposes and similar cases. Consider, for example, a text field in the interactive label that contains invalid text. In this case the interactive label should not stop. Otherwise, the user cannot correct the text. If the method returns false, the stopInteraction method will not be called.

  • The method stopInteraction tells the ALcdGXYInteractiveLabelProvider that the last provided interactive label should apply all outstanding changes, and prepare the label to be removed from the user interface. It returns a boolean to indicate if the provider was successful in stopping the interactive label. If the method returns false, the label will not be removed from the user interface and the user can keep interacting with the label. Consider, for example, an interactive label with a text field that contains invalid text. When the label is asked to stop the interaction, it can return false and change the text field.

  • The cancelInteraction method tells the ALcdGXYInteractiveLabelProvider to prepare the interactive label to be removed from the user interface without applying any changes. This request cannot be denied, consequently this method does not return a boolean.

As mentioned before, there can only be one interactive label from the same provider at the same time. As a result, the methods of ALcdGXYInteractiveLabelProvider are always called in a certain order. There are never two calls to provideInteractiveLabel before the previous interaction is stopped or canceled. This allows you to attach all the necessary listeners to the domain object and the AWT/Swing components in the provideInteractiveLabel method, and to remove these listeners in the implementation of the stopInteraction and cancelInteraction methods.

Activating interactive labels

TLcdGXYInteractiveLabelsController uses a mouse listener to show interactive labels. When the mouse hovers over a label, it provides an interactive label for that label. For touch input, where no hover exists, the interactive label is put in place at the first touch.

It is also possible to bypass the automatic behavior. When the setProvideInteractiveLabelOnMouseOver method of TLcdGXYInteractiveLabelsController is set to false, no interactive labels are shown automatically. You can then ask the ALcdGXYInteractiveLabelProvider to provide an interactive label with the provideInteractiveLabel method. To stop or cancel the label interaction, you can use the stopInteraction and cancelInteraction methods respectively as this no longer happens automatically either. Bypassing the automatic behavior allows you, for example, to implement a scenario in which a user cycles through the labels by pressing a certain key on the keyboard, making each label interactive one after the other.

Using mouse and touch events

In order to move the interactive labels, the ALcdGXYInteractiveLabelProvider can dispatch mouse or touch events that happen on the interactive label to the ILcdGXYView. The active ILcdGXYController can then receive the events and use those, for example, to move the label with the ILcdGXYLabelEditor. Which events are dispatched to the view and which events are dispatched to the interactive label is decided by the dispatchAWTEvent method of the ALcdGXYInteractiveLabelProvider. By default this method dispatches the event to the view when it happened on a JLabel or JPanel. It dispatches all other events to the originating component itself. As a result, when the user clicks and drags on a JLabel, the controller of the view can handle these events and can, for example, move the label. Clicking on, for example, a JSlider would as expected drag the knob. You can override this method to customize which events are dispatched to the interactive label and which are dispatched to the ILcdGXYView.

Whereas the dispatchMouseEvent is used strictly for mouse events, a more general method is available to dispatch AWT events: dispatchAWTEvent. Currently this method is called with touch events and mouse events. Mouse events are by default passed to the dispatchMouseEvent method by the dispatchAWTEvent method.

Placing the interactive label in a different java.awt.Container

By default the TLcdGXYInteractiveLabelsController uses the view itself as the java.awt.Container that is used to add the interactive label to the user interface. You can customize this by overriding addComponentToGXYView and related methods. Override this method, for example, if the ILcdGXYView implementation used in your application does not extend from java.awt.Container.