@carto/kepler.gl 0.0.65-alpha.labels → 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.65-alpha.labels",
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",
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 {
@@ -69,16 +69,6 @@ function TextLabelPanelFactory(RangeSlider, LayerConfigGroup, FieldSelector, Col
69
69
  const currentFields = textLabel.map(tl => tl.field && tl.field.name).filter(d => d);
70
70
 
71
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
- >*/
82
72
  <LayerConfigGroup label={'panel.text.label'} collapsible>
83
73
  <ConfigGroupCollapsibleHeader>
84
74
  <FieldSelector
@@ -22,53 +22,7 @@ import {console as Console} from 'global/window';
22
22
  import keymirror from 'keymirror';
23
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
- // TODO remove prior to release
28
- const SHOW_TEXT_BORDER = false;
29
-
30
- const [LEFT, TOP, RIGHT, BOTTOM] = [0, 1, 2, 3];
31
- class EnhancedTextLayer extends TextLayer {
32
- filterSubLayer({layer, renderPass}) {
33
- const background = layer.id.includes('background');
34
- if (renderPass === 'collision') {
35
- return background; // Only draw background layer in collision pass
36
- } else {
37
- return !background || (SHOW_TEXT_BORDER && this.props.getBorderWidth); // Do not draw background layer in other passes
38
- }
39
- }
40
- }
41
-
42
- // Workaround until https://github.com/visgl/deck.gl/pull/7513 is released
43
- DeckLayer.defaultProps.getFilterValue = {type: 'accessor', value: 0};
44
-
45
- // Workaround until https://github.com/visgl/deck.gl/pull/7507 is released
46
- function deepEqual(a, b) {
47
- if (a === b) {
48
- return true;
49
- }
50
- if (!a || !b) {
51
- return false;
52
- }
53
- for (const key in a) {
54
- const aValue = a[key];
55
- const bValue = b[key];
56
- const equals = aValue === bValue || deepEqual(aValue, bValue);
57
- if (!equals) {
58
- return false;
59
- }
60
- }
61
- return true;
62
- }
63
-
64
- DeckLayer.defaultProps.loadOptions = {
65
- type: 'object',
66
- value: {},
67
- optional: true,
68
- compare: true,
69
- equal: deepEqual
70
- };
71
-
25
+ import {_PointLabelLayer as PointLabelLayer} from '@deck.gl/carto';
72
26
  import DefaultLayerIcon from './default-layer-icon';
73
27
  import {diffUpdateTriggers} from './layer-update';
74
28
 
@@ -1357,110 +1311,89 @@ class Layer {
1357
1311
  };
1358
1312
  }
1359
1313
 
1360
- renderTextLabelLayer({getPosition, getPixelOffset, updateTriggers, sharedProps}, renderOpts) {
1314
+ renderTextLabelLayer(
1315
+ {getPosition, getRadius, radiusScale, updateTriggers, sharedProps},
1316
+ renderOpts
1317
+ ) {
1361
1318
  const {data, mapState} = renderOpts;
1362
1319
  const {textLabel, visibilityByZoom} = this.config;
1363
- // const extensions = [...sharedProps.extensions];
1364
1320
  const extensions = [...sharedProps.extensions, collisionFilterExtension];
1365
1321
 
1366
- return data.textLabels.reduce((accu, d, i) => {
1367
- if (d.getText) {
1368
- // Alignment based only on first label
1369
- const mainLabel = textLabel[0];
1370
- const {alignment, anchor, size} = mainLabel;
1371
- // Heuristics to avoid label overlap (should we base this on the text length?)
1372
- const paddingX = 12 * size;
1373
- const paddingY = 3 * size;
1374
- const backgroundPadding = [0, 0, 0, 0];
1375
- if (alignment === 'top') {
1376
- backgroundPadding[TOP] = paddingY;
1377
- } else if (alignment === 'bottom') {
1378
- backgroundPadding[BOTTOM] = paddingY;
1379
- } else {
1380
- backgroundPadding[TOP] = 0.5 * paddingY;
1381
- backgroundPadding[BOTTOM] = 0.5 * paddingY;
1382
- }
1383
- if (anchor === 'start') {
1384
- backgroundPadding[LEFT] = paddingX;
1385
- } else if (anchor === 'end') {
1386
- backgroundPadding[RIGHT] = paddingX;
1387
- } else {
1388
- backgroundPadding[LEFT] = 0.5 * paddingX;
1389
- backgroundPadding[RIGHT] = 0.5 * paddingX;
1390
- }
1322
+ if (!data || !data.textLabels || !data.textLabels[0] || !data.textLabels[0].getText) {
1323
+ return [];
1324
+ }
1391
1325
 
1392
- const pixelOffset = getPixelOffset(mainLabel);
1393
- if (i !== 0) {
1394
- // Place secondary label under main label (spacing 1 / 3 main label font size)
1395
- pixelOffset[1] += (1 + 1 / 3) * size;
1396
- }
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
+ }),
1397
1368
 
1398
- // TODO handle function case
1399
- backgroundPadding[LEFT] += pixelOffset[0];
1400
- backgroundPadding[RIGHT] -= pixelOffset[0];
1401
- backgroundPadding[TOP] += pixelOffset[1];
1402
- backgroundPadding[BOTTOM] -= pixelOffset[1];
1403
-
1404
- const {size: sizeScale} = textLabel[i];
1405
-
1406
- accu.push(
1407
- new EnhancedTextLayer({
1408
- ...sharedProps,
1409
- extensions,
1410
- collisionEnabled: true,
1411
- collisionGroup: 'labels',
1412
- id: `${this.id}-label-${textLabel[i].field?.name}`,
1413
- data: data.data,
1414
- visibilityByZoom,
1415
- getText: d.getText,
1416
- getPosition,
1417
- characterSet: d.characterSet,
1418
- getPixelOffset: pixelOffset,
1419
- getSize: 1,
1420
- sizeScale,
1421
- getTextAnchor: anchor,
1422
- getAlignmentBaseline: alignment,
1423
-
1424
- background: true,
1425
- backgroundPadding,
1426
-
1427
- // DEBUG
1428
- ...(SHOW_TEXT_BORDER && {
1429
- getBorderColor: [255, 0, 0, 80],
1430
- getBackgroundColor: [0, 0, 0, 0],
1431
- getBorderWidth: 1
1432
- }),
1433
-
1434
- getColor: textLabel[i].color,
1435
- fontFamily: 'Inter, sans',
1436
- fontSettings: {sdf: true},
1437
- fontWeight: 500,
1438
- outlineColor: textLabel[i].outlineColor,
1439
- outlineWidth: 4, // 1 seems to be thinner than 1px in CSS
1440
- parameters: {
1441
- // text will always show on top of all layers
1442
- depthTest: false
1443
- },
1444
-
1445
- getFilterValue: data.getFilterValue,
1446
- updateTriggers: {
1447
- ...updateTriggers,
1448
- getText: textLabel[i].field?.name,
1449
- getPixelOffset: {
1450
- ...updateTriggers.getPixelOffset,
1451
- ...updateTriggers.getRadius,
1452
- anchor: mainLabel.anchor,
1453
- alignment: mainLabel.alignment
1454
- },
1455
- getTextAnchor: mainLabel.anchor,
1456
- getAlignmentBaseline: mainLabel.alignment,
1457
- getColor: textLabel[i].color
1458
- }
1459
- })
1460
- );
1461
- }
1462
- return accu;
1463
- }, []);
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
+ ];
1464
1397
  }
1465
1398
 
1466
1399
  /**
@@ -45,13 +45,20 @@ export {DEFAULT_COLOR_RANGE};
45
45
  export const DEFAULT_TEXT_LABEL = {
46
46
  field: null,
47
47
  color: [44, 48, 50],
48
- outlineColor: [0, 0, 0],
48
+ outlineColor: [255, 255, 255],
49
49
  size: 12,
50
50
  offset: [0, 0],
51
51
  anchor: 'start',
52
52
  alignment: 'center'
53
53
  };
54
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
+
55
62
  /** @type {import('./layer-factory').ColorRange} */
56
63
  export const DEFAULT_CUSTOM_PALETTE = {
57
64
  name: 'color.customPalette',
@@ -21,31 +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 = textLabel.size / 4; // Padding based on font size (font size / 4)
39
- const pointPadding = 1 + 1 / 4; // Padding based on point size (point size / 4)
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)
40
34
 
41
35
  return typeof getRadius === 'function'
42
36
  ? d => [
43
- xMult * (getRadius(d) * radiusScale * pointPadding + padding),
44
- yMult * (getRadius(d) * radiusScale * pointPadding + sizeOffset)
37
+ xMult * (getRadius(d) * radiusScale * radiusPadding + xPadding),
38
+ yMult * (getRadius(d) * radiusScale * radiusPadding + yPadding)
45
39
  ]
46
40
  : [
47
- xMult * (getRadius * radiusScale * pointPadding + padding),
48
- yMult * (getRadius * radiusScale * pointPadding + sizeOffset)
41
+ xMult * (getRadius * radiusScale * radiusPadding + xPadding),
42
+ yMult * (getRadius * radiusScale * radiusPadding + yPadding)
49
43
  ];
50
44
  };
51
45
  }
@@ -55,40 +49,9 @@ export const textLabelAccessor = textLabel => dc => d => {
55
49
  return notNullorUndefined(val) ? String(val) : '';
56
50
  };
57
51
 
58
- export const formatTextLabelData = ({
59
- textLabel,
60
- triggerChanged,
61
- oldLayerData,
62
- data,
63
- dataContainer
64
- }) => {
52
+ export const formatTextLabelData = ({textLabel, dataContainer}) => {
65
53
  return textLabel.map((tl, i) => {
66
- if (!tl.field) {
67
- // if no field selected,
68
- return {
69
- getText: null,
70
- characterSet: []
71
- };
72
- }
73
-
74
- const getText = textLabelAccessor(tl)(dataContainer);
75
- let characterSet;
76
-
77
- if (
78
- !triggerChanged[`getLabelCharacterSet-${i}`] &&
79
- oldLayerData &&
80
- oldLayerData.textLabels &&
81
- oldLayerData.textLabels[i]
82
- ) {
83
- characterSet = oldLayerData.textLabels[i].characterSet;
84
- } else {
85
- const allLabels = tl.field ? data.map(getText) : [];
86
- characterSet = uniq(allLabels.join(''));
87
- }
88
-
89
- return {
90
- characterSet,
91
- getText
92
- };
54
+ const getText = tl.field ? textLabelAccessor(tl)(dataContainer) : null;
55
+ return {getText};
93
56
  });
94
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),
@@ -243,13 +243,7 @@ export default class PointLayer extends Layer {
243
243
  const getPosition = this.getPositionAccessor(dataContainer);
244
244
 
245
245
  // get all distinct characters in the text labels
246
- const textLabels = formatTextLabelData({
247
- textLabel,
248
- triggerChanged,
249
- oldLayerData,
250
- data,
251
- dataContainer
252
- });
246
+ const textLabels = formatTextLabelData({textLabel, dataContainer});
253
247
 
254
248
  const accessors = this.getAttributeAccessors({dataContainer});
255
249
 
@@ -285,11 +279,6 @@ export default class PointLayer extends Layer {
285
279
 
286
280
  const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
287
281
  const brushingProps = this.getBrushingExtensionProps(interactionConfig);
288
- const getPixelOffset = getTextOffsetByRadius(
289
- // TODO remove this scaling by half here and elsewhere
290
- customMarkers ? radiusScale / 2 : radiusScale,
291
- data.getRadius
292
- );
293
282
  const extensions = [...defaultLayerProps.extensions, brushingExtension];
294
283
 
295
284
  const updateTriggers = {
@@ -302,6 +291,7 @@ export default class PointLayer extends Layer {
302
291
  getFilterValue: data.getFilterValue,
303
292
  extensions,
304
293
  filterRange: defaultLayerProps.filterRange,
294
+ maskId: defaultLayerProps.maskId,
305
295
  visible: defaultLayerProps.visible,
306
296
  ...brushingProps
307
297
  };
@@ -311,7 +301,9 @@ export default class PointLayer extends Layer {
311
301
  {
312
302
  getPosition: data.getPosition,
313
303
  sharedProps,
314
- getPixelOffset,
304
+ // TODO remove this scaling by half here and elsewhere
305
+ radiusScale: customMarkers ? radiusScale / 2 : radiusScale,
306
+ getRadius: data.getRadius,
315
307
  updateTriggers
316
308
  },
317
309
  opts
@@ -72,10 +72,10 @@ export default {
72
72
  secondaryLabel: 'Secondary Label',
73
73
  fontSize: 'Font size',
74
74
  fontColor: 'Font color',
75
- outlineColor: 'Outline Color',
75
+ outlineColor: 'Outline color',
76
76
  textAnchor: 'Text anchor',
77
77
  alignment: 'Alignment',
78
- addMoreLabel: 'Add More Label'
78
+ addMoreLabel: 'Add secondary label'
79
79
  }
80
80
  },
81
81
  sidebar: {
@@ -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') {