Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -22,57 +22,64 @@ import { Button } from '@jupyterlab/ui-components';
import React, { useEffect, useState } from 'react';

import { LoadingIcon } from '@/src/shared/components/loading';
import { ClassificationMode } from '@/src/types';
import ColorRampSelector from './ColorRampSelector';
import ModeSelectRow from './ModeSelectRow';
import { COLOR_RAMP_DEFAULTS, ColorRampName } from '../../colorRampUtils';

interface IColorRampControlsProps {
modeOptions: string[];
modeOptions: ClassificationMode[];
layerParams: IDict;
classifyFunc: (
selectedMode: string,
numberOfShades: string,
selectedRamp: string,
selectedMode: ClassificationMode,
numberOfShades: number,
selectedRamp: ColorRampName,
setIsLoading: (isLoading: boolean) => void,
) => void;
showModeRow: boolean;
showRampSelector: boolean;
}

export type ColorRampControlsOptions = {
selectedRamp: string;
numberOfShades: string;
selectedMode: string;
selectedRamp: ColorRampName;
numberOfShades: number;
selectedMode: ClassificationMode;
};

const isValidNumberOfShades = (value: number) => !isNaN(value) && value > 0;

const ColorRampControls: React.FC<IColorRampControlsProps> = ({
layerParams,
modeOptions,
classifyFunc,
showModeRow,
showRampSelector,
}) => {
const [selectedRamp, setSelectedRamp] = useState('');
const [selectedMode, setSelectedMode] = useState('');
const [numberOfShades, setNumberOfShades] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedRamp, setSelectedRamp] = useState<ColorRampName>('viridis');
const [selectedMode, setSelectedMode] =
useState<ClassificationMode>('equal interval');
const [numberOfShades, setNumberOfShades] = useState<number>(9);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [warning, setWarning] = useState<string | null>(null);

useEffect(() => {
if (selectedRamp === '' && selectedMode === '' && numberOfShades === '') {
if (layerParams.symbologyState) {
populateOptions();
}
}, [layerParams]);
}, [
layerParams.symbologyState.nClasses,
layerParams.symbologyState.mode,
layerParams.symbologyState.colorRamp,
]);

useEffect(() => {
if (!selectedRamp) {
return;
}

const defaultClasses =
COLOR_RAMP_DEFAULTS[selectedRamp as ColorRampName] ?? 9;
const defaultClasses = COLOR_RAMP_DEFAULTS[selectedRamp] ?? 9;

setNumberOfShades(defaultClasses.toString());
setNumberOfShades(defaultClasses);
setWarning(null);
}, [selectedRamp]);

Expand All @@ -81,8 +88,8 @@ const ColorRampControls: React.FC<IColorRampControlsProps> = ({
return;
}

const minRequired = COLOR_RAMP_DEFAULTS[selectedRamp as ColorRampName];
const shades = parseInt(numberOfShades, 10);
const minRequired = COLOR_RAMP_DEFAULTS[selectedRamp];
const shades = numberOfShades;
const rampLabel = selectedRamp
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
Expand All @@ -97,20 +104,10 @@ const ColorRampControls: React.FC<IColorRampControlsProps> = ({
}
}, [selectedRamp, numberOfShades]);
const populateOptions = () => {
let nClasses, singleBandMode, colorRamp;

if (layerParams.symbologyState) {
nClasses = layerParams.symbologyState.nClasses;
singleBandMode = layerParams.symbologyState.mode;
colorRamp = layerParams.symbologyState.colorRamp;
}
const defaultRamp = colorRamp ? colorRamp : 'viridis';
const defaultClasses =
nClasses ?? COLOR_RAMP_DEFAULTS[defaultRamp as ColorRampName] ?? 9;

setNumberOfShades(defaultClasses.toString());
setSelectedMode(singleBandMode ? singleBandMode : 'equal interval');
setSelectedRamp(defaultRamp);
const { nClasses, mode, colorRamp } = layerParams.symbologyState ?? {};
setNumberOfShades(Number(nClasses ?? 9));
setSelectedMode((mode as ClassificationMode) ?? 'equal interval');
setSelectedRamp((colorRamp as ColorRampName) ?? 'viridis');
};

return (
Expand Down Expand Up @@ -146,7 +143,9 @@ const ColorRampControls: React.FC<IColorRampControlsProps> = ({
) : (
<Button
className="jp-Dialog-button jp-mod-accept jp-mod-styled"
disabled={!!warning}
disabled={
!isValidNumberOfShades(numberOfShades) || !selectedMode || !!warning
}
onClick={() =>
classifyFunc(
selectedMode,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,15 @@
import { Button } from '@jupyterlab/ui-components';
import React, { useEffect, useRef, useState } from 'react';

import { useColorMapList } from '@/src/dialogs/symbology/colorRampUtils';
import {
ColorRampName,
useColorMapList,
IColorMap,
} from '@/src/dialogs/symbology/colorRampUtils';
import ColorRampSelectorEntry from './ColorRampSelectorEntry';

export interface IColorMap {
name: string;
colors: string[];
}

interface IColorRampSelectorProps {
selectedRamp: string;
selectedRamp: ColorRampName;
Comment on lines -26 to +25
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think with this change. I think using ColorRampName instead of only type string is a good option.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💯

setSelected: (item: any) => void;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

import React, { useEffect } from 'react';

import { IColorMap } from './ColorRampSelector';
import { IColorMap } from '@/src/dialogs/symbology/colorRampUtils';

interface IColorRampSelectorEntryProps {
index: number;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React from 'react';

import { ClassificationMode } from '@/src/types';
interface IModeSelectRowProps {
numberOfShades: string;
setNumberOfShades: (value: string) => void;
selectedMode: string;
setSelectedMode: (value: string) => void;
modeOptions: string[];
numberOfShades: number;
setNumberOfShades: React.Dispatch<React.SetStateAction<number>>;
selectedMode: ClassificationMode;
setSelectedMode: React.Dispatch<React.SetStateAction<ClassificationMode>>;
modeOptions: ClassificationMode[];
}
const ModeSelectRow: React.FC<IModeSelectRowProps> = ({
numberOfShades,
Expand All @@ -22,7 +24,12 @@ const ModeSelectRow: React.FC<IModeSelectRowProps> = ({
name="class-number-input"
type="number"
value={selectedMode === 'continuous' ? 52 : numberOfShades}
onChange={event => setNumberOfShades(event.target.value)}
onChange={event => {
const value = Number(event.target.value);
if (!isNaN(value) && value > 0) {
setNumberOfShades(value);
}
}}
disabled={selectedMode === 'continuous'}
/>
</div>
Expand All @@ -34,10 +41,12 @@ const ModeSelectRow: React.FC<IModeSelectRowProps> = ({
id="mode-select"
className="jp-mod-styled"
value={selectedMode}
onChange={event => setSelectedMode(event.target.value)}
onChange={event =>
setSelectedMode(event.target.value as ClassificationMode)
}
>
{modeOptions.map(mode => (
<option key={mode} value={mode} selected={selectedMode === mode}>
<option key={mode} value={mode}>
{mode}
</option>
))}
Expand Down
3 changes: 2 additions & 1 deletion packages/base/src/dialogs/symbology/symbologyUtils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IJGISLayer } from '@jupytergis/schema';
import colormap from 'colormap';

import { ColorRampName } from './colorRampUtils';
import { IStopRow } from './symbologyDialog';

const COLOR_EXPR_STOPS_START = 3;
Expand Down Expand Up @@ -101,7 +102,7 @@ export namespace VectorUtils {
export namespace Utils {
export const getValueColorPairs = (
stops: number[],
selectedRamp: string,
selectedRamp: ColorRampName,
nClasses: number,
reverse = false,
) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import { Utils } from '@/src/dialogs/symbology/symbologyUtils';
import BandRow from '@/src/dialogs/symbology/tiff_layer/components/BandRow';
import { LoadingOverlay } from '@/src/shared/components/loading';
import { GlobalStateDbManager } from '@/src/store';
import { ClassificationMode } from '@/src/types';
import { ColorRampName } from '../../colorRampUtils';

export type InterpolationType = 'discrete' | 'linear' | 'exact';

Expand All @@ -38,7 +40,11 @@ const SingleBandPseudoColor: React.FC<ISymbologyDialogProps> = ({
}

const functions = ['discrete', 'linear', 'exact'];
const modeOptions = ['continuous', 'equal interval', 'quantile'];
const modeOptions = [
'continuous',
'equal interval',
'quantile',
] as const satisfies ClassificationMode[];

const stateDb = GlobalStateDbManager.getInstance().getStateDb();

Expand Down Expand Up @@ -282,9 +288,9 @@ const SingleBandPseudoColor: React.FC<ISymbologyDialogProps> = ({
};

const buildColorInfoFromClassification = async (
selectedMode: string,
numberOfShades: string,
selectedRamp: string,
selectedMode: ClassificationMode,
numberOfShades: number,
selectedRamp: ColorRampName,
setIsLoading: (isLoading: boolean) => void,
) => {
// Update layer state with selected options
Expand All @@ -299,7 +305,7 @@ const SingleBandPseudoColor: React.FC<ISymbologyDialogProps> = ({
const currentBand = bandRows[selectedBand - 1];
const source = model.getSource(layer?.parameters?.source);
const sourceInfo = source?.parameters?.urls[0];
const nClasses = selectedMode === 'continuous' ? 52 : +numberOfShades;
const nClasses = selectedMode === 'continuous' ? 52 : numberOfShades;

setIsLoading(true);
switch (selectedMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
} from '@/src/dialogs/symbology/symbologyDialog';
import { Utils, VectorUtils } from '@/src/dialogs/symbology/symbologyUtils';
import ValueSelect from '@/src/dialogs/symbology/vector_layer/components/ValueSelect';
import { SymbologyTab } from '@/src/types';
import { SymbologyTab, ClassificationMode } from '@/src/types';
import { ColorRampName } from '../../colorRampUtils';

const Categorized: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
model,
Expand Down Expand Up @@ -117,16 +118,16 @@ const Categorized: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
}, [selectedAttribute, stopRows, colorRampOptions]);

const buildColorInfoFromClassification = (
selectedMode: string,
numberOfShades: string,
selectedRamp: string,
selectedMode: ClassificationMode,
numberOfShades: number,
selectedRamp: ColorRampName,
setIsLoading: (isLoading: boolean) => void,
) => {
setColorRampOptions({
selectedFunction: '',
selectedRamp,
numberOfShades: '',
selectedMode: '',
numberOfShades,
selectedMode,
});

const stops = Array.from(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
} from '@/src/dialogs/symbology/symbologyDialog';
import { Utils, VectorUtils } from '@/src/dialogs/symbology/symbologyUtils';
import ValueSelect from '@/src/dialogs/symbology/vector_layer/components/ValueSelect';
import { ClassificationMode } from '@/src/types';
import { ColorRampName } from '../../colorRampUtils';

const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
model,
Expand All @@ -29,7 +31,7 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
'jenks',
'pretty',
'logarithmic',
];
] as const satisfies ClassificationMode[];

const selectableAttributeRef = useRef<string>();
const symbologyTabRef = useRef<string>();
Expand Down Expand Up @@ -206,9 +208,9 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
};

const buildColorInfoFromClassification = (
selectedMode: string,
numberOfShades: string,
selectedRamp: string,
selectedMode: ClassificationMode,
numberOfShades: number,
selectedRamp: ColorRampName,
) => {
setColorRampOptions({
selectedRamp,
Expand All @@ -224,31 +226,31 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
case 'quantile':
stops = VectorClassifications.calculateQuantileBreaks(
values,
+numberOfShades,
numberOfShades,
);
break;
case 'equal interval':
stops = VectorClassifications.calculateEqualIntervalBreaks(
values,
+numberOfShades,
numberOfShades,
);
break;
case 'jenks':
stops = VectorClassifications.calculateJenksBreaks(
values,
+numberOfShades,
numberOfShades,
);
break;
case 'pretty':
stops = VectorClassifications.calculatePrettyBreaks(
values,
+numberOfShades,
numberOfShades,
);
break;
case 'logarithmic':
stops = VectorClassifications.calculateLogarithmicBreaks(
values,
+numberOfShades,
numberOfShades,
);
break;
default:
Expand All @@ -262,7 +264,7 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({
: Utils.getValueColorPairs(
stops,
selectedRamp,
+numberOfShades,
numberOfShades,
reverseRamp,
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useEffect, useRef, useState } from 'react';

import ColorRampSelector from '@/src/dialogs/symbology/components/color_ramp/ColorRampSelector';
import { ISymbologyDialogProps } from '@/src/dialogs/symbology/symbologyDialog';
import { ColorRampName } from '../../colorRampUtils';

const Heatmap: React.FC<ISymbologyDialogProps> = ({
model,
Expand All @@ -18,7 +19,7 @@ const Heatmap: React.FC<ISymbologyDialogProps> = ({
if (!layer?.parameters) {
return;
}
const [selectedRamp, setSelectedRamp] = useState('');
const [selectedRamp, setSelectedRamp] = useState<ColorRampName>('viridis');
const [heatmapOptions, setHetamapOptions] = useState({
radius: 8,
blur: 15,
Expand Down
Loading
Loading