@carto/kepler.gl 0.0.48 → 0.0.49

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.
Files changed (34) hide show
  1. package/CHANGELOG.md +711 -0
  2. package/dist/components/common/field-selector.js +7 -3
  3. package/dist/components/common/item-selector/item-selector.js +6 -1
  4. package/dist/components/common/styled-components.js +2 -2
  5. package/dist/components/index.js +10 -1
  6. package/dist/components/kepler-gl.js +2 -1
  7. package/dist/components/side-panel/layer-panel/custom-markers-config-group.js +40 -0
  8. package/dist/components/side-panel/layer-panel/layer-configurator.js +16 -8
  9. package/dist/components/side-panel/layer-panel/vis-config-by-field-selector.js +21 -5
  10. package/dist/constants/default-settings.js +8 -6
  11. package/dist/layers/base-layer.js +92 -9
  12. package/dist/layers/layer-factory.js +23 -1
  13. package/dist/layers/point-layer/point-layer.js +125 -22
  14. package/dist/localization/en.js +9 -3
  15. package/dist/utils/index.js +7 -1
  16. package/dist/utils/layer-utils.js +12 -3
  17. package/package.json +1 -1
  18. package/src/.DS_Store +0 -0
  19. package/src/components/common/field-selector.js +12 -6
  20. package/src/components/common/item-selector/item-selector.js +4 -0
  21. package/src/components/common/styled-components.js +1 -1
  22. package/src/components/index.js +1 -0
  23. package/src/components/kepler-gl.js +1 -0
  24. package/src/components/side-panel/layer-panel/custom-markers-config-group.js +30 -0
  25. package/src/components/side-panel/layer-panel/layer-configurator.js +27 -10
  26. package/src/components/side-panel/layer-panel/vis-config-by-field-selector.js +21 -9
  27. package/src/constants/default-settings.js +12 -4
  28. package/src/layers/base-layer.js +74 -7
  29. package/src/layers/layer-factory.js +22 -0
  30. package/src/layers/point-layer/point-layer.js +133 -27
  31. package/src/localization/en.js +8 -2
  32. package/src/utils/index.js +6 -1
  33. package/src/utils/layer-utils.d.ts +3 -1
  34. package/src/utils/layer-utils.js +8 -2
@@ -62,6 +62,14 @@ function VisConfigByFieldSelectorFactory(InfoHelper, FieldSelector, ColorDimensi
62
62
  this.props.updateField(val);
63
63
  };
64
64
 
65
+ _onFieldSelectorFocused = () => {
66
+ this.setState({fieldSelectorActive: true});
67
+ };
68
+
69
+ _onFieldSelectorBlur = () => {
70
+ this.setState({fieldSelectorActive: false});
71
+ };
72
+
65
73
  render() {
66
74
  const {
67
75
  property,
@@ -80,7 +88,7 @@ function VisConfigByFieldSelectorFactory(InfoHelper, FieldSelector, ColorDimensi
80
88
  <SidePanelSection>
81
89
  <SidePanelSection>
82
90
  <PanelLabelWrapper>
83
- <PanelLabel>
91
+ <PanelLabel active={this.state?.fieldSelectorActive}>
84
92
  {(label && <FormattedMessage id={label} />) || (
85
93
  <FormattedMessage
86
94
  id="layer.propertyBasedOn"
@@ -110,10 +118,13 @@ function VisConfigByFieldSelectorFactory(InfoHelper, FieldSelector, ColorDimensi
110
118
  isLoading={this.props.isLoading}
111
119
  withAggregator={this.props.fieldsAggregated}
112
120
  aggregator={this.props.fieldsAggregated && this.props.aggregator}
121
+ onFocus={this._onFieldSelectorFocused}
122
+ onBlur={this._onFieldSelectorBlur}
113
123
  />
114
124
  </SidePanelSection>
115
- <div>
116
- {showScale && !this.props.isLoading ? (
125
+
126
+ {showScale && !this.props.isLoading ? (
127
+ <div>
117
128
  <DimensionScaleSelector
118
129
  scaleType={this.props.scaleType}
119
130
  options={scaleOptions}
@@ -121,10 +132,11 @@ function VisConfigByFieldSelectorFactory(InfoHelper, FieldSelector, ColorDimensi
121
132
  onSelect={this.props.updateScale}
122
133
  disabled={scaleOptions.length < 2}
123
134
  />
124
- ) : null}
125
- </div>
126
- <div>
127
- {showScaleCustomizable && !this.props.isLoading && (
135
+ </div>
136
+ ) : null}
137
+
138
+ {showScaleCustomizable && !this.props.isLoading && (
139
+ <div>
128
140
  <ColorDimensionScaleSelector
129
141
  label={
130
142
  <FormattedMessage
@@ -139,8 +151,8 @@ function VisConfigByFieldSelectorFactory(InfoHelper, FieldSelector, ColorDimensi
139
151
  onSelect={this.props.updateScale}
140
152
  onSetCustomPalette={this.props.updateCustomPalette}
141
153
  />
142
- )}
143
- </div>
154
+ </div>
155
+ )}
144
156
  </SidePanelSection>
145
157
  );
146
158
  }
@@ -431,8 +431,10 @@ export const CHANNEL_SCALES = keyMirror({
431
431
  color: null,
432
432
  radius: null,
433
433
  size: null,
434
+ customMarker: null,
434
435
  colorAggr: null,
435
- sizeAggr: null
436
+ sizeAggr: null,
437
+ identity: null
436
438
  });
437
439
 
438
440
  export const AGGREGATION_TYPES = {
@@ -505,7 +507,9 @@ export const FIELD_AGGREGATORS = [
505
507
  export const linearFieldScaleFunctions = {
506
508
  [CHANNEL_SCALES.color]: [SCALE_TYPES.quantize, SCALE_TYPES.quantile, SCALE_TYPES.custom],
507
509
  [CHANNEL_SCALES.radius]: [SCALE_TYPES.sqrt],
508
- [CHANNEL_SCALES.size]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log]
510
+ [CHANNEL_SCALES.size]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],
511
+ [CHANNEL_SCALES.customMarker]: [SCALE_TYPES.ordinal],
512
+ [CHANNEL_SCALES.identity]: []
509
513
  };
510
514
 
511
515
  export const linearFieldAggrScaleFunctions = {
@@ -533,7 +537,9 @@ export const linearFieldAggrScaleFunctions = {
533
537
  export const ordinalFieldScaleFunctions = {
534
538
  [CHANNEL_SCALES.color]: [SCALE_TYPES.ordinal],
535
539
  [CHANNEL_SCALES.radius]: [SCALE_TYPES.point],
536
- [CHANNEL_SCALES.size]: [SCALE_TYPES.point]
540
+ [CHANNEL_SCALES.size]: [SCALE_TYPES.point],
541
+ [CHANNEL_SCALES.customMarker]: [SCALE_TYPES.ordinal],
542
+ [CHANNEL_SCALES.identity]: []
537
543
  };
538
544
 
539
545
  export const ordinalFieldAggrScaleFunctions = {
@@ -554,7 +560,9 @@ export const ordinalFieldAggrScaleFunctions = {
554
560
  export const notSupportedScaleOpts = {
555
561
  [CHANNEL_SCALES.color]: [],
556
562
  [CHANNEL_SCALES.radius]: [],
557
- [CHANNEL_SCALES.size]: []
563
+ [CHANNEL_SCALES.size]: [],
564
+ [CHANNEL_SCALES.customMarker]: [],
565
+ [CHANNEL_SCALES.identity]: []
558
566
  };
559
567
 
560
568
  export const notSupportAggrOpts = {
@@ -56,6 +56,7 @@ import {getSampleData} from 'utils/table-utils/data-container-utils';
56
56
 
57
57
  import {hexToRgb, getColorGroupByName, reverseColorRange} from 'utils/color-utils';
58
58
  import {getMaskExtensionProps} from 'utils/layer-utils';
59
+ import {scaleOrdinal} from 'd3-scale';
59
60
 
60
61
  /** @typedef {import('./index').Layer} LayerClass} */
61
62
 
@@ -65,12 +66,17 @@ import {getMaskExtensionProps} from 'utils/layer-utils';
65
66
  */
66
67
  export const LAYER_ID_LENGTH = 6;
67
68
 
69
+ // Note, this is is SVG of solid circle encoded as data-uri
70
+ const FALLBACK_ICON =
71
+ 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNTAiLz4NCjwvc3ZnPg==';
72
+
68
73
  const MAX_SAMPLE_SIZE = 5000;
69
74
  const defaultDomain = [0, 1];
70
75
  const dataFilterExtension = new DataFilterExtension({filterSize: MAX_GPU_FILTERS});
71
76
 
72
77
  const defaultDataAccessor = dc => d => d;
73
78
  const defaultGetFieldValue = (field, d) => field.valueAccessor(d);
79
+ const scaleIdentity = v => v;
74
80
 
75
81
  export const OVERLAY_TYPE = keymirror({
76
82
  deckgl: null,
@@ -808,6 +814,31 @@ class Layer {
808
814
  return this.getVisChannelScale(colorScale, colorDomain, colorRange.colors.map(hexToRgb));
809
815
  }
810
816
 
817
+ getCustomMarkersScale(range) {
818
+ let unknownValue = FALLBACK_ICON;
819
+ if (!range) {
820
+ return () => unknownValue;
821
+ }
822
+
823
+ const mapping = new Map();
824
+
825
+ if (range.othersMarker) {
826
+ unknownValue = range.othersMarker;
827
+ }
828
+ for (const {value, markerUrl} of range.markerMap) {
829
+ if (markerUrl) {
830
+ mapping.set(value, markerUrl);
831
+ }
832
+ }
833
+
834
+ // @ts-ignore d3 scale
835
+ const scale = scaleOrdinal()
836
+ .domain(Array.from(mapping.keys()))
837
+ .range(Array.from(mapping.values()))
838
+ .unknown(unknownValue);
839
+ return scale;
840
+ }
841
+
811
842
  /**
812
843
  * Mapping from visual channels to deck.gl accesors
813
844
  * @param {Object} param Parameters
@@ -818,6 +849,7 @@ class Layer {
818
849
  getAttributeAccessors({dataAccessor = defaultDataAccessor, dataContainer}) {
819
850
  const attributeAccessors = {};
820
851
 
852
+ // eslint-disable-next-line complexity
821
853
  Object.keys(this.visualChannels).forEach(channel => {
822
854
  const {
823
855
  field,
@@ -833,7 +865,9 @@ class Layer {
833
865
  aggregation
834
866
  } = this.visualChannels[channel];
835
867
 
836
- const shouldGetScale = this.config[field];
868
+ const shouldGetScale = Boolean(
869
+ (scale || channelScaleType === CHANNEL_SCALES.identity) && this.config[field]
870
+ );
837
871
 
838
872
  if (shouldGetScale) {
839
873
  const args = [this.config[scale], this.config[domain], this.config.visConfig[range]];
@@ -844,8 +878,12 @@ class Layer {
844
878
  const aggregationValue = this.config.visConfig[aggregation];
845
879
 
846
880
  const scaleFunction =
847
- channelScaleType === CHANNEL_SCALES.color
881
+ channelScaleType === CHANNEL_SCALES.identity
882
+ ? scaleIdentity
883
+ : channelScaleType === CHANNEL_SCALES.color
848
884
  ? this.getColorScale(...args)
885
+ : channelScaleType === CHANNEL_SCALES.customMarker
886
+ ? this.getCustomMarkersScale(this.config.visConfig[range])
849
887
  : this.getVisChannelScale(...args, isFixed);
850
888
 
851
889
  attributeAccessors[accessor] = d =>
@@ -863,7 +901,7 @@ class Layer {
863
901
  typeof defaultValue === 'function' ? defaultValue(this.config) : defaultValue;
864
902
  }
865
903
 
866
- if (!attributeAccessors[accessor]) {
904
+ if (!(accessor in attributeAccessors)) {
867
905
  Console.warn(`Failed to provide accessor function for ${accessor || channel}`);
868
906
  }
869
907
  });
@@ -1137,16 +1175,15 @@ class Layer {
1137
1175
 
1138
1176
  calculateLayerDomain(dataset, visualChannel) {
1139
1177
  const {scale} = visualChannel;
1140
- const scaleType = this.config[scale];
1141
-
1142
1178
  const field = this.config[visualChannel.field];
1143
- if (!field) {
1179
+ if (!field || !scale) {
1144
1180
  // if colorField or sizeField were set back to null
1145
1181
  return defaultDomain;
1146
1182
  }
1147
1183
 
1184
+ const scaleType = this.config[scale];
1148
1185
  const dataSetField = dataset.fields.find(f => f.name === field.name);
1149
- if (!dataSetField) {
1186
+ if (!scaleType || !dataSetField) {
1150
1187
  // if new dataset doesn't contain previously set field
1151
1188
  return defaultDomain;
1152
1189
  }
@@ -1191,6 +1228,12 @@ class Layer {
1191
1228
  return fixed ? 1 : (this.config[field] ? 1 : radius);
1192
1229
  }
1193
1230
 
1231
+ getMaxMarkerSize() {
1232
+ const {sizeField, visConfig} = this.config;
1233
+ const {radiusRange, radius} = visConfig;
1234
+ return Math.ceil((sizeField ? radiusRange[1] : radius));
1235
+ }
1236
+
1194
1237
  getRadiusUnit(fixedRadius) {
1195
1238
  const radiusChannel = Object.values(this.visualChannels).find(vc => vc.property === 'radius');
1196
1239
 
@@ -1300,6 +1343,30 @@ class Layer {
1300
1343
  }, []);
1301
1344
  }
1302
1345
 
1346
+ /**
1347
+ * Create deck.gl `IconLayer` "autoPack" mode accessor for custom markers.
1348
+ *
1349
+ * @param {*} customMarkersStatus Record<url, UrlStatus> provided by builder, UrlStatus `ok` means URL is ok and ready to render
1350
+ * @param {*} iconUrlAccessor deck.gl accessor (or value) that resolves to URL of icon for given row
1351
+ * @returns deck.gl accessor that returns UnpackedIcon ready to consume by IconLayer
1352
+ */
1353
+ getCustomMarkerIconAccessor(customMarkersStatus, iconUrlAccessor) {
1354
+ const maxIconSize = this.getMaxMarkerSize();
1355
+
1356
+ return d => {
1357
+ const wantedUrl =
1358
+ (typeof iconUrlAccessor === 'function') ? iconUrlAccessor(d) : iconUrlAccessor;
1359
+ const url = customMarkersStatus?.[wantedUrl] === 'ok' ? wantedUrl : FALLBACK_ICON;
1360
+ return {
1361
+ id: `${url}@@${maxIconSize}`,
1362
+ url,
1363
+ width: maxIconSize,
1364
+ height: maxIconSize,
1365
+ mask: true
1366
+ };
1367
+ };
1368
+ }
1369
+
1303
1370
  calculateDataAttribute(keplerTable, getPosition) {
1304
1371
  // implemented in subclasses
1305
1372
  return [];
@@ -26,6 +26,7 @@ import {DEFAULT_COLOR_RANGE} from 'constants/color-ranges';
26
26
  export const PROPERTY_GROUPS = keyMirror({
27
27
  color: null,
28
28
  stroke: null,
29
+ customMarkers: null,
29
30
  radius: null,
30
31
  height: null,
31
32
  angle: null,
@@ -429,6 +430,27 @@ export const LAYER_VIS_CONFIGS = {
429
430
  step: 0.1,
430
431
  group: PROPERTY_GROUPS.cell,
431
432
  property: 'radius'
433
+ },
434
+ customMarkers: {
435
+ type: 'boolean',
436
+ defaultValue: false,
437
+ label: 'layerVisConfigs.customMarkers',
438
+ group: PROPERTY_GROUPS.customMarkers,
439
+ property: 'customMarkers'
440
+ },
441
+ customMarkersUrl: {
442
+ type: 'text',
443
+ defaultValue: '',
444
+ label: 'layerVisConfigs.addUrl',
445
+ group: PROPERTY_GROUPS.customMarkers,
446
+ property: 'customMarkersUrl'
447
+ },
448
+ customMarkersRange: {
449
+ type: 'markers-range-select',
450
+ defaultValue: undefined,
451
+ label: 'layerVisConfigs.colorRange',
452
+ group: PROPERTY_GROUPS.customMarkers,
453
+ property: 'customMarkersRange'
432
454
  }
433
455
  };
434
456
 
@@ -20,12 +20,14 @@
20
20
 
21
21
  import {BrushingExtension} from '@deck.gl/extensions';
22
22
  import {ScatterplotLayer} from '@deck.gl/layers';
23
+ import {IconLayer} from '@deck.gl/layers';
23
24
 
24
25
  import Layer from '../base-layer';
25
26
  import {hexToRgb} from 'utils/color-utils';
27
+ import {negateAccessor} from 'utils/layer-utils';
26
28
  import {findDefaultColorField} from 'utils/dataset-utils';
27
29
  import PointLayerIcon from './point-layer-icon';
28
- import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES} from 'constants/default-settings';
30
+ import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES, UNKNOWN_COLOR} from 'constants/default-settings';
29
31
 
30
32
  import {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';
31
33
 
@@ -43,6 +45,9 @@ const brushingExtension = new BrushingExtension();
43
45
  export const pointVisConfigs = {
44
46
  radius: 'radius',
45
47
  fixedRadius: 'fixedRadius',
48
+ customMarkers: 'customMarkers',
49
+ customMarkersUrl: 'customMarkersUrl',
50
+ customMarkersRange: 'customMarkersRange',
46
51
  opacity: 'opacity',
47
52
  outline: 'outline',
48
53
  thickness: 'thickness',
@@ -122,6 +127,31 @@ export default class PointLayer extends Layer {
122
127
  channelScaleType: 'radius',
123
128
  accessor: 'getRadius',
124
129
  defaultValue: 1
130
+ },
131
+ customMarkers: {
132
+ property: 'customMarkers',
133
+ key: 'customMarkers',
134
+ field: 'customMarkersField',
135
+ scale: 'customMarkersScale',
136
+ domain: 'customMarkersDomain',
137
+ range: 'customMarkersRange',
138
+ fixed: 'customMarkersUrl',
139
+ channelScaleType: CHANNEL_SCALES.customMarker,
140
+ accessor: 'getIconUrl',
141
+ supportedFieldTypes: ['string'],
142
+ defaultValue: config => config.visConfig.customMarkersUrl,
143
+ condition: config => config.visConfig.customMarkers
144
+ },
145
+ rotation: {
146
+ property: 'rotation',
147
+ key: 'rotation',
148
+ field: 'rotationField',
149
+ channelScaleType: CHANNEL_SCALES.identity,
150
+ accessor: 'getRotation',
151
+ supportedFieldTypes: ['float', 'integer', 'real', 'number'],
152
+ defaultValue: () => 0,
153
+ nullValue: 0,
154
+ condition: config => config.rotationField
125
155
  }
126
156
  };
127
157
  }
@@ -181,7 +211,10 @@ export default class PointLayer extends Layer {
181
211
  // add stroke color visual channel
182
212
  strokeColorField: null,
183
213
  strokeColorDomain: [0, 1],
184
- strokeColorScale: 'quantile'
214
+ strokeColorScale: 'quantile',
215
+
216
+ customMarkersField: null,
217
+ customMarkersScale: 'ordinal'
185
218
  };
186
219
  }
187
220
 
@@ -237,22 +270,22 @@ export default class PointLayer extends Layer {
237
270
  this.updateMeta({bounds});
238
271
  }
239
272
 
273
+ isLayerHovered(objectInfo) {
274
+ return (
275
+ (objectInfo?.picked && objectInfo?.layer?.props?.id === this.id) ||
276
+ objectInfo?.layer?.props?.id === `${this.id}-icon`
277
+ );
278
+ }
279
+
280
+ // eslint-disable-next-line complexity
240
281
  renderLayer(opts) {
241
- const {data, gpuFilter, objectHovered, mapState, interactionConfig} = opts;
282
+ const {data, gpuFilter, objectHovered, mapState, interactionConfig, customMarkersStatus} = opts;
242
283
 
243
284
  // if no field size is defined we need to pass fixed radius = false
244
285
  const fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);
245
286
  const radiusScale = this.getRadiusScale(fixedRadius);
246
287
  const radiusUnits = this.getRadiusUnit(fixedRadius);
247
288
 
248
- const layerProps = {
249
- stroked: this.config.visConfig.outline,
250
- filled: this.config.visConfig.filled,
251
- lineWidthScale: this.config.visConfig.thickness,
252
- radiusScale,
253
- ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})
254
- };
255
-
256
289
  const updateTriggers = {
257
290
  getPosition: this.config.columns,
258
291
  getFilterValue: gpuFilter.filterValueUpdateTriggers,
@@ -278,16 +311,97 @@ export default class PointLayer extends Layer {
278
311
  };
279
312
  const hoveredObject = this.hasHoveredObject(objectHovered);
280
313
 
314
+ const textLabelLayer = this.renderTextLabelLayer(
315
+ {
316
+ getPosition: data.getPosition,
317
+ sharedProps,
318
+ getPixelOffset,
319
+ updateTriggers
320
+ },
321
+ opts
322
+ );
323
+ const commonLayerProps = {
324
+ ...defaultLayerProps,
325
+ parameters: {
326
+ // circles will be flat on the map when the altitude column is not used
327
+ depthTest: this.config.columns.altitude?.fieldIdx > -1
328
+ }
329
+ };
330
+
331
+ const customMarkersField = this.config.customMarkersField;
332
+ const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
333
+ const customMarkersConfigured =
334
+ customMarkers && Boolean(customMarkersUrl || (customMarkersField && customMarkersRange));
335
+ const customMarkersMisConfigured = customMarkers && !customMarkersConfigured;
336
+
337
+ if (customMarkersConfigured) {
338
+ const maxIconSize = this.getMaxMarkerSize();
339
+ const getIcon = this.getCustomMarkerIconAccessor(customMarkersStatus, data.getIconUrl);
340
+ const iconLayerProps = {
341
+ getIcon,
342
+ getPosition: data.getPosition,
343
+ getSize: data.getRadius,
344
+ getAngle: negateAccessor(data.getRotation),
345
+ sizeScale: radiusScale,
346
+ sizeUnits: 'pixels',
347
+ loadOptions: {
348
+ image: {
349
+ type: 'imagebitmap'
350
+ },
351
+ imagebitmap: {
352
+ resizeWidth: maxIconSize,
353
+ resizeHeight: maxIconSize,
354
+ resizeQuality: 'high'
355
+ }
356
+ }
357
+ };
358
+
359
+ return [
360
+ new IconLayer({
361
+ ...commonLayerProps,
362
+ ...iconLayerProps,
363
+ id: `${defaultLayerProps.id}-icon`,
364
+ data: data.data,
365
+ getColor: this.config.visConfig.filled ? data.getFillColor : UNKNOWN_COLOR,
366
+ getFilterValue: data.getFilterValue,
367
+ updateTriggers: {
368
+ getFilterValue: updateTriggers.getFilterValue,
369
+ getColor: [updateTriggers.getFillColor, this.config.visConfig.filled],
370
+ getIcon: [updateTriggers.getIconUrl, maxIconSize, customMarkersStatus],
371
+ getSize: updateTriggers.getRadius,
372
+ getAngle: updateTriggers.getRotation
373
+ }
374
+ }),
375
+ ...(hoveredObject
376
+ ? [
377
+ new IconLayer({
378
+ ...iconLayerProps,
379
+ ...this.getDefaultHoverLayerProps(),
380
+ id: `${defaultLayerProps.id}-icon-hovered`,
381
+ data: [hoveredObject],
382
+ getColor: this.config.highlightColor
383
+ })
384
+ ]
385
+ : []),
386
+ ...textLabelLayer
387
+ ];
388
+ }
389
+
390
+ const scatterPlotLayerProps = {
391
+ stroked: this.config.visConfig.outline,
392
+ filled: this.config.visConfig.filled,
393
+ lineWidthScale: this.config.visConfig.thickness,
394
+ // TODO: invent better way to render something when customMarkers are not yet configured
395
+ radiusScale: customMarkersMisConfigured ? radiusScale / 2 : radiusScale,
396
+ ...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})
397
+ };
398
+
281
399
  return [
282
400
  new ScatterplotLayer({
283
- ...defaultLayerProps,
284
401
  ...brushingProps,
285
- ...layerProps,
286
402
  ...data,
287
- parameters: {
288
- // circles will be flat on the map when the altitude column is not used
289
- depthTest: this.config.columns.altitude?.fieldIdx > -1
290
- },
403
+ ...commonLayerProps,
404
+ ...scatterPlotLayerProps,
291
405
  radiusUnits,
292
406
  lineWidthUnits: 'pixels',
293
407
  updateTriggers,
@@ -298,7 +412,7 @@ export default class PointLayer extends Layer {
298
412
  ? [
299
413
  new ScatterplotLayer({
300
414
  ...this.getDefaultHoverLayerProps(),
301
- ...layerProps,
415
+ ...scatterPlotLayerProps,
302
416
  data: [hoveredObject],
303
417
  getLineColor: this.config.highlightColor,
304
418
  getFillColor: this.config.highlightColor,
@@ -308,15 +422,7 @@ export default class PointLayer extends Layer {
308
422
  ]
309
423
  : []),
310
424
  // text label layer
311
- ...this.renderTextLabelLayer(
312
- {
313
- getPosition: data.getPosition,
314
- sharedProps,
315
- getPixelOffset,
316
- updateTriggers
317
- },
318
- opts
319
- )
425
+ ...textLabelLayer
320
426
  ];
321
427
  }
322
428
  }
@@ -34,7 +34,9 @@ export default {
34
34
  density: 'density',
35
35
  height: 'height',
36
36
  sum: 'sum',
37
- pointCount: 'Point Count'
37
+ pointCount: 'Point Count',
38
+ customMarkers: 'Markers',
39
+ rotation: 'Rotation'
38
40
  },
39
41
  placeholder: {
40
42
  search: 'Search',
@@ -85,6 +87,7 @@ export default {
85
87
  layer: {
86
88
  required: 'Required*',
87
89
  radius: 'Radius',
90
+ size: 'Size',
88
91
  color: 'Color',
89
92
  fillColor: 'Fill Color',
90
93
  outline: 'Outline',
@@ -101,6 +104,7 @@ export default {
101
104
  elevationByDescription: 'When off, height is based on count of points',
102
105
  colorByDescription: 'When off, color is based on count of points',
103
106
  aggregateBy: 'Aggregate {field} by',
107
+ addUrl: 'Add URL',
104
108
  '3DModel': '3D Model',
105
109
  '3DModelOptions': '3D Model Options',
106
110
  type: {
@@ -161,7 +165,9 @@ export default {
161
165
  weightIntensity: 'Weight Intensity',
162
166
  zoomScale: 'Zoom Scale',
163
167
  heightRange: 'Height Range',
164
- heightMultiplier: 'Height Multiplier'
168
+ heightMultiplier: 'Height Multiplier',
169
+ customMarkers: 'Custom markers',
170
+ url: 'Add URL'
165
171
  },
166
172
  layerVisibility: {
167
173
  title: 'Visibility by zoom level'
@@ -30,7 +30,12 @@ export {createNewDataEntry, datasetColorMaker, findDefaultColorField} from './da
30
30
  export {hexToRgb} from './color-utils';
31
31
  export {errorNotification} from './notifications-utils';
32
32
  export {dataURItoBlob, downloadFile} from './export-utils';
33
- export {calculateLayerData, prepareLayersToRender, prepareLayersForDeck} from './layer-utils';
33
+ export {
34
+ calculateLayerData,
35
+ prepareLayersToRender,
36
+ prepareLayersForDeck,
37
+ negateAccessor
38
+ } from './layer-utils';
34
39
  export {
35
40
  applyFilterFieldName,
36
41
  applyFiltersToDatasets,
@@ -73,4 +73,6 @@ export function getMaskExtensionProps(
73
73
  ): {
74
74
  maskId?: string,
75
75
  extensions: Array<unknown>
76
- }
76
+ }
77
+
78
+ export function negateAccessor<T extends ((d: any) => number) | number>(accessor: T): T;
@@ -126,7 +126,8 @@ export function renderDeckGlLayer(props, layerCallbacks, idx) {
126
126
  interactionConfig,
127
127
  animationConfig,
128
128
  mapLayers,
129
- maskPolygon
129
+ maskPolygon,
130
+ customMarkersStatus
130
131
  } = props;
131
132
  const layer = layers[idx];
132
133
  const data = layerData[idx];
@@ -144,7 +145,8 @@ export function renderDeckGlLayer(props, layerCallbacks, idx) {
144
145
  animationConfig,
145
146
  objectHovered,
146
147
  visible,
147
- maskPolygon
148
+ maskPolygon,
149
+ customMarkersStatus
148
150
  });
149
151
  }
150
152
 
@@ -195,3 +197,7 @@ export function getMaskExtensionProps(maskPolygon) {
195
197
  extensions: [maskExtension]
196
198
  };
197
199
  }
200
+
201
+ export function negateAccessor(accessor) {
202
+ return typeof accessor === 'function' ? (d, i) => -accessor(d, i) : -accessor;
203
+ }