docs/api-reference/layers/text-layer.md
import {TextLayerDemo} from '@site/src/doc-demos/layers';
<TextLayerDemo />The TextLayer renders text labels at given coordinates.
TextLayer is a CompositeLayer that wraps around the IconLayer. It automatically creates an atlas texture from the specified font settings and characterSet.
import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';
<Tabs groupId="language"> <TabItem value="js" label="JavaScript">import {Deck} from '@deck.gl/core';
import {TextLayer} from '@deck.gl/layers';
const layer = new TextLayer({
id: 'TextLayer',
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-stations.json',
getPosition: d => d.coordinates,
getText: d => d.name,
getAlignmentBaseline: 'center',
getColor: [255, 128, 0],
getSize: 16,
getTextAnchor: 'middle',
pickable: true
});
new Deck({
initialViewState: {
longitude: -122.4,
latitude: 37.74,
zoom: 11
},
controller: true,
getTooltip: ({object}) => object && object.name,
layers: [layer]
});
import {Deck, PickingInfo} from '@deck.gl/core';
import {TextLayer} from '@deck.gl/layers';
type BartStation = {
name: string;
coordinates: [longitude: number, latitude: number];
};
const layer = new TextLayer<BartStation>({
id: 'TextLayer',
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-stations.json',
getPosition: (d: BartStation) => d.coordinates,
getText: (d: BartStation) => d.name,
getAlignmentBaseline: 'center',
getColor: [255, 128, 0],
getSize: 16,
getTextAnchor: 'middle',
pickable: true
});
new Deck({
initialViewState: {
longitude: -122.4,
latitude: 37.74,
zoom: 11
},
controller: true,
getTooltip: ({object}: PickingInfo<BartStation>) => object && object.name,
layers: [layer]
});
import React from 'react';
import {DeckGL} from '@deck.gl/react';
import {} from '@deck.gl/layers';
import type {PickingInfo} from '@deck.gl/core';
type BartStation = {
name: string;
coordinates: [longitude: number, latitude: number];
};
function App() {
const layer = new TextLayer<BartStation>({
id: 'TextLayer',
data: 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/website/bart-stations.json',
getPosition: (d: BartStation) => d.coordinates,
getText: (d: BartStation) => d.name,
getAlignmentBaseline: 'center',
getColor: [255, 128, 0],
getSize: 16,
getTextAnchor: 'middle',
pickable: true
});
return <DeckGL
initialViewState={{
longitude: -122.4,
latitude: 37.74,
zoom: 11
}}
controller
getTooltip={({object}: PickingInfo<BartStation>) => object && object.name}
layers={[layer]}
/>;
}
To install the dependencies from NPM:
npm install deck.gl
# or
npm install @deck.gl/core @deck.gl/layers
import {TextLayer} from '@deck.gl/layers';
import type {TextLayerProps} from '@deck.gl/layers';
new TextLayer<DataT>(...props: TextLayerProps<DataT>[]);
To use pre-bundled scripts:
<script src="https://unpkg.com/deck.gl@^9.0.0/dist.min.js"></script>
<!-- or -->
<script src="https://unpkg.com/@deck.gl/core@^9.0.0/dist.min.js"></script>
<script src="https://unpkg.com/@deck.gl/layers@^9.0.0/dist.min.js"></script>
new deck.TextLayer({});
Inherits from all Base Layer and CompositeLayer properties.
sizeScale (number, optional) {#sizescale}Text size multiplier.
sizeUnits (string, optional) {#sizeunits}pixelsThe units of the size, one of 'meters', 'common', and 'pixels'. See unit system.
sizeMinPixels (number, optional) {#sizeminpixels}0The minimum size in pixels. When using non-pixel sizeUnits, this prop can be used to prevent the icon from getting too small when zoomed out.
sizeMaxPixels (number, optional) {#sizemaxpixels}Number.MAX_SAFE_INTEGERThe maximum size in pixels. When using non-pixel sizeUnits, this prop can be used to prevent the icon from getting too big when zoomed in.
billboard (boolean, optional) {#billboard}trueIf true, the text always faces camera. Otherwise the text faces up (z).
background (boolean, optional) {#background}falseWhether to render background for the text blocks.
If a valid content box is defined by getContentBox, the background will fill this box. Otherwise, background is generated around the natural bounding box of the text.
backgroundBorderRadius (number | number[4], optional) {#backgroundborderradius}0The border-radius of the background, a number or an array of 4 numbers.
[bottom_right_corner, top_right_corner, bottom_left_corner, top_left_corner] border radius in pixel.backgroundPadding (number[4], optional) {#backgroundpadding}[0, 0, 0, 0]The padding of the background, an array of either 2 or 4 numbers.
[padding_x, padding_y] in pixels.[padding_left, padding_top, padding_right, padding_bottom] in pixels.fontFamily (string, optional) {#fontfamily}'Monaco, monospace'Specifies a prioritized list of one or more font family names and/or generic family names. Follow the specs for CSS font-family.
See the remarks section below for tips on using web fonts.
characterSet (string[] | Set<string> | string, optional) {#characterset}Specifies a list of characters to include in the font.
'auto', automatically detects the characters used in the data. This option has a performance overhead and may cause the layer to take longer to load if the data is very large.getText, a warning will be logged to the JavaScript console.Note that there is a limit to the number of unique characters supported by a single layer. The maximum number subjects to fontSettings.fontSize and the MAX_TEXTURE_SIZE of the device/browser.
fontWeight (number | string, optional) {#fontweight}normal.css font-weight.
lineHeight (number, optional) {#lineheight}1.0.A unitless number that will be multiplied with the current font size to set the line height.
fontSettings (object, optional) {#fontsettings}Advance options for fine tuning the appearance and performance of the generated shared fontAtlas.
Options:
fontSize (number): Font size in pixels. Default is 64. This option is only applied for generating fontAtlas, it does not impact the size of displayed text labels. Larger fontSize will give you a sharper look when rendering text labels with very large font sizes. But larger fontSize requires more time and space to generate the fontAtlas.buffer (number): Whitespace buffer around each side of the character. Default is 4. In general, bigger fontSize requires bigger buffer. Increase buffer will add more space between each character when layout characterSet in fontAtlas. This option could be tuned to provide sufficient space for drawing each character and avoiding overlapping of neighboring characters.sdf (boolean): Flag to enable / disable sdf. Default is false. sdf (Signed Distance Fields) will provide a sharper look when rendering with very large or small font sizes. TextLayer integrates with TinySDF which implements the sdf algorithm.radius (number): How many pixels around the glyph shape to use for encoding distance. Default is 12. Bigger radius yields higher quality outcome. Only applies when sdf: true.cutoff (number): How much of the radius (relative) is used for the inside part the glyph. Default is 0.25. Bigger cutoff makes character thinner. Smaller cutoff makes character look thicker. Only applies when sdf: true.smoothing (number): How much smoothing to apply to the text edges. Default 0.1. Only applies when sdf: true._getFontRenderer (function, optional) {#_getfontrenderer}Experimental - supplies a custom glyph renderer.
(settings: Required<FontSettings>) => FontRenderer
If provided, this callback is invoked with the resolved font settings and should return an object with:
measure: given a character, returns glyph metrics. The metrics are expected to contain the following fields:
advance (number): horizontal distance to move the cursor before placing the next glyph, in pixels.width (number): width of the visible glyph bounds, in pixels.ascent (number): distance from the baseline to the top of the glyph, in pixels.descent (number): distance from the baseline to the bottom of the glyph, in pixels.draw: renders a character to glyph image.
data (ImageData): rasterized glyph pixels.left (number, optional): x offset from the glyph origin to the left edge of data, in pixels. Default 0.top (number, optional): y offset from the glyph origin to the top edge of data, in pixels. Default 0.This hook overrides the default glyph generation path, including the built-in SDF renderer used when fontSettings.sdf is enabled.
wordBreak (string, optional) {#wordbreak}break-wordAvailable options are break-all and break-word. A valid maxWidth has to be provided to use wordBreak.
maxWidth (number, optional) {#maxwidth}-1A unitless number that will be multiplied with the current text size to set the width limit of a string. If specified, when the text is longer than the width limit, it will be wrapped into multiple lines using the strategy of wordBreak.
For example, maxWidth: 10.0 used with getSize: 12 is roughly the equivalent of max-width: 120px in CSS.
contentCutoffPixels (number[2], optional) {#contentcutoffpixels}[0, 0]Minimum visible region of the content box, as [width, height] in screen pixels. If the visible width or height is smaller than the specified cutoff, the corresponding text is hidden completely.
This prop can be used to set the minimum length of clipped texts to improve readability.
Only effective with a valid content box returned by getContentBox.
contentAlignHorizontal (string, optional) {#contentalignhorizontal}'none'Align the text horizontally to the visible region of the content box.
This prop can be used to keep the text visible while zooming and panning, similar to the CSS position: 'sticky' behavior.
Only effective with a valid content box returned by getContentBox. Usually used with a matching getTextAnchor prop. See the demo of "dynamic align" in content box behavior.
Supported values:
'none''start''center''end'contentAlignVertical (string, optional) {#contentalignvertical}'none'Align the text vertically to the visible region of the content box.
This prop can be used to keep the text visible while zooming and panning, similar to the CSS position: 'sticky' behavior.
Only effective with a valid content box returned by getContentBox. Usually used with a matching getAlignmentBaseline prop. See the demo of "dynamic align" in content box behavior.
Supported values:
'none''start''center''end'outlineWidth (number, optional) {#outlinewidth}0Width of outline around the text, relative to the font size. Only effective if fontSettings.sdf is true.
outlineColor (Color, optional) {#outlinecolor}[0, 0, 0, 255]Color of outline around the text, in [r, g, b, [a]]. Each channel is a number between 0-255 and a is 255 if not supplied.
getText (Accessor<string>, optional) {#gettext}x => x.textMethod called to retrieve the content of each text label.
getPosition (Accessor<Position>, optional) {#getposition}x => x.positionMethod called to retrieve the location of each text label.
getSize (Accessor<number>, optional) {#getsize}32The font size of each text label, in units specified by sizeUnits (default pixels).
getColor (Accessor<Color>, optional) {#getcolor}[0, 0, 0, 255]The rgba color is in the format of [r, g, b, [a]]. Each channel is a number between 0-255 and a is 255 if not supplied.
getAngle (Accessor<number>, optional) {#getangle}0The rotating angle of each text label, in degrees.
getTextAnchor (Accessor<string>, optional) {#gettextanchor}'middle'The text anchor. Available options include 'start', 'middle' and 'end'.
getAlignmentBaseline (Accessor<string>, optional) {#getalignmentbaseline}'center'The alignment baseline. Available options include 'top', 'center' and 'bottom'.
getPixelOffset (Accessor<number[2]>, optional) {#getpixeloffset}[0, 0]Screen space offset relative to the coordinates in pixel unit.
getContentBox (Accessor<number[4]>, optional) {#getcontentbox}[0, 0, -1, -1]Called to retrieve the context box that contains the text. Characters that overflow the area are not displayed. Returns [x, y, width, height], where all values are world space (meter) offsets from the text anchor position.
x, y define the rectangle's origin relative to the text anchor.width, height define the rectangle size.width disables clipping on the X axis.height disables clipping on the Y axis.Read more about content box behavior.
getBackgroundColor (Accessor<Color>, optional) {#getbackgroundcolor}[255, 255, 255, 255]The background color. Only effective if background: true.
The rgba color is in the format of [r, g, b, [a]]. Each channel is a number between 0-255 and a is 255 if not supplied.
getBorderColor (Accessor<Color>, optional) {#getbordercolor}[0, 0, 0, 255]The border color of the background. Only effective if background: true.
The rgba color is in the format of [r, g, b, [a]]. Each channel is a number between 0-255 and a is 255 if not supplied.
getBorderWidth (Accessor<number>, optional) {#getborderwidth}0The border thickness of each text label, in pixels. Only effective if background: true.
The TextLayer renders the following sublayers:
characters - an IconLayer rendering all the characters.background - the background for each text block, if background: true.This section is about the special requirements when supplying attributes directly to a TextLayer.
Because each text string has a different number of characters, when data.attributes.getText is supplied, the layer also requires an array data.startIndices that describes the character index at the start of each text object. For example, if there are 3 text objects of 2, 3, and 4 characters each, startIndices should be [0, 2, 5, 9].
Additionally, all other attributes (getColor, getWidth, etc.), if supplied, must contain the same layout (number of characters) as the getText buffer.
Example use case:
const TEXT_DATA = [
{
text: 'Hello',
position: [-122.4, 37.7],
color: [255, 0, 0]
},
{
text: 'World',
position: [-122.5, 37.8],
color: [0, 0, 255]
},
// ...
];
new TextLayer({
data: TEXT_DATA,
getText: d => d.text,
getPosition: d => d.position,
getColor: d => d.color
})
The equivalent binary attributes would be:
// USE BINARY
// Flatten the text by converting to unicode value
// Non-Latin characters may require Uint16Array
// [72, 101, 108, 108, 111, ...]
const texts = new Uint8Array(TEXT_DATA.map(d => Array.from(d.text).map(char => char.charCodeAt(0))).flat());
// The position attribute must supply one position for each character
// [-122.4, 37.7, -122.4, 37.7, -122.4, 37.7, ...]
const positions = new Float64Array(TEXT_DATA.map(d => Array.from(d.text).map(_ => d.position)).flat(2));
// The color attribute must supply one color for each character
// [255, 0, 0, 255, 0, 0, 255, 0, 0, ...]
const colors = new Uint8Array(TEXT_DATA.map(d => Array.from(d.text).map(_ => d.color)).flat(2));
// The "layout" that tells TextLayer where each string starts
const startIndices = new Uint16Array(TEXT_DATA.reduce((acc, d) => {
const lastIndex = acc[acc.length - 1];
acc.push(lastIndex + d.text.length);
return acc;
}, [0]));
new TextLayer({
data: {
length: TEXT_DATA.length,
startIndices: startIndices, // this is required to render the texts correctly!
attributes: {
getText: {value: texts},
getPosition: {value: positions, size: 2},
getColor: {value: colors, size: 3}
}
}
})
To use background: true with binary data, the background attributes must be supplied separately via data.attributes.background. Each attribute is packed with one vertex per object.
data.attributes.background may contain the following keys:
getPosition: corresponds to the getPosition accessorgetAngle: corresponds to the getAngle accessorgetSize: corresponds to the getSize accessorgetPixelOffset: corresponds to the getPixelOffset accessorgetFillColor: corresponds to the getBackgroundColor accessorgetLineColor: corresponds to the getBorderColor accessorgetLineWidth: corresponds to the getBorderWidth accessorFollowing the above example, additional attributes are required to render the background:
// The background position attribute supplies one position for each text block
const backgroundPositions = new Float64Array(TEXT_DATA.map(d => d.position).flat());
// The background color attribute supplies one color for each text block
const backgroundColors = new Uint8Array(TEXT_DATA.map(d => d.bgColor).flat());
new TextLayer({
data: {
length: TEXT_DATA.length,
startIndices: startIndices, // this is required to render the texts correctly!
attributes: {
getText: {value: texts},
getPosition: {value: positions, size: 2},
getColor: {value: colors, size: 3},
background: {
getPosition: {value: backgroundPosition, size: 2},
getFillColor: {value: backgroundColors, size: 3}
}
}
},
background: true
})
The TextLayer creates a font texture when it is first added with the fillText API. If the font specified by fontFamily is not loaded at this point, it will fall back to using the default font just like regular CSS behavior. The loading sequence may become an issue when a web font is used, due to lazy loading.
One way to force a web font to load before the script execution is to preload the font resource:
<link rel="preload" href="https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2" as="font" crossorigin="anonymous" type="font/woff2" />
<style>
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2');
}
</style>
Another way is to use the FontFace API to load a web font before adding the TextLayer:
import {Deck} from '@deck.gl/core';
import {TextLayer} from '@deck.gl/layers';
const deckInstance = new Deck({...});
renderLayers();
async function renderLayers() {
const font = new FontFace('Material Icons', 'url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)');
// wait for font to be loaded
await font.load();
// add font to document
document.fonts.add(font);
// add TextLayer
const textLayer = new TextLayer({
fontFamily: 'Material Icons',
// ...
});
deckInstance.setProps({
layers: [textLayer]
});
}
import React, {useState, useEffect} from 'react';
import {DeckGL} from '@deck.gl/react';
import {TextLayer} from '@deck.gl/layers';
function App() {
const [fontLoaded, setFontLoaded] = useState<boolean>(false);
useEffect(() => {
(async () => {
const font = new FontFace('Material Icons', 'url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2)');
// wait for font to be loaded
await font.load();
// add font to document
document.fonts.add(font);
setFontLoaded(true);
})();
}, []);
const textLayer = fontLoaded && new TextLayer({
fontFamily: 'Material Icons',
// ...
});
return <DeckGL
// ...
layers={[textLayer]}
/>;
}
The TextLayer has full support for Unicode characters. To reference a Unicode character in JavaScript you can either use a string literal ('日本語', '©') or escaped code point ('\u{1F436}').
At the moment this layer doesn't render multi-color emojis.
To conserve memory, DeckGL caches the most 3 used fontAtlas by default. Creating a fontAtlas is a CPU intesive operation specially if fontSettings.sdf is set to true.
If you are using much more than 3 fonts, you might experience perforamnce hits because DeckGL constantly tries to evict the least most used fontAtlas from cache and recreate them when needed.
To mitigate the potential performance degradation, you can override the fontAtlas default cache limit by setting TextLayer.fontAtlasCacheLimit value:
import {TextLayer} from '@deck.gl/layers';
TextLayer.fontAtlasCacheLimit = 10;
// ... rest of the application
It is recommended to set fontAtlasCacheLimit once in your application since it recreates the cache which removes existing cached fontAtlas.
Below is an interactive demo of the content box clipping and alignment behavior. Note the coordination between the props.
<iframe height="480" style={{width:'100%'}} scrolling="no" title="deck.gl TextLayer content box" src="https://codepen.io/vis-gl/embed/QwKxLRE?default-tab=result" frameborder="no" loading="lazy" allowtransparency="true"> See the Pen <a href="https://codepen.io/vis-gl/pen/QwKxLRE"> deck.gl TextLayer content box</a> by vis.gl (<a href="https://codepen.io/vis-gl">@vis-gl</a>) on <a href="https://codepen.io">CodePen</a>. </iframe>You may observe that text simply disappears when a content box is specified. This is likely because the content box is "too tight" - the characters' bounding boxes interset with the content box, resulting in them being clipped at all times. This is because for many fonts, the actual dimensions of the glyphs can be larger than the number indicated by font size. For example, the Arial fontface with the default getSize: 32 and lineHeight: 1 produces a single-line text block of 32px heigh, but glyphs could be as tall as 36px. If the borders of the content box are right against the text block, then taller glyphs will be clipped. This can be mitigated by either setting a padding with getPixelOffset, or using a larger lineHeight.