@carto/kepler.gl 0.0.64 → 0.0.65-alpha.labels.10

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/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@carto/kepler.gl",
3
3
  "author": "CARTO",
4
4
  "homepage": "https://github.com/CartoDB/carto-kepler#readme",
5
- "version": "0.0.64",
5
+ "version": "0.0.65-alpha.labels.10",
6
6
  "description": "kepler.gl is a webgl based application to visualize large scale location data in the browser",
7
7
  "license": "MIT",
8
8
  "main": "dist/index.js",
@@ -238,13 +238,14 @@
238
238
  "tough-cookie": "4.0.0"
239
239
  },
240
240
  "peerDependencies": {
241
- "@deck.gl/aggregation-layers": "^8.7.3",
242
- "@deck.gl/core": "^8.7.3",
243
- "@deck.gl/extensions": "^8.7.3",
244
- "@deck.gl/geo-layers": "^8.7.3",
245
- "@deck.gl/layers": "^8.7.3",
246
- "@deck.gl/mesh-layers": "^8.7.3",
247
- "@deck.gl/react": "^8.7.3",
241
+ "@deck.gl/aggregation-layers": "^8.9.5",
242
+ "@deck.gl/carto": "^8.9.5",
243
+ "@deck.gl/core": "^8.9.5",
244
+ "@deck.gl/extensions": "^8.9.5",
245
+ "@deck.gl/geo-layers": "^8.9.5",
246
+ "@deck.gl/layers": "^8.9.5",
247
+ "@deck.gl/mesh-layers": "^8.9.5",
248
+ "@deck.gl/react": "^8.9.5",
248
249
  "@loaders.gl/core": "^3.1.5",
249
250
  "@loaders.gl/csv": "^3.1.5",
250
251
  "@loaders.gl/gltf": "^3.1.5",
@@ -270,6 +271,10 @@
270
271
  "engines": {
271
272
  "node": ">=12.0.0"
272
273
  },
274
+ "maintainers": [
275
+ "Shan He <heshan0131@gmail.com>",
276
+ "Giuseppe Macri <gmacri@uber.com>"
277
+ ],
273
278
  "volta": {
274
279
  "node": "12.19.0"
275
280
  }
Binary file
@@ -40,7 +40,7 @@ export const ConfigGroupCollapsibleContent = styled.div.attrs({
40
40
  overflow: visible;
41
41
  transition: max-height 0.3s ease-out;
42
42
  height: max-content;
43
- max-height: 600px;
43
+ max-height: 650px;
44
44
  `;
45
45
 
46
46
  export const ConfigGroupCollapsibleHeader = styled.div.attrs({
@@ -62,7 +62,7 @@ export const StyledLayerConfigGroup = styled.div`
62
62
  &.collapsed {
63
63
  .layer-config-group__header__collapsible {
64
64
  overflow: visible;
65
- max-height: 600px;
65
+ max-height: 650px;
66
66
  }
67
67
  .layer-config-group__content {
68
68
  .layer-config-group__content__collapsible {
@@ -246,6 +246,7 @@ export default function LayerConfiguratorFactory(
246
246
  {/* text label */}
247
247
  <TextLabelPanel
248
248
  fields={visConfiguratorProps.fields}
249
+ layer={layer}
249
250
  updateLayerTextLabel={this.props.updateLayerTextLabel}
250
251
  textLabel={layer.config.textLabel}
251
252
  colorPalette={visConfiguratorProps.colorPalette}
@@ -65,8 +65,9 @@ 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 (
71
72
  <LayerConfigGroup label={'panel.text.label'} collapsible>
72
73
  <ConfigGroupCollapsibleHeader>
@@ -80,11 +81,11 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
80
81
  <ConfigGroupCollapsibleContent>
81
82
  {textLabel.map((tl, idx) => (
82
83
  <div
83
- key={tl.field ? tl.field.name : `null-${idx}`}
84
+ key={`${tl.field ? tl.field.name : 'null'}-${idx}`}
84
85
  className="side-panel-section-group"
85
86
  >
86
87
  <PanelLabel>
87
- <FormattedMessage id={'panel.text.labelWithId'} values={{labelId: idx + 1}} />
88
+ <FormattedMessage id={`panel.text.${idx === 0 ? 'main' : 'secondary'}Label`} />
88
89
  </PanelLabel>
89
90
  <SidePanelSection>
90
91
  <FieldSelector
@@ -120,33 +121,51 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
120
121
  />
121
122
  </SidePanelSection>
122
123
  <SidePanelSection>
123
- <SpaceBetweenFlexbox>
124
- <SBFlexboxItem>
125
- <PanelLabel>
126
- <FormattedMessage id="panel.text.textAnchor" />
127
- </PanelLabel>
128
- <ItemSelector
129
- {...LAYER_TEXT_CONFIGS.textAnchor}
130
- selectedItems={tl.anchor}
131
- onChange={val => updateLayerTextLabel(idx, 'anchor', val)}
132
- />
133
- </SBFlexboxItem>
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>
124
+ <PanelLabel>
125
+ <FormattedMessage id="panel.text.outlineColor" />
126
+ </PanelLabel>
127
+ <ColorSelector
128
+ colorSets={[
129
+ {
130
+ selectedColor: tl.outlineColor,
131
+ setColor: v => updateLayerTextLabel(idx, 'outlineColor', v)
132
+ }
133
+ ]}
134
+ />
145
135
  </SidePanelSection>
136
+ {idx === 0 && (
137
+ <SidePanelSection>
138
+ <SpaceBetweenFlexbox>
139
+ <SBFlexboxItem>
140
+ <PanelLabel>
141
+ <FormattedMessage id="panel.text.textAnchor" />
142
+ </PanelLabel>
143
+ <ItemSelector
144
+ {...LAYER_TEXT_CONFIGS.textAnchor}
145
+ selectedItems={tl.anchor}
146
+ onChange={val => updateLayerTextLabel(idx, 'anchor', val)}
147
+ />
148
+ </SBFlexboxItem>
149
+ <SBFlexboxItem>
150
+ <PanelLabel>
151
+ <FormattedMessage id="panel.text.alignment" />
152
+ </PanelLabel>
153
+ <ItemSelector
154
+ {...LAYER_TEXT_CONFIGS.textAlignment}
155
+ selectedItems={tl.alignment}
156
+ onChange={val => updateLayerTextLabel(idx, 'alignment', val)}
157
+ />
158
+ </SBFlexboxItem>
159
+ </SpaceBetweenFlexbox>
160
+ </SidePanelSection>
161
+ )}
146
162
  </div>
147
163
  ))}
148
164
  <SidePanelSection>
149
- <AddButton onClick={val => updateLayerTextLabel(textLabel.length)} />
165
+ <AddButton
166
+ disabled={textLabel.length >= 2}
167
+ onClick={val => updateLayerTextLabel(textLabel.length)}
168
+ />
150
169
  </SidePanelSection>
151
170
  </ConfigGroupCollapsibleContent>
152
171
  </LayerConfigGroup>
@@ -20,40 +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 {TextLayer} from '@deck.gl/layers';
26
-
27
- // Workaround until https://github.com/visgl/deck.gl/pull/7513 is released
28
- DeckLayer.defaultProps.getFilterValue = {type: 'accessor', value: 0};
29
-
30
- // Workaround until https://github.com/visgl/deck.gl/pull/7507 is released
31
- function deepEqual(a, b) {
32
- if (a === b) {
33
- return true;
34
- }
35
- if (!a || !b) {
36
- return false;
37
- }
38
- for (const key in a) {
39
- const aValue = a[key];
40
- const bValue = b[key];
41
- const equals = aValue === bValue || deepEqual(aValue, bValue);
42
- if (!equals) {
43
- return false;
44
- }
45
- }
46
- return true;
47
- }
48
-
49
- DeckLayer.defaultProps.loadOptions = {
50
- type: 'object',
51
- value: {},
52
- optional: true,
53
- compare: true,
54
- equal: deepEqual
55
- };
56
-
25
+ import {_PointLabelLayer as PointLabelLayer} from '@deck.gl/carto';
57
26
  import DefaultLayerIcon from './default-layer-icon';
58
27
  import {diffUpdateTriggers} from './layer-update';
59
28
 
@@ -103,6 +72,7 @@ const FALLBACK_ICON =
103
72
  const MAX_SAMPLE_SIZE = 5000;
104
73
  const defaultDomain = [0, 1];
105
74
  const dataFilterExtension = new DataFilterExtension({filterSize: MAX_GPU_FILTERS});
75
+ const collisionFilterExtension = new CollisionFilterExtension();
106
76
 
107
77
  const defaultDataAccessor = dc => d => d;
108
78
  const defaultGetFieldValue = (field, d) => field.valueAccessor(d);
@@ -1341,51 +1311,89 @@ class Layer {
1341
1311
  };
1342
1312
  }
1343
1313
 
1344
- renderTextLabelLayer({getPosition, getPixelOffset, updateTriggers, sharedProps}, renderOpts) {
1314
+ renderTextLabelLayer(
1315
+ {getPosition, getRadius, radiusScale, updateTriggers, sharedProps},
1316
+ renderOpts
1317
+ ) {
1345
1318
  const {data, mapState} = renderOpts;
1346
1319
  const {textLabel, visibilityByZoom} = this.config;
1320
+ const extensions = [...sharedProps.extensions, collisionFilterExtension];
1347
1321
 
1348
- return data.textLabels.reduce((accu, d, i) => {
1349
- if (d.getText) {
1350
- accu.push(
1351
- new TextLayer({
1352
- ...sharedProps,
1353
- id: `${this.id}-label-${textLabel[i].field?.name}`,
1354
- data: data.data,
1355
- visibilityByZoom,
1356
- getText: d.getText,
1357
- getPosition,
1358
- characterSet: d.characterSet,
1359
- getPixelOffset: getPixelOffset(textLabel[i]),
1360
- getSize: 1,
1361
- sizeScale: textLabel[i].size,
1362
- getTextAnchor: textLabel[i].anchor,
1363
- getAlignmentBaseline: textLabel[i].alignment,
1364
- getColor: textLabel[i].color,
1365
- parameters: {
1366
- // text will always show on top of all layers
1367
- depthTest: false
1368
- },
1369
-
1370
- getFilterValue: data.getFilterValue,
1371
- updateTriggers: {
1372
- ...updateTriggers,
1373
- getText: textLabel[i].field?.name,
1374
- getPixelOffset: {
1375
- ...updateTriggers.getPixelOffset,
1376
- ...updateTriggers.getRadius,
1377
- anchor: textLabel[i].anchor,
1378
- alignment: textLabel[i].alignment
1379
- },
1380
- getTextAnchor: textLabel[i].anchor,
1381
- getAlignmentBaseline: textLabel[i].alignment,
1382
- getColor: textLabel[i].color
1383
- }
1384
- })
1385
- );
1386
- }
1387
- return accu;
1388
- }, []);
1322
+ if (!data || !data.textLabels || !data.textLabels[0] || !data.textLabels[0].getText) {
1323
+ return [];
1324
+ }
1325
+
1326
+ const [mainLabel, secondaryLabel] = textLabel;
1327
+ const {
1328
+ alignment,
1329
+ anchor,
1330
+ color: getColor,
1331
+ field: mainField,
1332
+ outlineColor,
1333
+ size: sizeScale
1334
+ } = mainLabel;
1335
+ const {
1336
+ color: getSecondaryColor,
1337
+ field: secondaryField,
1338
+ outlineColor: secondaryOutlineColor,
1339
+ size: secondarySizeScale
1340
+ } = secondaryLabel || {};
1341
+
1342
+ return [
1343
+ new PointLabelLayer({
1344
+ ...sharedProps,
1345
+
1346
+ // Recreate layer when labels change otherwise rendering breaks
1347
+ // TODO could be more efficient with updateTriggers?
1348
+ id: `${this.id}-${mainField?.name}-${secondaryField?.name}-label`,
1349
+ data: data.data,
1350
+ visibilityByZoom,
1351
+ extensions,
1352
+
1353
+ characterSet: 'auto',
1354
+ collisionEnabled: true,
1355
+ collisionGroup: `${this.id}-labels`,
1356
+
1357
+ getText: data.textLabels[0].getText,
1358
+ getColor,
1359
+ sizeScale,
1360
+ outlineColor,
1361
+
1362
+ ...(secondaryField && {
1363
+ getSecondaryText: data.textLabels[1].getText,
1364
+ getSecondaryColor,
1365
+ secondarySizeScale,
1366
+ secondaryOutlineColor
1367
+ }),
1368
+
1369
+ // Position
1370
+ getPosition,
1371
+ getRadius,
1372
+ radiusScale,
1373
+ getTextAnchor: anchor,
1374
+ getAlignmentBaseline: alignment,
1375
+
1376
+ // Style
1377
+ fontFamily: 'Inter, sans',
1378
+ fontSettings: {sdf: true},
1379
+ fontWeight: 500,
1380
+ outlineWidth: 4,
1381
+ parameters: {
1382
+ // text will always show on top of all layers
1383
+ depthTest: false
1384
+ },
1385
+
1386
+ getFilterValue: data.getFilterValue,
1387
+ updateTriggers: {
1388
+ ...updateTriggers,
1389
+ getText: mainField?.name,
1390
+ getColor: mainField?.color,
1391
+ getTextAnchor: mainLabel.anchor,
1392
+ getAlignmentBaseline: mainLabel.alignment,
1393
+ getSecondaryText: secondaryField?.name
1394
+ }
1395
+ })
1396
+ ];
1389
1397
  }
1390
1398
 
1391
1399
  /**
@@ -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,13 +44,21 @@ export {DEFAULT_COLOR_RANGE};
43
44
  /** @type {import('./layer-factory').LayerTextLabel} */
44
45
  export const DEFAULT_TEXT_LABEL = {
45
46
  field: null,
46
- color: [255, 255, 255],
47
- size: 18,
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'
51
53
  };
52
54
 
55
+ /** @type {import('./layer-factory').LayerTextLabel} */
56
+ export const DEFAULT_SECONDARY_TEXT_LABEL = {
57
+ ...DEFAULT_TEXT_LABEL,
58
+ color: [128, 130, 132],
59
+ size: 10
60
+ };
61
+
53
62
  /** @type {import('./layer-factory').ColorRange} */
54
63
  export const DEFAULT_CUSTOM_PALETTE = {
55
64
  name: 'color.customPalette',
@@ -387,6 +396,13 @@ export const LAYER_VIS_CONFIGS = {
387
396
  group: PROPERTY_GROUPS.display,
388
397
  property: 'filled'
389
398
  },
399
+ labels: {
400
+ type: 'boolean',
401
+ label: 'layerVisConfigs.labels',
402
+ defaultValue: false,
403
+ group: PROPERTY_GROUPS.labels,
404
+ property: 'labels'
405
+ },
390
406
  extruded: {
391
407
  type: 'boolean',
392
408
  defaultValue: false,
@@ -430,6 +446,13 @@ export const LAYER_VIS_CONFIGS = {
430
446
  group: PROPERTY_GROUPS.customMarkers,
431
447
  property: 'customMarkers'
432
448
  },
449
+ labelsEnabled: {
450
+ type: 'boolean',
451
+ defaultValue: true,
452
+ label: 'layerVisConfigs.labelsEnabled',
453
+ group: PROPERTY_GROUPS.labels,
454
+ property: 'labelsEnabled'
455
+ },
433
456
  customMarkersUrl: {
434
457
  type: 'text',
435
458
  defaultValue: '',
@@ -450,8 +473,8 @@ export const LAYER_VIS_CONFIGS = {
450
473
  export const LAYER_TEXT_CONFIGS = {
451
474
  fontSize: {
452
475
  type: 'number',
453
- range: [1, 100],
454
- value0: 1,
476
+ range: [6, 32],
477
+ value0: 6,
455
478
  step: 1,
456
479
  isRanged: false,
457
480
  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 padding = defaultPadding;
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 + padding),
43
- yMult * (getRadius(d) * radiusScale + padding + sizeOffset)
37
+ xMult * (getRadius(d) * radiusScale * radiusPadding + xPadding),
38
+ yMult * (getRadius(d) * radiusScale * radiusPadding + yPadding)
44
39
  ]
45
40
  : [
46
- xMult * (getRadius * radiusScale + padding),
47
- yMult * (getRadius * radiusScale + padding + sizeOffset)
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
- if (!tl.field) {
66
- // if no field selected,
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 {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';
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 = {
@@ -296,6 +291,7 @@ export default class PointLayer extends Layer {
296
291
  getFilterValue: data.getFilterValue,
297
292
  extensions,
298
293
  filterRange: defaultLayerProps.filterRange,
294
+ maskId: defaultLayerProps.maskId,
299
295
  visible: defaultLayerProps.visible,
300
296
  ...brushingProps
301
297
  };
@@ -305,7 +301,9 @@ export default class PointLayer extends Layer {
305
301
  {
306
302
  getPosition: data.getPosition,
307
303
  sharedProps,
308
- getPixelOffset,
304
+ // TODO remove this scaling by half here and elsewhere
305
+ radiusScale: customMarkers ? radiusScale / 2 : radiusScale,
306
+ getRadius: data.getRadius,
309
307
  updateTriggers
310
308
  },
311
309
  opts
@@ -319,7 +317,6 @@ export default class PointLayer extends Layer {
319
317
  };
320
318
 
321
319
  const customMarkersField = this.config.customMarkersField;
322
- const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
323
320
  const customMarkersConfigured =
324
321
  customMarkers && Boolean(customMarkersUrl || (customMarkersField && customMarkersRange));
325
322
  const customMarkersMisConfigured = customMarkers && !customMarkersConfigured;
@@ -68,12 +68,14 @@ export default {
68
68
  panel: {
69
69
  text: {
70
70
  label: 'label',
71
- labelWithId: 'Label {labelId}',
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
- addMoreLabel: 'Add More Label'
78
+ addMoreLabel: 'Add secondary label'
77
79
  }
78
80
  },
79
81
  sidebar: {
@@ -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
 
@@ -76,7 +76,7 @@ import {
76
76
  } from 'utils/split-map-utils';
77
77
 
78
78
  import {Layer, LayerClasses, LAYER_ID_LENGTH} from 'layers';
79
- import {DEFAULT_TEXT_LABEL} from 'layers/layer-factory';
79
+ import {DEFAULT_TEXT_LABEL, DEFAULT_SECONDARY_TEXT_LABEL} from 'layers/layer-factory';
80
80
  import {EDITOR_MODES, SORT_ORDER, FILTER_TYPES} from 'constants/default-settings';
81
81
  import {pick_, merge_, swap_} from './composer-helpers';
82
82
  import {processFileContent} from 'actions/vis-state-actions';
@@ -315,16 +315,19 @@ function addOrRemoveTextLabels(newFields, textLabel) {
315
315
 
316
316
  // delete
317
317
  newTextLabel = newTextLabel.filter(tl => tl.field && !deleteFields.includes(tl.field.name));
318
- newTextLabel = !newTextLabel.length ? [DEFAULT_TEXT_LABEL] : newTextLabel;
319
318
 
320
319
  // add
321
- newTextLabel = [
322
- ...newTextLabel.filter(tl => tl.field),
323
- ...addFields.map(af => ({
324
- ...DEFAULT_TEXT_LABEL,
325
- field: af
326
- }))
327
- ];
320
+ newTextLabel = newTextLabel.filter(tl => tl.field);
321
+ const defaultLabel = newTextLabel.length ? DEFAULT_SECONDARY_TEXT_LABEL : DEFAULT_TEXT_LABEL;
322
+ newTextLabel = [...newTextLabel, ...addFields.map(af => ({...defaultLabel, field: af}))];
323
+
324
+ // limit to 2 labels (overwrite 2nd label with 3rd)
325
+ if (newTextLabel.length > 2) {
326
+ newTextLabel = [newTextLabel[0], newTextLabel[2]];
327
+ }
328
+
329
+ // default
330
+ newTextLabel = !newTextLabel.length ? [DEFAULT_TEXT_LABEL] : newTextLabel;
328
331
 
329
332
  return newTextLabel;
330
333
  }
@@ -359,7 +362,7 @@ export function layerTextLabelChangeUpdater(state, action) {
359
362
  let newTextLabel = textLabel.slice();
360
363
  if (!textLabel[idx] && idx === textLabel.length) {
361
364
  // if idx is set to length, add empty text label
362
- newTextLabel = [...textLabel, DEFAULT_TEXT_LABEL];
365
+ newTextLabel = [...textLabel, idx === 0 ? DEFAULT_TEXT_LABEL : DEFAULT_SECONDARY_TEXT_LABEL];
363
366
  }
364
367
 
365
368
  if (idx === 'all' && prop === 'fields') {