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.
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:
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 theretrieveLabels
method in a subclass. There is an extension of this class:TLcdGXYDataObjectLabelPainter
which takes the text from the properties of a domain object that implementILcdDataObject
. -
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”.Figure 1. Offset icons can reduce clutter and improve readabilitySecondly, 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 theTLcdGXYLabelPainterAdapter
, 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 aTLcdGXYLabelPainterAdapter
to render an icon representation of the domain object and aTLcdGXYLabelPainter
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 specifiedjava/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. TheALcdGXYLabelStamp
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 returnsfalse
for points that fall outside of the circle andtrue
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 ILcdGXYLabelingAlgorithm
s 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
):
-
TLcdGXYLocationListLabelingAlgorithm
is a default decluttering algorithm, iterating over a number of customizable positions relative to the domain object’s anchor point. -
TLcdGXYOnPathLabelingAlgorithm
is a decluttering algorithm that places labels along the border of the domain object. -
TLcdGXYInPathLabelingAlgorithm
is similar toTLcdGXYOnPathLabelingAlgorithm
but positions labels inside the shape instead of on its perimeter. -
TLcdGXYCurvedPathLabelingAlgorithm
is similar toTLcdGXYOnPathLabelingAlgorithm
but drapes curved labels on the border of the domain object. -
TLcdGXYCompositeLabelingAlgorithm
allows you to specify a label placement algorithm per layer, per object, or even per label. The composite algorithm partitions the labels into groups that are passed on to the relevant delegate algorithms. For more information on how to use composite algorithms, refer to thelabel.placement.*
sample. -
TLcdGXYCompositeDiscretePlacementsLabelingAlgorithm
is similar toTLcdGXYCompositeLabelingAlgorithm
but allows interleaving the algorithms during the placement step. This means that you can, for example, first place a line label, then a point label, then another line label, and so on. For more information on how to use and configure algorithms, refer to thelabel.placement.*
sample.
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 thegetLocationX
andgetLocationY
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 ofILcdGXYLabelPainter2
. It can, for example, be the anchor point of the domain object as specified by theanchorPointSFCT
method of theILcdGXYPainter
of the domain object.
-
-
If the
getLocationIndex
method of theTLcdLabelLocation
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 theILcdGXYLabelPainter2
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.
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 theALcdGXYInteractiveLabelProvider
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 theALcdGXYInteractiveLabelProvider
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 returnsfalse
, thestopInteraction
method will not be called. -
The method
stopInteraction
tells theALcdGXYInteractiveLabelProvider
that the last provided interactive label should apply all outstanding changes, and prepare the label to be removed from the user interface. It returns aboolean
to indicate if the provider was successful in stopping the interactive label. If the method returnsfalse
, 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 returnfalse
and change the text field. -
The
cancelInteraction
method tells theALcdGXYInteractiveLabelProvider
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
.