Skip to content

Commit

Permalink
[Enhancement] Choose layer color by default (point layer) (#1367)
Browse files Browse the repository at this point in the history
Add simple heuristic to detect a column to set the color by. Looks for first non-lat/non-lng real column.

Signed-off-by: Shan He <heshan0131@gmail.com>
Co-authored-by: Isaac Brodsky <isaac@unfolded.ai>
  • Loading branch information
heshan0131 and Isaac Brodsky committed Dec 10, 2020
1 parent 823405a commit eff0a15
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/layers/geojson-layer/geojson-layer.js
Expand Up @@ -243,7 +243,7 @@ export default class GeoJsonLayer extends Layer {
this.updateMeta({bounds, fixedRadius, featureTypes});
}

setInitialLayerConfig(allData) {
setInitialLayerConfig({allData}) {
this.updateLayerMeta(allData);

const {featureTypes} = this.meta;
Expand Down
14 changes: 14 additions & 0 deletions src/layers/h3-hexagon-layer/h3-hexagon-layer.js
Expand Up @@ -19,6 +19,7 @@
// THE SOFTWARE.

import Layer from '../base-layer';
import {findDefaultColorField} from 'utils/dataset-utils';
import {GeoJsonLayer} from '@deck.gl/layers';
import {H3HexagonLayer} from '@deck.gl/geo-layers';
import EnhancedColumnLayer from 'deckgl-layers/column-layer/enhanced-column-layer';
Expand Down Expand Up @@ -97,6 +98,19 @@ export default class HexagonIdLayer extends Layer {
};
}

setInitialLayerConfig(dataset) {
const defaultColorField = findDefaultColorField(dataset);

if (defaultColorField) {
this.updateLayerConfig({
colorField: defaultColorField
});
this.updateLayerVisualChannel(dataset, 'color');
}

return this;
}

static findDefaultLayerProps({fields = [], allData = []}) {
const hexFields = getHexFields(fields, allData);
if (!hexFields.length) {
Expand Down
14 changes: 14 additions & 0 deletions src/layers/point-layer/point-layer.js
Expand Up @@ -23,6 +23,7 @@ import {ScatterplotLayer} from '@deck.gl/layers';

import Layer from '../base-layer';
import {hexToRgb} from 'utils/color-utils';
import {findDefaultColorField} from 'utils/dataset-utils';
import PointLayerIcon from './point-layer-icon';
import {DEFAULT_LAYER_COLOR, CHANNEL_SCALES} from 'constants/default-settings';

Expand Down Expand Up @@ -126,6 +127,19 @@ export default class PointLayer extends Layer {
};
}

setInitialLayerConfig(dataset) {
const defaultColorField = findDefaultColorField(dataset);

if (defaultColorField) {
this.updateLayerConfig({
colorField: defaultColorField
});
this.updateLayerVisualChannel(dataset, 'color');
}

return this;
}

static findDefaultLayerProps({fieldPairs = []}) {
const props = [];

Expand Down
2 changes: 1 addition & 1 deletion src/layers/trip-layer/trip-layer.js
Expand Up @@ -235,7 +235,7 @@ export default class TripLayer extends Layer {
this.updateMeta({bounds, featureTypes, getFeature});
}

setInitialLayerConfig(allData) {
setInitialLayerConfig({allData}) {
this.updateLayerMeta(allData);
return this;
}
Expand Down
29 changes: 28 additions & 1 deletion src/utils/dataset-utils.js
Expand Up @@ -20,7 +20,7 @@

import {hexToRgb} from './color-utils';
import uniq from 'lodash.uniq';
import {TRIP_POINT_FIELDS, SORT_ORDER} from 'constants/default-settings';
import {ALL_FIELD_TYPES, TRIP_POINT_FIELDS, SORT_ORDER} from 'constants/default-settings';
import {generateHashId} from './utils';
import {validateInputData} from 'processors/data-processor';
import {getGpuFilterProps} from 'utils/gpu-filter-utils';
Expand Down Expand Up @@ -213,3 +213,30 @@ export function sortDatasetByColumn(dataset, column, mode) {
sortOrder
};
}

/**
* Choose a field to use as the default color field of a layer.
*
* Right now this implements a very simple heuristic looking
* for a real-type field that is not lat/lon.
*
* In the future we could consider other things:
* Consider integer fields
* look for highest dynamic range (using a sample of the data)
* Look for particular names to select ("value", "color", etc)
* Look for particular names to avoid ("" - the Pandas index column)
*
* @param dataset
*/
export function findDefaultColorField({fields, fieldPairs = []}) {
const defaultField = fields.find(
f =>
f.type === ALL_FIELD_TYPES.real &&
// Do not permit lat, lon fields
!fieldPairs.find(pair => pair.pair.lat.value === f.name || pair.pair.lng.value === f.name)
);
if (!defaultField) {
return null;
}
return defaultField;
}
2 changes: 1 addition & 1 deletion src/utils/layer-utils.js
Expand Up @@ -48,7 +48,7 @@ export function findDefaultLayer(dataset, layerClasses) {
return layerProps.map(props => {
const layer = new layerClasses[props.type](props);
return typeof layer.setInitialLayerConfig === 'function' && Array.isArray(dataset.allData)
? layer.setInitialLayerConfig(dataset.allData)
? layer.setInitialLayerConfig(dataset)
: layer;
});
}
Expand Down
51 changes: 51 additions & 0 deletions test/node/utils/dataset-utils-test.js
@@ -0,0 +1,51 @@
// Copyright (c) 2020 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

import test from 'tape';
import {findDefaultColorField} from 'utils/dataset-utils';
import {createNewDataEntry} from 'utils/dataset-utils';
import {processCsvData} from 'processors/data-processor';

import csvData from 'test/fixtures/test-layer-data';

test('datasetUtils.findDefaultColorField', t => {
const dataset = createNewDataEntry({
info: {id: 'taro'},
data: processCsvData(csvData)
})['taro'];

const defaultField = findDefaultColorField(dataset);
// Unfortunately lat_1 is not detected as part of a field pair :(
t.equals(defaultField.name, 'lat_1', 'default field name is OK');

t.end();
});

test('datasetUtils.findDefaultColorField empty', t => {
const dataset = createNewDataEntry({
info: {id: 'taro'},
data: processCsvData('a\na')
})['taro'];

const defaultField = findDefaultColorField(dataset);
t.notOk(defaultField, 'default field is null');

t.end();
});
1 change: 1 addition & 0 deletions test/node/utils/index.js
Expand Up @@ -22,6 +22,7 @@ import './data-utils-test';
import './data-processor-test';
import './filter-utils-test';
import './gpu-filter-utils-test';
import './dataset-utils-test';
import './layer-utils-test';
import './data-scale-utils-test';
import './interaction-utils-test';
Expand Down

0 comments on commit eff0a15

Please sign in to comment.