@carto/kepler.gl 0.0.64 → 0.0.65-alpha.labels.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -651
- package/dist/components/map-container.js +2 -1
- package/dist/components/side-panel/layer-panel/layer-configurator.js +2 -1
- package/dist/components/side-panel/layer-panel/text-label-panel.js +83 -61
- package/dist/constants/default-settings.js +2 -2
- package/dist/deckgl-layers/point-label-layer/point-label-layer.js +234 -0
- package/dist/index.js +12 -2
- package/dist/layers/base-layer.js +78 -41
- package/dist/layers/layer-factory.js +21 -5
- package/dist/layers/layer-text-label.js +11 -31
- package/dist/layers/point-layer/point-layer.js +9 -10
- package/dist/localization/en.js +5 -2
- package/dist/processors/data-processor.js +1 -4
- package/package.json +5 -1
- package/src/components/map-container.js +1 -0
- package/src/components/side-panel/layer-panel/layer-configurator.js +1 -0
- package/src/components/side-panel/layer-panel/text-label-panel.js +55 -26
- package/src/deckgl-layers/point-label-layer/point-label-layer.js +174 -0
- package/src/index.js +3 -0
- package/src/layers/base-layer.js +81 -44
- package/src/layers/layer-factory.js +20 -4
- package/src/layers/layer-text-label.js +11 -47
- package/src/layers/point-layer/point-layer.js +7 -11
- package/src/localization/en.js +4 -1
- package/src/processors/data-processor.js +1 -4
|
@@ -65,9 +65,20 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
|
|
|
65
65
|
};
|
|
66
66
|
|
|
67
67
|
render() {
|
|
68
|
-
const {updateLayerTextLabel, textLabel, fields, AddButton} = this.props;
|
|
68
|
+
const {layer, updateLayerTextLabel, textLabel, fields, AddButton} = this.props;
|
|
69
69
|
const currentFields = textLabel.map(tl => tl.field && tl.field.name).filter(d => d);
|
|
70
|
+
|
|
70
71
|
return (
|
|
72
|
+
// Switch only works with group/property set to customMarkers???
|
|
73
|
+
/* <LayerConfigGroup
|
|
74
|
+
type="boolean"
|
|
75
|
+
disabled={undefined}
|
|
76
|
+
layer={layer}
|
|
77
|
+
group="labels"
|
|
78
|
+
property="labelsEnabled"
|
|
79
|
+
label="panel.text.label"
|
|
80
|
+
collapsible
|
|
81
|
+
>*/
|
|
71
82
|
<LayerConfigGroup label={'panel.text.label'} collapsible>
|
|
72
83
|
<ConfigGroupCollapsibleHeader>
|
|
73
84
|
<FieldSelector
|
|
@@ -80,11 +91,11 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
|
|
|
80
91
|
<ConfigGroupCollapsibleContent>
|
|
81
92
|
{textLabel.map((tl, idx) => (
|
|
82
93
|
<div
|
|
83
|
-
key={tl.field ? tl.field.name :
|
|
94
|
+
key={`${tl.field ? tl.field.name : 'null'}-${idx}`}
|
|
84
95
|
className="side-panel-section-group"
|
|
85
96
|
>
|
|
86
97
|
<PanelLabel>
|
|
87
|
-
<FormattedMessage id={
|
|
98
|
+
<FormattedMessage id={`panel.text.${idx === 0 ? 'main' : 'secondary'}Label`} />
|
|
88
99
|
</PanelLabel>
|
|
89
100
|
<SidePanelSection>
|
|
90
101
|
<FieldSelector
|
|
@@ -120,33 +131,51 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
|
|
|
120
131
|
/>
|
|
121
132
|
</SidePanelSection>
|
|
122
133
|
<SidePanelSection>
|
|
123
|
-
<
|
|
124
|
-
<
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
<SBFlexboxItem>
|
|
135
|
-
<PanelLabel>
|
|
136
|
-
<FormattedMessage id="panel.text.alignment" />
|
|
137
|
-
</PanelLabel>
|
|
138
|
-
<ItemSelector
|
|
139
|
-
{...LAYER_TEXT_CONFIGS.textAlignment}
|
|
140
|
-
selectedItems={tl.alignment}
|
|
141
|
-
onChange={val => updateLayerTextLabel(idx, 'alignment', val)}
|
|
142
|
-
/>
|
|
143
|
-
</SBFlexboxItem>
|
|
144
|
-
</SpaceBetweenFlexbox>
|
|
134
|
+
<PanelLabel>
|
|
135
|
+
<FormattedMessage id="panel.text.outlineColor" />
|
|
136
|
+
</PanelLabel>
|
|
137
|
+
<ColorSelector
|
|
138
|
+
colorSets={[
|
|
139
|
+
{
|
|
140
|
+
selectedColor: tl.outlineColor,
|
|
141
|
+
setColor: v => updateLayerTextLabel(idx, 'outlineColor', v)
|
|
142
|
+
}
|
|
143
|
+
]}
|
|
144
|
+
/>
|
|
145
145
|
</SidePanelSection>
|
|
146
|
+
{idx === 0 && (
|
|
147
|
+
<SidePanelSection>
|
|
148
|
+
<SpaceBetweenFlexbox>
|
|
149
|
+
<SBFlexboxItem>
|
|
150
|
+
<PanelLabel>
|
|
151
|
+
<FormattedMessage id="panel.text.textAnchor" />
|
|
152
|
+
</PanelLabel>
|
|
153
|
+
<ItemSelector
|
|
154
|
+
{...LAYER_TEXT_CONFIGS.textAnchor}
|
|
155
|
+
selectedItems={tl.anchor}
|
|
156
|
+
onChange={val => updateLayerTextLabel(idx, 'anchor', val)}
|
|
157
|
+
/>
|
|
158
|
+
</SBFlexboxItem>
|
|
159
|
+
<SBFlexboxItem>
|
|
160
|
+
<PanelLabel>
|
|
161
|
+
<FormattedMessage id="panel.text.alignment" />
|
|
162
|
+
</PanelLabel>
|
|
163
|
+
<ItemSelector
|
|
164
|
+
{...LAYER_TEXT_CONFIGS.textAlignment}
|
|
165
|
+
selectedItems={tl.alignment}
|
|
166
|
+
onChange={val => updateLayerTextLabel(idx, 'alignment', val)}
|
|
167
|
+
/>
|
|
168
|
+
</SBFlexboxItem>
|
|
169
|
+
</SpaceBetweenFlexbox>
|
|
170
|
+
</SidePanelSection>
|
|
171
|
+
)}
|
|
146
172
|
</div>
|
|
147
173
|
))}
|
|
148
174
|
<SidePanelSection>
|
|
149
|
-
<AddButton
|
|
175
|
+
<AddButton
|
|
176
|
+
disabled={textLabel.length >= 2}
|
|
177
|
+
onClick={val => updateLayerTextLabel(textLabel.length)}
|
|
178
|
+
/>
|
|
150
179
|
</SidePanelSection>
|
|
151
180
|
</ConfigGroupCollapsibleContent>
|
|
152
181
|
</LayerConfigGroup>
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import {CompositeLayer} from '@deck.gl/core';
|
|
2
|
+
import {TextLayer} from '@deck.gl/layers';
|
|
3
|
+
|
|
4
|
+
// TODO remove prior to release
|
|
5
|
+
const SHOW_TEXT_BORDER = false;
|
|
6
|
+
|
|
7
|
+
const [LEFT, TOP, RIGHT, BOTTOM] = [0, 1, 2, 3];
|
|
8
|
+
class EnhancedTextLayer extends TextLayer {
|
|
9
|
+
filterSubLayer({layer, renderPass}) {
|
|
10
|
+
const background = layer.id.includes('primary-background');
|
|
11
|
+
if (renderPass === 'collision') {
|
|
12
|
+
return background; // Only draw primary background layer in collision pass
|
|
13
|
+
} else {
|
|
14
|
+
return !background || (SHOW_TEXT_BORDER && this.props.getBorderWidth); // Do not draw background layer in other passes
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// TODO fix in deck.gl
|
|
20
|
+
TextLayer.defaultProps.fontSettings = {type: 'object', value: {}, optional: true, compare: 2};
|
|
21
|
+
|
|
22
|
+
const defaultProps = {
|
|
23
|
+
...TextLayer.defaultProps,
|
|
24
|
+
getRadius: {type: 'accessor', value: 1},
|
|
25
|
+
radiusScale: {type: 'number', min: 0, value: 1}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class PointLabelLayer extends CompositeLayer {
|
|
29
|
+
static defaultProps = defaultProps;
|
|
30
|
+
|
|
31
|
+
calculatePixelOffset(secondary) {
|
|
32
|
+
const {
|
|
33
|
+
getTextAnchor: anchor,
|
|
34
|
+
getAlignmentBaseline: alignment,
|
|
35
|
+
getRadius,
|
|
36
|
+
radiusScale,
|
|
37
|
+
sizeScale
|
|
38
|
+
} = this.props;
|
|
39
|
+
const xMult = anchor === 'middle' ? 0 : anchor === 'start' ? 1 : -1;
|
|
40
|
+
const yMult = alignment === 'center' ? 0 : alignment === 'bottom' ? 1 : -1;
|
|
41
|
+
|
|
42
|
+
// Place secondary label under main label (spacing 1 / 3 main label font size)
|
|
43
|
+
const yOffset = secondary ? (1 + 1 / 3) * sizeScale : 0;
|
|
44
|
+
const xPadding = sizeScale / 4; // Padding based on font size (font size / 4)
|
|
45
|
+
const yPadding = sizeScale;
|
|
46
|
+
const radiusPadding = 1 + 1 / 4; // Padding based on point radius (radius/ 4)
|
|
47
|
+
|
|
48
|
+
return typeof getRadius === 'function'
|
|
49
|
+
? d => {
|
|
50
|
+
const r = (d ? getRadius(d) : 1) * radiusScale * radiusPadding;
|
|
51
|
+
return [xMult * (r + xPadding), yMult * (r + yPadding) + yOffset];
|
|
52
|
+
}
|
|
53
|
+
: [
|
|
54
|
+
xMult * (getRadius * radiusScale * radiusPadding + xPadding),
|
|
55
|
+
yMult * (getRadius * radiusScale * radiusPadding + yPadding) + yOffset
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
calculateBackgroundPadding(getPixelOffset) {
|
|
60
|
+
// Alignment based only on first label
|
|
61
|
+
const {alignment, anchor, sizeScale} = this.props;
|
|
62
|
+
|
|
63
|
+
// Heuristics to avoid label overlap (should we base this on the text length?)
|
|
64
|
+
const paddingX = 12 * sizeScale;
|
|
65
|
+
const paddingY = 3 * sizeScale;
|
|
66
|
+
const backgroundPadding = [0, 0, 0, 0];
|
|
67
|
+
if (alignment === 'top') {
|
|
68
|
+
backgroundPadding[TOP] = paddingY;
|
|
69
|
+
} else if (alignment === 'bottom') {
|
|
70
|
+
backgroundPadding[BOTTOM] = paddingY;
|
|
71
|
+
} else {
|
|
72
|
+
backgroundPadding[TOP] = 0.5 * paddingY;
|
|
73
|
+
backgroundPadding[BOTTOM] = 0.5 * paddingY;
|
|
74
|
+
}
|
|
75
|
+
if (anchor === 'start') {
|
|
76
|
+
backgroundPadding[LEFT] = paddingX;
|
|
77
|
+
} else if (anchor === 'end') {
|
|
78
|
+
backgroundPadding[RIGHT] = paddingX;
|
|
79
|
+
} else {
|
|
80
|
+
backgroundPadding[LEFT] = 0.5 * paddingX;
|
|
81
|
+
backgroundPadding[RIGHT] = 0.5 * paddingX;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// For getRadius function, invoke without data, as there is no backgroundPadding accessor
|
|
85
|
+
const pixelOffset = Array.isArray(getPixelOffset) ? getPixelOffset : getPixelOffset();
|
|
86
|
+
backgroundPadding[LEFT] += pixelOffset[0];
|
|
87
|
+
backgroundPadding[RIGHT] -= pixelOffset[0];
|
|
88
|
+
backgroundPadding[TOP] += pixelOffset[1];
|
|
89
|
+
backgroundPadding[BOTTOM] -= pixelOffset[1];
|
|
90
|
+
|
|
91
|
+
return backgroundPadding;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
renderTextLayer(id, props) {
|
|
95
|
+
const {
|
|
96
|
+
data,
|
|
97
|
+
|
|
98
|
+
fontFamily,
|
|
99
|
+
fontSettings,
|
|
100
|
+
fontWeight,
|
|
101
|
+
outlineColor,
|
|
102
|
+
outlineWidth,
|
|
103
|
+
sizeScale,
|
|
104
|
+
|
|
105
|
+
getAlignmentBaseline,
|
|
106
|
+
getColor,
|
|
107
|
+
getPosition,
|
|
108
|
+
getTextAnchor,
|
|
109
|
+
|
|
110
|
+
updateTriggers
|
|
111
|
+
} = this.props;
|
|
112
|
+
|
|
113
|
+
return new EnhancedTextLayer(
|
|
114
|
+
this.getSubLayerProps({
|
|
115
|
+
id,
|
|
116
|
+
data: data,
|
|
117
|
+
|
|
118
|
+
fontFamily,
|
|
119
|
+
fontSettings,
|
|
120
|
+
fontWeight,
|
|
121
|
+
outlineColor,
|
|
122
|
+
outlineWidth,
|
|
123
|
+
sizeScale,
|
|
124
|
+
|
|
125
|
+
getAlignmentBaseline,
|
|
126
|
+
getColor,
|
|
127
|
+
getPosition,
|
|
128
|
+
getTextAnchor,
|
|
129
|
+
|
|
130
|
+
updateTriggers
|
|
131
|
+
}),
|
|
132
|
+
{
|
|
133
|
+
getSize: 1
|
|
134
|
+
},
|
|
135
|
+
// DEBUG
|
|
136
|
+
SHOW_TEXT_BORDER
|
|
137
|
+
? {getBackgroundColor: [0, 0, 0, 0], getBorderColor: [255, 0, 0, 80], getBorderWidth: 1}
|
|
138
|
+
: {},
|
|
139
|
+
props
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
renderLayers() {
|
|
144
|
+
const {
|
|
145
|
+
getText,
|
|
146
|
+
getSecondaryColor,
|
|
147
|
+
getSecondaryText,
|
|
148
|
+
secondaryOutlineColor,
|
|
149
|
+
secondarySizeScale
|
|
150
|
+
} = this.props;
|
|
151
|
+
const getPixelOffset = this.calculatePixelOffset();
|
|
152
|
+
const backgroundPadding = this.calculateBackgroundPadding(getPixelOffset);
|
|
153
|
+
const out = [
|
|
154
|
+
this.renderTextLayer('primary', {
|
|
155
|
+
backgroundPadding,
|
|
156
|
+
getText,
|
|
157
|
+
getPixelOffset,
|
|
158
|
+
background: true // Only use background for primary label for faster collisions
|
|
159
|
+
}),
|
|
160
|
+
getSecondaryText &&
|
|
161
|
+
this.renderTextLayer('secondary', {
|
|
162
|
+
getText: getSecondaryText,
|
|
163
|
+
getPixelOffset: this.calculatePixelOffset(true),
|
|
164
|
+
|
|
165
|
+
// Optional overrides
|
|
166
|
+
...(getSecondaryColor && {getColor: getSecondaryColor}),
|
|
167
|
+
...(secondarySizeScale && {sizeScale: secondarySizeScale}),
|
|
168
|
+
...(secondaryOutlineColor && {outlineColor: secondaryOutlineColor})
|
|
169
|
+
})
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
return out;
|
|
173
|
+
}
|
|
174
|
+
}
|
package/src/index.js
CHANGED
package/src/layers/base-layer.js
CHANGED
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
|
|
21
21
|
import {console as Console} from 'global/window';
|
|
22
22
|
import keymirror from 'keymirror';
|
|
23
|
-
import {DataFilterExtension} from '@deck.gl/extensions';
|
|
23
|
+
import {CollisionFilterExtension, DataFilterExtension} from '@deck.gl/extensions';
|
|
24
24
|
import {COORDINATE_SYSTEM, Layer as DeckLayer} from '@deck.gl/core';
|
|
25
|
-
import {
|
|
25
|
+
import {PointLabelLayer} from '../deckgl-layers/point-label-layer/point-label-layer';
|
|
26
26
|
|
|
27
27
|
// Workaround until https://github.com/visgl/deck.gl/pull/7513 is released
|
|
28
28
|
DeckLayer.defaultProps.getFilterValue = {type: 'accessor', value: 0};
|
|
@@ -103,6 +103,7 @@ const FALLBACK_ICON =
|
|
|
103
103
|
const MAX_SAMPLE_SIZE = 5000;
|
|
104
104
|
const defaultDomain = [0, 1];
|
|
105
105
|
const dataFilterExtension = new DataFilterExtension({filterSize: MAX_GPU_FILTERS});
|
|
106
|
+
const collisionFilterExtension = new CollisionFilterExtension();
|
|
106
107
|
|
|
107
108
|
const defaultDataAccessor = dc => d => d;
|
|
108
109
|
const defaultGetFieldValue = (field, d) => field.valueAccessor(d);
|
|
@@ -1341,51 +1342,87 @@ class Layer {
|
|
|
1341
1342
|
};
|
|
1342
1343
|
}
|
|
1343
1344
|
|
|
1344
|
-
renderTextLabelLayer(
|
|
1345
|
+
renderTextLabelLayer(
|
|
1346
|
+
{getPosition, getRadius, radiusScale, updateTriggers, sharedProps},
|
|
1347
|
+
renderOpts
|
|
1348
|
+
) {
|
|
1345
1349
|
const {data, mapState} = renderOpts;
|
|
1346
1350
|
const {textLabel, visibilityByZoom} = this.config;
|
|
1351
|
+
// const extensions = [...sharedProps.extensions];
|
|
1352
|
+
const extensions = [...sharedProps.extensions, collisionFilterExtension];
|
|
1347
1353
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
)
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1354
|
+
if (!data || !data.textLabels || !data.textLabels[0] || !data.textLabels[0].getText) {
|
|
1355
|
+
return null;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// TODO other labels
|
|
1359
|
+
const i = 0;
|
|
1360
|
+
|
|
1361
|
+
const [mainLabel, secondaryLabel] = textLabel;
|
|
1362
|
+
const {alignment, anchor, color: getColor, outlineColor, size: sizeScale} = mainLabel;
|
|
1363
|
+
const {
|
|
1364
|
+
color: getSecondaryColor,
|
|
1365
|
+
outlineColor: secondaryOutlineColor,
|
|
1366
|
+
size: secondarySizeScale
|
|
1367
|
+
} = secondaryLabel || {};
|
|
1368
|
+
|
|
1369
|
+
return [
|
|
1370
|
+
new PointLabelLayer({
|
|
1371
|
+
...sharedProps,
|
|
1372
|
+
id: `${this.id}-label`,
|
|
1373
|
+
data: data.data,
|
|
1374
|
+
visibilityByZoom,
|
|
1375
|
+
characterSet: 'auto',
|
|
1376
|
+
|
|
1377
|
+
extensions,
|
|
1378
|
+
collisionEnabled: true,
|
|
1379
|
+
collisionGroup: `${this.id}-labels`,
|
|
1380
|
+
|
|
1381
|
+
getText: data.textLabels[0].getText,
|
|
1382
|
+
getColor,
|
|
1383
|
+
sizeScale,
|
|
1384
|
+
outlineColor,
|
|
1385
|
+
|
|
1386
|
+
...(secondaryLabel && {
|
|
1387
|
+
getSecondaryText: data.textLabels[1].getText,
|
|
1388
|
+
getSecondaryColor,
|
|
1389
|
+
secondarySizeScale,
|
|
1390
|
+
secondaryOutlineColor
|
|
1391
|
+
}),
|
|
1392
|
+
|
|
1393
|
+
// Position
|
|
1394
|
+
getPosition,
|
|
1395
|
+
getRadius,
|
|
1396
|
+
radiusScale,
|
|
1397
|
+
getTextAnchor: anchor,
|
|
1398
|
+
getAlignmentBaseline: alignment,
|
|
1399
|
+
|
|
1400
|
+
// Style
|
|
1401
|
+
fontFamily: 'Inter, sans',
|
|
1402
|
+
fontSettings: {sdf: true},
|
|
1403
|
+
fontWeight: 500,
|
|
1404
|
+
outlineWidth: 4,
|
|
1405
|
+
parameters: {
|
|
1406
|
+
// text will always show on top of all layers
|
|
1407
|
+
depthTest: false
|
|
1408
|
+
},
|
|
1409
|
+
|
|
1410
|
+
getFilterValue: data.getFilterValue,
|
|
1411
|
+
updateTriggers: {
|
|
1412
|
+
...updateTriggers,
|
|
1413
|
+
getText: textLabel[i].field?.name,
|
|
1414
|
+
getPixelOffset: {
|
|
1415
|
+
...updateTriggers.getPixelOffset,
|
|
1416
|
+
...updateTriggers.getRadius,
|
|
1417
|
+
anchor: mainLabel.anchor,
|
|
1418
|
+
alignment: mainLabel.alignment
|
|
1419
|
+
},
|
|
1420
|
+
getTextAnchor: mainLabel.anchor,
|
|
1421
|
+
getAlignmentBaseline: mainLabel.alignment,
|
|
1422
|
+
getColor: textLabel[i].color
|
|
1423
|
+
}
|
|
1424
|
+
})
|
|
1425
|
+
];
|
|
1389
1426
|
}
|
|
1390
1427
|
|
|
1391
1428
|
/**
|
|
@@ -27,6 +27,7 @@ export const PROPERTY_GROUPS = keyMirror({
|
|
|
27
27
|
color: null,
|
|
28
28
|
stroke: null,
|
|
29
29
|
customMarkers: null,
|
|
30
|
+
labels: null,
|
|
30
31
|
radius: null,
|
|
31
32
|
height: null,
|
|
32
33
|
angle: null,
|
|
@@ -43,8 +44,9 @@ export {DEFAULT_COLOR_RANGE};
|
|
|
43
44
|
/** @type {import('./layer-factory').LayerTextLabel} */
|
|
44
45
|
export const DEFAULT_TEXT_LABEL = {
|
|
45
46
|
field: null,
|
|
46
|
-
color: [
|
|
47
|
-
|
|
47
|
+
color: [44, 48, 50],
|
|
48
|
+
outlineColor: [255, 255, 255],
|
|
49
|
+
size: 12,
|
|
48
50
|
offset: [0, 0],
|
|
49
51
|
anchor: 'start',
|
|
50
52
|
alignment: 'center'
|
|
@@ -387,6 +389,13 @@ export const LAYER_VIS_CONFIGS = {
|
|
|
387
389
|
group: PROPERTY_GROUPS.display,
|
|
388
390
|
property: 'filled'
|
|
389
391
|
},
|
|
392
|
+
labels: {
|
|
393
|
+
type: 'boolean',
|
|
394
|
+
label: 'layerVisConfigs.labels',
|
|
395
|
+
defaultValue: false,
|
|
396
|
+
group: PROPERTY_GROUPS.labels,
|
|
397
|
+
property: 'labels'
|
|
398
|
+
},
|
|
390
399
|
extruded: {
|
|
391
400
|
type: 'boolean',
|
|
392
401
|
defaultValue: false,
|
|
@@ -430,6 +439,13 @@ export const LAYER_VIS_CONFIGS = {
|
|
|
430
439
|
group: PROPERTY_GROUPS.customMarkers,
|
|
431
440
|
property: 'customMarkers'
|
|
432
441
|
},
|
|
442
|
+
labelsEnabled: {
|
|
443
|
+
type: 'boolean',
|
|
444
|
+
defaultValue: true,
|
|
445
|
+
label: 'layerVisConfigs.labelsEnabled',
|
|
446
|
+
group: PROPERTY_GROUPS.labels,
|
|
447
|
+
property: 'labelsEnabled'
|
|
448
|
+
},
|
|
433
449
|
customMarkersUrl: {
|
|
434
450
|
type: 'text',
|
|
435
451
|
defaultValue: '',
|
|
@@ -450,8 +466,8 @@ export const LAYER_VIS_CONFIGS = {
|
|
|
450
466
|
export const LAYER_TEXT_CONFIGS = {
|
|
451
467
|
fontSize: {
|
|
452
468
|
type: 'number',
|
|
453
|
-
range: [
|
|
454
|
-
value0:
|
|
469
|
+
range: [6, 32],
|
|
470
|
+
value0: 6,
|
|
455
471
|
step: 1,
|
|
456
472
|
isRanged: false,
|
|
457
473
|
label: 'Font size',
|
|
@@ -21,30 +21,25 @@
|
|
|
21
21
|
import {notNullorUndefined} from 'utils/data-utils';
|
|
22
22
|
import uniq from 'lodash.uniq';
|
|
23
23
|
|
|
24
|
-
export const defaultPadding = 20;
|
|
25
|
-
|
|
26
24
|
export function getTextOffsetByRadius(radiusScale, getRadius) {
|
|
27
25
|
return textLabel => {
|
|
28
26
|
const xMult = textLabel.anchor === 'middle' ? 0 : textLabel.anchor === 'start' ? 1 : -1;
|
|
29
27
|
const yMult = textLabel.alignment === 'center' ? 0 : textLabel.alignment === 'bottom' ? 1 : -1;
|
|
30
28
|
|
|
31
|
-
const sizeOffset =
|
|
32
|
-
textLabel.alignment === 'center'
|
|
33
|
-
? 0
|
|
34
|
-
: textLabel.alignment === 'bottom'
|
|
35
|
-
? textLabel.size
|
|
36
|
-
: textLabel.size;
|
|
29
|
+
const sizeOffset = Math.abs(yMult * textLabel.size);
|
|
37
30
|
|
|
38
|
-
const
|
|
31
|
+
const xPadding = textLabel.size / 4; // Padding based on font size (font size / 4)
|
|
32
|
+
const yPadding = textLabel.size;
|
|
33
|
+
const radiusPadding = 1 + 1 / 4; // Padding based on point radius (radius/ 4)
|
|
39
34
|
|
|
40
35
|
return typeof getRadius === 'function'
|
|
41
36
|
? d => [
|
|
42
|
-
xMult * (getRadius(d) * radiusScale +
|
|
43
|
-
yMult * (getRadius(d) * radiusScale
|
|
37
|
+
xMult * (getRadius(d) * radiusScale * radiusPadding + xPadding),
|
|
38
|
+
yMult * (getRadius(d) * radiusScale * radiusPadding + yPadding)
|
|
44
39
|
]
|
|
45
40
|
: [
|
|
46
|
-
xMult * (getRadius * radiusScale +
|
|
47
|
-
yMult * (getRadius * radiusScale
|
|
41
|
+
xMult * (getRadius * radiusScale * radiusPadding + xPadding),
|
|
42
|
+
yMult * (getRadius * radiusScale * radiusPadding + yPadding)
|
|
48
43
|
];
|
|
49
44
|
};
|
|
50
45
|
}
|
|
@@ -54,40 +49,9 @@ export const textLabelAccessor = textLabel => dc => d => {
|
|
|
54
49
|
return notNullorUndefined(val) ? String(val) : '';
|
|
55
50
|
};
|
|
56
51
|
|
|
57
|
-
export const formatTextLabelData = ({
|
|
58
|
-
textLabel,
|
|
59
|
-
triggerChanged,
|
|
60
|
-
oldLayerData,
|
|
61
|
-
data,
|
|
62
|
-
dataContainer
|
|
63
|
-
}) => {
|
|
52
|
+
export const formatTextLabelData = ({textLabel, dataContainer}) => {
|
|
64
53
|
return textLabel.map((tl, i) => {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
getText: null,
|
|
69
|
-
characterSet: []
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const getText = textLabelAccessor(tl)(dataContainer);
|
|
74
|
-
let characterSet;
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
!triggerChanged[`getLabelCharacterSet-${i}`] &&
|
|
78
|
-
oldLayerData &&
|
|
79
|
-
oldLayerData.textLabels &&
|
|
80
|
-
oldLayerData.textLabels[i]
|
|
81
|
-
) {
|
|
82
|
-
characterSet = oldLayerData.textLabels[i].characterSet;
|
|
83
|
-
} else {
|
|
84
|
-
const allLabels = tl.field ? data.map(getText) : [];
|
|
85
|
-
characterSet = uniq(allLabels.join(''));
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return {
|
|
89
|
-
characterSet,
|
|
90
|
-
getText
|
|
91
|
-
};
|
|
54
|
+
const getText = tl.field ? textLabelAccessor(tl)(dataContainer) : null;
|
|
55
|
+
return {getText};
|
|
92
56
|
});
|
|
93
57
|
};
|
|
@@ -29,7 +29,7 @@ import {findDefaultColorField} from 'utils/dataset-utils';
|
|
|
29
29
|
import PointLayerIcon from './point-layer-icon';
|
|
30
30
|
import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES, UNKNOWN_COLOR} from 'constants/default-settings';
|
|
31
31
|
|
|
32
|
-
import {
|
|
32
|
+
import {formatTextLabelData} from '../layer-text-label';
|
|
33
33
|
|
|
34
34
|
export const pointPosAccessor = ({lat, lng, altitude}) => dc => d => [
|
|
35
35
|
dc.valueAt(d.index, lng.fieldIdx),
|
|
@@ -47,6 +47,7 @@ export const pointVisConfigs = {
|
|
|
47
47
|
customMarkers: 'customMarkers',
|
|
48
48
|
customMarkersUrl: 'customMarkersUrl',
|
|
49
49
|
customMarkersRange: 'customMarkersRange',
|
|
50
|
+
labelsEnabled: 'labelsEnabled',
|
|
50
51
|
opacity: 'opacity',
|
|
51
52
|
outline: 'outline',
|
|
52
53
|
thickness: 'thickness',
|
|
@@ -242,13 +243,7 @@ export default class PointLayer extends Layer {
|
|
|
242
243
|
const getPosition = this.getPositionAccessor(dataContainer);
|
|
243
244
|
|
|
244
245
|
// get all distinct characters in the text labels
|
|
245
|
-
const textLabels = formatTextLabelData({
|
|
246
|
-
textLabel,
|
|
247
|
-
triggerChanged,
|
|
248
|
-
oldLayerData,
|
|
249
|
-
data,
|
|
250
|
-
dataContainer
|
|
251
|
-
});
|
|
246
|
+
const textLabels = formatTextLabelData({textLabel, dataContainer});
|
|
252
247
|
|
|
253
248
|
const accessors = this.getAttributeAccessors({dataContainer});
|
|
254
249
|
|
|
@@ -278,12 +273,12 @@ export default class PointLayer extends Layer {
|
|
|
278
273
|
// eslint-disable-next-line complexity, max-statements
|
|
279
274
|
renderLayer(opts) {
|
|
280
275
|
const {data, gpuFilter, objectHovered, interactionConfig} = opts;
|
|
276
|
+
const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
|
|
281
277
|
|
|
282
278
|
const radiusScale = this.getRadiusScale();
|
|
283
279
|
|
|
284
280
|
const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
|
|
285
281
|
const brushingProps = this.getBrushingExtensionProps(interactionConfig);
|
|
286
|
-
const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius);
|
|
287
282
|
const extensions = [...defaultLayerProps.extensions, brushingExtension];
|
|
288
283
|
|
|
289
284
|
const updateTriggers = {
|
|
@@ -305,7 +300,9 @@ export default class PointLayer extends Layer {
|
|
|
305
300
|
{
|
|
306
301
|
getPosition: data.getPosition,
|
|
307
302
|
sharedProps,
|
|
308
|
-
|
|
303
|
+
// TODO remove this scaling by half here and elsewhere
|
|
304
|
+
radiusScale: customMarkers ? radiusScale / 2 : radiusScale,
|
|
305
|
+
getRadius: data.getRadius,
|
|
309
306
|
updateTriggers
|
|
310
307
|
},
|
|
311
308
|
opts
|
|
@@ -319,7 +316,6 @@ export default class PointLayer extends Layer {
|
|
|
319
316
|
};
|
|
320
317
|
|
|
321
318
|
const customMarkersField = this.config.customMarkersField;
|
|
322
|
-
const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
|
|
323
319
|
const customMarkersConfigured =
|
|
324
320
|
customMarkers && Boolean(customMarkersUrl || (customMarkersField && customMarkersRange));
|
|
325
321
|
const customMarkersMisConfigured = customMarkers && !customMarkersConfigured;
|
package/src/localization/en.js
CHANGED
|
@@ -68,9 +68,11 @@ export default {
|
|
|
68
68
|
panel: {
|
|
69
69
|
text: {
|
|
70
70
|
label: 'label',
|
|
71
|
-
|
|
71
|
+
mainLabel: 'Main Label',
|
|
72
|
+
secondaryLabel: 'Secondary Label',
|
|
72
73
|
fontSize: 'Font size',
|
|
73
74
|
fontColor: 'Font color',
|
|
75
|
+
outlineColor: 'Outline Color',
|
|
74
76
|
textAnchor: 'Text anchor',
|
|
75
77
|
alignment: 'Alignment',
|
|
76
78
|
addMoreLabel: 'Add More Label'
|
|
@@ -157,6 +159,7 @@ export default {
|
|
|
157
159
|
highPrecisionRenderingDescription: 'High precision will result in slower performance',
|
|
158
160
|
height: 'Height',
|
|
159
161
|
heightDescription: 'Click button at top right of the map to switch to 3d view',
|
|
162
|
+
labels: 'Labels',
|
|
160
163
|
fill: 'Fill',
|
|
161
164
|
enablePolygonHeight: 'Enable Polygon Height',
|
|
162
165
|
showWireframe: 'Show Wireframe',
|
|
@@ -288,10 +288,7 @@ export function getFieldsFromData(data, fieldOrder) {
|
|
|
288
288
|
// add a check for epoch timestamp
|
|
289
289
|
const metadata = Analyzer.computeColMeta(
|
|
290
290
|
data,
|
|
291
|
-
[
|
|
292
|
-
{regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'},
|
|
293
|
-
{regex: /.*census/g, dataType: 'STRING'}
|
|
294
|
-
],
|
|
291
|
+
[{regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'}],
|
|
295
292
|
{ignoredDataTypes: IGNORE_DATA_TYPES}
|
|
296
293
|
);
|
|
297
294
|
|