This article explains the support that LuciadLightspeed offers for labeling the representations of your domain objects in a Lightspeed view.
-
Activating labeling describes how to enable labels for your domain objects.
-
Customizing the label content and look describes different styling options available for labels and how to define label content.
-
Positioning labels describes how to control the position of the labels.
-
Configuring label decluttering describes how to configure label decluttering.
-
Interacting with labels describes how to interactively move labels or edit them.
-
Using obstacle providers describes how to set up label-free areas on the screen.
-
Painting 3D labels describes how to handle painting order for 3D labels.
-
Advanced label placement describes the details of the label placement algorithms.
What is a label?
A label is any description or classifier you attach to a domain object representation: a single word, a multi-line text string, an icon, or even an interactive panel.
LuciadLightspeed makes no distinction between regular painting and label painting. Just as regular painters, label painters
implement the ILspPainter
interface. Label painters can also be added to a layer, and they can be customized with an ILspStyler
.
There are a few notable differences between label objects and regular painted objects though:
-
Labels are usually painted on top of all regular object representations. See also Layer ordering.
-
Labels are usually painted in screen coordinates, not as objects that are part of the displayed world.
-
The position and visibility of individual labels can be coordinated across different layers.
Activating labeling
The recommended way to activate label painting for a model is to attach ALspLabelStyler
objects to a layer’s label painter using a TLspShapeLayerBuilder
. The labels will appear if the styler returns at least one style for the domain objects. Program: Enabling label painting by setting stylers on a TLspShapeLayerBuilder
demonstrates how you can do this.
TLspShapeLayerBuilder
TLspShapeLayerBuilder layerBuilder = TLspShapeLayerBuilder.newBuilder(); layerBuilder.labelStyler(TLspPaintState.REGULAR, styler); // other layer builder calls ... ILspLayer layer = layerBuilder.build();
You can also just set one or more fixed styles directly on the layer builder, without using a styler. Program: Enabling label painting by setting styles on a TLspShapeLayerBuilder
demonstrates how you can do this.
TLspShapeLayerBuilder
layerBuilder.labelStyles(TLspPaintState.REGULAR, TLspTextStyle.newBuilder().build());
Alternatively, if you are not using a layer builder to create a TLspLayer
, you can set a TLspLabelPainter
on the layer for paint representation LABEL
, and attach the stylers to that painter. Program: Enabling label painting by attaching a TLspLabelPainter
to a layer shows how you can do this.
TLspLabelPainter
to a layer
TLspLayer layer = new TLspLayer(); TLspLabelPainter labelPainter = new TLspLabelPainter(); labelPainter.setStyler(TLspPaintState.REGULAR, styler); layer.setPainter(TLspPaintRepresentation.LABEL, labelPainter);
Customizing the label content and look
You can handle all of the customization of the labels through ALspLabelStyler
objects. Styling allows you to specify the content, the look, the positioning and the number of displayed labels. ALspLabelStyler
objects offer advanced options like defining multiple labels or specifying positioning or priorities. In some cases a regular
ILspStyler
is sufficient. It doesn’t offer advanced labeling options though.
The criteria for choosing an ALspLabelStyler
for labels are very much the same as those for choosing stylers for regular shape painting. You can pick a simple TLspLabelStyler
with just one style, or you can decide to use advanced stylers that use the view scale to manage level-of-detail, or that
handle animations. As with regular painting, you can set a styler for each TLspPaintState
: REGULAR
, SELECTED
and EDITED
. If you do not specify a styler for SELECTED
, the painter will fall back to a modified REGULAR
styler. If you do not specify a styler for EDITED
, the painter will fall back to the SELECTED
styler if available and otherwise to a modified REGULAR
styler.
To see your labels, define your label styler so that it applies at least one style for the domain objects, for example a TLspTextStyle
or a TLspIconStyle
. Otherwise, nothing is displayed. For more information about applying text or icon styles, see Using text as labels and Using images as labels. Program: Very basic label stylers shows the most basic label stylers possible.
ILspStyler regularStyler = TLspTextStyle.newBuilder().textColor(Color.white).build(); ILspStyler selectedStyler = TLspTextStyle.newBuilder().textColor(Color.red).build();
Program: A styler that applies different styles to different domain objects shows a custom label styler that applies different styles to different domain objects.
public class MyStyler extends ALspLabelStyler { @Override public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { for (Object domainObject : aObjects) { if (...) { aStyleCollector.object(domainObject) .styles(someStyles) .algorithm(someAlgorithm) .submit(); } else { aStyleCollector.object(domainObject) .styles(otherStyles) .algorithm(otherAlgorithm) .submit(); } } } }
Using text as labels
The most common purpose of labels is to display textual information next to the domain object. You can display a textual
label by using a TLspTextStyle
in your label styler, as already shown in Program: Very basic label stylers.
TLspTextStyle
allows you to specify not only the presence of a label, but also the look of the label text. TLspTextStyle
offers basic settings such as font, color and halo. It also allows you to set the alignment and line spacing for multi-line
labels. Table 1, “Text label styling options” demonstrates these settings.
Style | Result |
---|---|
The default text style: |
|
Text style with a different font: |
|
Text style in red without halo: |
|
Text style for multi-line labels: |
|
If you would like to add properties of your domain objects as label content,
use TLspDataObjectLabelTextProviderStyle
. This implementation of ALspLabelTextProviderStyle
is highly suitable for the usage of object properties as label text. See the section below for more information.
Customizing text label content
By default, the text content of a label is retrieved using the toString()
method of the domain object. You can override the default content by adding an ALspLabelTextProviderStyle
to your label styler. You can implement a text provider style yourself, or use a TLspDataObjectLabelTextProviderStyle
to add properties of ILcdDataObject
domain objects to your label. For more information about ILcdDataObject
objects, see Unified access to domain objects.
Formulate the label content as expressions. If you specify more than one expression, each expression appears on a separate
line in the label, as shown in Program: Using two ILcdDataObject
properties as one multi-line text label.
ILcdDataObject
properties as one multi-line text label
TLspDataObjectLabelTextProviderStyle.newBuilder(). expressions("STATE_NAME", "POP1996").build()
Changing text label content
Changes to the text content of labels are not necessarily picked up immediately.
This means you have to notify the painters when a domain object’s text has changed. You can use two kinds of events to notify painters:
-
Model change events for the relevant domain objects, for example through the
ILcdModel.elementChanged
method. These events should for example be used when the content of a data object is changed. -
Style change events for the relevant domain objects, for example through the
ALspStyler.fireStyleChangeEvent
method. These events should for example be used when using a newTLspDataObjectLabelTextProviderStyle
with different expressions.
Using images as labels
Beside text content, you can also display arbitrary image content as a label. To display an image as a label, use a TLspIconStyle
in your stylers. The TLspIconStyle
contains an ILcdIcon
. There are various ways to use this feature:
-
A simple
TLcdImageIcon
based on a file. You can define the styler so that a property of the domain object determines which file is selected. -
A custom
ILcdIcon
implementation that uses AWTGraphics
for icon drawing.
Changing icon-based label content
Similar to text content, labels painted by icons are not updated automatically when the icon changes. Updates are triggered by model change events or style change events only.
To detect a possible change in the icon, the painter uses the equals
method. Therefore, two different icons should not compare as equal. For performance reasons though, make sure that identical
icons always compare as equal. Preferably use the same instance.
Using Swing components as labels
Beside text and images, you can also display Swing components as a label. To do this, use ALspSwingLabelStyler
. This styler has a method that returns a Swing component for a specific label. Using this component, the styler generates
an image that is used by the label painter. Because of this it is not possible to interact with the used component. To see
how it is possible to interact with labels that are composed of Swing components, see Creating interactive labels that users can edit.
lightspeed.labels.interactive
sample.
Options for additional styling
In addition to the text and image styling options described above, you have a few other styles at your disposal to change the look of your labels. These additional styles can be used either with text labels or image labels:
-
TLspPinLineStyle
: indicate that a pin line should be drawn from the label to its domain object anchor point. -
TLspLabelBoxStyle
: specifies stylings such as the background color, the presence of a label frame or a halo. -
TLspLabelOpacityStyle
: specifies transparency of the labels, or a color to blend the label with. This style can be used efficiently with animations, highlighting or other changes that happen frequently, as this style is applied on the label using the graphics hardware without any texture redrawing.
Table 2, “Additional labeling styling options” shows how to use these styles and the results.
Style | Result |
---|---|
Pins: |
|
Frame and fill: |
|
Opacity and blend color: |
|
Defining multiple labels for a domain object
To display more than one label for your domain objects you need a custom ILspStyler
that uses the ALspLabelStyleCollector.label(aSublabelID)
call. As shown in Program: Defining multiple labels for domain objects in a ALspLabelStyler
, you can specify different sets of styles for different sublabels for one domain object using ALspLabelStyleCollector
.
The argument aSublabelID
is an object that identifies the sublabel for a domain object. You would typically use a fixed set of different sublabels,
such as a Java enum
or a set of incrementing numbers.
You can safely use your own objects by following these guidelines:
-
Make sure the sublabel ID is not equal for different labels on the same domain object. Otherwise only one of the two labels is displayed.
-
The sublabel ID must be equal for the same label of a domain object on subsequent styling calls. Although the label will be displayed correctly, an unequal sublabel ID for the same label creates unnecessary overhead: the painter will assume one label was removed and a new one was added.
-
The sublabel ID can be shared between different domain objects, so you can use constants.
-
This
label()
call is optional if you need just one label: a default sublabel ID of"single"
will be used.
Program: Defining multiple labels for domain objects in a ALspLabelStyler
shows you how you can define multiple labels.
ALspLabelStyler
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects).label(1).styles(someStyles).submit(); aStyleCollector.objects(aObjects).label(2).styles(otherStyles).submit(); aStyleCollector.objects(aObjects).label(3).styles(moreStyles).submit(); }
Positioning labels
This section describes the way LuciadLightspeed positions labels with respect to the shape representing the domain object.
By default, LuciadLightspeed uses the type of shape to determine where to place labels, as shown in Figure 2, “Default label positions for points, lines and polygons”:
-
For point shapes, the labels are placed at an offset in one of the compass directions relative to the point. Figure 3, “All possible locations when using
TLspLabelLocationProvider
” shows these possible positions. -
For line shapes, the labels are placed along the line.
-
For area shapes, the labels are placed inside the area, near the center of mass.
You can control the positioning of your labels in various ways using the styling API.
The styling API allows you to:
-
Define one or more relative locations. See Configuring relative label positions for more information.
-
Override the anchor point relative to which the position is calculated. See Defining a different anchor point for labels for more information.
-
Specify another label relative to which the position is calculated. See Defining labels with a dependency on other labels for more information.
Alternatively, you can use a custom label placement algorithm, although this is more involved. For more information, refer to Using an algorithm for label decluttering.
Configuring relative label positions
You can fully customize the positioning of your label by specifying an algorithm in your ILspStyler
using ALspLabelStyleCollector
. Using an algorithm for label decluttering describes the algorithms delivered with LuciadLightspeed and how to use them.
For convenience, labels on point-based domain objects are automatically positioned relative to the point using the TLspLabelingAlgorithm
. By default, one of the eight compass direction locations shown in Figure 3, “All possible locations when using TLspLabelLocationProvider
” is used, or the label is placed on top of the point itself.
TLspLabelLocationProvider
You can override these positions by specifying a different set of locations in your styler:
-
Using the
locations
method onALspLabelStyleCollector
. Note that this is a convenience method that internally calls thealgorithm
method with aTLspLabelingAlgorithm
configured withe aTLspLabelLocationProvider
. -
Using the
algorithm
method onALspLabelStyleCollector
with aTLspLabelingAlgorithm
configured with anALspLabelLocationProvider
. This can for example beTLspLabelLocationProvider
, which is a convenience implementation that allows you to use the eight compass directions or place the label on top of the point, as shown in Figure 3, “All possible locations when usingTLspLabelLocationProvider
”. For example, Program: Defining two possible locations, above and below the object, offset by 50 pixels limits the label positioning possibilities to two locations, namely above and below the point. The offset between point and label is set to 50 pixels. Alternatively a custom implementation ofALspLabelLocationProvider
can be provided. Consult the API documentation for more details. -
Using the
algorithm
method onALspLabelStyleCollector
with an otherILspLabelingAlgorithm
implementations.
private final ALspLabelLocationProvider fLocationProvider = new TLspLabelLocationProvider(50, Location.NORTH, Location.SOUTH); private final ILspLabelingAlgorithm fAlgorithm = new TLspLabelingAlgorithm(fLocationProvider); public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects).algorithm(fAlgorithm).submit(); }
Defining a different anchor point for labels
By default, a label is positioned relative to the domain object’s focus point. Defining a different anchor point for the label may help you achieve the following goals:
-
Add labels to different shapes in an
ILcdShapeList
. -
Add labels to different parts of a shape.
-
Label a domain object which itself is no shape, by specifying a shape that should be labeled. For example, by using a property of the domain object that defines a location.
To specify a different anchor point for a label, you can use ALspStyleCollector.geometry()
calls.
Just as for regular styling, use either a specific ILcdShape
for each domain object, or pass an ALspStyleTargetProvider
that calculates the anchor point when requested.
It is important to note that the anchor points defined this way need to be specified in the reference of the ILcdModel
that contains the domain objects.
Figure 4, “Labels anchored to specific points” shows such an example: each river polyline not only has a label for its name, but also a label indicating the river’s spring
and mouth. Program: Defining different anchor points for labels shows the ALspStyler
and ALspStyleTargetProvider
used to accomplish this.
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { ALspLabelTextProviderStyle textProvider = new ALspLabelTextProviderStyle() { public String[] getText(Object aDomainObject, Object aSublabelID, TLspContext aContext) { return new String[] {aDomainObject.toString() + " " + aSublabelID.toString()}; } }; ALspStyleTargetProvider firstPointProvider = new ALspStyleTargetProvider() { public void getStyleTargetsSFCT(Object aObject, TLspContext aContext, List<Object> aResultSFCT) { ILcdPointList points = (ILcdPointList) aObject; aResultSFCT.add(points.getPoint(0)); } }; ALspStyleTargetProvider lastPointProvider = new ALspStyleTargetProvider() { public void getStyleTargetsSFCT(Object aObject, TLspContext aContext, List<Object> aResultSFCT) { ILcdPointList points = (ILcdPointList) aObject; aResultSFCT.add(points.getPoint(points.getPointCount() - 1)); } }; aStyleCollector.objects(aObjects).label("river").styles(textStyle).submit(); aStyleCollector.objects(aObjects).label("spring").geometry(firstPointProvider).styles(textStyle, pinStyle, textProvider).submit(); aStyleCollector.objects(aObjects).label("mouth").geometry(lastPointProvider).styles(textStyle, pinStyle, textProvider).submit(); }
Defining labels with a dependency on other labels
If you are using multiple labels per domain object, you can position one label relative to another label, instead of to the
labeled domain object. To achieve this, use the ALspLabelStyleCollector.anchorLabel(aSublabelID)
call, and specify a label previously defined for the domain object as sublabel ID.
For example, Figure 5, “Labels positioned relative to other labels” show a city with a couple of labels: the top icon represents the "info" label; with the city and state next to it as text labels. The bottom icon represents a "statistics" label with the city’s population and housing units numbers next to it. If the icon labels move, the text labels stay with them.
Program: Defining labels positioned relative to other labels shows the ALspLabelStyler
used to accomplish this.
public void style( Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext ) { aStyleCollector.objects(aObjects).label("info").styles(infoStyles).submit(); aStyleCollector.objects(aObjects).label("name").anchorLabel("info").styles(nameStyles).submit(); aStyleCollector.objects(aObjects).label("state").anchorLabel("info").styles(stateStyles).submit(); aStyleCollector.objects(aObjects).label("statistics").styles(fStatsStyles).submit(); aStyleCollector.objects(aObjects).label("population").anchorLabel("statistics").styles(populationStyles).submit(); aStyleCollector.objects(aObjects).label("housing_units").anchorLabel("statistics").styles(housingUnitsStyles).submit(); }
Configuring labels along a polyline’s path
By default, for polyline objects, LuciadLightspeed uses straight labels that are aligned with the polyline at a certain point.
Sometimes however, you want the labels to closely follow the polyline, so that the labels themselves are bent according to
the polyline. You can do this by using the TLspCurvedPathLabelingAlgorithm
.
Program: Defining labels closely following a polyline shows how to use this algorithm in your styler. Figure 6, “Labels positioning along a path (left) versus following a path (right)” shows the default behavior, regular labels placed aligned with a path, next to curved labels following a path closely using
TLspCurvedPathLabelingAlgorithm
.
private final ILspLabelingAlgorithm fCurvedPathAlgorithm = new TLspCurvedPathLabelingAlgorithm(); public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects).styles(...).algorithm(fCurvedPathAlgorithm).submit(); }
Configuring label decluttering
Each Lightspeed view has decluttering algorithms to make sure labels never overlap with each other. By default, all layers in the view and their labels are automatically decluttered together, so that no label in the view overlaps with another label.
You can customize this behavior for each layer or even for each individual label. To do so, you must define and use a placement group.
A declutter group specifies:
-
The decluttering algorithm to use:
-
If you want labels to be decluttered, use a
TLspLabelConflictChecker
. -
If you do not want any label decluttering, use a
TLspNoDeclutterLabelConflictChecker
. -
You can also provide your own implementation of
ILspLabelConflictChecker
.
-
-
Areas in the view that cannot be used to place labels. You can optionally attach
ILspLabelObstacleProvider
objects to a group usingTLspLabelPlacer.addLabelObstacleProvider
. Using obstacle providers explains this in detail.
The label placement process treats each placement group separately, so labels from different placement groups can overlap.
You can add your own placement group using TLspLabelPlacer.addPlacementGroup
. Each view already contains four pre-defined placement groups that you can use:
-
TLspLabelPlacer.DEFAULT_DECLUTTER_GROUP
: this group is used by default for all layers and all their labels. No labels in this group can overlap. -
TLspLabelPlacer.DEFAULT_NO_DECLUTTER_GROUP
: this group can be used for labels that should always be shown, even if they overlap. -
TLspLabelPlacer.DEFAULT_GRID_GROUP
: this group is used for grid labels by default. Because this is a separate group, grid labels are decluttered amongst themselves, but they can still overlap with other labels. -
TLspLabelPlacer.DEFAULT_REALTIME_GROUP
: this convenience group can be used when labeling dynamic data, for example tracks. This group allows them to be decluttered independently from static labels.
The easiest way to use a declutter group is through the ALspLabelStyleCollector
, e.g. by using ALspLabelStyler
. You can specify the group to use for all labels, or for differently for individual labels if necessary. Program: Defining the declutter group for labels shows an example.
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects) .label(1) .group(TLspLabelPlacer.DEFAULT_DECLUTTER_GROUP) .styles(...) .submit(); aStyleCollector.objects(aObjects) .label(2) .group(TLspLabelPlacer.DEFAULT_NO_DECLUTTER_GROUP) .styles(...) .submit(); }
Setting label priorities across layers
During decluttering, the label placement process tries to find a location for each label where no other label has been placed yet. Because certain labels may be more important than others, you can specify a priority for each label type. All labels in the declutter group are sorted by this priority and placed in this order. High priority labels are placed first, and lower priority labels may be skipped if there is no spare view room left. Since a declutter group can contain labels from different layers, the priorities are compared across layers.
The priorities are compared across different layers, so you have to align them. Also note that a lower number means a higher priority. |
Priorities are defined through the ALspLabelStyleCollector
:
-
Use a fixed priority. This can be used in simple cases where all labels have the same priority. Program: Defining label priorities shows this: domain objects have two labels with different (static) priorities.
-
Use a
ILspLabelPriorityProvider
. This is often easier when each label has its own calculated priority. Program: Defining label priorities using aILspLabelPriorityProvider
shows this: labels of capital cities have precedence over others.
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects).label(1).priority(100).styles(...).submit(); aStyleCollector.objects(aObjects).label(2).priority(200).styles(...).submit(); }
ILspLabelPriorityProvider
ILspLabelPriorityProvider citiesPriorityProvider = new ILspLabelPriorityProvider() { public int getPriority(TLspLabelID aLabelID, TLspPaintState aPaintState, TLspContext aContext) { if (isCapital( aDomainObject )) { return 10; } else { return 20; } } } public void style( Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext ) { aStyleCollector.objects( aObjects ).priority(citiesPriorityProvider).styles( ... ).submit(); }
Interacting with labels
Users can interact with labels just like they can with representations of domain objects: they can select labels and perform certain operations on them. Moving labels explains how to move labels. If you want to offer more complex interaction, you can use interactive labels, which allow you to represent your label with any Swing component and interact with it. This is explained in Creating interactive labels that users can edit.
Moving labels
Just as domain object modifications can be triggered by input events, an input event can also trigger the editing of one or more labels painted in the view. You can make use of the LuciadLightspeed label editor classes to modify the label positions.
You can easily enable label editing on a TLspShapeLayerBuilder
, as shown in Program: Enabling editable labels using a TLspShapeLayerBuilder
. The layer will use the default label editor implementation, TLspLabelEditor
.
TLspShapeLayerBuilder
layerBuilder.labelEditable(true);
If you create your own layer without using a layer builder, the following steps are required:
-
Choose an editor: either use the default
TLspLabelEditor
, or create a custom implementation ofILspEditor
. -
Create an editable layer: to configure a layer as editable, set the editable flag to
true
:layer.setEditable(true)
and register the chosen label editor with the correct paint representation:layer.setEditor(paintRepresentation, chosenEditor)
. -
Set the edit controller: set the
TLspEditController
on the view withview.setController(new TLspEditController())
, so that the editor can receive input events from the UI handles it provides. Note that the default controller can edit as well.
The default label editor implementation provided by LuciadLightspeed is TLspLabelEditor
. This label editor converts label locations to TLspStampLabelLocation
objects. This makes it possible to move a label anywhere on the screen.
Creating interactive labels that users can edit
An interactive label is a label that users can interact with to modify properties of domain objects. You can configure interactive
labels using an ALspInteractiveLabelProvider
. To display and edit interactive labels, you can use a TLspInteractiveLabelsController
. For more information about the usage of controllers, see Interacting with the view<<lsp_advanced_gui>.
For performance reasons, interactive labels are only shown when the user moves the mouse cursor over a regular label. If the
ALspInteractiveLabelProvider
indicates that it can provide an interactive label, the interactive label is presented to the user instead of the regular
label.
lightspeed.labels.interactive
sample.
Implementing an ALspInteractiveLabelProvider
When you are implementing an ALspInteractiveLabelProvider
, the most important method is the startInteraction
method. This method returns the Swing component with which the user can interact. This component can be any Swing component:
it can be a single text field or a JPanel
containing check boxes, combo boxes, and more.
There can be only one interactive label from the same provider at any time. Therefore, the implementation of this method can
always return the same instance of the Swing component. The methods of ALspInteractiveLabelProvider
are always called in a certain order. There are never two calls to startInteraction
before the previous interaction is stopped or canceled. This allows you to attach listeners to the domain object and the
Swing components in the startInteraction
method, for example, and to remove these listeners in the implementation of the stopInteraction
and cancelInteraction
methods.
Activating interactive labels
TLspInteractiveLabelsController
shows interactive labels based on mouse-moved events. When the mouse hovers over a label, it provides an interactive label
for that label. For touch input, where hover actions do not exist, 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 TLspInteractiveLabelsController
is set to false
, no interactive labels are shown automatically. You can then ask the ALspInteractiveLabelProvider
to provide an interactive label with the startInteraction
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 ALspInteractiveLabelProvider
can dispatch mouse or touch events that happen on the interactive label to the view. The active ILspController
can then receive the events and use those, for example, to move the label with the label editor. Which events are dispatched
to the view and which events are dispatched to the interactive label is decided by the dispatchAWTEvent
method of the ALspInteractiveLabelProvider
. 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 view.
Using obstacle providers
The label decluttering algorithms used by the ILspLabelPlacer
can ensure that no two labels overlap on the screen.
However, it is often useful to mark areas in the screen where no labels should be displayed. For example:
-
The body representations of domain objects. You could avoid displaying labels over icons in the view.
-
GUI components overlaid on the view. You could avoid displaying labels behind those components since they would be hard to read.
To accomplish this, you can attach ILspLabelObstacleProvider
objects to a declutter group. An obstacle provider returns a set of areas on the screen where no labels should be placed.
To attach an obstacle provider to a declutter group, use TLspLabelPlacer.addLabelObstacleProvider()
. Note that you are responsible for removing the obstacle provider from the placer at the appropriate time.
Since an obstacle provider is attached to a declutter group, all labels and algorithms used in that group take the areas returned by the providers into account. |
Painting 3D labels
All the labeling functionality described above applies both to 2D and 3D views. Positioning and decluttering work in the same way, although terrain can influence the visibility of the labels.
You need to take into account a subtlety with respect to painting order, however. In 2D, labels are always painted on top
of other content. In 3D, labels are painted in the OPAQUE
paint phase. This means other content painted in the TRANSPARENT
phase can be over the labels if it is closer to the camera. Figure 9, “Labels can be obscured by content in 3D.” shows this.
If you do not want this behavior, you can activate full overlay using TLspLabelPainter.setOverlayLabels
. Figure 10, “Labels overlaid in 3D.” shows the result.
Advanced label placement
The previous sections explain the various ways to configure labeling in your application and should be enough for most usages. This section provides more background information on label placement.
It is possible for the view to position and declutter labels. For this purpose ILspLabelPlacer
is used. Whereas label painters determine the content and appearance of the labels, ILspLabelPlacer
computes if and where the label is positioned. On top of that, it stores the label location information so that the painter,
for example, can access it.
Note: the default label placer works asynchronously. This means the label visibility is not updated on every paint, and can scale better with many labels. |
The label placement and decluttering functions, of which the goal is to avoid overlap between labels, can take multiple labels of a group of domain objects into account. The group can be so large as to contain all the objects visible in the view, including objects of different layers. This allows for the more advanced placement of labels, to improve readability and avoid overlap.
Using label placers to locate labels explains the label placer implementations and how they use an algorithm to compute the label position. It has a subsection that details the labeling algorithm implementations and one that shows how custom labeling algorithms can be created or custom priorities can be set. Accessing label locations programmatically explains how label locations can be accessed programmatically.
Using label placers to locate labels
Because a label placer operates on the labels of all layers, it is located in the ILspView
, and can be retrieved using the getLabelPlacer
method. In the Lightspeed view implementations, a default ILspLabelPlacer
is already set. It can be configured in the layers. If this does not suffice, you can configure the label placer manually,
and set it on the layer using the setLabelPlacer
method of ILspView
.
Lightspeed views offers one label placer implementation:
-
TLspLabelPlacer
is an implementation ofILspLabelPlacer
. It can be configured with a small set of methods.
Using an algorithm for label decluttering
The LuciadLightspeed label placer implementations use labeling algorithms to place and declutter labels. It is possible to customize the placement of labels by choosing a different labeling algorithm.
LuciadLightspeed offers the following labeling algorithms:
-
TLspLabelingAlgorithm
is a decluttering algorithm that iterates over a number of customizable locations. -
TLspOnPathLabelingAlgorithm
is a decluttering algorithm that places straight labels along a path. -
TLspInPathLabelingAlgorithm
is similar toTLspOnPathLabelingAlgorithm
but positions labels inside a shape instead of on its perimeter. -
TLspCurvedPathLabelingAlgorithm
is similar toTLspOnPathLabelingAlgorithm
but places curved labels on a path. -
TLspCompositeLabelingAlgorithm
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. -
TLspCompositeDiscreteLabelingAlgorithm
is similar toTLspCompositeLabelingAlgorithm
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.
See the package com.luciad.view.lightspeed.label.algorithm
for more information.
Enabling a certain algorithm can be done through the ALspLabelStyler
, as shown in Program: Specifying labeling algorithms using ALspLabelStyleCollector
.
ALspLabelStyleCollector
public void style(Collection<?> aObjects, ALspLabelStyleCollector aStyleCollector, TLspContext aContext) { aStyleCollector.objects(aObjects).label(1).algorithm(algorithm1).submit(); aStyleCollector.objects(aObjects).label(2).algorithm(algorithm2).submit(); }
Accessing label locations programmatically
Apart from placing labels, ILspLabelPlacer
also keeps track of the storage location of labels. For this, it uses ALspLabelLocations
, accessed with the getLabelLocations()
method of ILspLabelPlacer
. This object makes it possible to check if labels are visible and where they are located, or to listen to visibility or location
changes. It also makes it possible to manipulate label locations or visibility manually.