@carto/api-client 0.5.7 → 0.5.8-alpha-others-orderby.2
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/build/api-client.cjs +88 -16
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +41 -13
- package/build/api-client.d.ts +41 -13
- package/build/api-client.js +85 -16
- package/build/api-client.js.map +1 -1
- package/build/worker-compat.js +58 -12
- package/build/worker-compat.js.map +1 -1
- package/build/worker.js +58 -12
- package/build/worker.js.map +1 -1
- package/package.json +1 -1
- package/src/operations/groupBy.ts +56 -12
- package/src/operations/groupByDate.ts +6 -1
- package/src/widget-sources/constants.ts +6 -0
- package/src/widget-sources/index.ts +1 -0
- package/src/widget-sources/types.ts +25 -1
- package/src/widget-sources/widget-remote-source.ts +28 -5
- package/src/widget-sources/widget-tileset-source-impl.ts +19 -2
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.
|
|
11
|
+
"version": "0.5.8-alpha-others-orderby.2",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"publishConfig": {
|
|
14
14
|
"access": "public"
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import {aggregationFunctions, aggregate} from './aggregation.js';
|
|
2
2
|
import type {AggregationType} from '../types.js';
|
|
3
3
|
import type {FeatureData} from '../types-internal.js';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}[];
|
|
4
|
+
import type {
|
|
5
|
+
CategoryOrderBy,
|
|
6
|
+
CategoryResponseEntry,
|
|
7
|
+
CategoryResponseRaw,
|
|
8
|
+
} from '../widget-sources/types.js';
|
|
10
9
|
|
|
11
10
|
/** @privateRemarks Source: @carto/react-core */
|
|
12
11
|
export function groupValuesByColumn({
|
|
@@ -15,15 +14,19 @@ export function groupValuesByColumn({
|
|
|
15
14
|
joinOperation,
|
|
16
15
|
keysColumn,
|
|
17
16
|
operation,
|
|
17
|
+
othersThreshold,
|
|
18
|
+
orderBy = 'frequency_desc',
|
|
18
19
|
}: {
|
|
19
20
|
data: FeatureData[];
|
|
20
21
|
valuesColumns?: string[];
|
|
21
22
|
joinOperation?: AggregationType;
|
|
22
23
|
keysColumn: string;
|
|
23
24
|
operation: AggregationType;
|
|
24
|
-
|
|
25
|
+
othersThreshold?: number;
|
|
26
|
+
orderBy?: CategoryOrderBy;
|
|
27
|
+
}): CategoryResponseRaw | null {
|
|
25
28
|
if (Array.isArray(data) && data.length === 0) {
|
|
26
|
-
return null;
|
|
29
|
+
return {rows: null};
|
|
27
30
|
}
|
|
28
31
|
const groups = data.reduce((accumulator, item) => {
|
|
29
32
|
const group = item[keysColumn];
|
|
@@ -48,12 +51,53 @@ export function groupValuesByColumn({
|
|
|
48
51
|
const targetOperation =
|
|
49
52
|
aggregationFunctions[operation as Exclude<AggregationType, 'custom'>];
|
|
50
53
|
|
|
51
|
-
if (targetOperation) {
|
|
52
|
-
return
|
|
54
|
+
if (!targetOperation) {
|
|
55
|
+
return {rows: []};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const allCategories = Array.from(groups)
|
|
59
|
+
.map(([name, value]) => ({
|
|
53
60
|
name,
|
|
54
61
|
value: targetOperation(value),
|
|
55
|
-
}))
|
|
62
|
+
}))
|
|
63
|
+
.sort(getSorter(orderBy));
|
|
64
|
+
|
|
65
|
+
if (othersThreshold && allCategories.length > othersThreshold) {
|
|
66
|
+
const otherValue = allCategories
|
|
67
|
+
.slice(othersThreshold)
|
|
68
|
+
.flatMap(({name}) => groups.get(name));
|
|
69
|
+
return {
|
|
70
|
+
rows: allCategories,
|
|
71
|
+
metadata: {
|
|
72
|
+
others: targetOperation(otherValue),
|
|
73
|
+
},
|
|
74
|
+
};
|
|
56
75
|
}
|
|
57
76
|
|
|
58
|
-
return
|
|
77
|
+
return {
|
|
78
|
+
rows: allCategories,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function getSorter(
|
|
83
|
+
orderBy: CategoryOrderBy
|
|
84
|
+
): (a: CategoryResponseEntry, b: CategoryResponseEntry) => number {
|
|
85
|
+
switch (orderBy) {
|
|
86
|
+
case 'frequency_asc':
|
|
87
|
+
// 'value ASC, name ASC'
|
|
88
|
+
return (a, b) => a.value - b.value || localeCompare(a.name, b.name);
|
|
89
|
+
case 'frequency_desc':
|
|
90
|
+
// 'value DESC, name ASC'
|
|
91
|
+
return (a, b) => b.value - a.value || localeCompare(a.name, b.name);
|
|
92
|
+
case 'alphabetical_asc':
|
|
93
|
+
// 'name ASC, value DESC'
|
|
94
|
+
return (a, b) => localeCompare(a.name, b.name) || b.value - a.value;
|
|
95
|
+
case 'alphabetical_desc':
|
|
96
|
+
// 'name DESC, value DESC'
|
|
97
|
+
return (a, b) => localeCompare(b.name, a.name) || b.value - a.value;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function localeCompare(a?: string | null, b?: string | null) {
|
|
102
|
+
return (a ?? 'null').localeCompare(b ?? 'null');
|
|
59
103
|
}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type {AggregationType, GroupDateType} from '../types.js';
|
|
2
2
|
import {getUTCMonday} from '../utils/dateUtils.js';
|
|
3
3
|
import {aggregate, aggregationFunctions} from './aggregation.js';
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
/** @privateRemarks Source: @carto/react-core */
|
|
6
|
+
export type GroupByFeature = {
|
|
7
|
+
name: string;
|
|
8
|
+
value: number;
|
|
9
|
+
}[];
|
|
5
10
|
|
|
6
11
|
const GROUP_KEY_FN_MAPPING: Record<GroupDateType, (date: Date) => number> = {
|
|
7
12
|
year: (date: Date) => Date.UTC(date.getUTCFullYear()),
|
|
@@ -31,6 +31,12 @@ export interface BaseRequestOptions {
|
|
|
31
31
|
filterOwner?: string;
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
+
export type CategoryOrderBy =
|
|
35
|
+
| 'frequency_asc' // sort by aggregate value ascending, then by name ascending
|
|
36
|
+
| 'frequency_desc' // sort by aggregate value descending, then by name ascending
|
|
37
|
+
| 'alphabetical_asc' // sort by category name ascending, then by value descending
|
|
38
|
+
| 'alphabetical_desc'; // sort by category name descending, then by value descending
|
|
39
|
+
|
|
34
40
|
/**
|
|
35
41
|
* Examples:
|
|
36
42
|
* * population by state
|
|
@@ -59,6 +65,15 @@ export interface CategoryRequestOptions extends BaseRequestOptions {
|
|
|
59
65
|
operationColumn?: string;
|
|
60
66
|
/** Local only. */
|
|
61
67
|
joinOperation?: 'count' | 'avg' | 'min' | 'max' | 'sum';
|
|
68
|
+
/** Calculate `_carto_others` category for all categories after first N (N is threshold). */
|
|
69
|
+
othersThreshold?: number;
|
|
70
|
+
/**
|
|
71
|
+
* Order categories by frequency or alphabetically.
|
|
72
|
+
* @default 'frequency_desc'
|
|
73
|
+
*/
|
|
74
|
+
orderBy?: CategoryOrderBy;
|
|
75
|
+
/** Return raw result (CategoryResponseRaw). */
|
|
76
|
+
rawResult?: boolean;
|
|
62
77
|
}
|
|
63
78
|
|
|
64
79
|
/**
|
|
@@ -205,8 +220,17 @@ export type FeaturesResponse = {rows: Record<string, unknown>[]};
|
|
|
205
220
|
/** Response from {@link WidgetRemoteSource#getFormula}. */
|
|
206
221
|
export type FormulaResponse = {value: number | null};
|
|
207
222
|
|
|
223
|
+
/** Entry in the category widget response, see {@link WidgetRemoteSource#getCategories}. */
|
|
224
|
+
export type CategoryResponseEntry = {name: string | null; value: number};
|
|
208
225
|
/** Response from {@link WidgetRemoteSource#getCategories}. */
|
|
209
|
-
export type CategoryResponse =
|
|
226
|
+
export type CategoryResponse = CategoryResponseEntry[];
|
|
227
|
+
|
|
228
|
+
export type CategoryResponseRaw = {
|
|
229
|
+
rows: CategoryResponseEntry[] | null;
|
|
230
|
+
metadata?: {
|
|
231
|
+
others?: number;
|
|
232
|
+
};
|
|
233
|
+
};
|
|
210
234
|
|
|
211
235
|
/** Response from {@link WidgetRemoteSource#getRange}. */
|
|
212
236
|
export type RangeResponse = {min: number; max: number} | null;
|
|
@@ -23,6 +23,7 @@ import {WidgetSource, type WidgetSourceProps} from './widget-source.js';
|
|
|
23
23
|
import type {Filters} from '../types.js';
|
|
24
24
|
import {AggregationTypes, ApiVersion} from '../constants.js';
|
|
25
25
|
import {getApplicableFilters} from '../filters.js';
|
|
26
|
+
import {OTHERS_CATEGORY_NAME} from './constants.js';
|
|
26
27
|
|
|
27
28
|
export type WidgetRemoteSourceProps = WidgetSourceProps;
|
|
28
29
|
|
|
@@ -72,17 +73,23 @@ export abstract class WidgetRemoteSource<
|
|
|
72
73
|
filterOwner,
|
|
73
74
|
spatialFilter,
|
|
74
75
|
spatialFiltersMode,
|
|
76
|
+
rawResult,
|
|
75
77
|
...params
|
|
76
78
|
} = options;
|
|
77
|
-
const {
|
|
79
|
+
const {
|
|
80
|
+
column,
|
|
81
|
+
operation,
|
|
82
|
+
operationColumn,
|
|
83
|
+
operationExp,
|
|
84
|
+
othersThreshold,
|
|
85
|
+
orderBy,
|
|
86
|
+
} = params;
|
|
78
87
|
|
|
79
88
|
if (operation === AggregationTypes.Custom) {
|
|
80
89
|
assert(operationExp, 'operationExp is required for custom operation');
|
|
81
90
|
}
|
|
82
91
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return executeModel({
|
|
92
|
+
const result = await executeModel({
|
|
86
93
|
model: 'category',
|
|
87
94
|
source: {
|
|
88
95
|
...this.getModelSource(filters, filterOwner),
|
|
@@ -94,9 +101,25 @@ export abstract class WidgetRemoteSource<
|
|
|
94
101
|
operation,
|
|
95
102
|
operationExp,
|
|
96
103
|
operationColumn: operationColumn || column,
|
|
104
|
+
othersThreshold,
|
|
105
|
+
orderBy,
|
|
97
106
|
},
|
|
98
107
|
opts: {signal, headers: this.props.headers},
|
|
99
|
-
})
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const normalizedRows = normalizeObjectKeys(result.rows || []);
|
|
111
|
+
if (rawResult) {
|
|
112
|
+
return result as unknown as CategoryResponse;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!othersThreshold) {
|
|
116
|
+
return normalizedRows;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return [
|
|
120
|
+
...normalizedRows,
|
|
121
|
+
{name: OTHERS_CATEGORY_NAME, value: result?.metadata?.others as number},
|
|
122
|
+
];
|
|
100
123
|
}
|
|
101
124
|
|
|
102
125
|
async getFeatures(
|
|
@@ -40,6 +40,7 @@ import {booleanEqual} from '@turf/boolean-equal';
|
|
|
40
40
|
import type {WidgetTilesetSourceProps} from './widget-tileset-source.js';
|
|
41
41
|
import {getApplicableFilters} from '../filters.js';
|
|
42
42
|
import {AggregationTypes} from '../constants.js';
|
|
43
|
+
import {OTHERS_CATEGORY_NAME} from './constants.js';
|
|
43
44
|
|
|
44
45
|
// TODO(cleanup): Parameter defaults in source functions and widget API calls are
|
|
45
46
|
// currently duplicated and possibly inconsistent. Consider consolidating and
|
|
@@ -189,6 +190,9 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
|
|
|
189
190
|
filters,
|
|
190
191
|
filterOwner,
|
|
191
192
|
spatialFilter,
|
|
193
|
+
othersThreshold,
|
|
194
|
+
orderBy = 'frequency_desc',
|
|
195
|
+
rawResult,
|
|
192
196
|
}: CategoryRequestOptions): Promise<CategoryResponse> {
|
|
193
197
|
const filteredFeatures = this._getFilteredFeatures(
|
|
194
198
|
spatialFilter,
|
|
@@ -202,15 +206,28 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
|
|
|
202
206
|
|
|
203
207
|
assertColumn(this._features, column, operationColumn as string);
|
|
204
208
|
|
|
205
|
-
const
|
|
209
|
+
const result = groupValuesByColumn({
|
|
206
210
|
data: filteredFeatures,
|
|
207
211
|
valuesColumns: normalizeColumns(operationColumn || column),
|
|
208
212
|
joinOperation,
|
|
209
213
|
keysColumn: column,
|
|
210
214
|
operation,
|
|
215
|
+
othersThreshold,
|
|
216
|
+
orderBy,
|
|
211
217
|
});
|
|
212
218
|
|
|
213
|
-
|
|
219
|
+
if (rawResult) {
|
|
220
|
+
return result as unknown as CategoryResponse;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!othersThreshold) {
|
|
224
|
+
return result?.rows || [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return [
|
|
228
|
+
...(result?.rows || []),
|
|
229
|
+
{name: OTHERS_CATEGORY_NAME, value: result?.metadata?.others as number},
|
|
230
|
+
];
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
override async getScatter({
|