Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEAT] support domain.domainStops in layer color, render color legend based on zoom #2815

Merged
merged 3 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 17 additions & 11 deletions src/components/src/common/color-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import {InlineInput} from './styled-components';

const ROW_H = 15;
const GAP = 4;
const GAP = 2;
const RECT_W = 20;

const stopClickPropagation = e => e.stopPropagation();
Expand Down Expand Up @@ -224,15 +224,15 @@
}

export function useLayerColorLegends(
layer,
scaleType,
domain,
range,
isFixed,
fieldType,
labelFormat,
mapState
) {
layer: ColorLegendProps['layer'],
scaleType: ColorLegendProps['scaleType'],
domain: ColorLegendProps['domain'],
range: ColorLegendProps['range'],
isFixed: ColorLegendProps['isFixed'],
fieldType: ColorLegendProps['fieldType'],
labelFormat: ColorLegendProps['labelFormat'],
mapState: ColorLegendProps['mapState']
): Legend[] {
const scale = useMemo(
() => getLayerColorScale({range, domain, scaleType, isFixed, layer}),
[range, domain, scaleType, isFixed, layer]
Expand Down Expand Up @@ -260,7 +260,7 @@
return LegendsWithCustomLegends;
}

type ColorLegendProps = {
export type ColorLegendProps = {
layer: Layer;
scaleType: string;
domain: number[] | string[];
Expand All @@ -274,6 +274,12 @@
onUpdateColorLegend?: (colorLegends: {[key: HexColor]: string}) => void;
};

export type Legend = {
data: string;
label: string;
override?: boolean;
};

ColorLegendFactory.deps = [LegendRowFactory];
function ColorLegendFactory(LegendRow: ReturnType<typeof LegendRowFactory>) {
const ColorLegend: React.FC<ColorLegendProps> = ({
Expand Down Expand Up @@ -318,7 +324,7 @@
color => {
/* eslint-disable no-unused-vars */
// @ts-ignore
const {[color]: _, ...rest} = colorLegends;

Check warning on line 327 in src/components/src/common/color-legend.tsx

View workflow job for this annotation

GitHub Actions / build (18.x)

'_' is assigned a value but never used
if (onUpdateColorLegend && rest) {
onUpdateColorLegend(rest);
}
Expand Down
52 changes: 37 additions & 15 deletions src/components/src/map/layer-hover-info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@

import React, {useMemo} from 'react';
import styled from 'styled-components';
import {TooltipField} from '@kepler.gl/types';
import {CompareType, Field, Merge, TooltipField} from '@kepler.gl/types';
import {CenterFlexbox} from '../common/styled-components';
import {Layers} from '../common/icons';
import PropTypes from 'prop-types';
import {notNullorUndefined} from '@kepler.gl/common-utils';
import {DataRow} from '@kepler.gl/utils';
import {Layer} from '@kepler.gl/layers';
import {
AggregationLayerHoverData,
LayerHoverProp,
getTooltipDisplayDeltaValue,
getTooltipDisplayValue
} from '@kepler.gl/reducers';
import {useIntl} from 'react-intl';
import {VisState} from '@kepler.gl/schemas';
import {capitalizeFirstLetter} from '@kepler.gl/utils';

export const StyledLayerName = styled(CenterFlexbox)`
Expand Down Expand Up @@ -99,36 +102,55 @@ const Row: React.FC<RowProps> = ({name, value, deltaValue, url}) => {
);
};

const EntryInfo = ({fieldsToShow, fields, data, primaryData, compareType}) => (
export type EntryInfoProps = Merge<LayerHoverProp, {fieldsToShow: TooltipField[]}>;

const EntryInfo: React.FC<EntryInfoProps> = ({fieldsToShow, ...props}) => (
<tbody>
{fieldsToShow.map(item => (
<EntryInfoRow
key={item.name}
item={item}
fields={fields}
data={data}
primaryData={primaryData}
compareType={compareType}
/>
<EntryInfoRow key={item.name} item={item} {...props} />
))}
</tbody>
);

const EntryInfoRow = ({item, fields, data, primaryData, compareType}) => {
export type EntryInfoRowProps = {
data: LayerHoverProp['data'];
fields: Field[];
layer: Layer;
primaryData?: LayerHoverProp['primaryData'];
compareType?: CompareType;
currentTime?: VisState['animationConfig']['currentTime'];
item: TooltipField;
};

const EntryInfoRow: React.FC<EntryInfoRowProps> = ({
layer,
item,
fields,
data,
primaryData,
compareType,
currentTime
}) => {
const fieldIdx = fields.findIndex(f => f.name === item.name);
if (fieldIdx < 0) {
return null;
}
const field = fields[fieldIdx];
const value = data.valueAt(fieldIdx);
const fieldValueAccessor = layer.accessVSFieldValue(field, currentTime);
const value = fieldValueAccessor(field, data instanceof DataRow ? {index: data._rowIndex} : data);
const primaryValue = primaryData
? fieldValueAccessor(
field,
primaryData instanceof DataRow ? {index: primaryData._rowIndex} : primaryData
)
: null;
const displayValue = getTooltipDisplayValue({item, field, value});

const displayDeltaValue = primaryData
? getTooltipDisplayDeltaValue({
field,
data,
fieldIdx,
primaryData,
value,
primaryValue,
compareType
})
: null;
Expand Down
11 changes: 9 additions & 2 deletions src/components/src/map/map-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export type LayerColorLegendProps = {
onLayerVisConfigChange?: (oldLayer: Layer, newVisConfig: Partial<LayerVisConfig>) => void;
layer: Layer;
disableEdit?: boolean;
mapState?: MapState;
};

LayerColorLegendFactory.deps = [ColorLegendFactory, SingleColorLegendFactory];
Expand All @@ -131,11 +132,14 @@ export function LayerColorLegendFactory(
layer,
colorChannel,
disableEdit,
onLayerVisConfigChange
onLayerVisConfigChange,
mapState
}) => {
const enableColorBy = description.measure;
const {scale, field, domain, range, property} = colorChannel;
const {scale, field, domain, range, property, fixed} = colorChannel;
const [colorScale, colorField, colorDomain] = [scale, field, domain].map(k => config[k]);
const isFixed = fixed && config.visConfig[fixed];

const colorRange = config.visConfig[range];
const onUpdateColorLegend = useCallback(
colorLegends => {
Expand Down Expand Up @@ -166,6 +170,8 @@ export function LayerColorLegendFactory(
range={colorRange}
onUpdateColorLegend={onUpdateColorLegend}
disableEdit={disableEdit}
isFixed={isFixed}
mapState={mapState}
/>
) : (
<SingleColorLegend
Expand Down Expand Up @@ -304,6 +310,7 @@ export function LayerLegendContentFactory(
description={layer.getVisualChannelDescription(colorChannel.key)}
layer={layer}
disableEdit={disableEdit}
mapState={mapState}
onLayerVisConfigChange={onLayerVisConfigChange}
/>
))}
Expand Down
91 changes: 78 additions & 13 deletions src/layers/src/base-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ import {
hasColorMap,
hexToRgb,
isPlainObject,
reverseColorRange
reverseColorRange,
isDomainStops
} from '@kepler.gl/utils';
import {generateHashId, toArray, notNullorUndefined} from '@kepler.gl/common-utils';
import {Datasets, GpuFilter, KeplerTable} from '@kepler.gl/table';
Expand All @@ -68,6 +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';

export type VisualChannelDomain = number[] | string[];
export type VisualChannelField = Field | null;
Expand Down Expand Up @@ -229,6 +232,11 @@ export type BaseLayerConstructorProps = {
id?: string;
} & LayerBaseConfigPartial;

export type GetVisChannelScaleReturnType = {
(z: number): any;
byZoom?: boolean;
} | null;

class Layer {
id: string;
meta: Record<string, any>;
Expand Down Expand Up @@ -1049,7 +1057,11 @@ class Layer {
);
}

getColorScale(colorScale: string, colorDomain: VisualChannelDomain, colorRange: ColorRange) {
getColorScale(
colorScale: string,
colorDomain: VisualChannelDomain,
colorRange: ColorRange
): GetVisChannelScaleReturnType {
if (hasColorMap(colorRange) && colorScale === SCALE_TYPES.custom) {
const cMap = new Map();
colorRange.colorMap?.forEach(([k, v]) => {
Expand All @@ -1061,11 +1073,14 @@ class Layer {
const scale = getScaleFunction(scaleType, cMap.values(), cMap.keys(), false);
scale.unknown(cMap.get(UNKNOWN_COLOR_KEY) || NO_VALUE_COLOR);

return scale;
return scale as () => any;
}
return this.getVisChannelScale(colorScale, colorDomain, colorRange.colors.map(hexToRgb));
}

accessVSFieldValue(field, indexKey) {
return defaultGetFieldValue;
}
/**
* Mapping from visual channels to deck.gl accesors
* @param {Object} param Parameters
Expand All @@ -1075,10 +1090,12 @@ class Layer {
*/
getAttributeAccessors({
dataAccessor = defaultDataAccessor,
dataContainer
dataContainer,
indexKey
}: {
dataAccessor?: typeof defaultDataAccessor;
dataContainer: DataContainerInterface;
indexKey?: number;
}) {
const attributeAccessors: {[key: string]: any} = {};

Expand Down Expand Up @@ -1116,14 +1133,35 @@ class Layer {
isFixed
);

attributeAccessors[accessor] = d =>
this.getEncodedChannelValue(
// @ts-ignore
scaleFunction,
dataAccessor(dataContainer)(d),
this.config[field],
nullValue
);
const getFieldValue = this.accessVSFieldValue(this.config[field], indexKey);

if (scaleFunction) {
attributeAccessors[accessor] = scaleFunction.byZoom
? memoize(z => {
const scaleFunc = scaleFunction(z);
return d =>
this.getEncodedChannelValue(
scaleFunc,
dataAccessor(dataContainer)(d),
this.config[field],
nullValue,
getFieldValue
);
})
: d =>
this.getEncodedChannelValue(
scaleFunction,
dataAccessor(dataContainer)(d),
this.config[field],
nullValue,
getFieldValue
);

// set getFillColorByZoom to true
if (scaleFunction.byZoom) {
attributeAccessors[`${accessor}ByZoom`] = true;
}
}
} else if (typeof getAttributeValue === 'function') {
attributeAccessors[accessor] = getAttributeValue(this.config);
} else {
Expand All @@ -1145,7 +1183,34 @@ class Layer {
domain: VisualChannelDomain,
range: any,
fixed?: boolean
): () => any | null {
): GetVisChannelScaleReturnType {
if (isDomainStops(domain)) {
// color is based on zoom
const zSteps = domain.z;
// get scale function by z
// {
// z: [z, z, z],
// stops: [[min, max], [min, max]],
// interpolation: 'interpolate'
// }

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];
}

return SCALE_FUNC[fixed ? 'linear' : scale]()
.domain(scaleDomain)
.range(fixed ? scaleDomain : range);
};
getScale.byZoom = true;
return getScale;
}

return SCALE_FUNC[fixed ? 'linear' : scale]()
.domain(domain)
.range(fixed ? domain : range);
Expand Down
Loading
Loading