@carto/kepler.gl 0.0.47 → 0.0.48-alpha.custom-markers.3
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/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 +19 -3
- package/dist/constants/default-settings.js +6 -5
- package/dist/layers/base-layer.js +111 -5
- package/dist/layers/layer-factory.js +23 -1
- package/dist/layers/point-layer/point-layer.js +123 -22
- package/dist/localization/en.js +9 -3
- package/dist/utils/layer-utils.js +5 -3
- package/dist/utils/table-utils/kepler-table.js +2 -3
- package/package.json +7 -4
- 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 +11 -1
- package/src/constants/default-settings.js +7 -3
- package/src/layers/base-layer.js +80 -2
- package/src/layers/layer-factory.js +22 -0
- package/src/layers/point-layer/point-layer.js +132 -27
- package/src/localization/en.js +8 -2
- package/src/utils/layer-utils.js +4 -2
- package/src/utils/table-utils/kepler-table.js +1 -1
|
@@ -85,7 +85,7 @@ export const SBFlexboxNoMargin = styled.div`
|
|
|
85
85
|
export const PanelLabel = styled.label.attrs({
|
|
86
86
|
className: 'side-panel-panel__label'
|
|
87
87
|
})`
|
|
88
|
-
color: ${props => props.theme.labelColor};
|
|
88
|
+
color: ${props => (props.active ? props.theme.labelColorActive : props.theme.labelColor)};
|
|
89
89
|
display: inline-block;
|
|
90
90
|
font-size: 11px;
|
|
91
91
|
font-weight: 400;
|
package/src/components/index.js
CHANGED
|
@@ -73,6 +73,7 @@ export {
|
|
|
73
73
|
export {default as LayerPanelFactory} from './side-panel/layer-panel/layer-panel';
|
|
74
74
|
export {default as LayerPanelHeaderFactory} from './side-panel/layer-panel/layer-panel-header';
|
|
75
75
|
export {default as LayerConfiguratorFactory} from './side-panel/layer-panel/layer-configurator';
|
|
76
|
+
export {default as CustomMarkersConfigGroupFactory} from './side-panel/layer-panel/custom-markers-config-group';
|
|
76
77
|
export {default as TextLabelPanelFactory} from './side-panel/layer-panel/text-label-panel';
|
|
77
78
|
export {LayerConfigGroupLabelFactory} from './side-panel/layer-panel/layer-config-group';
|
|
78
79
|
export {default as LayerVisibilityByZoomFactory} from './side-panel/layer-panel/layer-visibility-by-zoom';
|
|
@@ -130,6 +130,7 @@ export const mapFieldsSelector = props => ({
|
|
|
130
130
|
clicked: props.visState.clicked,
|
|
131
131
|
mousePos: props.visState.mousePos,
|
|
132
132
|
animationConfig: props.visState.animationConfig,
|
|
133
|
+
customMarkersStatus: props.visState.customMarkersStatus,
|
|
133
134
|
|
|
134
135
|
// uiState
|
|
135
136
|
activeSidePanel: props.uiState.activeSidePanel,
|
|
@@ -0,0 +1,30 @@
|
|
|
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
|
+
|
|
23
|
+
export default function CustomMarkersConfigGroupFactory() {
|
|
24
|
+
const CustomMarkersConfigGroup = () => {
|
|
25
|
+
return <div>To be overriden</div>;
|
|
26
|
+
};
|
|
27
|
+
return CustomMarkersConfigGroup;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
CustomMarkersConfigGroupFactory.deps = [];
|
|
@@ -36,6 +36,7 @@ import SourceDataSelectorFactory from 'components/side-panel/common/source-data-
|
|
|
36
36
|
import VisConfigSwitchFactory from './vis-config-switch';
|
|
37
37
|
import VisConfigSliderFactory from './vis-config-slider';
|
|
38
38
|
import LayerConfigGroupFactory, {ConfigGroupCollapsibleContent} from './layer-config-group';
|
|
39
|
+
import CustomMarkersConfigGroupFactory from './custom-markers-config-group';
|
|
39
40
|
import TextLabelPanelFactory from './text-label-panel';
|
|
40
41
|
|
|
41
42
|
import {capitalizeFirstLetter} from 'utils/utils';
|
|
@@ -102,7 +103,8 @@ LayerConfiguratorFactory.deps = [
|
|
|
102
103
|
LayerVisibilityByZoomFactory,
|
|
103
104
|
LayerColorSelectorFactory,
|
|
104
105
|
LayerColorRangeSelectorFactory,
|
|
105
|
-
ArcLayerColorSelectorFactory
|
|
106
|
+
ArcLayerColorSelectorFactory,
|
|
107
|
+
CustomMarkersConfigGroupFactory
|
|
106
108
|
];
|
|
107
109
|
|
|
108
110
|
export default function LayerConfiguratorFactory(
|
|
@@ -118,7 +120,8 @@ export default function LayerConfiguratorFactory(
|
|
|
118
120
|
LayerVisibilityByZoom,
|
|
119
121
|
LayerColorSelector,
|
|
120
122
|
LayerColorRangeSelector,
|
|
121
|
-
ArcLayerColorSelector
|
|
123
|
+
ArcLayerColorSelector,
|
|
124
|
+
CustomMarkersConfigGroup
|
|
122
125
|
) {
|
|
123
126
|
class LayerConfigurator extends Component {
|
|
124
127
|
static propTypes = {
|
|
@@ -199,8 +202,19 @@ export default function LayerConfiguratorFactory(
|
|
|
199
202
|
</LayerConfigGroup>
|
|
200
203
|
) : null}
|
|
201
204
|
|
|
205
|
+
{layer.type === LAYER_TYPES.point ? (
|
|
206
|
+
<CustomMarkersConfigGroup
|
|
207
|
+
layer={layer}
|
|
208
|
+
visConfiguratorProps={visConfiguratorProps}
|
|
209
|
+
layerChannelConfigProps={layerChannelConfigProps}
|
|
210
|
+
/>
|
|
211
|
+
) : null}
|
|
212
|
+
|
|
202
213
|
{/* Radius */}
|
|
203
|
-
<LayerConfigGroup
|
|
214
|
+
<LayerConfigGroup
|
|
215
|
+
label={layer.config.visConfig.customMarkers ? 'layer.size' : 'layer.radius'}
|
|
216
|
+
collapsible
|
|
217
|
+
>
|
|
204
218
|
{!layer.config.sizeField ? (
|
|
205
219
|
<VisConfigSlider
|
|
206
220
|
{...layer.visConfigSettings.radius}
|
|
@@ -225,6 +239,7 @@ export default function LayerConfiguratorFactory(
|
|
|
225
239
|
<VisConfigSwitch
|
|
226
240
|
{...layer.visConfigSettings.fixedRadius}
|
|
227
241
|
{...visConfiguratorProps}
|
|
242
|
+
disabled={layer.config.visConfig.customMarkers}
|
|
228
243
|
/>
|
|
229
244
|
) : null}
|
|
230
245
|
</ConfigGroupCollapsibleContent>
|
|
@@ -1031,7 +1046,6 @@ export function LayerColorSelectorFactory(ColorSelector) {
|
|
|
1031
1046
|
class LayerColorSelector extends Component {
|
|
1032
1047
|
render() {
|
|
1033
1048
|
const {layer, onChange, selectedColor, property = 'color', setColorUI} = this.props;
|
|
1034
|
-
|
|
1035
1049
|
return (
|
|
1036
1050
|
<SidePanelSection>
|
|
1037
1051
|
<PanelLabel>
|
|
@@ -1060,7 +1074,6 @@ export function ArcLayerColorSelectorFactory(ColorSelector) {
|
|
|
1060
1074
|
class ArcLayerColorSelector extends Component {
|
|
1061
1075
|
render() {
|
|
1062
1076
|
const {layer, onChangeConfig, onChangeVisConfig, property = 'color', setColorUI} = this.props;
|
|
1063
|
-
|
|
1064
1077
|
return (
|
|
1065
1078
|
<SidePanelSection>
|
|
1066
1079
|
<PanelLabel>
|
|
@@ -1153,14 +1166,17 @@ export function ChannelByValueSelectorFactory(VisConfigByFieldSelector) {
|
|
|
1153
1166
|
const scaleOptions = layer.getScaleOptions(channel.key);
|
|
1154
1167
|
const showScaleCustomizable =
|
|
1155
1168
|
channelScaleType === CHANNEL_SCALES.color &&
|
|
1169
|
+
scale &&
|
|
1156
1170
|
layer.config[scale] &&
|
|
1157
1171
|
Boolean(layer.config[field]) &&
|
|
1158
|
-
scaleOptions
|
|
1159
|
-
const showScale =
|
|
1172
|
+
scaleOptions?.length > 0;
|
|
1173
|
+
const showScale = Boolean(
|
|
1160
1174
|
!showScaleCustomizable &&
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1175
|
+
scale &&
|
|
1176
|
+
!layer.isAggregated &&
|
|
1177
|
+
layer.config[scale] &&
|
|
1178
|
+
scaleOptions?.length > 1
|
|
1179
|
+
);
|
|
1164
1180
|
const defaultDescription = 'layerConfiguration.defaultDescription';
|
|
1165
1181
|
const visRange = layer.config.visConfig[range];
|
|
1166
1182
|
const aggregator = layer.config.visConfig[aggregation];
|
|
@@ -1290,4 +1306,5 @@ export const AggregationTypeSelector = ({layer, channel, onChange}) => {
|
|
|
1290
1306
|
</SidePanelSection>
|
|
1291
1307
|
);
|
|
1292
1308
|
};
|
|
1309
|
+
|
|
1293
1310
|
/* eslint-enable max-params */
|
|
@@ -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,6 +118,8 @@ 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
125
|
<div>
|
|
@@ -431,6 +431,7 @@ 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
436
|
sizeAggr: null
|
|
436
437
|
});
|
|
@@ -505,7 +506,8 @@ export const FIELD_AGGREGATORS = [
|
|
|
505
506
|
export const linearFieldScaleFunctions = {
|
|
506
507
|
[CHANNEL_SCALES.color]: [SCALE_TYPES.quantize, SCALE_TYPES.quantile, SCALE_TYPES.custom],
|
|
507
508
|
[CHANNEL_SCALES.radius]: [SCALE_TYPES.sqrt],
|
|
508
|
-
[CHANNEL_SCALES.size]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log]
|
|
509
|
+
[CHANNEL_SCALES.size]: [SCALE_TYPES.linear, SCALE_TYPES.sqrt, SCALE_TYPES.log],
|
|
510
|
+
[CHANNEL_SCALES.customMarker]: [SCALE_TYPES.ordinal]
|
|
509
511
|
};
|
|
510
512
|
|
|
511
513
|
export const linearFieldAggrScaleFunctions = {
|
|
@@ -533,7 +535,8 @@ export const linearFieldAggrScaleFunctions = {
|
|
|
533
535
|
export const ordinalFieldScaleFunctions = {
|
|
534
536
|
[CHANNEL_SCALES.color]: [SCALE_TYPES.ordinal],
|
|
535
537
|
[CHANNEL_SCALES.radius]: [SCALE_TYPES.point],
|
|
536
|
-
[CHANNEL_SCALES.size]: [SCALE_TYPES.point]
|
|
538
|
+
[CHANNEL_SCALES.size]: [SCALE_TYPES.point],
|
|
539
|
+
[CHANNEL_SCALES.customMarker]: [SCALE_TYPES.ordinal]
|
|
537
540
|
};
|
|
538
541
|
|
|
539
542
|
export const ordinalFieldAggrScaleFunctions = {
|
|
@@ -554,7 +557,8 @@ export const ordinalFieldAggrScaleFunctions = {
|
|
|
554
557
|
export const notSupportedScaleOpts = {
|
|
555
558
|
[CHANNEL_SCALES.color]: [],
|
|
556
559
|
[CHANNEL_SCALES.radius]: [],
|
|
557
|
-
[CHANNEL_SCALES.size]: []
|
|
560
|
+
[CHANNEL_SCALES.size]: [],
|
|
561
|
+
[CHANNEL_SCALES.customMarker]: []
|
|
558
562
|
};
|
|
559
563
|
|
|
560
564
|
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,6 +66,10 @@ 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});
|
|
@@ -808,6 +813,39 @@ class Layer {
|
|
|
808
813
|
return this.getVisChannelScale(colorScale, colorDomain, colorRange.colors.map(hexToRgb));
|
|
809
814
|
}
|
|
810
815
|
|
|
816
|
+
getCustomMarkersScale(range) {
|
|
817
|
+
let unknownValue = FALLBACK_ICON;
|
|
818
|
+
if (!range) {
|
|
819
|
+
return () => unknownValue;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const mapping = new Map();
|
|
823
|
+
|
|
824
|
+
if (Array.isArray(range)) {
|
|
825
|
+
for (const {value, markerUrl} of range) {
|
|
826
|
+
if (value === null) {
|
|
827
|
+
unknownValue = markerUrl;
|
|
828
|
+
} else if (markerUrl) {
|
|
829
|
+
mapping.set(value, markerUrl);
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
} else {
|
|
833
|
+
unknownValue = range.othersMarker;
|
|
834
|
+
for (const {value, markerUrl} of range.markerMap) {
|
|
835
|
+
if (markerUrl) {
|
|
836
|
+
mapping.set(value, markerUrl);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
// @ts-ignore d3 scale
|
|
842
|
+
const scale = scaleOrdinal()
|
|
843
|
+
.domain(Array.from(mapping.keys()))
|
|
844
|
+
.range(Array.from(mapping.values()))
|
|
845
|
+
.unknown(unknownValue);
|
|
846
|
+
return scale;
|
|
847
|
+
}
|
|
848
|
+
|
|
811
849
|
/**
|
|
812
850
|
* Mapping from visual channels to deck.gl accesors
|
|
813
851
|
* @param {Object} param Parameters
|
|
@@ -818,6 +856,7 @@ class Layer {
|
|
|
818
856
|
getAttributeAccessors({dataAccessor = defaultDataAccessor, dataContainer}) {
|
|
819
857
|
const attributeAccessors = {};
|
|
820
858
|
|
|
859
|
+
// eslint-disable-next-line complexity
|
|
821
860
|
Object.keys(this.visualChannels).forEach(channel => {
|
|
822
861
|
const {
|
|
823
862
|
field,
|
|
@@ -833,8 +872,13 @@ class Layer {
|
|
|
833
872
|
aggregation
|
|
834
873
|
} = this.visualChannels[channel];
|
|
835
874
|
|
|
836
|
-
const shouldGetScale =
|
|
875
|
+
const shouldGetScale = Boolean(
|
|
876
|
+
(scale || channelScaleType === 'identity') && this.config[field]
|
|
877
|
+
);
|
|
837
878
|
|
|
879
|
+
if (field === 'rotationField') {
|
|
880
|
+
console.log('recreating rotationField accessor', this.config[field]);
|
|
881
|
+
}
|
|
838
882
|
if (shouldGetScale) {
|
|
839
883
|
const args = [this.config[scale], this.config[domain], this.config.visConfig[range]];
|
|
840
884
|
const isFixed = fixed && this.config.visConfig[fixed];
|
|
@@ -844,8 +888,12 @@ class Layer {
|
|
|
844
888
|
const aggregationValue = this.config.visConfig[aggregation];
|
|
845
889
|
|
|
846
890
|
const scaleFunction =
|
|
847
|
-
channelScaleType ===
|
|
891
|
+
channelScaleType === 'identity'
|
|
892
|
+
? v => v
|
|
893
|
+
: channelScaleType === CHANNEL_SCALES.color
|
|
848
894
|
? this.getColorScale(...args)
|
|
895
|
+
: channelScaleType === CHANNEL_SCALES.customMarker
|
|
896
|
+
? this.getCustomMarkersScale(this.config.visConfig[range])
|
|
849
897
|
: this.getVisChannelScale(...args, isFixed);
|
|
850
898
|
|
|
851
899
|
attributeAccessors[accessor] = d =>
|
|
@@ -1191,6 +1239,12 @@ class Layer {
|
|
|
1191
1239
|
return fixed ? 1 : (this.config[field] ? 1 : radius);
|
|
1192
1240
|
}
|
|
1193
1241
|
|
|
1242
|
+
getMaxMarkerSize() {
|
|
1243
|
+
const {sizeField, visConfig} = this.config;
|
|
1244
|
+
const {radiusRange, radius} = visConfig;
|
|
1245
|
+
return Math.ceil((sizeField ? radiusRange[1] : radius));
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1194
1248
|
getRadiusUnit(fixedRadius) {
|
|
1195
1249
|
const radiusChannel = Object.values(this.visualChannels).find(vc => vc.property === 'radius');
|
|
1196
1250
|
|
|
@@ -1300,6 +1354,30 @@ class Layer {
|
|
|
1300
1354
|
}, []);
|
|
1301
1355
|
}
|
|
1302
1356
|
|
|
1357
|
+
/**
|
|
1358
|
+
* Create deck.gl `IconLayer` "autoPack" mode accessor for custom markers.
|
|
1359
|
+
*
|
|
1360
|
+
* @param {*} customMarkersStatus Record<url, UrlStatus> provided by builder, UrlStatus `ok` means URL is ok and ready to render
|
|
1361
|
+
* @param {*} iconUrlAccessor deck.gl accessor (or value) that resolves to URL of icon for given row
|
|
1362
|
+
* @returns deck.gl accessor that returns UnpackedIcon ready to consume by IconLayer
|
|
1363
|
+
*/
|
|
1364
|
+
getCustomMarkerIconAccessor(customMarkersStatus, iconUrlAccessor) {
|
|
1365
|
+
const maxIconSize = this.getMaxMarkerSize();
|
|
1366
|
+
|
|
1367
|
+
return d => {
|
|
1368
|
+
const wantedUrl =
|
|
1369
|
+
(typeof iconUrlAccessor === 'function') ? iconUrlAccessor(d) : iconUrlAccessor;
|
|
1370
|
+
const url = customMarkersStatus?.[wantedUrl] === 'ok' ? wantedUrl : FALLBACK_ICON;
|
|
1371
|
+
return {
|
|
1372
|
+
id: `${url}@@${maxIconSize}`,
|
|
1373
|
+
url,
|
|
1374
|
+
width: maxIconSize,
|
|
1375
|
+
height: maxIconSize,
|
|
1376
|
+
mask: true
|
|
1377
|
+
};
|
|
1378
|
+
};
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1303
1381
|
calculateDataAttribute(keplerTable, getPosition) {
|
|
1304
1382
|
// implemented in subclasses
|
|
1305
1383
|
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,13 @@
|
|
|
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';
|
|
26
27
|
import {findDefaultColorField} from 'utils/dataset-utils';
|
|
27
28
|
import PointLayerIcon from './point-layer-icon';
|
|
28
|
-
import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES} from 'constants/default-settings';
|
|
29
|
+
import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES, UNKNOWN_COLOR} from 'constants/default-settings';
|
|
29
30
|
|
|
30
31
|
import {getTextOffsetByRadius, formatTextLabelData} from '../layer-text-label';
|
|
31
32
|
|
|
@@ -43,6 +44,9 @@ const brushingExtension = new BrushingExtension();
|
|
|
43
44
|
export const pointVisConfigs = {
|
|
44
45
|
radius: 'radius',
|
|
45
46
|
fixedRadius: 'fixedRadius',
|
|
47
|
+
customMarkers: 'customMarkers',
|
|
48
|
+
customMarkersUrl: 'customMarkersUrl',
|
|
49
|
+
customMarkersRange: 'customMarkersRange',
|
|
46
50
|
opacity: 'opacity',
|
|
47
51
|
outline: 'outline',
|
|
48
52
|
thickness: 'thickness',
|
|
@@ -122,6 +126,31 @@ export default class PointLayer extends Layer {
|
|
|
122
126
|
channelScaleType: 'radius',
|
|
123
127
|
accessor: 'getRadius',
|
|
124
128
|
defaultValue: 1
|
|
129
|
+
},
|
|
130
|
+
customMarkers: {
|
|
131
|
+
property: 'customMarkers',
|
|
132
|
+
key: 'customMarkers',
|
|
133
|
+
field: 'customMarkersField',
|
|
134
|
+
scale: 'customMarkersScale',
|
|
135
|
+
domain: 'customMarkersDomain',
|
|
136
|
+
range: 'customMarkersRange',
|
|
137
|
+
fixed: 'customMarkersUrl',
|
|
138
|
+
channelScaleType: CHANNEL_SCALES.customMarker,
|
|
139
|
+
accessor: 'getIconUrl',
|
|
140
|
+
supportedFieldTypes: ['string'],
|
|
141
|
+
defaultValue: config => config.visConfig.customMarkersUrl,
|
|
142
|
+
condition: config => config.visConfig.customMarkers
|
|
143
|
+
},
|
|
144
|
+
rotation: {
|
|
145
|
+
property: 'rotation',
|
|
146
|
+
key: 'rotation',
|
|
147
|
+
field: 'rotationField',
|
|
148
|
+
channelScaleType: 'identity',
|
|
149
|
+
accessor: 'getAngle',
|
|
150
|
+
supportedFieldTypes: ['float', 'integer', 'real', 'number'],
|
|
151
|
+
defaultValue: () => 0,
|
|
152
|
+
nullValue: 0,
|
|
153
|
+
condition: config => config.rotationField
|
|
125
154
|
}
|
|
126
155
|
};
|
|
127
156
|
}
|
|
@@ -181,7 +210,10 @@ export default class PointLayer extends Layer {
|
|
|
181
210
|
// add stroke color visual channel
|
|
182
211
|
strokeColorField: null,
|
|
183
212
|
strokeColorDomain: [0, 1],
|
|
184
|
-
strokeColorScale: 'quantile'
|
|
213
|
+
strokeColorScale: 'quantile',
|
|
214
|
+
|
|
215
|
+
customMarkersField: null,
|
|
216
|
+
customMarkersScale: 'ordinal'
|
|
185
217
|
};
|
|
186
218
|
}
|
|
187
219
|
|
|
@@ -237,22 +269,22 @@ export default class PointLayer extends Layer {
|
|
|
237
269
|
this.updateMeta({bounds});
|
|
238
270
|
}
|
|
239
271
|
|
|
272
|
+
isLayerHovered(objectInfo) {
|
|
273
|
+
return (
|
|
274
|
+
(objectInfo?.picked && objectInfo?.layer?.props?.id === this.id) ||
|
|
275
|
+
objectInfo?.layer?.props?.id === `${this.id}-icon`
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// eslint-disable-next-line complexity
|
|
240
280
|
renderLayer(opts) {
|
|
241
|
-
const {data, gpuFilter, objectHovered, mapState, interactionConfig} = opts;
|
|
281
|
+
const {data, gpuFilter, objectHovered, mapState, interactionConfig, customMarkersStatus} = opts;
|
|
242
282
|
|
|
243
283
|
// if no field size is defined we need to pass fixed radius = false
|
|
244
284
|
const fixedRadius = this.config.visConfig.fixedRadius && Boolean(this.config.sizeField);
|
|
245
285
|
const radiusScale = this.getRadiusScale(fixedRadius);
|
|
246
286
|
const radiusUnits = this.getRadiusUnit(fixedRadius);
|
|
247
287
|
|
|
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
288
|
const updateTriggers = {
|
|
257
289
|
getPosition: this.config.columns,
|
|
258
290
|
getFilterValue: gpuFilter.filterValueUpdateTriggers,
|
|
@@ -278,16 +310,97 @@ export default class PointLayer extends Layer {
|
|
|
278
310
|
};
|
|
279
311
|
const hoveredObject = this.hasHoveredObject(objectHovered);
|
|
280
312
|
|
|
313
|
+
const textLabelLayer = this.renderTextLabelLayer(
|
|
314
|
+
{
|
|
315
|
+
getPosition: data.getPosition,
|
|
316
|
+
sharedProps,
|
|
317
|
+
getPixelOffset,
|
|
318
|
+
updateTriggers
|
|
319
|
+
},
|
|
320
|
+
opts
|
|
321
|
+
);
|
|
322
|
+
const commonLayerProps = {
|
|
323
|
+
...defaultLayerProps,
|
|
324
|
+
parameters: {
|
|
325
|
+
// circles will be flat on the map when the altitude column is not used
|
|
326
|
+
depthTest: this.config.columns.altitude?.fieldIdx > -1
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const customMarkersField = this.config.customMarkersField;
|
|
331
|
+
const {customMarkers, customMarkersUrl, customMarkersRange} = this.config.visConfig;
|
|
332
|
+
const customMarkersConfigured =
|
|
333
|
+
customMarkers && Boolean(customMarkersUrl || (customMarkersField && customMarkersRange));
|
|
334
|
+
const customMarkersMisConfigured = customMarkers && !customMarkersConfigured;
|
|
335
|
+
|
|
336
|
+
if (customMarkersConfigured) {
|
|
337
|
+
const maxIconSize = this.getMaxMarkerSize();
|
|
338
|
+
const getIcon = this.getCustomMarkerIconAccessor(customMarkersStatus, data.getIconUrl);
|
|
339
|
+
const iconLayerProps = {
|
|
340
|
+
getIcon,
|
|
341
|
+
getPosition: data.getPosition,
|
|
342
|
+
getSize: data.getRadius,
|
|
343
|
+
getAngle: data.getAngle,
|
|
344
|
+
sizeScale: radiusScale,
|
|
345
|
+
sizeUnits: 'pixels',
|
|
346
|
+
loadOptions: {
|
|
347
|
+
image: {
|
|
348
|
+
type: 'imagebitmap'
|
|
349
|
+
},
|
|
350
|
+
imagebitmap: {
|
|
351
|
+
resizeWidth: maxIconSize,
|
|
352
|
+
resizeHeight: maxIconSize,
|
|
353
|
+
resizeQuality: 'high'
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
return [
|
|
359
|
+
new IconLayer({
|
|
360
|
+
...commonLayerProps,
|
|
361
|
+
...iconLayerProps,
|
|
362
|
+
id: `${defaultLayerProps.id}-icon`,
|
|
363
|
+
data: data.data,
|
|
364
|
+
getColor: this.config.visConfig.filled ? data.getFillColor : UNKNOWN_COLOR,
|
|
365
|
+
getFilterValue: data.getFilterValue,
|
|
366
|
+
updateTriggers: {
|
|
367
|
+
getFilterValue: updateTriggers.getFilterValue,
|
|
368
|
+
getColor: [updateTriggers.getFillColor, this.config.visConfig.filled],
|
|
369
|
+
getIcon: [updateTriggers.getIconUrl, maxIconSize, customMarkersStatus],
|
|
370
|
+
getSize: updateTriggers.getRadius,
|
|
371
|
+
getAngle: updateTriggers.getAngle
|
|
372
|
+
}
|
|
373
|
+
}),
|
|
374
|
+
...(hoveredObject
|
|
375
|
+
? [
|
|
376
|
+
new IconLayer({
|
|
377
|
+
...iconLayerProps,
|
|
378
|
+
...this.getDefaultHoverLayerProps(),
|
|
379
|
+
id: `${defaultLayerProps.id}-icon-hovered`,
|
|
380
|
+
data: [hoveredObject],
|
|
381
|
+
getColor: this.config.highlightColor
|
|
382
|
+
})
|
|
383
|
+
]
|
|
384
|
+
: []),
|
|
385
|
+
...textLabelLayer
|
|
386
|
+
];
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const scatterPlotLayerProps = {
|
|
390
|
+
stroked: this.config.visConfig.outline,
|
|
391
|
+
filled: this.config.visConfig.filled,
|
|
392
|
+
lineWidthScale: this.config.visConfig.thickness,
|
|
393
|
+
// TODO: invent better way to render something when customMarkers are not yet configured
|
|
394
|
+
radiusScale: customMarkersMisConfigured ? radiusScale / 2 : radiusScale,
|
|
395
|
+
...(this.config.visConfig.fixedRadius ? {} : {radiusMaxPixels: 500})
|
|
396
|
+
};
|
|
397
|
+
|
|
281
398
|
return [
|
|
282
399
|
new ScatterplotLayer({
|
|
283
|
-
...defaultLayerProps,
|
|
284
400
|
...brushingProps,
|
|
285
|
-
...layerProps,
|
|
286
401
|
...data,
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
depthTest: this.config.columns.altitude?.fieldIdx > -1
|
|
290
|
-
},
|
|
402
|
+
...commonLayerProps,
|
|
403
|
+
...scatterPlotLayerProps,
|
|
291
404
|
radiusUnits,
|
|
292
405
|
lineWidthUnits: 'pixels',
|
|
293
406
|
updateTriggers,
|
|
@@ -298,7 +411,7 @@ export default class PointLayer extends Layer {
|
|
|
298
411
|
? [
|
|
299
412
|
new ScatterplotLayer({
|
|
300
413
|
...this.getDefaultHoverLayerProps(),
|
|
301
|
-
...
|
|
414
|
+
...scatterPlotLayerProps,
|
|
302
415
|
data: [hoveredObject],
|
|
303
416
|
getLineColor: this.config.highlightColor,
|
|
304
417
|
getFillColor: this.config.highlightColor,
|
|
@@ -308,15 +421,7 @@ export default class PointLayer extends Layer {
|
|
|
308
421
|
]
|
|
309
422
|
: []),
|
|
310
423
|
// text label layer
|
|
311
|
-
...
|
|
312
|
-
{
|
|
313
|
-
getPosition: data.getPosition,
|
|
314
|
-
sharedProps,
|
|
315
|
-
getPixelOffset,
|
|
316
|
-
updateTriggers
|
|
317
|
-
},
|
|
318
|
-
opts
|
|
319
|
-
)
|
|
424
|
+
...textLabelLayer
|
|
320
425
|
];
|
|
321
426
|
}
|
|
322
427
|
}
|
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/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
|
|