@carto/kepler.gl 0.0.71 → 0.0.72-alpha.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.
- package/dist/components/common/item-selector/accessor.js +2 -2
- package/dist/components/common/item-selector/dropdown-list.js +12 -9
- package/dist/components/common/item-selector/item-selector.js +2 -2
- package/dist/components/side-panel/layer-panel/color-range-selector.js +28 -7
- package/dist/components/side-panel/layer-panel/color-selector.js +4 -1
- package/dist/components/side-panel/layer-panel/hex-column-config.js +93 -0
- package/dist/components/side-panel/layer-panel/layer-configurator.js +39 -11
- package/dist/constants/color-ranges.js +12 -2
- package/dist/constants/default-settings.js +7 -4
- package/dist/layers/base-layer.js +197 -49
- package/dist/layers/point-layer/point-layer.js +2 -3
- package/dist/localization/en.js +5 -2
- package/dist/reducers/vis-state-merger.js +16 -2
- package/dist/schemas/vis-state-schema.js +69 -34
- package/dist/utils/color-utils.js +17 -21
- package/dist/utils/custom-scales.js +28 -0
- package/dist/utils/data-utils.js +31 -1
- package/dist/utils/index.js +7 -1
- package/dist/utils/layer-utils.js +2 -1
- package/dist/utils/table-utils/kepler-table.js +20 -1
- package/package.json +2 -1
- package/src/components/common/item-selector/accessor.js +1 -1
- package/src/components/common/item-selector/dropdown-list.js +23 -15
- package/src/components/common/item-selector/item-selector.js +1 -1
- package/src/components/side-panel/layer-panel/color-range-selector.js +30 -7
- package/src/components/side-panel/layer-panel/color-selector.js +3 -0
- package/src/components/side-panel/layer-panel/hex-column-config.js +44 -0
- package/src/components/side-panel/layer-panel/layer-configurator.js +47 -7
- package/src/constants/color-ranges.js +9 -0
- package/src/constants/default-settings.js +5 -2
- package/src/layers/base-layer.js +138 -30
- package/src/layers/index.d.ts +3 -1
- package/src/layers/layer-factory.d.ts +1 -0
- package/src/layers/point-layer/point-layer.js +1 -1
- package/src/localization/en.js +4 -1
- package/src/reducers/vis-state-merger.js +13 -1
- package/src/schemas/vis-state-schema.js +24 -1
- package/src/utils/color-utils.js +9 -0
- package/src/utils/custom-scales.js +20 -0
- package/src/utils/data-utils.d.ts +1 -0
- package/src/utils/data-utils.js +19 -0
- package/src/utils/index.js +1 -1
- package/src/utils/layer-utils.js +1 -0
- package/src/utils/table-utils/kepler-table.js +22 -0
package/src/layers/base-layer.js
CHANGED
|
@@ -25,6 +25,7 @@ import {COORDINATE_SYSTEM, Layer as DeckLayer} from '@deck.gl/core';
|
|
|
25
25
|
import {_PointLabelLayer as PointLabelLayer} from '@deck.gl/carto';
|
|
26
26
|
import DefaultLayerIcon from './default-layer-icon';
|
|
27
27
|
import {diffUpdateTriggers} from './layer-update';
|
|
28
|
+
import {rgb} from 'd3-color';
|
|
28
29
|
|
|
29
30
|
import {
|
|
30
31
|
ALL_FIELD_TYPES,
|
|
@@ -38,7 +39,7 @@ import {
|
|
|
38
39
|
UNKNOWN_COLOR,
|
|
39
40
|
ZOOM_LEVEL_LIMITS
|
|
40
41
|
} from 'constants/default-settings';
|
|
41
|
-
import {COLOR_RANGES} from 'constants/color-ranges';
|
|
42
|
+
import {COLOR_RANGES, hexColor} from 'constants/color-ranges';
|
|
42
43
|
import {DataVizColors} from 'constants/custom-color-ranges';
|
|
43
44
|
import {
|
|
44
45
|
LAYER_VIS_CONFIGS,
|
|
@@ -53,7 +54,7 @@ import {generateHashId, isPlainObject} from 'utils/utils';
|
|
|
53
54
|
import {getLatLngBounds, notNullorUndefined} from 'utils/data-utils';
|
|
54
55
|
import {getSampleData} from 'utils/table-utils/data-container-utils';
|
|
55
56
|
|
|
56
|
-
import {hexToRgb, getColorGroupByName, reverseColorRange} from 'utils/color-utils';
|
|
57
|
+
import {hexToRgb, getColorGroupByName, reverseColorRange, checkColorOrFallback} from 'utils/color-utils';
|
|
57
58
|
import {getMaskExtensionProps} from 'utils/layer-utils';
|
|
58
59
|
import {scaleOrdinal} from 'd3-scale';
|
|
59
60
|
|
|
@@ -100,10 +101,12 @@ export const colorMaker = generateColor();
|
|
|
100
101
|
class Layer {
|
|
101
102
|
constructor(props = {}) {
|
|
102
103
|
this.id = props.id || generateHashId(LAYER_ID_LENGTH);
|
|
103
|
-
|
|
104
104
|
// meta
|
|
105
105
|
this.meta = {};
|
|
106
106
|
|
|
107
|
+
// legendSettings
|
|
108
|
+
this.legendSettings = {};
|
|
109
|
+
|
|
107
110
|
// visConfigSettings
|
|
108
111
|
this.visConfigSettings = {};
|
|
109
112
|
|
|
@@ -609,8 +612,9 @@ class Layer {
|
|
|
609
612
|
};
|
|
610
613
|
|
|
611
614
|
this.updateLayerConfig({colorUI});
|
|
612
|
-
// if colorUI[prop] is colorRange
|
|
613
|
-
const isColorRange =
|
|
615
|
+
// if colorUI[prop] is colorRange. If type is hexColor then it's also a range
|
|
616
|
+
const isColorRange =
|
|
617
|
+
visConfig[prop] && (visConfig[prop].colors || visConfig[prop].type === hexColor);
|
|
614
618
|
|
|
615
619
|
if (isColorRange) {
|
|
616
620
|
this.updateColorUIByColorRange(newConfig, prop);
|
|
@@ -663,8 +667,10 @@ class Layer {
|
|
|
663
667
|
colorRangeConfig: {
|
|
664
668
|
...colorUI[prop].colorRangeConfig,
|
|
665
669
|
type: visConfig[prop].type,
|
|
666
|
-
steps: visConfig[prop].colors
|
|
667
|
-
reversed: Boolean(visConfig[prop].reversed)
|
|
670
|
+
steps: visConfig[prop].colors?.length,
|
|
671
|
+
reversed: Boolean(visConfig[prop].reversed),
|
|
672
|
+
hexColumn: this.getHexColumn(prop),
|
|
673
|
+
isHexColumn: this.isColoredByColorColumn(prop)
|
|
668
674
|
}
|
|
669
675
|
}
|
|
670
676
|
}
|
|
@@ -732,6 +738,12 @@ class Layer {
|
|
|
732
738
|
updateCategoryColorMap(colorMap, domain, colorRange) {
|
|
733
739
|
const colors = colorRange.colors;
|
|
734
740
|
const assignedCategories = [];
|
|
741
|
+
if (colorRange.type === hexColor) {
|
|
742
|
+
return {
|
|
743
|
+
...colorRange,
|
|
744
|
+
colorMap: []
|
|
745
|
+
};
|
|
746
|
+
}
|
|
735
747
|
const newColorMap = colorMap.slice(0, colors.length).map((cMap, index) => {
|
|
736
748
|
assignedCategories.push(cMap[0]);
|
|
737
749
|
return [cMap[0], colors[index]];
|
|
@@ -821,7 +833,12 @@ class Layer {
|
|
|
821
833
|
}
|
|
822
834
|
|
|
823
835
|
getColorScale(colorScale, colorDomain, colorRange) {
|
|
824
|
-
if (
|
|
836
|
+
if (colorRange.type === hexColor) {
|
|
837
|
+
return (_, color) => {
|
|
838
|
+
const {r, g, b} = rgb(color);
|
|
839
|
+
return [r, g, b].some(isNaN) ? UNKNOWN_COLOR : [r, g, b];
|
|
840
|
+
};
|
|
841
|
+
} else if (Array.isArray(colorRange.colorMap)) {
|
|
825
842
|
const cMap = new Map();
|
|
826
843
|
colorRange.colorMap.forEach(([k, v]) => {
|
|
827
844
|
cMap.set(k, typeof v === 'string' ? hexToRgb(v) : v);
|
|
@@ -914,13 +931,15 @@ class Layer {
|
|
|
914
931
|
: this.getVisChannelScale(...args, isFixed);
|
|
915
932
|
|
|
916
933
|
attributeAccessors[accessor] = d =>
|
|
917
|
-
this.getEncodedChannelValue(
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
934
|
+
this.getEncodedChannelValue({
|
|
935
|
+
channel,
|
|
936
|
+
dataContainer,
|
|
937
|
+
scale: scaleFunction,
|
|
938
|
+
data: dataAccessor(dataContainer)(d),
|
|
939
|
+
field: fieldConfig,
|
|
940
|
+
_aggregation: aggregationValue,
|
|
922
941
|
nullValue
|
|
923
|
-
);
|
|
942
|
+
});
|
|
924
943
|
} else if (typeof getAttributeValue === 'function') {
|
|
925
944
|
attributeAccessors[accessor] = getAttributeValue(this.config);
|
|
926
945
|
} else {
|
|
@@ -990,17 +1009,35 @@ class Layer {
|
|
|
990
1009
|
return triggerChanged;
|
|
991
1010
|
}
|
|
992
1011
|
|
|
993
|
-
getEncodedChannelValue(
|
|
1012
|
+
getEncodedChannelValue({
|
|
1013
|
+
channel,
|
|
994
1014
|
scale,
|
|
995
1015
|
data,
|
|
996
1016
|
field,
|
|
1017
|
+
dataContainer,
|
|
997
1018
|
// In builder we overwrite the getValue function and we use the _aggregation parameter in spatial index layers as the QuadkeyLayer.
|
|
998
1019
|
_aggregation,
|
|
999
1020
|
nullValue = NO_VALUE_COLOR,
|
|
1000
1021
|
getValue = defaultGetFieldValue
|
|
1001
|
-
) {
|
|
1022
|
+
}) {
|
|
1023
|
+
let colorColumnValue;
|
|
1002
1024
|
const {type} = field;
|
|
1003
1025
|
const value = getValue(field, data);
|
|
1026
|
+
const channelConfig = this.config.visConfig[this.visualChannels[channel].range];
|
|
1027
|
+
const channelField = this.config[this.visualChannels[channel].field];
|
|
1028
|
+
|
|
1029
|
+
if (channelConfig.type === hexColor) {
|
|
1030
|
+
const columnField = {
|
|
1031
|
+
id: channelField.colorColumn,
|
|
1032
|
+
name: channelField.colorColumn,
|
|
1033
|
+
fieldIdx: channelField.colorColumnIdx,
|
|
1034
|
+
valueAccessor: d => {
|
|
1035
|
+
return dataContainer.valueAt(d.index, channelField.colorColumnIdx);
|
|
1036
|
+
}
|
|
1037
|
+
};
|
|
1038
|
+
|
|
1039
|
+
colorColumnValue = getValue(columnField, data);
|
|
1040
|
+
}
|
|
1004
1041
|
|
|
1005
1042
|
if (!notNullorUndefined(value)) {
|
|
1006
1043
|
return nullValue;
|
|
@@ -1010,9 +1047,9 @@ class Layer {
|
|
|
1010
1047
|
if (type === ALL_FIELD_TYPES.timestamp) {
|
|
1011
1048
|
// shouldn't need to convert here
|
|
1012
1049
|
// scale Function should take care of it
|
|
1013
|
-
attributeValue = scale(new Date(value));
|
|
1050
|
+
attributeValue = scale(new Date(value), colorColumnValue);
|
|
1014
1051
|
} else {
|
|
1015
|
-
attributeValue = scale(value);
|
|
1052
|
+
attributeValue = scale(value, colorColumnValue);
|
|
1016
1053
|
}
|
|
1017
1054
|
|
|
1018
1055
|
if (!notNullorUndefined(attributeValue)) {
|
|
@@ -1026,6 +1063,10 @@ class Layer {
|
|
|
1026
1063
|
this.meta = {...this.meta, ...meta};
|
|
1027
1064
|
}
|
|
1028
1065
|
|
|
1066
|
+
updateLayerLegend(settings) {
|
|
1067
|
+
this.legendSettings = {...this.legendSettings, ...settings};
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1029
1070
|
getDataUpdateTriggers({filteredIndex, id, allData}) {
|
|
1030
1071
|
const {columns} = this.config;
|
|
1031
1072
|
|
|
@@ -1046,13 +1087,14 @@ class Layer {
|
|
|
1046
1087
|
if (!this.config.dataId) {
|
|
1047
1088
|
return {};
|
|
1048
1089
|
}
|
|
1049
|
-
const
|
|
1050
|
-
const {dataContainer} =
|
|
1090
|
+
const dataset = this.getDataset(datasets);
|
|
1091
|
+
const {dataContainer} = dataset;
|
|
1051
1092
|
|
|
1052
|
-
this.updateVisualChannelFields(
|
|
1093
|
+
this.updateVisualChannelFields(dataset);
|
|
1094
|
+
this.updateHexColorDomain(dataset);
|
|
1053
1095
|
|
|
1054
1096
|
const getPosition = this.getPositionAccessor(dataContainer);
|
|
1055
|
-
const dataUpdateTriggers = this.getDataUpdateTriggers(
|
|
1097
|
+
const dataUpdateTriggers = this.getDataUpdateTriggers(dataset);
|
|
1056
1098
|
const triggerChanged = this.getChangedTriggers(dataUpdateTriggers);
|
|
1057
1099
|
|
|
1058
1100
|
if (triggerChanged.getMeta) {
|
|
@@ -1065,7 +1107,7 @@ class Layer {
|
|
|
1065
1107
|
// same data
|
|
1066
1108
|
data = oldLayerData.data;
|
|
1067
1109
|
} else {
|
|
1068
|
-
data = this.calculateDataAttribute(
|
|
1110
|
+
data = this.calculateDataAttribute(dataset, getPosition);
|
|
1069
1111
|
}
|
|
1070
1112
|
|
|
1071
1113
|
return {data, triggerChanged};
|
|
@@ -1077,12 +1119,20 @@ class Layer {
|
|
|
1077
1119
|
* @param {Object} dataset
|
|
1078
1120
|
*/
|
|
1079
1121
|
updateVisualChannelFields(dataset) {
|
|
1080
|
-
for (const {field} of Object.values(this.visualChannels)) {
|
|
1122
|
+
for (const {field, range} of Object.values(this.visualChannels)) {
|
|
1081
1123
|
const oldField = this.config[field];
|
|
1124
|
+
const isUsingHexColorCode = this.config.visConfig[range].hexColor;
|
|
1082
1125
|
if (!oldField) {
|
|
1083
1126
|
return;
|
|
1084
1127
|
}
|
|
1085
|
-
|
|
1128
|
+
let newDataSetField = dataset.fields.find(fd => fd.name === oldField.name) || null;
|
|
1129
|
+
if (isUsingHexColorCode) {
|
|
1130
|
+
newDataSetField = {
|
|
1131
|
+
...newDataSetField,
|
|
1132
|
+
colorColumn: newDataSetField.colorColumn || oldField.colorColumn,
|
|
1133
|
+
colorColumnIdx: newDataSetField.colorColumnIdx || oldField.colorColumnIdx
|
|
1134
|
+
};
|
|
1135
|
+
}
|
|
1086
1136
|
this.updateLayerConfig({[field]: newDataSetField});
|
|
1087
1137
|
}
|
|
1088
1138
|
}
|
|
@@ -1103,6 +1153,7 @@ class Layer {
|
|
|
1103
1153
|
Object.values(this.visualChannels).forEach(channel => {
|
|
1104
1154
|
const {scale} = channel;
|
|
1105
1155
|
const scaleType = this.config[scale];
|
|
1156
|
+
|
|
1106
1157
|
// ordinal domain is based on dataContainer, if only filter changed
|
|
1107
1158
|
// no need to update ordinal domain
|
|
1108
1159
|
if (!newFilter || scaleType !== SCALE_TYPES.ordinal) {
|
|
@@ -1153,8 +1204,11 @@ class Layer {
|
|
|
1153
1204
|
*/
|
|
1154
1205
|
validateScale(channel) {
|
|
1155
1206
|
const visualChannel = this.visualChannels[channel];
|
|
1156
|
-
const {scale} = visualChannel;
|
|
1157
|
-
if (
|
|
1207
|
+
const {scale, range} = visualChannel;
|
|
1208
|
+
if (
|
|
1209
|
+
!scale ||
|
|
1210
|
+
(this.isColoredByColorColumn(range) && this.config[scale] === SCALE_TYPES.identity)
|
|
1211
|
+
) {
|
|
1158
1212
|
// visualChannel doesn't have scale
|
|
1159
1213
|
return;
|
|
1160
1214
|
}
|
|
@@ -1224,6 +1278,49 @@ class Layer {
|
|
|
1224
1278
|
return dataset.getColumnLayerDomain(dataSetField, scaleType) || defaultDomain;
|
|
1225
1279
|
}
|
|
1226
1280
|
|
|
1281
|
+
updateHexColorDomain(dataset) {
|
|
1282
|
+
if (!dataset) {
|
|
1283
|
+
return this;
|
|
1284
|
+
}
|
|
1285
|
+
Object.values(this.visualChannels).forEach(channel => {
|
|
1286
|
+
const channelConfig = this.config.visConfig[channel.range];
|
|
1287
|
+
const field = this.config[channel.field];
|
|
1288
|
+
|
|
1289
|
+
if (channelConfig?.type === hexColor) {
|
|
1290
|
+
const dataSetField = dataset.fields.find(f => f.name === field.colorColumn);
|
|
1291
|
+
const colors = dataset
|
|
1292
|
+
.getColumnLayerDomain(dataSetField, SCALE_TYPES.ordinal)
|
|
1293
|
+
.map(c => checkColorOrFallback(c));
|
|
1294
|
+
this.updateLayerVisConfig({
|
|
1295
|
+
[channel.range]: {...channelConfig, colors}
|
|
1296
|
+
});
|
|
1297
|
+
}
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
return this;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
calculateHexColumnLegend(datasets) {
|
|
1304
|
+
const dataset = this.getDataset(datasets);
|
|
1305
|
+
if (!datasets) {
|
|
1306
|
+
return;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
const legendSettings = Object.keys(this.visualChannels).reduce((legends, channel) => {
|
|
1310
|
+
const {range, field: channelField} = this.visualChannels[channel];
|
|
1311
|
+
const channelConfig = this.config.visConfig[range];
|
|
1312
|
+
const field = this.config[channelField];
|
|
1313
|
+
|
|
1314
|
+
if (channelConfig?.type === hexColor) {
|
|
1315
|
+
const dataSetField = dataset.fields.find(f => f.name === field.colorColumn);
|
|
1316
|
+
const legend = dataset.calculateLegend(field, dataSetField);
|
|
1317
|
+
legends[channel] = legend;
|
|
1318
|
+
}
|
|
1319
|
+
return legends;
|
|
1320
|
+
}, {});
|
|
1321
|
+
this.updateLayerLegend(legendSettings);
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1227
1324
|
hasHoveredObject(objectInfo) {
|
|
1228
1325
|
return this.isLayerHovered(objectInfo) && objectInfo.object ? objectInfo.object : null;
|
|
1229
1326
|
}
|
|
@@ -1251,9 +1348,9 @@ class Layer {
|
|
|
1251
1348
|
|
|
1252
1349
|
if (!radiusChannel || this.config[radiusChannel.field]) {
|
|
1253
1350
|
return 1;
|
|
1254
|
-
} else {
|
|
1255
|
-
return this.config.visConfig.radius;
|
|
1256
1351
|
}
|
|
1352
|
+
|
|
1353
|
+
return this.config.visConfig.radius;
|
|
1257
1354
|
}
|
|
1258
1355
|
|
|
1259
1356
|
getMaxMarkerSize() {
|
|
@@ -1315,7 +1412,7 @@ class Layer {
|
|
|
1315
1412
|
{getPosition, getRadius, radiusScale, updateTriggers, sharedProps},
|
|
1316
1413
|
renderOpts
|
|
1317
1414
|
) {
|
|
1318
|
-
const {data
|
|
1415
|
+
const {data} = renderOpts;
|
|
1319
1416
|
const {textLabel, visibilityByZoom} = this.config;
|
|
1320
1417
|
const extensions = [...sharedProps.extensions, collisionFilterExtension];
|
|
1321
1418
|
|
|
@@ -1434,6 +1531,17 @@ class Layer {
|
|
|
1434
1531
|
// implemented in subclasses
|
|
1435
1532
|
return () => null;
|
|
1436
1533
|
}
|
|
1534
|
+
|
|
1535
|
+
getHexColumn(range) {
|
|
1536
|
+
const field = Object.values(this.visualChannels)?.find(value => value.range === range)?.field;
|
|
1537
|
+
if (field) {
|
|
1538
|
+
return this.config[field].colorColumn;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
isColoredByColorColumn(range) {
|
|
1543
|
+
return this.config.visConfig[range]?.hexColor;
|
|
1544
|
+
}
|
|
1437
1545
|
}
|
|
1438
1546
|
|
|
1439
1547
|
export default Layer;
|
package/src/layers/index.d.ts
CHANGED
|
@@ -92,6 +92,8 @@ export class Layer {
|
|
|
92
92
|
hasInteractivity: boolean;
|
|
93
93
|
hasAllColumns(): boolean;
|
|
94
94
|
updateLayerConfig(p: Partial<LayerConfig>): Layer;
|
|
95
|
+
updateLayerMeta(p: any): Layer
|
|
96
|
+
updateLayerLegend(p: any): Layer
|
|
95
97
|
updateLayerDomain(datasets: Datasets, filter?: Filter): Layer;
|
|
96
98
|
updateLayerVisualChannel(dataset: KeplerTable, channel: string): Layer;
|
|
97
99
|
shouldCalculateLayerData(props: string[]): boolean;
|
|
@@ -100,7 +102,7 @@ export class Layer {
|
|
|
100
102
|
isValidToSave(): boolean;
|
|
101
103
|
validateVisualChannel(channel: string);
|
|
102
104
|
getVisualChannelDescription(key: string): {label: string, measure: string};
|
|
103
|
-
|
|
105
|
+
calculateHexColumnLegend(datasets: Datasets): any;
|
|
104
106
|
static findDefaultLayerProps(dataset: KeplerTable, foundLayers?: any[]);
|
|
105
107
|
// static findDefaultColumnField(defaultFields, allFields)
|
|
106
108
|
}
|
|
@@ -238,7 +238,7 @@ export default class PointLayer extends Layer {
|
|
|
238
238
|
formatLayerData(datasets, oldLayerData) {
|
|
239
239
|
const {textLabel} = this.config;
|
|
240
240
|
const {gpuFilter, dataContainer} = datasets[this.config.dataId];
|
|
241
|
-
const {data
|
|
241
|
+
const {data} = this.updateData(datasets, oldLayerData);
|
|
242
242
|
const getPosition = this.getPositionAccessor(dataContainer);
|
|
243
243
|
|
|
244
244
|
// get all distinct characters in the text labels
|
package/src/localization/en.js
CHANGED
|
@@ -423,6 +423,7 @@ export function validateSavedTextLabel(fields, [layerTextLabel], savedTextLabel)
|
|
|
423
423
|
export function validateSavedVisualChannels(fields, newLayer, savedLayer) {
|
|
424
424
|
Object.values(newLayer.visualChannels).forEach(({field, scale, key}) => {
|
|
425
425
|
let foundField;
|
|
426
|
+
const colorFieldProps = {};
|
|
426
427
|
if (savedLayer.config) {
|
|
427
428
|
if (savedLayer.config[field]) {
|
|
428
429
|
foundField = fields.find(
|
|
@@ -430,8 +431,19 @@ export function validateSavedVisualChannels(fields, newLayer, savedLayer) {
|
|
|
430
431
|
);
|
|
431
432
|
}
|
|
432
433
|
|
|
434
|
+
if (savedLayer.config[field]?.colorColumn) {
|
|
435
|
+
const colorColumnField = fields.find(
|
|
436
|
+
fd => savedLayer.config && fd.name === savedLayer.config[field].colorColumn
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
if (colorColumnField) {
|
|
440
|
+
colorFieldProps.colorColumn = colorColumnField.name;
|
|
441
|
+
colorFieldProps.colorColumnIdx = colorColumnField.fieldIdx;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
433
445
|
const foundChannel = {
|
|
434
|
-
...(foundField ? {[field]: foundField} : {}),
|
|
446
|
+
...(foundField ? {[field]: {...foundField, ...colorFieldProps}} : {}),
|
|
435
447
|
...(savedLayer.config[scale] ? {[scale]: savedLayer.config[scale]} : {})
|
|
436
448
|
};
|
|
437
449
|
if (Object.keys(foundChannel).length) {
|
|
@@ -340,7 +340,7 @@ class VisualChannelSchemaV1 extends Schema {
|
|
|
340
340
|
(accu, key) => ({
|
|
341
341
|
...accu,
|
|
342
342
|
[visualChannels[key].field]: layer.config[visualChannels[key].field]
|
|
343
|
-
? pick(layer.config[visualChannels[key].field], ['name', 'type'])
|
|
343
|
+
? pick(layer.config[visualChannels[key].field], ['name', 'type', 'colorColumn'])
|
|
344
344
|
: null,
|
|
345
345
|
[visualChannels[key].scale]: layer.config[visualChannels[key].scale]
|
|
346
346
|
}),
|
|
@@ -365,6 +365,25 @@ class VisualChannelSchemaV1 extends Schema {
|
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
|
+
/**
|
|
369
|
+
* V1: save [channel]: [{ label, color }, ...] for legend
|
|
370
|
+
*/
|
|
371
|
+
class LegendSettingsV1 extends Schema {
|
|
372
|
+
save(legendSettings, parents) {
|
|
373
|
+
return {
|
|
374
|
+
[this.key]: Object.keys(legendSettings).reduce((accu, key) => {
|
|
375
|
+
accu[key] = legendSettings[key];
|
|
376
|
+
return accu;
|
|
377
|
+
}, {})
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
load(ls, parents, accumulator) {
|
|
381
|
+
return {
|
|
382
|
+
...accumulator,
|
|
383
|
+
[this.key]: ls
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
368
387
|
const visConfigModificationV1 = {
|
|
369
388
|
point: (visConfig, parents, accumulated) => {
|
|
370
389
|
const modified = {};
|
|
@@ -459,6 +478,10 @@ export const layerPropsV1 = {
|
|
|
459
478
|
})
|
|
460
479
|
}
|
|
461
480
|
}),
|
|
481
|
+
legendSettings: new LegendSettingsV1({
|
|
482
|
+
version: VERSIONS.v1,
|
|
483
|
+
key: 'legendSettings'
|
|
484
|
+
}),
|
|
462
485
|
visualChannels: new VisualChannelSchemaV1({
|
|
463
486
|
version: VERSIONS.v1,
|
|
464
487
|
key: 'visualChannels'
|
package/src/utils/color-utils.js
CHANGED
|
@@ -18,6 +18,9 @@
|
|
|
18
18
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
19
19
|
// THE SOFTWARE.
|
|
20
20
|
|
|
21
|
+
import {UNKNOWN_COLOR} from 'constants/default-settings';
|
|
22
|
+
import {rgb as d3Rgb} from 'd3-color';
|
|
23
|
+
|
|
21
24
|
/**
|
|
22
25
|
* get r g b from hex code
|
|
23
26
|
*
|
|
@@ -104,3 +107,9 @@ export function createLinearGradient(direction, colors) {
|
|
|
104
107
|
|
|
105
108
|
return `linear-gradient(to ${direction}, ${bands.join(',')})`;
|
|
106
109
|
}
|
|
110
|
+
|
|
111
|
+
export function checkColorOrFallback(hexColor, fallback = UNKNOWN_COLOR) {
|
|
112
|
+
const color = d3Rgb(hexColor);
|
|
113
|
+
const {r, g, b} = color;
|
|
114
|
+
return [r, g, b].some(isNaN) ? d3Rgb(...fallback).formatHex() : hexColor;
|
|
115
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function scaleIdentity() {
|
|
2
|
+
let unknown;
|
|
3
|
+
function scale(x) {
|
|
4
|
+
return x === null ? unknown : x;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
scale.invert = scale;
|
|
8
|
+
|
|
9
|
+
scale.domain = scale.range = scaleIdentity;
|
|
10
|
+
|
|
11
|
+
scale.unknown = u => {
|
|
12
|
+
if (u) {
|
|
13
|
+
unknown = u;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return scale;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
return scale;
|
|
20
|
+
}
|
|
@@ -13,6 +13,7 @@ export function isPlainObject(d: any): boolean;
|
|
|
13
13
|
export function numberSort(a: any, b: any): any;
|
|
14
14
|
export function unique(values: any[]): any[];
|
|
15
15
|
export function uniqueSortedByFrequency(values: any[]): any[];
|
|
16
|
+
export function uniqueSortedPairByFrequency(values: any[]): any[];
|
|
16
17
|
export function arrayMove(array: any[], from: number, to: number);
|
|
17
18
|
export function getSortingFunction(fieldType: string): numberSort | undefined;
|
|
18
19
|
export function preciseRound(num: number, decimals: number): string;
|
package/src/utils/data-utils.js
CHANGED
|
@@ -426,3 +426,22 @@ export function datetimeFormatter(timezone) {
|
|
|
426
426
|
.format(format)
|
|
427
427
|
: format => ts => moment.utc(ts).format(format);
|
|
428
428
|
}
|
|
429
|
+
|
|
430
|
+
export function uniqueSortedPairByFrequency(values) {
|
|
431
|
+
const frequencies = values.reduce((res, value) => {
|
|
432
|
+
if (notNullorUndefined(value)) {
|
|
433
|
+
if (!res[value]) {
|
|
434
|
+
res[value] = {
|
|
435
|
+
key: value,
|
|
436
|
+
frequency: 1
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
res[value].frequency += 1;
|
|
440
|
+
}
|
|
441
|
+
return res;
|
|
442
|
+
}, {});
|
|
443
|
+
|
|
444
|
+
return Object.values(frequencies)
|
|
445
|
+
.sort((a, b) => b.frequency - a.frequency)
|
|
446
|
+
.map(({key: [left, right]}) => ({left, right}));
|
|
447
|
+
}
|
package/src/utils/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export {
|
|
|
27
27
|
FIELD_DISPLAY_FORMAT
|
|
28
28
|
} from './data-utils';
|
|
29
29
|
export {createNewDataEntry, datasetColorMaker, findDefaultColorField} from './dataset-utils';
|
|
30
|
-
export {hexToRgb} from './color-utils';
|
|
30
|
+
export {hexToRgb, checkColorOrFallback} from './color-utils';
|
|
31
31
|
export {errorNotification} from './notifications-utils';
|
|
32
32
|
export {dataURItoBlob, downloadFile} from './export-utils';
|
|
33
33
|
export {
|
package/src/utils/layer-utils.js
CHANGED
|
@@ -45,6 +45,8 @@ import {
|
|
|
45
45
|
import {ALL_FIELD_TYPES, SCALE_TYPES} from 'constants/default-settings';
|
|
46
46
|
|
|
47
47
|
import {createDataContainer} from './data-container-utils';
|
|
48
|
+
import {uniqueSortedPairByFrequency} from 'utils/data-utils';
|
|
49
|
+
import {checkColorOrFallback} from 'utils/color-utils';
|
|
48
50
|
|
|
49
51
|
// Unique identifier of each field
|
|
50
52
|
const FID_KEY = 'name';
|
|
@@ -342,6 +344,26 @@ class KeplerTable {
|
|
|
342
344
|
}
|
|
343
345
|
}
|
|
344
346
|
|
|
347
|
+
calculateLegend(leftField, rightField) {
|
|
348
|
+
const limitOfNumberOfLegendEntries = 20;
|
|
349
|
+
const leftValues = this.dataContainer.mapIndex(leftField.valueAccessor);
|
|
350
|
+
const rightValues = this.dataContainer.mapIndex(rightField.valueAccessor);
|
|
351
|
+
|
|
352
|
+
const pairing = leftValues.reduce((previous, current, idx) => {
|
|
353
|
+
previous.push([current, rightValues[idx]]);
|
|
354
|
+
return previous;
|
|
355
|
+
}, []);
|
|
356
|
+
|
|
357
|
+
return uniqueSortedPairByFrequency(pairing)
|
|
358
|
+
.map(row => {
|
|
359
|
+
return {
|
|
360
|
+
label: row.left,
|
|
361
|
+
color: checkColorOrFallback(row.right)
|
|
362
|
+
};
|
|
363
|
+
})
|
|
364
|
+
.slice(0, limitOfNumberOfLegendEntries + 1); // Limit to limitOfNumberOfLegendEntries + 1, then limit to limitOfNumberOfLegendEntries in LegendManagers
|
|
365
|
+
}
|
|
366
|
+
|
|
345
367
|
/**
|
|
346
368
|
* Get a sample of rows to calculate layer boundaries
|
|
347
369
|
*/
|