@carto/api-client 0.5.16 → 0.5.18-alpha.colormapfix-1

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.
@@ -16,7 +16,6 @@ import {
16
16
  TEXT_NUMBER_FORMATTER,
17
17
  TEXT_LABEL_INDEX,
18
18
  TEXT_OUTLINE_OPACITY,
19
- type D3Scale,
20
19
  type ScaleType,
21
20
  } from './layer-map.js';
22
21
 
@@ -32,19 +31,38 @@ import type {
32
31
  VisualChannelField,
33
32
  } from './types.js';
34
33
  import {isRemoteCalculationSupported} from './utils.js';
34
+ import {
35
+ getRasterTileLayerStylePropsRgb,
36
+ getRasterTileLayerStylePropsScaledBand,
37
+ } from './raster-layer.js';
38
+ import type {TilejsonResult} from '../sources/types.js';
35
39
 
36
40
  export type Scale = {
41
+ type: ScaleType;
37
42
  field: VisualChannelField;
43
+
44
+ /** Natural domain of the scale, as defined by the data */
38
45
  domain: string[] | number[];
39
- range: string[] | number[];
40
- type: ScaleType;
46
+
47
+ /** Domain of the user to construct d3 scale */
48
+ scaleDomain?: string[] | number[];
49
+ range?: string[] | number[];
41
50
  };
42
51
 
52
+ export type ScaleKey =
53
+ | 'fillColor'
54
+ | 'pointRadius'
55
+ | 'lineColor'
56
+ | 'elevation'
57
+ | 'weight';
58
+
59
+ export type Scales = Partial<Record<ScaleKey, Scale>>;
60
+
43
61
  export type LayerDescriptor = {
44
62
  type: LayerType;
45
63
  props: Record<string, any>;
46
64
  filters?: Filters;
47
- scales: Record<ScaleKey, Scale>;
65
+ scales: Scales;
48
66
  };
49
67
 
50
68
  export type ParseMapResult = {
@@ -68,13 +86,62 @@ export type ParseMapResult = {
68
86
  layers: LayerDescriptor[];
69
87
  };
70
88
 
89
+ export function getLayerDescriptor({
90
+ mapConfig,
91
+ layer,
92
+ dataset,
93
+ }: {
94
+ mapConfig: KeplerMapConfig;
95
+ layer: MapConfigLayer;
96
+ dataset: Dataset;
97
+ }) {
98
+ const {filters, visState} = mapConfig;
99
+ const {layerBlending, interactionConfig} = visState;
100
+ const {id, type, config, visualChannels} = layer;
101
+ const {data, id: datasetId} = dataset;
102
+
103
+ const {propMap, defaultProps} = getLayerProps(type, config, dataset);
104
+
105
+ const styleProps = createStyleProps(config, propMap);
106
+
107
+ const {channelProps, scales} = createChannelProps(
108
+ id,
109
+ type,
110
+ config,
111
+ visualChannels,
112
+ data,
113
+ dataset
114
+ );
115
+ const layerDescriptor: LayerDescriptor = {
116
+ type,
117
+ filters:
118
+ isEmptyObject(filters) || isRemoteCalculationSupported(dataset)
119
+ ? undefined
120
+ : filters[datasetId],
121
+ props: {
122
+ id,
123
+ data,
124
+ ...defaultProps,
125
+ ...createInteractionProps(interactionConfig),
126
+ ...styleProps,
127
+ ...channelProps,
128
+ ...createParametersProp(layerBlending, styleProps.parameters || {}), // Must come after style
129
+ ...createLoadOptions(data.accessToken),
130
+ },
131
+ scales,
132
+ };
133
+ return layerDescriptor;
134
+ }
135
+
71
136
  export function parseMap(json: any) {
72
137
  const {keplerMapConfig, datasets, token} = json;
73
138
  assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
74
- const config = keplerMapConfig.config as KeplerMapConfig;
75
- const {filters, mapState, mapStyle, popupSettings, legendSettings} = config;
76
- const {layers, layerBlending, interactionConfig} = config.visState;
139
+ const mapConfig = keplerMapConfig.config as KeplerMapConfig;
140
+ const {mapState, mapStyle, popupSettings, legendSettings, visState} =
141
+ mapConfig;
142
+ const {layers} = visState;
77
143
 
144
+ const layersReverse = [...layers].reverse();
78
145
  return {
79
146
  id: json.id,
80
147
  title: json.title,
@@ -87,57 +154,24 @@ export function parseMap(json: any) {
87
154
  popupSettings,
88
155
  legendSettings,
89
156
  token,
90
- layers: layers
91
- .reverse()
92
- .map(({id, type, config, visualChannels}: MapConfigLayer) => {
93
- try {
94
- const {dataId} = config;
95
- const dataset: Dataset | null = datasets.find(
96
- (d: any) => d.id === dataId
97
- );
98
- assert(dataset, `No dataset matching dataId: ${dataId}`);
99
- const {data} = dataset;
100
- assert(data, `No data loaded for dataId: ${dataId}`);
101
-
102
- const {propMap, defaultProps} = getLayerProps(type, config, dataset);
103
-
104
- const styleProps = createStyleProps(config, propMap);
105
-
106
- const {channelProps, scales} = createChannelProps(
107
- id,
108
- type,
109
- config,
110
- visualChannels,
111
- data,
112
- dataset
113
- );
114
- const layer: LayerDescriptor = {
115
- type,
116
- filters:
117
- isEmptyObject(filters) || isRemoteCalculationSupported(dataset)
118
- ? undefined
119
- : filters[dataId],
120
- props: {
121
- id,
122
- data,
123
- ...defaultProps,
124
- ...createInteractionProps(interactionConfig),
125
- ...styleProps,
126
- ...channelProps,
127
- ...createParametersProp(
128
- layerBlending,
129
- styleProps.parameters || {}
130
- ), // Must come after style
131
- ...createLoadOptions(token),
132
- },
133
- scales,
134
- };
135
- return layer;
136
- } catch (e: any) {
137
- console.error(e.message);
138
- return undefined;
139
- }
140
- }),
157
+ layers: layersReverse.map((layer: MapConfigLayer) => {
158
+ try {
159
+ const {dataId} = layer.config;
160
+ const dataset: Dataset | null = datasets.find(
161
+ (d: any) => d.id === dataId
162
+ );
163
+ assert(dataset, `No dataset matching dataId: ${dataId}`);
164
+ const layerDescriptor = getLayerDescriptor({
165
+ mapConfig,
166
+ layer,
167
+ dataset,
168
+ });
169
+ return layerDescriptor;
170
+ } catch (e: any) {
171
+ console.error(e.message);
172
+ return undefined;
173
+ }
174
+ }),
141
175
  };
142
176
  }
143
177
 
@@ -204,7 +238,7 @@ function createStyleProps(config: MapLayerConfig, mapping: any) {
204
238
  if (Array.isArray(result[colorAccessor])) {
205
239
  const color = [...result[colorAccessor]];
206
240
  const opacityKey = OPACITY_MAP[colorAccessor];
207
- const opacity = config.visConfig[opacityKey as keyof VisConfig];
241
+ const opacity = config.visConfig[opacityKey as keyof VisConfig] as number;
208
242
  color[3] = opacityToAlpha(opacity);
209
243
  result[colorAccessor] = color;
210
244
  }
@@ -216,62 +250,75 @@ function createStyleProps(config: MapLayerConfig, mapping: any) {
216
250
  return result;
217
251
  }
218
252
 
219
- function domainAndRangeFromScale(
220
- scale: D3Scale
221
- ): Pick<Scale, 'domain' | 'range'> {
222
- return {
223
- domain: scale.domain(),
224
- range: scale.range(),
225
- };
226
- }
227
-
228
- export type ScaleKey =
229
- | 'fillColor'
230
- | 'pointRadius'
231
- | 'lineColor'
232
- | 'elevation'
233
- | 'weight';
234
-
235
253
  function createChannelProps(
236
254
  id: string,
237
255
  layerType: LayerType,
238
256
  config: MapLayerConfig,
239
257
  visualChannels: VisualChannels,
240
- data: any,
258
+ data: TilejsonResult,
241
259
  dataset: Dataset
242
- ): {channelProps: Record<string, any>; scales: Record<ScaleKey, Scale>} {
243
- const {
244
- colorField,
245
- colorScale,
246
- radiusField,
247
- radiusScale,
248
- strokeColorField,
249
- strokeColorScale,
250
- weightField,
251
- } = visualChannels;
252
- const {heightField, heightScale} = visualChannels;
260
+ ): {
261
+ channelProps: Record<string, any>;
262
+ scales: Partial<Record<ScaleKey, Scale>>;
263
+ } {
264
+ if (layerType === 'raster') {
265
+ const rasterMetadata = data.raster_metadata;
266
+ if (!rasterMetadata) {
267
+ return {
268
+ channelProps: {},
269
+ scales: {},
270
+ };
271
+ }
272
+ const rasterStyleType = config.visConfig.rasterStyleType;
273
+ if (rasterStyleType === 'Rgb') {
274
+ return {
275
+ channelProps: getRasterTileLayerStylePropsRgb({
276
+ layerConfig: config,
277
+ rasterMetadata,
278
+ visualChannels,
279
+ }),
280
+ scales: {}, // TODO
281
+ };
282
+ } else {
283
+ return {
284
+ channelProps: getRasterTileLayerStylePropsScaledBand({
285
+ layerConfig: config,
286
+ visualChannels,
287
+ rasterMetadata,
288
+ }),
289
+ scales: {
290
+ // TODO
291
+ },
292
+ };
293
+ }
294
+ }
253
295
  const {textLabel, visConfig} = config;
254
296
  const result: Record<string, any> = {};
297
+ const updateTriggers: Record<string, any> = {};
255
298
 
256
299
  const scales: Record<string, Scale> = {};
257
300
 
258
- if (colorField) {
259
- const {colorAggregation: aggregation, colorRange: range} = visConfig;
260
- const {accessor, scale} = getColorAccessor(
261
- colorField,
262
- colorScale!,
263
- {aggregation, range},
264
- visConfig.opacity,
265
- data
266
- );
267
- result.getFillColor = accessor;
268
- scales.fillColor = {
269
- field: colorField,
270
- type: colorScale!,
271
- ...domainAndRangeFromScale(scale),
272
- };
273
- } else if (visConfig.filled) {
274
- scales.fillColor = {} as any;
301
+ // fill color
302
+ {
303
+ const {colorField, colorScale} = visualChannels;
304
+ const {colorRange, colorAggregation} = visConfig;
305
+ if (colorField && colorScale && colorRange) {
306
+ const {accessor, ...scaleProps} = getColorAccessor(
307
+ colorField,
308
+ colorScale,
309
+ {aggregation: colorAggregation, range: colorRange},
310
+ visConfig.opacity,
311
+ data
312
+ );
313
+ result.getFillColor = accessor;
314
+ scales.fillColor = updateTriggers.getFillColor = {
315
+ field: colorField,
316
+ type: colorScale,
317
+ ...scaleProps,
318
+ };
319
+ } else {
320
+ scales.fillColor = {} as any;
321
+ }
275
322
  }
276
323
 
277
324
  if (layerType === 'clusterTile') {
@@ -288,6 +335,8 @@ function createChannelProps(
288
335
  return d.properties[aggregationExpAlias];
289
336
  };
290
337
 
338
+ updateTriggers.getWeight = aggregationExpAlias;
339
+
291
340
  result.getPointRadius = (d: any, info: any) => {
292
341
  return calculateClusterRadius(
293
342
  d.properties,
@@ -296,6 +345,10 @@ function createChannelProps(
296
345
  aggregationExpAlias
297
346
  );
298
347
  };
348
+ updateTriggers.getPointRadius = {
349
+ aggregationExpAlias,
350
+ radiusRange: visConfig.radiusRange,
351
+ };
299
352
 
300
353
  result.textCharacterSet = 'auto';
301
354
  result.textFontFamily = 'Inter, sans';
@@ -305,6 +358,8 @@ function createChannelProps(
305
358
  result.getText = (d: any) =>
306
359
  TEXT_NUMBER_FORMATTER.format(d.properties[aggregationExpAlias]);
307
360
 
361
+ updateTriggers.getText = aggregationExpAlias;
362
+
308
363
  result.getTextColor = config.textLabel[TEXT_LABEL_INDEX].color;
309
364
  result.textOutlineColor = [
310
365
  ...(config.textLabel[TEXT_LABEL_INDEX].outlineColor as number[]),
@@ -322,73 +377,122 @@ function createChannelProps(
322
377
  );
323
378
  return calculateClusterTextFontSize(radius);
324
379
  };
325
- }
326
380
 
327
- if (radiusField) {
328
- const {accessor, scale} = getSizeAccessor(
329
- radiusField,
330
- radiusScale,
331
- visConfig.sizeAggregation,
332
- visConfig.radiusRange || visConfig.sizeRange,
333
- data
334
- );
335
- result.getPointRadius = accessor;
336
- scales.pointRadius = {
337
- field: radiusField,
338
- type: radiusScale || 'identity',
339
- ...domainAndRangeFromScale(scale),
381
+ updateTriggers.getTextSize = {
382
+ aggregationExpAlias,
383
+ radiusRange: visConfig.radiusRange,
340
384
  };
341
385
  }
342
386
 
343
- if (strokeColorField) {
344
- const opacity =
345
- visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
346
- const {strokeColorAggregation: aggregation, strokeColorRange: range} =
347
- visConfig;
348
- const {accessor, scale} = getColorAccessor(
349
- strokeColorField,
350
- strokeColorScale!,
351
- {aggregation, range},
352
- opacity,
353
- data
354
- );
355
- result.getLineColor = accessor;
356
- scales.lineColor = {
357
- field: strokeColorField,
358
- type: strokeColorScale!,
359
- ...domainAndRangeFromScale(scale),
360
- };
387
+ // point radius
388
+ {
389
+ const radiusRange = visConfig.radiusRange;
390
+ const {radiusField, radiusScale} = visualChannels;
391
+ if (radiusField && radiusRange && radiusScale) {
392
+ const {accessor, ...scaleProps} = getSizeAccessor(
393
+ radiusField,
394
+ radiusScale,
395
+ visConfig.sizeAggregation,
396
+ radiusRange,
397
+ data
398
+ );
399
+ result.getPointRadius = accessor;
400
+ scales.pointRadius = updateTriggers.getPointRadius = {
401
+ field: radiusField,
402
+ type: radiusScale,
403
+ ...scaleProps,
404
+ };
405
+ }
361
406
  }
362
- if (heightField && visConfig.enable3d) {
363
- const {accessor, scale} = getSizeAccessor(
364
- heightField,
365
- heightScale,
366
- visConfig.heightAggregation,
367
- visConfig.heightRange || visConfig.sizeRange,
368
- data
369
- );
370
- result.getElevation = accessor;
371
- scales.elevation = {
372
- field: heightField,
373
- type: heightScale || 'identity',
374
- ...domainAndRangeFromScale(scale),
375
- };
407
+
408
+ // stroke/ouline color
409
+ {
410
+ const strokeColorRange = visConfig.strokeColorRange;
411
+ const {strokeColorScale, strokeColorField} = visualChannels;
412
+ if (strokeColorField && strokeColorRange && strokeColorScale) {
413
+ const {strokeColorAggregation: aggregation} = visConfig;
414
+ const opacity =
415
+ visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
416
+
417
+ const {accessor, ...scaleProps} = getColorAccessor(
418
+ strokeColorField,
419
+ strokeColorScale,
420
+ {aggregation, range: strokeColorRange},
421
+ opacity,
422
+ data
423
+ );
424
+ result.getLineColor = accessor;
425
+ scales.lineColor = updateTriggers.getLineColor = {
426
+ field: strokeColorField,
427
+ type: strokeColorScale,
428
+ ...scaleProps,
429
+ };
430
+ }
376
431
  }
377
432
 
378
- if (weightField) {
379
- const {accessor, scale} = getSizeAccessor(
380
- weightField,
381
- undefined,
382
- visConfig.weightAggregation,
383
- undefined,
384
- data
385
- );
386
- result.getWeight = accessor;
387
- scales.weight = {
388
- field: weightField,
389
- type: 'identity',
390
- ...domainAndRangeFromScale(scale),
391
- };
433
+ // stroke/line width
434
+ {
435
+ const {sizeField: strokeWidthField, sizeScale: strokeWidthScale} =
436
+ visualChannels;
437
+ const {sizeRange, sizeAggregation} = visConfig;
438
+
439
+ if (strokeWidthField && sizeRange) {
440
+ const {accessor, ...scaleProps} = getSizeAccessor(
441
+ strokeWidthField,
442
+ strokeWidthScale,
443
+ sizeAggregation,
444
+ sizeRange,
445
+ data
446
+ );
447
+ result.getLineWidth = accessor;
448
+ scales.lineWidth = updateTriggers.getLineWidth = {
449
+ field: strokeWidthField,
450
+ type: strokeWidthScale || 'identity',
451
+ ...scaleProps,
452
+ };
453
+ }
454
+ }
455
+
456
+ // height / elevation
457
+ {
458
+ const {enable3d, heightRange} = visConfig;
459
+ const {heightField, heightScale} = visualChannels;
460
+ if (heightField && heightRange && enable3d) {
461
+ const {accessor, ...scaleProps} = getSizeAccessor(
462
+ heightField,
463
+ heightScale,
464
+ visConfig.heightAggregation,
465
+ heightRange,
466
+ data
467
+ );
468
+ result.getElevation = accessor;
469
+ scales.elevation = updateTriggers.getElevation = {
470
+ field: heightField,
471
+ type: heightScale || 'identity',
472
+ ...scaleProps,
473
+ };
474
+ }
475
+ }
476
+
477
+ // weight
478
+ {
479
+ const {weightField} = visualChannels;
480
+ const {weightAggregation} = visConfig;
481
+ if (weightField && weightAggregation) {
482
+ const {accessor, ...scaleProps} = getSizeAccessor(
483
+ weightField,
484
+ undefined,
485
+ weightAggregation,
486
+ undefined,
487
+ data
488
+ );
489
+ result.getWeight = accessor;
490
+ scales.weight = updateTriggers.getWeight = {
491
+ field: weightField,
492
+ type: 'identity' as ScaleType,
493
+ ...scaleProps,
494
+ };
495
+ }
392
496
  }
393
497
 
394
498
  if (visConfig.customMarkers) {
@@ -407,6 +511,12 @@ function createChannelProps(
407
511
  {fallbackUrl: customMarkersUrl, maxIconSize, useMaskedIcons},
408
512
  data
409
513
  );
514
+ updateTriggers.getIcon = {
515
+ customMarkersUrl,
516
+ customMarkersRange,
517
+ maxIconSize,
518
+ useMaskedIcons,
519
+ };
410
520
  result._subLayerProps = {
411
521
  'points-icon': {
412
522
  loadOptions: {
@@ -424,10 +534,12 @@ function createChannelProps(
424
534
 
425
535
  if (getFillColor && useMaskedIcons) {
426
536
  result.getIconColor = getFillColor;
537
+ updateTriggers.getIconColor = updateTriggers.getFillColor;
427
538
  }
428
539
 
429
540
  if (getPointRadius) {
430
541
  result.getIconSize = getPointRadius;
542
+ updateTriggers.getIconSize = updateTriggers.getPointRadius;
431
543
  }
432
544
 
433
545
  if (visualChannels.rotationField) {
@@ -439,6 +551,7 @@ function createChannelProps(
439
551
  data
440
552
  );
441
553
  result.getIconAngle = negateAccessor(accessor);
554
+ updateTriggers.getIconAngle = updateTriggers.getRotationField;
442
555
  }
443
556
  } else if (layerType === 'tileset') {
444
557
  result.pointType = 'circle';
@@ -494,7 +607,13 @@ function createChannelProps(
494
607
  };
495
608
  }
496
609
 
497
- return {channelProps: result, scales};
610
+ return {
611
+ channelProps: {
612
+ ...result,
613
+ updateTriggers,
614
+ },
615
+ scales,
616
+ };
498
617
  }
499
618
 
500
619
  function createLoadOptions(accessToken: string) {