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

[fix] Custom Color Scale fixes #2875

Merged
merged 3 commits into from
Dec 31, 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
8 changes: 4 additions & 4 deletions src/components/src/hooks/use-legend-position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@
? {y: topOffset, anchorY: 'top'}
: {y: bottomOffset, anchorY: 'bottom'})
};
}, [isSidePanelShown]);

Check warning on line 86 in src/components/src/hooks/use-legend-position.ts

View workflow job for this annotation

GitHub Actions / build (18.x)

React Hook useCallback has missing dependencies: 'legendContentRef' and 'sidePanelWidth'. Either include them or remove the dependency array
const updatePosition = useCallback(() => onChangeSettings({position: calcPosition()}), [
calcPosition,
onChangeSettings
]);
const updatePosition = useCallback(
() => onChangeSettings({position: calcPosition()}),
[calcPosition, onChangeSettings]
);

const startResize = useCallback(() => {
const content = legendContentRef.current?.querySelector('.map-control__panel-content');
Expand Down
4 changes: 4 additions & 0 deletions src/components/src/map/map-legend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import React, {FC, useCallback, useState, ComponentType} from 'react';
import styled from 'styled-components';
import {rgb} from 'd3-color';
import {format as d3Format} from 'd3-format';
import {useIntl} from 'react-intl';
import ColorLegendFactory, {LegendRowFactory} from '../common/color-legend';
import RadiusLegend from '../common/radius-legend';
Expand Down Expand Up @@ -204,6 +205,9 @@ export function LayerColorLegendFactory(
disableEdit={disableEdit}
isFixed={isFixed}
mapState={mapState}
labelFormat={
colorField?.displayFormat ? d3Format(colorField?.displayFormat) : null
}
/>
) : (
<SingleColorLegend
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ function ColorBreaksPanelFactory(

return (
<ColorBreaksPanelWrapper>
{dataset ? (
{dataset && allBins.length > 1 ? (
<ColumnStatsChart
colorField={colorField}
dataset={dataset}
Expand Down
42 changes: 16 additions & 26 deletions src/components/src/side-panel/layer-panel/color-scale-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
getLayerColorScale,
getLegendOfScale,
histogramFromValues,
histogramFromOrdinal,
histogramFromThreshold,
getHistogramDomain,
hasColorMap
Expand Down Expand Up @@ -157,17 +156,15 @@ function ColorScaleSelectorFactory(
[range, domain, scaleType, layer]
);

const colorBreaks = useMemo(
() =>
colorScale
? getLegendOfScale({
scale: colorScale,
scaleType,
fieldType: field?.type ?? ALL_FIELD_TYPES.real
})
: null,
[colorScale, scaleType, field?.type]
);
const colorBreaks = useMemo(() => {
return colorScale
? getLegendOfScale({
scale: colorScale.byZoom && domain ? colorScale(domain?.length - 1) : colorScale,
scaleType,
fieldType: field?.type ?? ALL_FIELD_TYPES.real
})
: null;
}, [colorScale, scaleType, field?.type, domain]);

const columnStats = field?.filterProps?.columnStats;

Expand All @@ -188,10 +185,8 @@ function ColorScaleSelectorFactory(
}
return columnStats?.bins
? columnStats?.bins
: field?.type === ALL_FIELD_TYPES.string
? histogramFromOrdinal(colorScale?.domain() || [], dataset.allIndexes, fieldValueAccessor)
: histogramFromValues(dataset.allIndexes, HISTOGRAM_BINS, fieldValueAccessor);
}, [aggregatedBins, columnStats, dataset, fieldValueAccessor, colorScale, field?.type]);
}, [aggregatedBins, columnStats, dataset, fieldValueAccessor]);

const histogramDomain = useMemo(() => {
return getHistogramDomain({aggregatedBins, columnStats, dataset, fieldValueAccessor});
Expand Down Expand Up @@ -221,22 +216,17 @@ function ColorScaleSelectorFactory(
val => {
// highlight selected option
if (getOptionValue(val) === SCALE_TYPES.custom) {
const colorMap = range.colorMap
? range.colorMap
: colorBreaks
? colorBreaksToColorMap(colorBreaks)
: undefined;
const colors =
field?.type === ALL_FIELD_TYPES.string
? range.colors.slice(0, colorMap?.length)
: range.colors;
// update custom breaks
const customPalette: NestedPartial<ColorRange> = {
name: 'color.customPalette',
type: 'custom',
category: 'Custom',
colors,
colorMap
colors: range.colors,
colorMap: range.colorMap
? range.colorMap
: colorBreaks
? colorBreaksToColorMap(colorBreaks)
: null
};
setColorUI({
showColorChart: true,
Expand Down
14 changes: 1 addition & 13 deletions src/components/src/side-panel/layer-panel/custom-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -407,19 +407,7 @@ export const CustomPaletteInput: React.FC<CustomPaletteInputProps> = ({
editColorMap={editColorMapValue}
editable
/>
) : (
colorBreaks &&
colorBreaks[index] && (
<ColorPaletteInput
value={colorBreaks[index].label}
id={`color-palette-input-${index}-left`}
width="auto"
textAlign="end"
editable={false}
onChange={v => v}
/>
)
)}
) : null}
</div>
<div className="custom-palette-input__right">
{!disableAppend ? (
Expand Down
11 changes: 10 additions & 1 deletion src/constants/src/color-palettes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,14 @@ const FSQWarmTone = {
colorBlindSafe: true
};

const FSQCoolTone = {
name: 'FSQ Cool Tone',
type: PALETTE_TYPES.QUA,
category: CATEGORIES.COLORBLIND,
colors: ['#11439F', '#297EE8', '#95C6C9', '#FECE5A', '#FFDDBF', '#9FB1B7', '#5281B5', '#B9D0FB'],
colorBlindSafe: true
};

/**
* Build Categorical color palette
*/
Expand Down Expand Up @@ -572,7 +580,8 @@ const BRANDED_PALETTES: ColorPalette[] = [
TolLight,
OkabeIto,
FSQBrand,
FSQWarmTone
FSQWarmTone,
FSQCoolTone
]
.map(recipe =>
recipe.type === PALETTE_TYPES.QUA ? buildCategoricalPalette(recipe) : buildCustomPalette(recipe)
Expand Down
2 changes: 1 addition & 1 deletion src/constants/src/default-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ export const linearFieldAggrScaleFunctions = {
};

export const ordinalFieldScaleFunctions = {
[CHANNEL_SCALES.color]: [SCALE_TYPES.ordinal, SCALE_TYPES.custom],
[CHANNEL_SCALES.color]: [SCALE_TYPES.ordinal],
[CHANNEL_SCALES.radius]: [SCALE_TYPES.point],
[CHANNEL_SCALES.size]: [SCALE_TYPES.point]
};
Expand Down
39 changes: 19 additions & 20 deletions src/layers/src/base-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ import {
ValueOf,
VisualChannel,
VisualChannels,
VisualChannelDomain
VisualChannelDomain,
VisualChannelField,
VisualChannelScale
} from '@kepler.gl/types';
import {
getScaleFunction,
Expand All @@ -81,10 +83,15 @@ import {
getThresholdsFromQuantiles
} from '@kepler.gl/utils';

export type {LayerBaseConfig, VisualChannel, VisualChannels, VisualChannelDomain, AggregatedBin};

export type VisualChannelField = Field | null;
export type VisualChannelScale = keyof typeof SCALE_TYPES;
export type {
AggregatedBin,
LayerBaseConfig,
VisualChannel,
VisualChannels,
VisualChannelDomain,
VisualChannelField,
VisualChannelScale
};

export type LayerBaseConfigPartial = {dataId: LayerBaseConfig['dataId']} & Partial<LayerBaseConfig>;

Expand Down Expand Up @@ -194,7 +201,7 @@ class Layer implements KeplerLayer {
visConfigSettings: {
[key: string]: ValueOf<LayerVisConfigSettings>;
};
config: LayerBaseConfig;
config: LayerBaseConfig & Partial<LayerColorConfig & LayerSizeConfig>;
// TODO: define _oldDataUpdateTriggers
_oldDataUpdateTriggers: any;

Expand Down Expand Up @@ -789,9 +796,10 @@ class Layer implements KeplerLayer {
return columns;
}

updateLayerConfig<LayerConfig extends LayerBaseConfig = LayerBaseConfig>(
newConfig: Partial<LayerConfig>
): Layer {
updateLayerConfig<
LayerConfig extends LayerBaseConfig &
Partial<LayerColorConfig & LayerSizeConfig> = LayerBaseConfig
>(newConfig: Partial<LayerConfig>): Layer {
this.config = {...this.config, ...newConfig};
return this;
}
Expand Down Expand Up @@ -1022,22 +1030,13 @@ class Layer implements KeplerLayer {
colorDomain: VisualChannelDomain,
colorRange: ColorRange
): GetVisChannelScaleReturnType {
if (
hasColorMap(colorRange) &&
(colorScale === SCALE_TYPES.custom || colorScale === SCALE_TYPES.ordinal)
) {
if (hasColorMap(colorRange) && colorScale === SCALE_TYPES.custom) {
const cMap = new Map();
colorRange.colorMap?.forEach(([k, v]) => {
cMap.set(k, typeof v === 'string' ? hexToRgb(v) : v);
});

// in kepler, scaleThreshold function will be used for SCALE_TYPES.custom
// so we adjust scaleType to SCALE_TYPES.custom to ordinal if necessary
const isOrdinalDomain = (colorDomain as (string | number)[]).every(
i => typeof i === 'string'
);
const scaleType =
colorScale === SCALE_TYPES.custom && !isOrdinalDomain ? colorScale : SCALE_TYPES.ordinal;
const scaleType = colorScale === SCALE_TYPES.custom ? colorScale : SCALE_TYPES.ordinal;

const scale = getScaleFunction(scaleType, cMap.values(), cMap.keys(), false);
scale.unknown(cMap.get(UNKNOWN_COLOR_KEY) || NO_VALUE_COLOR);
Expand Down
1 change: 0 additions & 1 deletion src/layers/src/point-layer/point-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,6 @@ export default class PointLayer extends Layer {

if (defaultColorField) {
this.updateLayerConfig({
// @ts-expect-error Remove this after updateLayerConfig converted into generic function
colorField: defaultColorField
});
this.updateLayerVisualChannel(dataset, 'color');
Expand Down
21 changes: 19 additions & 2 deletions src/reducers/src/vis-state-updaters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -629,7 +629,7 @@ export function layerToggleVisibilityUpdater(
return toggleLayerForMapUpdater(newState, toggleLayerForMap(mapIndex, layerId));
} else {
// [case 2]: toggle global layer visibility
let newLayer = layer.updateLayerConfig({isVisible});
const newLayer = layer.updateLayerConfig({isVisible});
const idx = newState.layers.findIndex(l => l.id === layerId);

newState = updatelayerVisibilty(newState, newLayer, isVisible);
Expand Down Expand Up @@ -3242,7 +3242,24 @@ export function setColumnDisplayFormatUpdater(
});

const newDataset = copyTableAndUpdate(dataset, {fields: newFields as Field[]});
return pick_('datasets')(merge_({[dataId]: newDataset}))(state);
let newState = pick_('datasets')(merge_({[dataId]: newDataset}))(state);

// update colorField displayFormat
newState = {
...newState,
layers: newState.layers.map(layer =>
layer.config?.colorField?.name && layer.config.colorField.name in formats
? layer.updateLayerConfig({
colorField: {
...layer.config.colorField,
displayFormat: formats[layer.config.colorField.name]
}
})
: layer
)
};

return newState;
}

/**
Expand Down
4 changes: 1 addition & 3 deletions src/table/src/kepler-table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,9 +496,7 @@ class KeplerTable<F extends Field = Field> {
case SCALE_TYPES.linear:
case SCALE_TYPES.sqrt:
default:
return field.type === ALL_FIELD_TYPES.string
? getOrdinalDomain(dataContainer, valueAccessor)
: getLinearDomain(filteredIndexForDomain, indexValueAccessor);
return getLinearDomain(filteredIndexForDomain, indexValueAccessor);
}
}

Expand Down
5 changes: 4 additions & 1 deletion src/types/layers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ export type SupportedColumnMode = {
hasHelp?: boolean;
};

export type VisualChannelField = Field | null;
export type VisualChannelScale = keyof typeof SCALE_TYPES;

export type LayerColorConfig = {
colorField: VisualChannelField;
colorDomain: VisualChannelDomain;
Expand Down Expand Up @@ -185,7 +188,7 @@ export type ColorRange = {
category?: string;
colors: HexColor[];
reversed?: boolean;
colorMap?: ColorMap;
colorMap?: ColorMap | null;
colorLegends?: ColorLegends;
};

Expand Down
26 changes: 9 additions & 17 deletions src/utils/src/data-scale-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export type D3ScaleFunction = Record<string, any> & ((x: any) => any);
type FilterProps = any;
type KeplerTable = any;

export type LabelFormat = (n: number) => string;
export type LabelFormat = (n: number, type?: string) => string;
type dataValueAccessor = <T>(param: T) => T;
type dataContainerValueAccessor = (d: {index: number}, dc: DataContainerInterface) => any;
type sort = (a: any, b: any) => any;
Expand Down Expand Up @@ -230,14 +230,13 @@ export function getQuantLegends(scale: D3ScaleFunction, labelFormat: LabelFormat
if (typeof scale.invertExtent !== 'function') {
return [];
}
const thresholdLabelFormat = (n, type) =>
n && labelFormat ? labelFormat(n) : n ? formatNumber(n, type) : 'no value';
const labels =
scale.scaleType === 'threshold' || scale.scaleType === 'custom'
? getThresholdLabels(
scale,
scale.scaleType === 'custom'
? customScaleLabelFormat
: n => (n ? formatNumber(n) : 'no value')
)
scale.scaleType === 'threshold'
? getThresholdLabels(scale, thresholdLabelFormat)
: scale.scaleType === 'custom'
? getThresholdLabels(scale, customScaleLabelFormat)
: getScaleLabels(scale, labelFormat);

const data = scale.range();
Expand Down Expand Up @@ -295,7 +294,7 @@ export function getLegendOfScale({
if (!scale || scale.byZoom) {
return [];
}
if (scaleType === SCALE_TYPES.ordinal || fieldType === ALL_FIELD_TYPES.string) {
if (scaleType === SCALE_TYPES.ordinal) {
return getOrdinalLegends(scale);
}

Expand Down Expand Up @@ -391,18 +390,11 @@ export function colorBreaksToColorMap(colorBreaks: ColorBreak[] | ColorBreakOrdi
/**
* Convert colorRange.colorMap into color breaks UI input
*/
export function colorMapToColorBreaks(colorMap?: ColorMap): ColorBreak[] | null {
export function colorMapToColorBreaks(colorMap?: ColorMap | null): ColorBreak[] | null {
if (!colorMap) {
return null;
}
const colorBreaks = colorMap.map(([value, color], i) => {
if (typeof value === 'string') {
// for ordinal string value
return {
data: color,
label: value
};
}
const range =
i === 0
? // first
Expand Down
3 changes: 2 additions & 1 deletion src/utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ export {
getDomainStepsbyZoom,
getThresholdsFromQuantiles,
getQuantLabelFormat,
getHistogramDomain
getHistogramDomain,
getQuantLegends
} from './data-scale-utils';
export type {ColorBreak, ColorBreakOrdinal, DomainQuantiles, DomainStops} from './data-scale-utils';

Expand Down
Loading
Loading