@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.
- package/CHANGELOG.md +711 -0
- package/dist/components/common/field-selector.js +7 -3
- package/dist/components/common/item-selector/item-selector.js +6 -1
- package/dist/components/common/styled-components.js +2 -2
- package/dist/components/index.js +10 -1
- package/dist/components/kepler-gl.js +2 -1
- package/dist/components/side-panel/layer-panel/custom-markers-config-group.js +40 -0
- package/dist/components/side-panel/layer-panel/layer-configurator.js +16 -8
- package/dist/components/side-panel/layer-panel/vis-config-by-field-selector.js +21 -5
- package/dist/constants/default-settings.js +8 -6
- package/dist/layers/base-layer.js +92 -9
- package/dist/layers/layer-factory.js +23 -1
- package/dist/layers/point-layer/point-layer.js +125 -22
- package/dist/localization/en.js +9 -3
- package/dist/utils/index.js +7 -1
- package/dist/utils/layer-utils.js +12 -3
- package/package.json +1 -1
- package/src/.DS_Store +0 -0
- package/src/components/common/field-selector.js +12 -6
- package/src/components/common/item-selector/item-selector.js +4 -0
- package/src/components/common/styled-components.js +1 -1
- package/src/components/index.js +1 -0
- package/src/components/kepler-gl.js +1 -0
- package/src/components/side-panel/layer-panel/custom-markers-config-group.js +30 -0
- package/src/components/side-panel/layer-panel/layer-configurator.js +27 -10
- package/src/components/side-panel/layer-panel/vis-config-by-field-selector.js +21 -9
- package/src/constants/default-settings.js +12 -4
- package/src/layers/base-layer.js +74 -7
- package/src/layers/layer-factory.js +22 -0
- package/src/layers/point-layer/point-layer.js +133 -27
- package/src/localization/en.js +8 -2
- package/src/utils/index.js +6 -1
- package/src/utils/layer-utils.d.ts +3 -1
- 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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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 = {
|
package/src/layers/base-layer.js
CHANGED
|
@@ -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 =
|
|
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.
|
|
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
|
|
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
|
-
|
|
288
|
-
|
|
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
|
-
...
|
|
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
|
-
...
|
|
312
|
-
{
|
|
313
|
-
getPosition: data.getPosition,
|
|
314
|
-
sharedProps,
|
|
315
|
-
getPixelOffset,
|
|
316
|
-
updateTriggers
|
|
317
|
-
},
|
|
318
|
-
opts
|
|
319
|
-
)
|
|
425
|
+
...textLabelLayer
|
|
320
426
|
];
|
|
321
427
|
}
|
|
322
428
|
}
|
package/src/localization/en.js
CHANGED
|
@@ -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'
|
package/src/utils/index.js
CHANGED
|
@@ -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 {
|
|
33
|
+
export {
|
|
34
|
+
calculateLayerData,
|
|
35
|
+
prepareLayersToRender,
|
|
36
|
+
prepareLayersForDeck,
|
|
37
|
+
negateAccessor
|
|
38
|
+
} from './layer-utils';
|
|
34
39
|
export {
|
|
35
40
|
applyFilterFieldName,
|
|
36
41
|
applyFiltersToDatasets,
|
package/src/utils/layer-utils.js
CHANGED
|
@@ -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
|
+
}
|