Skip to content

Commit 66a6249

Browse files
committed
[Feat] Add an array field type
> fix keplergl#1637 and supersede keplergl#1666 Allow to import features with multiple values properties and convert them to an `array` type. These fields can then be picked as filters or shown in the tooltip.
1 parent 9416be4 commit 66a6249

File tree

11 files changed

+79
-16
lines changed

11 files changed

+79
-16
lines changed

src/constants/src/default-settings.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ export const ALL_FIELD_TYPES = keyMirror({
329329
boolean: null,
330330
date: null,
331331
geojson: null,
332+
array: null,
332333
integer: null,
333334
real: null,
334335
string: null,
@@ -408,6 +409,10 @@ export const FIELD_TYPE_DISPLAY = {
408409
label: 'geo',
409410
color: BLUE2
410411
},
412+
[ALL_FIELD_TYPES.array]: {
413+
label: 'array',
414+
color: BLUE2
415+
},
411416
[ALL_FIELD_TYPES.integer]: {
412417
label: 'int',
413418
color: ORANGE
@@ -633,6 +638,17 @@ export const FIELD_OPTS = {
633638
legend: d => '...',
634639
tooltip: []
635640
}
641+
},
642+
array: {
643+
type: 'array',
644+
scale: {
645+
...notSupportedScaleOpts,
646+
...notSupportAggrOpts
647+
},
648+
format: {
649+
legend: d => '...',
650+
tooltip: []
651+
}
636652
}
637653
};
638654

src/processors/src/data-processor.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,16 @@ export const PARSE_FIELD_VALUE_FROM_STRING = {
6060
valid: (d: unknown): boolean => parseFloat(d) === d,
6161
// Note this will result in NaN for some string
6262
parse: parseFloat
63+
},
64+
[ALL_FIELD_TYPES.array]: {
65+
valid: (d: unknown): boolean => Array.isArray(d),
66+
parse: (d: unknown): any => {
67+
try {
68+
return JSON.parse(d as string);
69+
} catch (e) {
70+
return [];
71+
}
72+
}
6373
}
6474
};
6575

src/utils/src/data-scale-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ export function getOrdinalDomain(
4747
): string[] {
4848
const values = dataContainer.mapIndex(valueAccessor);
4949

50-
return unique(values)
50+
return unique(values.flat())
5151
.filter(notNullorUndefined)
5252
.sort();
5353
}

src/utils/src/data-utils.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,8 @@ export function roundValToStep(minValue: number, step: number, val: number): num
257257
* Used in render tooltip value
258258
*/
259259
export const defaultFormatter: FieldFormatter = v => (notNullorUndefined(v) ? String(v) : '');
260+
export const arrayFormatter: FieldFormatter = v =>
261+
Array.isArray(v) ? `[${String(v)}]` : defaultFormatter(v);
260262

261263
export const FIELD_DISPLAY_FORMAT: {
262264
[key: string]: FieldFormatter;
@@ -268,13 +270,8 @@ export const FIELD_DISPLAY_FORMAT: {
268270
[ALL_FIELD_TYPES.boolean]: defaultFormatter,
269271
[ALL_FIELD_TYPES.date]: defaultFormatter,
270272
[ALL_FIELD_TYPES.geojson]: d =>
271-
typeof d === 'string'
272-
? d
273-
: isPlainObject(d)
274-
? JSON.stringify(d)
275-
: Array.isArray(d)
276-
? `[${String(d)}]`
277-
: ''
273+
typeof d === 'string' ? d : isPlainObject(d) ? JSON.stringify(d) : arrayFormatter(d),
274+
[ALL_FIELD_TYPES.array]: arrayFormatter
278275
};
279276

280277
/**

src/utils/src/dataset-utils.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ export function getFieldsFromData(data: RowData, fieldOrder: string[]): Field[]
418418
const metadata = Analyzer.computeColMeta(
419419
data,
420420
[
421+
{regex: /.*array/g, dataType: 'ARRAY'},
421422
{regex: /.*geojson|all_points/g, dataType: 'GEOMETRY'},
422423
{regex: /.*census/g, dataType: 'STRING'}
423424
],
@@ -524,10 +525,11 @@ export function analyzerTypeToFieldType(aType: string): string {
524525
case GEOMETRY:
525526
case GEOMETRY_FROM_STRING:
526527
case PAIR_GEOMETRY_FROM_STRING:
527-
case ARRAY:
528528
case OBJECT:
529-
// TODO: create a new data type for objects and arrays
529+
// TODO: create a new data type for objects
530530
return ALL_FIELD_TYPES.geojson;
531+
case ARRAY:
532+
return ALL_FIELD_TYPES.array;
531533
case NUMBER:
532534
case STRING:
533535
case ZIPCODE:

src/utils/src/filter-utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,7 @@ export function getFilterProps(
384384

385385
case ALL_FIELD_TYPES.string:
386386
case ALL_FIELD_TYPES.date:
387+
case ALL_FIELD_TYPES.array:
387388
// @ts-expect-error
388389
return {
389390
...filterProps,
@@ -486,6 +487,9 @@ export function getFilterFunction<L extends {config: {dataId: string | null}; id
486487
case FILTER_TYPES.range:
487488
return data => isInRange(valueAccessor(data), filter.value);
488489
case FILTER_TYPES.multiSelect:
490+
if (field?.type === ALL_FIELD_TYPES.array) {
491+
return data => valueAccessor(data).some(v => filter.value.includes(v));
492+
}
489493
return data => filter.value.includes(valueAccessor(data));
490494
case FILTER_TYPES.select:
491495
return data => valueAccessor(data) === filter.value;
@@ -1046,6 +1050,7 @@ export function mergeFilterDomainStep(
10461050
switch (filterProps.fieldType) {
10471051
case ALL_FIELD_TYPES.string:
10481052
case ALL_FIELD_TYPES.date:
1053+
case ALL_FIELD_TYPES.array:
10491054
return {
10501055
...newFilter,
10511056
domain: unique(combinedDomain).sort()

src/utils/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export {
5555
normalizeSliderValue,
5656
roundValToStep,
5757
defaultFormatter,
58+
arrayFormatter,
5859
FIELD_DISPLAY_FORMAT,
5960
parseFieldValue,
6061
arrayMove,
@@ -211,4 +212,4 @@ export {
211212
export {DataRow} from './data-row';
212213

213214
export type {Centroid} from './h3-utils';
214-
export {getCentroid, idToPolygonGeo, h3IsValid, getHexFields} from './h3-utils';
215+
export {getCentroid, idToPolygonGeo, h3IsValid, getHexFields} from './h3-utils';

test/fixtures/geojson.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,7 +559,7 @@ export const geoStyleFields = [
559559
displayName: 'fillColor',
560560
format: '',
561561
fieldIdx: 1,
562-
type: 'geojson',
562+
type: 'array',
563563
analyzerType: 'ARRAY',
564564
valueAccessor: values => values[1]
565565
},
@@ -569,7 +569,7 @@ export const geoStyleFields = [
569569
displayName: 'lineColor',
570570
format: '',
571571
fieldIdx: 2,
572-
type: 'geojson',
572+
type: 'array',
573573
analyzerType: 'ARRAY',
574574
valueAccessor: values => values[2]
575575
},

test/node/utils/data-processor-test.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,27 @@ test('Processor -> parseCsvRowsByFieldType -> boolean', t => {
426426
t.end();
427427
});
428428

429+
test('Processor -> parseCsvRowsByFieldType -> array', t => {
430+
const field = {
431+
type: ALL_FIELD_TYPES.array
432+
};
433+
434+
const rows = [
435+
['[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]'],
436+
['["tag1", "tag2", "tag3", "tag4", "tag5", "tag6", "tag7"]']
437+
];
438+
439+
const expected = [
440+
[[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]],
441+
[['tag1', 'tag2', 'tag3', 'tag4', 'tag5', 'tag6', 'tag7']]
442+
];
443+
444+
parseCsvRowsByFieldType(rows, -1, field, 0);
445+
446+
t.same(rows, expected, 'should parsed arrays properly');
447+
t.end();
448+
});
449+
429450
test('Processor -> getSampleForTypeAnalyze', t => {
430451
const fields = ['string', 'int', 'bool', 'time'];
431452

test/node/utils/data-scale-utils-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,15 @@ function numberSort(a, b) {
3333
}
3434

3535
test('DataScaleUtils -> getOrdinalDomain', t => {
36-
const data = [['a'], ['a'], ['b'], [undefined], [null], [0], null];
36+
const data = [['a'], ['a'], ['b'], [undefined], [null], [0], null, [['c', 'd', null]]];
3737

3838
function valueAccessor(d, dc) {
3939
return dc.valueAt(d.index, 0);
4040
}
4141

4242
t.deepEqual(
4343
getOrdinalDomain(createDataContainer(data), valueAccessor),
44-
[0, 'a', 'b'],
44+
[0, 'a', 'b', 'c', 'd'],
4545
'should get correct ordinal domain'
4646
);
4747

test/node/utils/data-utils-test.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ import {
2929
snapToMarks,
3030
arrayMove,
3131
getFormatter,
32-
defaultFormatter
32+
defaultFormatter,
33+
arrayFormatter
3334
} from '@kepler.gl/utils';
3435
import {ALL_FIELD_TYPES} from '@kepler.gl/constants';
3536

@@ -120,6 +121,16 @@ test('dataUtils -> defaultFormatter', t => {
120121
t.end();
121122
});
122123

124+
test('dataUtils -> arrayFormatter', t => {
125+
t.equal(arrayFormatter([]), '[]', 'arrayFormatter should be correct');
126+
t.equal(arrayFormatter(['tag1']), '[tag1]', 'arrayFormatter should be correct');
127+
t.equal(arrayFormatter(undefined), '', 'arrayFormatter should be correct');
128+
t.equal(arrayFormatter(NaN), 'NaN', 'arrayFormatter should be correct');
129+
t.equal(arrayFormatter(null), '', 'arrayFormatter should be correct');
130+
131+
t.end();
132+
});
133+
123134
test('dataUtils -> getFormatter', t => {
124135
const TEST_CASES = [
125136
{

0 commit comments

Comments
 (0)