@carto/kepler.gl 0.0.66 → 0.0.67

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.66",
5
+ "version": "0.0.67",
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
  }
@@ -212,7 +212,7 @@ export default function PlotContainerFactory(MapContainer, MapsLayout) {
212
212
  const mapContainers = !isSplit ? (
213
213
  <MapContainer index={0} primary={true} {...mapProps} />
214
214
  ) : (
215
- <MapsLayout>
215
+ <MapsLayout isExport={true}>
216
216
  {splitMaps.map((settings, index) => (
217
217
  <MapContainer
218
218
  key={index}
@@ -0,0 +1,33 @@
1
+ // Copyright (c) 2022 Uber Technologies, Inc.
2
+ //
3
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ // of this software and associated documentation files (the "Software"), to deal
5
+ // in the Software without restriction, including without limitation the rights
6
+ // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ // copies of the Software, and to permit persons to whom the Software is
8
+ // furnished to do so, subject to the following conditions:
9
+ //
10
+ // The above copyright notice and this permission notice shall be included in
11
+ // all copies or substantial portions of the Software.
12
+ //
13
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ // THE SOFTWARE.
20
+
21
+ import React from 'react';
22
+ import {injectIntl} from 'react-intl';
23
+
24
+ CustomMarkerCategoriesSelectorFactory.deps = [];
25
+ function CustomMarkerCategoriesSelectorFactory() {
26
+ const CustomMarkerCategoriesSelector = () => {
27
+ return <div>Component replaced by cloud native</div>;
28
+ };
29
+
30
+ return injectIntl(CustomMarkerCategoriesSelector);
31
+ }
32
+
33
+ export default CustomMarkerCategoriesSelectorFactory;
@@ -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 {
@@ -67,6 +67,7 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
67
67
  render() {
68
68
  const {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>
@@ -403,11 +403,11 @@ export const FILED_TYPE_DISPLAY = {
403
403
  color: BLUE2
404
404
  },
405
405
  [ALL_FIELD_TYPES.integer]: {
406
- label: 'int',
406
+ label: 'number',
407
407
  color: ORANGE
408
408
  },
409
409
  [ALL_FIELD_TYPES.real]: {
410
- label: 'float',
410
+ label: 'number',
411
411
  color: ORANGE
412
412
  },
413
413
  [ALL_FIELD_TYPES.string]: {
@@ -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
  /**
@@ -43,13 +43,21 @@ export {DEFAULT_COLOR_RANGE};
43
43
  /** @type {import('./layer-factory').LayerTextLabel} */
44
44
  export const DEFAULT_TEXT_LABEL = {
45
45
  field: null,
46
- color: [255, 255, 255],
47
- size: 18,
46
+ color: [44, 48, 50],
47
+ outlineColor: [255, 255, 255],
48
+ size: 12,
48
49
  offset: [0, 0],
49
50
  anchor: 'start',
50
51
  alignment: 'center'
51
52
  };
52
53
 
54
+ /** @type {import('./layer-factory').LayerTextLabel} */
55
+ export const DEFAULT_SECONDARY_TEXT_LABEL = {
56
+ ...DEFAULT_TEXT_LABEL,
57
+ color: [128, 130, 132],
58
+ size: 10
59
+ };
60
+
53
61
  /** @type {import('./layer-factory').ColorRange} */
54
62
  export const DEFAULT_CUSTOM_PALETTE = {
55
63
  name: 'color.customPalette',
@@ -450,8 +458,8 @@ export const LAYER_VIS_CONFIGS = {
450
458
  export const LAYER_TEXT_CONFIGS = {
451
459
  fontSize: {
452
460
  type: 'number',
453
- range: [1, 100],
454
- value0: 1,
461
+ range: [6, 32],
462
+ value0: 6,
455
463
  step: 1,
456
464
  isRanged: false,
457
465
  label: 'Font size',
@@ -54,40 +54,9 @@ export const textLabelAccessor = textLabel => dc => d => {
54
54
  return notNullorUndefined(val) ? String(val) : '';
55
55
  };
56
56
 
57
- export const formatTextLabelData = ({
58
- textLabel,
59
- triggerChanged,
60
- oldLayerData,
61
- data,
62
- dataContainer
63
- }) => {
57
+ export const formatTextLabelData = ({textLabel, dataContainer}) => {
64
58
  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
- };
59
+ const getText = tl.field ? textLabelAccessor(tl)(dataContainer) : null;
60
+ return {getText};
92
61
  });
93
62
  };
@@ -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),
@@ -242,13 +242,7 @@ export default class PointLayer extends Layer {
242
242
  const getPosition = this.getPositionAccessor(dataContainer);
243
243
 
244
244
  // get all distinct characters in the text labels
245
- const textLabels = formatTextLabelData({
246
- textLabel,
247
- triggerChanged,
248
- oldLayerData,
249
- data,
250
- dataContainer
251
- });
245
+ const textLabels = formatTextLabelData({textLabel, dataContainer});
252
246
 
253
247
  const accessors = this.getAttributeAccessors({dataContainer});
254
248
 
@@ -278,12 +272,12 @@ export default class PointLayer extends Layer {
278
272
  // eslint-disable-next-line complexity, max-statements
279
273
  renderLayer(opts) {
280
274
  const {data, gpuFilter, objectHovered, interactionConfig} = opts;
275
+ const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
281
276
 
282
277
  const radiusScale = this.getRadiusScale();
283
278
 
284
279
  const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
285
280
  const brushingProps = this.getBrushingExtensionProps(interactionConfig);
286
- const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius);
287
281
  const extensions = [...defaultLayerProps.extensions, brushingExtension];
288
282
 
289
283
  const updateTriggers = {
@@ -296,6 +290,7 @@ export default class PointLayer extends Layer {
296
290
  getFilterValue: data.getFilterValue,
297
291
  extensions,
298
292
  filterRange: defaultLayerProps.filterRange,
293
+ maskId: defaultLayerProps.maskId,
299
294
  visible: defaultLayerProps.visible,
300
295
  ...brushingProps
301
296
  };
@@ -305,7 +300,9 @@ export default class PointLayer extends Layer {
305
300
  {
306
301
  getPosition: data.getPosition,
307
302
  sharedProps,
308
- getPixelOffset,
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;
@@ -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: {
@@ -81,7 +83,7 @@ export default {
81
83
  layer: 'Layers',
82
84
  filter: 'Filters',
83
85
  interaction: 'Interactions',
84
- basemap: 'Base map'
86
+ basemap: 'Basemap'
85
87
  }
86
88
  },
87
89
  layer: {
@@ -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',
@@ -213,7 +216,7 @@ export default {
213
216
  hideLayerPanel: 'Hide layer panel',
214
217
  showLayerPanel: 'Show layer panel',
215
218
  moveToTop: 'Move to top of data layers',
216
- selectBaseMapStyle: 'Select Base Map Style',
219
+ selectBaseMapStyle: 'Select Basemap Style',
217
220
  delete: 'Delete',
218
221
  timePlayback: 'Time Playback',
219
222
  cloudStorage: 'Cloud Storage',
@@ -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') {