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

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.
@@ -20,10 +20,25 @@
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
25
  import {TextLayer} from '@deck.gl/layers';
26
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
+
27
42
  // Workaround until https://github.com/visgl/deck.gl/pull/7513 is released
28
43
  DeckLayer.defaultProps.getFilterValue = {type: 'accessor', value: 0};
29
44
 
@@ -103,6 +118,7 @@ const FALLBACK_ICON =
103
118
  const MAX_SAMPLE_SIZE = 5000;
104
119
  const defaultDomain = [0, 1];
105
120
  const dataFilterExtension = new DataFilterExtension({filterSize: MAX_GPU_FILTERS});
121
+ const collisionFilterExtension = new CollisionFilterExtension();
106
122
 
107
123
  const defaultDataAccessor = dc => d => d;
108
124
  const defaultGetFieldValue = (field, d) => field.valueAccessor(d);
@@ -1344,24 +1360,83 @@ class Layer {
1344
1360
  renderTextLabelLayer({getPosition, getPixelOffset, updateTriggers, sharedProps}, renderOpts) {
1345
1361
  const {data, mapState} = renderOpts;
1346
1362
  const {textLabel, visibilityByZoom} = this.config;
1363
+ // const extensions = [...sharedProps.extensions];
1364
+ const extensions = [...sharedProps.extensions, collisionFilterExtension];
1347
1365
 
1348
1366
  return data.textLabels.reduce((accu, d, i) => {
1349
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
+ }
1391
+
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
+ }
1397
+
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
+
1350
1406
  accu.push(
1351
- new TextLayer({
1407
+ new EnhancedTextLayer({
1352
1408
  ...sharedProps,
1409
+ extensions,
1410
+ collisionEnabled: true,
1411
+ collisionGroup: 'labels',
1353
1412
  id: `${this.id}-label-${textLabel[i].field?.name}`,
1354
1413
  data: data.data,
1355
1414
  visibilityByZoom,
1356
1415
  getText: d.getText,
1357
1416
  getPosition,
1358
1417
  characterSet: d.characterSet,
1359
- getPixelOffset: getPixelOffset(textLabel[i]),
1418
+ getPixelOffset: pixelOffset,
1360
1419
  getSize: 1,
1361
- sizeScale: textLabel[i].size,
1362
- getTextAnchor: textLabel[i].anchor,
1363
- getAlignmentBaseline: textLabel[i].alignment,
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
+
1364
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
1365
1440
  parameters: {
1366
1441
  // text will always show on top of all layers
1367
1442
  depthTest: false
@@ -1374,11 +1449,11 @@ class Layer {
1374
1449
  getPixelOffset: {
1375
1450
  ...updateTriggers.getPixelOffset,
1376
1451
  ...updateTriggers.getRadius,
1377
- anchor: textLabel[i].anchor,
1378
- alignment: textLabel[i].alignment
1452
+ anchor: mainLabel.anchor,
1453
+ alignment: mainLabel.alignment
1379
1454
  },
1380
- getTextAnchor: textLabel[i].anchor,
1381
- getAlignmentBaseline: textLabel[i].alignment,
1455
+ getTextAnchor: mainLabel.anchor,
1456
+ getAlignmentBaseline: mainLabel.alignment,
1382
1457
  getColor: textLabel[i].color
1383
1458
  }
1384
1459
  })
@@ -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: [255, 255, 255],
47
- size: 18,
47
+ color: [44, 48, 50],
48
+ outlineColor: [0, 0, 0],
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: [1, 100],
454
- value0: 1,
469
+ range: [6, 32],
470
+ value0: 6,
455
471
  step: 1,
456
472
  isRanged: false,
457
473
  label: 'Font size',
@@ -35,16 +35,17 @@ export function getTextOffsetByRadius(radiusScale, getRadius) {
35
35
  ? textLabel.size
36
36
  : textLabel.size;
37
37
 
38
- const padding = defaultPadding;
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)
39
40
 
40
41
  return typeof getRadius === 'function'
41
42
  ? d => [
42
- xMult * (getRadius(d) * radiusScale + padding),
43
- yMult * (getRadius(d) * radiusScale + padding + sizeOffset)
43
+ xMult * (getRadius(d) * radiusScale * pointPadding + padding),
44
+ yMult * (getRadius(d) * radiusScale * pointPadding + sizeOffset)
44
45
  ]
45
46
  : [
46
- xMult * (getRadius * radiusScale + padding),
47
- yMult * (getRadius * radiusScale + padding + sizeOffset)
47
+ xMult * (getRadius * radiusScale * pointPadding + padding),
48
+ yMult * (getRadius * radiusScale * pointPadding + sizeOffset)
48
49
  ];
49
50
  };
50
51
  }
@@ -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',
@@ -278,12 +279,17 @@ export default class PointLayer extends Layer {
278
279
  // eslint-disable-next-line complexity, max-statements
279
280
  renderLayer(opts) {
280
281
  const {data, gpuFilter, objectHovered, interactionConfig} = opts;
282
+ const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
281
283
 
282
284
  const radiusScale = this.getRadiusScale();
283
285
 
284
286
  const defaultLayerProps = this.getDefaultDeckLayerProps(opts);
285
287
  const brushingProps = this.getBrushingExtensionProps(interactionConfig);
286
- const getPixelOffset = getTextOffsetByRadius(radiusScale, data.getRadius);
288
+ const getPixelOffset = getTextOffsetByRadius(
289
+ // TODO remove this scaling by half here and elsewhere
290
+ customMarkers ? radiusScale / 2 : radiusScale,
291
+ data.getRadius
292
+ );
287
293
  const extensions = [...defaultLayerProps.extensions, brushingExtension];
288
294
 
289
295
  const updateTriggers = {
@@ -319,7 +325,6 @@ export default class PointLayer extends Layer {
319
325
  };
320
326
 
321
327
  const customMarkersField = this.config.customMarkersField;
322
- const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
323
328
  const customMarkersConfigured =
324
329
  customMarkers && Boolean(customMarkersUrl || (customMarkersField && customMarkersRange));
325
330
  const customMarkersMisConfigured = customMarkers && !customMarkersConfigured;
@@ -68,9 +68,11 @@ 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
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