Skip to content

Commit

Permalink
[Feat] handle layer color scale by field.domainQuantiles (#2829)
Browse files Browse the repository at this point in the history
* handle layer domain quantile
* add threshold scale

Signed-off-by: Ihor Dykhta <dikhta.igor@gmail.com>
Co-authored-by: Ilya Boyandin <iboyandin@foursquare.com>
  • Loading branch information
igorDykhta and ilyabo authored Dec 10, 2024
1 parent 5f7c26b commit bded7af
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 23 deletions.
36 changes: 20 additions & 16 deletions src/layers/src/base-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ import {
ValueOf
} from '@kepler.gl/types';
import {getScaleFunction, initializeLayerColorMap} from '@kepler.gl/utils';
import {bisectLeft} from 'd3-array';
import memoize from 'lodash.memoize';
import {isDomainQuantile, getDomainStepsbyZoom, getThresholdsFromQuantiles} from '@kepler.gl/utils';

export type VisualChannelDomain = number[] | string[];
export type VisualChannelField = Field | null;
Expand Down Expand Up @@ -198,6 +198,7 @@ const dataFilterExtension = new DataFilterExtension({

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const defaultDataAccessor = dc => d => d;
const identity = d => d;
// Can't use fiedValueAccesor because need the raw data to render tooltip
// SHAN: Revisit here
export const defaultGetFieldValue = (field, d) => field.valueAccessor(d);
Expand Down Expand Up @@ -1184,7 +1185,20 @@ class Layer {
range: any,
fixed?: boolean
): GetVisChannelScaleReturnType {
if (isDomainStops(domain)) {
// if quantile is provided per zoom
if (isDomainQuantile(domain) && scale === SCALE_TYPES.quantile) {
const zSteps = domain.z;

const getScale = function getScaleByZoom(z) {
const scaleDomain = getDomainStepsbyZoom(domain.quantiles, zSteps, z);
const thresholds = getThresholdsFromQuantiles(scaleDomain, range.length);

return getScaleFunction('threshold', range, thresholds, false);
};

getScale.byZoom = true;
return getScale;
} else if (isDomainStops(domain)) {
// color is based on zoom
const zSteps = domain.z;
// get scale function by z
Expand All @@ -1195,18 +1209,11 @@ class Layer {
// }

const getScale = function getScaleByZoom(z) {
let scaleDomain;
const i = bisectLeft(zSteps, z);
if (i === 0) {
scaleDomain = domain.stops[0];
} else {
scaleDomain = domain.stops[i - 1];
}
const scaleDomain = getDomainStepsbyZoom(domain.stops, zSteps, z);

return SCALE_FUNC[fixed ? 'linear' : scale]()
.domain(scaleDomain)
.range(fixed ? scaleDomain : range);
return getScaleFunction(scale, range, scaleDomain, fixed);
};

getScale.byZoom = true;
return getScale;
}
Expand All @@ -1218,13 +1225,10 @@ class Layer {

/**
* Get longitude and latitude bounds of the data.
* @param {import('utils/table-utils/data-container-interface').DataContainerInterface} dataContainer DataContainer to calculate bounds for.
* @param {(d: {index: number}, dc: import('utils/table-utils/data-container-interface').DataContainerInterface) => number[]} getPosition Access kepler.gl layer data from deck.gl layer
* @return {number[]|null} bounds of the data.
*/
getPointsBounds(
dataContainer: DataContainerInterface,
getPosition?: (x: any, dc: DataContainerInterface) => number[]
getPosition: (x: any, dc: DataContainerInterface) => number[] = identity
): number[] | null {
// no need to loop through the entire dataset
// get a sample of data to calculate bounds
Expand Down
10 changes: 5 additions & 5 deletions src/utils/src/data-scale-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,20 +126,20 @@ export function getThresholdsFromQuantiles(

/**
* get the domain at zoom
* @type {typeof import('./data-scale-utils').getDomainStepsbyZoom}
*/
export function getDomainStepsbyZoom(domain: any[], steps: number[], z: number): any {
const i = bisectLeft(steps, z);

if (i === 0) {
return domain[0];
if (steps[i] === z) {
// If z is an integer value exactly matching a step, return the corresponding domain
return domain[i];
}
return domain[i - 1];
// Otherwise, return the next coarsest domain
return domain[Math.max(i - 1, 0)];
}

/**
* Get d3 scale function
* @type {typeof import('./data-scale-utils').getScaleFunction}
*/
export function getScaleFunction(
scale: string,
Expand Down
5 changes: 4 additions & 1 deletion src/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,10 @@ export {
getVisualChannelScaleByZoom,
initializeLayerColorMap,
isNumericColorBreaks,
isDomainStops
isDomainStops,
isDomainQuantile,
getDomainStepsbyZoom,
getThresholdsFromQuantiles
} from './data-scale-utils';
export type {ColorBreak, ColorBreakOrdinal, DomainQuantiles, DomainStops} from './data-scale-utils';

Expand Down
53 changes: 52 additions & 1 deletion test/node/utils/data-scale-utils-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import {
getQuantileDomain,
getLinearDomain,
getLogDomain,
createDataContainer
createDataContainer,
getThresholdsFromQuantiles,
getDomainStepsbyZoom
} from '@kepler.gl/utils';

function numberSort(a, b) {
Expand Down Expand Up @@ -111,3 +113,52 @@ test('DataScaleUtils -> getLogDomain', t => {

t.end();
});

test('DataScaleUtils -> getThresholdsFromQuantiles', t => {
t.deepEqual(
getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], 3),
[1.6666666666666665, 3.333333333333333],
'should get correct thresholds from quantiles'
);

t.deepEqual(
getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], 1),
[],
'should get correct thresholds from quantiles'
);

t.deepEqual(
getThresholdsFromQuantiles([0, 1, 2, 3, 4, 5], undefined),
[0, 5],
'should get correct thresholds from quantiles'
);
t.end();
});

test('DataScaleUtils -> getDomainStepsbyZoom', t => {
const domain = [
[0, 1],
[0, 2],
[0, 3]
];
const steps = [0, 2, 4];
[
{z: 0, expected: [0, 1]},
{z: 0.5, expected: [0, 1]},
{z: 1, expected: [0, 1]},
{z: 1.2, expected: [0, 1]},
{z: 2, expected: [0, 2]},
{z: 3.5, expected: [0, 2]},
{z: 4, expected: [0, 3]},
{z: 4.5, expected: [0, 3]},
{z: 10, expected: [0, 3]}
].forEach(({z, expected}) => {
t.deepEqual(
getDomainStepsbyZoom(domain, steps, z),
expected,
`should get correct domain from zoom ${z}`
);
});

t.end();
});

0 comments on commit bded7af

Please sign in to comment.