@carto/api-client 0.5.27-alpha.482101a.112 → 0.5.27

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/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "homepage": "https://github.com/CartoDB/carto-api-client#readme",
9
9
  "author": "Don McCurdy <donmccurdy@carto.com>",
10
10
  "packageManager": "yarn@4.3.1",
11
- "version": "0.5.27-alpha.482101a.112",
11
+ "version": "0.5.27",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -68,7 +68,7 @@
68
68
  "dependencies": {
69
69
  "@loaders.gl/schema": "^4.3.3",
70
70
  "@types/geojson": "^7946.0.16",
71
- "d3-format": "^3.1.0",
71
+ "d3-format": "^3.1.2",
72
72
  "d3-scale": "^4.0.2",
73
73
  "h3-js": "^4.1.0",
74
74
  "jsep": "^1.4.0",
@@ -124,7 +124,7 @@
124
124
  "tsup": "^8.3.6",
125
125
  "typescript": "~5.9.2",
126
126
  "typescript-eslint": "^8.26.1",
127
- "vite": "^7.0.0",
127
+ "vite": "^7.3.2",
128
128
  "vitest": "3.2.4"
129
129
  },
130
130
  "resolutions": {
@@ -342,9 +342,17 @@ function findAccessorKey(keys: string[], properties: any): string[] {
342
342
  }
343
343
 
344
344
  export function getColorAccessor(
345
- {name, colorColumn}: VisualChannelField,
345
+ {name, colorColumn, accessorKey}: VisualChannelField,
346
346
  scaleType: ScaleType,
347
- {aggregation, range}: {aggregation?: string; range: ColorRange},
347
+ {
348
+ aggregation,
349
+ range,
350
+ domainOverride,
351
+ }: {
352
+ aggregation?: string;
353
+ range: ColorRange;
354
+ domainOverride?: [number, number];
355
+ },
348
356
  opacity: number | undefined,
349
357
  data: TilejsonResult
350
358
  ): {
@@ -353,15 +361,23 @@ export function getColorAccessor(
353
361
  scaleDomain: number[] | string[];
354
362
  range: string[];
355
363
  } {
364
+ // accessorKey (custom-agg alias) wins over colorColumn (legacy identity-color
365
+ // column), which wins over name (standard field).
366
+ const effectiveName = accessorKey ?? colorColumn ?? name;
367
+ const effectiveScaleType =
368
+ colorColumn && !accessorKey ? 'identity' : scaleType;
356
369
  const {scale, domain} = calculateLayerScale(
357
- colorColumn || name,
358
- colorColumn ? 'identity' : scaleType,
370
+ effectiveName,
371
+ effectiveScaleType,
359
372
  range,
360
- data
373
+ data,
374
+ domainOverride
361
375
  );
362
376
  const alpha = opacityToAlpha(opacity);
363
377
 
364
- let accessorKeys = getAccessorKeys(colorColumn || name, aggregation);
378
+ let accessorKeys = accessorKey
379
+ ? [accessorKey]
380
+ : getAccessorKeys(colorColumn || name, aggregation);
365
381
  const accessor = (properties: any) => {
366
382
  if (!(accessorKeys[0] in properties)) {
367
383
  accessorKeys = findAccessorKey(accessorKeys, properties);
@@ -383,13 +399,20 @@ export function calculateLayerScale(
383
399
  name: string,
384
400
  scaleType: ScaleType,
385
401
  range: ColorRange,
386
- data: TilejsonResult
402
+ data: TilejsonResult,
403
+ domainOverride?: [number, number]
387
404
  ): {scale: D3Scale; domain: string[] | number[]} {
388
405
  let scaleDomain: number[] | string[] | undefined;
389
406
  let scaleColors: string[] = [];
390
407
  const {colors} = range;
391
408
 
392
- const domain = calculateDomain(data, name, scaleType, colors.length);
409
+ // colorMap (explicit break values from Custom classification) wins over
410
+ // domainOverride (user Min/Max for Custom aggregation). colorMap defines
411
+ // its own domain, so the override does not apply.
412
+ const domain =
413
+ domainOverride && !range.colorMap
414
+ ? domainOverride
415
+ : calculateDomain(data, name, scaleType, colors.length);
393
416
  if (scaleType !== 'identity') {
394
417
  if (range.colorMap) {
395
418
  const {colorMap} = range;
@@ -506,11 +529,12 @@ export function negateAccessor(accessor: Accessor): Accessor {
506
529
  }
507
530
 
508
531
  export function getSizeAccessor(
509
- {name}: VisualChannelField,
532
+ {name, accessorKey}: VisualChannelField,
510
533
  scaleType: ScaleType | undefined,
511
534
  aggregation: string | null | undefined,
512
535
  range: number[] | undefined,
513
- data: TilejsonResult
536
+ data: TilejsonResult,
537
+ domainOverride?: [number, number]
514
538
  ): {
515
539
  accessor: any;
516
540
  domain: number[];
@@ -521,7 +545,10 @@ export function getSizeAccessor(
521
545
  let domain: number[] = [];
522
546
  if (scaleType && range) {
523
547
  if (aggregation !== AggregationTypes.Count) {
524
- domain = calculateDomain(data, name, scaleType) as number[];
548
+ const source = accessorKey ?? name;
549
+ domain =
550
+ domainOverride ??
551
+ (calculateDomain(data, source, scaleType) as number[]);
525
552
  (scale as D3Scale).domain(domain);
526
553
  } else {
527
554
  domain = (scale as D3Scale).domain();
@@ -529,7 +556,9 @@ export function getSizeAccessor(
529
556
  (scale as D3Scale).range(range);
530
557
  }
531
558
 
532
- let accessorKeys = getAccessorKeys(name, aggregation);
559
+ let accessorKeys = accessorKey
560
+ ? [accessorKey]
561
+ : getAccessorKeys(name, aggregation);
533
562
  const accessor = (properties: any) => {
534
563
  if (!(accessorKeys[0] in properties)) {
535
564
  accessorKeys = findAccessorKey(accessorKeys, properties);
@@ -597,14 +626,42 @@ export function getDefaultAggregationExpColumnAliasForLayerType(
597
626
  }
598
627
  }
599
628
 
629
+ function hashString(input: string): string {
630
+ let h = 0x811c9dc5;
631
+ for (let i = 0; i < input.length; i++) {
632
+ h ^= input.charCodeAt(i);
633
+ h = Math.imul(h, 0x01000193);
634
+ }
635
+ return (h >>> 0).toString(16).padStart(8, '0');
636
+ }
637
+
638
+ function normalizeExpression(exp: string): string {
639
+ return exp.trim().replace(/\s+/g, ' ');
640
+ }
641
+
642
+ function applyProviderCase(alias: string, provider: ProviderType): string {
643
+ return provider === 'snowflake' ? alias.toUpperCase() : alias;
644
+ }
645
+
600
646
  /** @privateRemarks Source: Builder */
601
647
  function getColumnAliasForAggregationExp(
602
648
  name: string,
603
649
  aggregation: string,
604
650
  provider: ProviderType
605
651
  ) {
606
- const columnAlias = `${name}_${aggregation}`;
607
- return provider === 'snowflake' ? columnAlias.toUpperCase() : columnAlias;
652
+ return applyProviderCase(`${name}_${aggregation}`, provider);
653
+ }
654
+
655
+ export function compileCustomAggregation(
656
+ exp: string,
657
+ opts: {provider: ProviderType}
658
+ ): string {
659
+ const normalized = normalizeExpression(exp);
660
+ if (!normalized) {
661
+ throw new Error('Custom aggregation expression must not be empty');
662
+ }
663
+ const hash = hashString(normalized);
664
+ return applyProviderCase('custom_agg_' + hash, opts.provider);
608
665
  }
609
666
 
610
667
  /** @privateRemarks Source: Builder */
@@ -2,6 +2,7 @@ import type {ColorParameters} from '@luma.gl/core';
2
2
  import {
3
3
  calculateClusterRadius,
4
4
  calculateClusterTextFontSize,
5
+ compileCustomAggregation,
5
6
  getDefaultAggregationExpColumnAliasForLayerType,
6
7
  getLayerProps,
7
8
  getColorAccessor,
@@ -35,6 +36,7 @@ import {
35
36
  getRasterTileLayerStylePropsRgb,
36
37
  getRasterTileLayerStylePropsScaledBand,
37
38
  } from './raster-layer.js';
39
+ import type {ProviderType} from '../types.js';
38
40
  import type {TilejsonResult} from '../sources/types.js';
39
41
 
40
42
  export type Scale = {
@@ -53,6 +55,7 @@ export type ScaleKey =
53
55
  | 'fillColor'
54
56
  | 'pointRadius'
55
57
  | 'lineColor'
58
+ | 'lineWidth'
56
59
  | 'elevation'
57
60
  | 'weight';
58
61
 
@@ -250,6 +253,37 @@ function createStyleProps(config: MapLayerConfig, mapping: any) {
250
253
  return result;
251
254
  }
252
255
 
256
+ function resolveCustomAggregation({
257
+ field,
258
+ aggregation,
259
+ expression,
260
+ domain,
261
+ providerId,
262
+ }: {
263
+ field: VisualChannelField | undefined;
264
+ aggregation: string | undefined;
265
+ expression: string | undefined;
266
+ domain: [number, number] | undefined;
267
+ providerId: ProviderType;
268
+ }): {
269
+ field: VisualChannelField | undefined;
270
+ aggregation: string | undefined;
271
+ domainOverride: [number, number] | undefined;
272
+ } {
273
+ if (aggregation !== 'custom') {
274
+ return {field, aggregation, domainOverride: undefined};
275
+ }
276
+ if (!field || !expression?.trim()) {
277
+ return {field, aggregation: undefined, domainOverride: undefined};
278
+ }
279
+ const alias = compileCustomAggregation(expression, {provider: providerId});
280
+ return {
281
+ field: {...field, accessorKey: alias},
282
+ aggregation: undefined,
283
+ domainOverride: domain,
284
+ };
285
+ }
286
+
253
287
  function createChannelProps(
254
288
  id: string,
255
289
  layerType: LayerType,
@@ -313,18 +347,25 @@ function createChannelProps(
313
347
  // fill color
314
348
  {
315
349
  const {colorField, colorScale} = visualChannels;
316
- const {colorRange, colorAggregation} = visConfig;
317
- if (colorField && colorScale && colorRange) {
350
+ const {colorRange} = visConfig;
351
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
352
+ field: colorField,
353
+ aggregation: visConfig.colorAggregation,
354
+ expression: visConfig.colorAggregationExp,
355
+ domain: visConfig.colorAggregationDomain,
356
+ providerId: dataset.providerId,
357
+ });
358
+ if (field && colorScale && colorRange) {
318
359
  const {accessor, ...scaleProps} = getColorAccessor(
319
- colorField,
360
+ field,
320
361
  colorScale,
321
- {aggregation: colorAggregation, range: colorRange},
362
+ {aggregation, range: colorRange, domainOverride},
322
363
  visConfig.opacity,
323
364
  data
324
365
  );
325
366
  result.getFillColor = accessor;
326
367
  scales.fillColor = updateTriggers.getFillColor = {
327
- field: colorField,
368
+ field,
328
369
  type: colorScale,
329
370
  ...scaleProps,
330
371
  };
@@ -398,44 +439,57 @@ function createChannelProps(
398
439
 
399
440
  // point radius
400
441
  {
401
- const radiusRange = visConfig.radiusRange;
402
442
  const {radiusField, radiusScale} = visualChannels;
403
- if (radiusField && radiusRange && radiusScale) {
443
+ const {radiusRange} = visConfig;
444
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
445
+ field: radiusField,
446
+ aggregation: visConfig.radiusAggregation,
447
+ expression: visConfig.radiusAggregationExp,
448
+ domain: visConfig.radiusAggregationDomain,
449
+ providerId: dataset.providerId,
450
+ });
451
+ if (field && radiusRange && radiusScale) {
404
452
  const {accessor, ...scaleProps} = getSizeAccessor(
405
- radiusField,
453
+ field,
406
454
  radiusScale,
407
- visConfig.radiusAggregation,
455
+ aggregation,
408
456
  radiusRange,
409
- data
457
+ data,
458
+ domainOverride
410
459
  );
411
460
  result.getPointRadius = accessor;
412
461
  scales.pointRadius = updateTriggers.getPointRadius = {
413
- field: radiusField,
462
+ field,
414
463
  type: radiusScale,
415
464
  ...scaleProps,
416
465
  };
417
466
  }
418
467
  }
419
468
 
420
- // stroke/ouline color
469
+ // stroke/outline color
421
470
  {
422
- const strokeColorRange = visConfig.strokeColorRange;
423
471
  const {strokeColorScale, strokeColorField} = visualChannels;
424
- if (strokeColorField && strokeColorRange && strokeColorScale) {
425
- const {strokeColorAggregation: aggregation} = visConfig;
472
+ const {strokeColorRange} = visConfig;
473
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
474
+ field: strokeColorField,
475
+ aggregation: visConfig.strokeColorAggregation,
476
+ expression: visConfig.strokeColorAggregationExp,
477
+ domain: visConfig.strokeColorAggregationDomain,
478
+ providerId: dataset.providerId,
479
+ });
480
+ if (field && strokeColorRange && strokeColorScale) {
426
481
  const opacity =
427
482
  visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
428
-
429
483
  const {accessor, ...scaleProps} = getColorAccessor(
430
- strokeColorField,
484
+ field,
431
485
  strokeColorScale,
432
- {aggregation, range: strokeColorRange},
486
+ {aggregation, range: strokeColorRange, domainOverride},
433
487
  opacity,
434
488
  data
435
489
  );
436
490
  result.getLineColor = accessor;
437
491
  scales.lineColor = updateTriggers.getLineColor = {
438
- field: strokeColorField,
492
+ field,
439
493
  type: strokeColorScale,
440
494
  ...scaleProps,
441
495
  };
@@ -446,19 +500,26 @@ function createChannelProps(
446
500
  {
447
501
  const {sizeField: strokeWidthField, sizeScale: strokeWidthScale} =
448
502
  visualChannels;
449
- const {sizeRange, sizeAggregation} = visConfig;
450
-
451
- if (strokeWidthField && sizeRange) {
503
+ const {sizeRange} = visConfig;
504
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
505
+ field: strokeWidthField,
506
+ aggregation: visConfig.sizeAggregation,
507
+ expression: visConfig.sizeAggregationExp,
508
+ domain: visConfig.sizeAggregationDomain,
509
+ providerId: dataset.providerId,
510
+ });
511
+ if (field && sizeRange) {
452
512
  const {accessor, ...scaleProps} = getSizeAccessor(
453
- strokeWidthField,
513
+ field,
454
514
  strokeWidthScale,
455
- sizeAggregation,
515
+ aggregation,
456
516
  sizeRange,
457
- data
517
+ data,
518
+ domainOverride
458
519
  );
459
520
  result.getLineWidth = accessor;
460
521
  scales.lineWidth = updateTriggers.getLineWidth = {
461
- field: strokeWidthField,
522
+ field,
462
523
  type: strokeWidthScale || 'identity',
463
524
  ...scaleProps,
464
525
  };
@@ -467,19 +528,27 @@ function createChannelProps(
467
528
 
468
529
  // height / elevation
469
530
  {
470
- const {enable3d, heightRange} = visConfig;
471
531
  const {heightField, heightScale} = visualChannels;
472
- if (heightField && heightRange && enable3d) {
532
+ const {enable3d, heightRange} = visConfig;
533
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
534
+ field: heightField,
535
+ aggregation: visConfig.heightAggregation,
536
+ expression: visConfig.heightAggregationExp,
537
+ domain: visConfig.heightAggregationDomain,
538
+ providerId: dataset.providerId,
539
+ });
540
+ if (field && heightRange && enable3d) {
473
541
  const {accessor, ...scaleProps} = getSizeAccessor(
474
- heightField,
542
+ field,
475
543
  heightScale,
476
- visConfig.heightAggregation,
544
+ aggregation,
477
545
  heightRange,
478
- data
546
+ data,
547
+ domainOverride
479
548
  );
480
549
  result.getElevation = accessor;
481
550
  scales.elevation = updateTriggers.getElevation = {
482
- field: heightField,
551
+ field,
483
552
  type: heightScale || 'identity',
484
553
  ...scaleProps,
485
554
  };
@@ -489,18 +558,25 @@ function createChannelProps(
489
558
  // weight
490
559
  {
491
560
  const {weightField} = visualChannels;
492
- const {weightAggregation} = visConfig;
493
- if (weightField && weightAggregation) {
561
+ const {field, aggregation, domainOverride} = resolveCustomAggregation({
562
+ field: weightField,
563
+ aggregation: visConfig.weightAggregation,
564
+ expression: visConfig.weightAggregationExp,
565
+ domain: visConfig.weightAggregationDomain,
566
+ providerId: dataset.providerId,
567
+ });
568
+ if (field && (aggregation || visConfig.weightAggregation === 'custom')) {
494
569
  const {accessor, ...scaleProps} = getSizeAccessor(
495
- weightField,
570
+ field,
496
571
  undefined,
497
- weightAggregation,
572
+ aggregation,
498
573
  undefined,
499
- data
574
+ data,
575
+ domainOverride
500
576
  );
501
577
  result.getWeight = accessor;
502
578
  scales.weight = updateTriggers.getWeight = {
503
- field: weightField,
579
+ field,
504
580
  type: 'identity' as ScaleType,
505
581
  ...scaleProps,
506
582
  };
@@ -6,6 +6,7 @@ export type VisualChannelField = {
6
6
  name: string;
7
7
  type: string;
8
8
  colorColumn?: string;
9
+ accessorKey?: string;
9
10
  };
10
11
 
11
12
  export type VisualChannels = {
@@ -65,6 +66,8 @@ export type VisConfig = {
65
66
  enable3d?: boolean;
66
67
 
67
68
  colorAggregation?: string;
69
+ colorAggregationExp?: string;
70
+ colorAggregationDomain?: [number, number];
68
71
  colorRange: ColorRange;
69
72
 
70
73
  customMarkers?: boolean;
@@ -74,18 +77,28 @@ export type VisConfig = {
74
77
  radius: number;
75
78
  radiusRange?: number[];
76
79
  radiusAggregation?: string;
80
+ radiusAggregationExp?: string;
81
+ radiusAggregationDomain?: [number, number];
77
82
 
78
83
  sizeAggregation?: string;
84
+ sizeAggregationExp?: string;
85
+ sizeAggregationDomain?: [number, number];
79
86
  sizeRange?: number[];
80
87
 
81
88
  strokeColorAggregation?: string;
89
+ strokeColorAggregationExp?: string;
90
+ strokeColorAggregationDomain?: [number, number];
82
91
  strokeOpacity?: number;
83
92
  strokeColorRange?: ColorRange;
84
93
 
85
94
  heightRange?: number[];
86
95
  heightAggregation?: string;
96
+ heightAggregationExp?: string;
97
+ heightAggregationDomain?: [number, number];
87
98
 
88
99
  weightAggregation?: string;
100
+ weightAggregationExp?: string;
101
+ weightAggregationDomain?: [number, number];
89
102
 
90
103
  // type = clusterTile
91
104
  clusterLevel?: number;