Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ 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 CanvasSelectComponent from './CanvasSelectComponent';
import ModeSelectRow from './ModeSelectRow';

interface IColorRampProps {
modeOptions: string[];
modeOptions: ClassificationMode[];
layerParams: IDict;
classifyFunc: (
selectedMode: string,
numberOfShades: string,
selectedMode: ClassificationMode | undefined,
numberOfShades: number | undefined,
selectedRamp: string,
setIsLoading: (isLoading: boolean) => void,
) => void;
Expand All @@ -21,8 +22,8 @@ interface IColorRampProps {

export type ColorRampOptions = {
selectedRamp: string;
numberOfShades: string;
selectedMode: string;
numberOfShades: number | undefined;
selectedMode: ClassificationMode | undefined;
};

const ColorRamp: React.FC<IColorRampProps> = ({
Expand All @@ -33,12 +34,14 @@ const ColorRamp: React.FC<IColorRampProps> = ({
showRampSelector,
}) => {
const [selectedRamp, setSelectedRamp] = useState('');
const [selectedMode, setSelectedMode] = useState('');
const [numberOfShades, setNumberOfShades] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [selectedMode, setSelectedMode] = useState<
ClassificationMode | undefined
>();
const [numberOfShades, setNumberOfShades] = useState<number | undefined>();
const [isLoading, setIsLoading] = useState<boolean>(false);

useEffect(() => {
if (selectedRamp === '' && selectedMode === '' && numberOfShades === '') {
if (selectedRamp === '') {
Copy link
Member

Choose a reason for hiding this comment

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

Should we still check if selectedMode and numberofShades are undefined here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure we can check if selectedMode and numberofShades are undefined here as it is More defensive ensures all three are unset before populating.

populateOptions();
}
}, [layerParams]);
Expand All @@ -52,7 +55,7 @@ const ColorRamp: React.FC<IColorRampProps> = ({
colorRamp = layerParams.symbologyState.colorRamp;
}
setNumberOfShades(nClasses ? nClasses : '9');
setSelectedMode(singleBandMode ? singleBandMode : 'equal interval');
setSelectedMode((singleBandMode as ClassificationMode) ?? 'equal interval');
setSelectedRamp(colorRamp ? colorRamp : 'viridis');
};

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 | undefined;
setNumberOfShades: (value: number | undefined) => void;
selectedMode: ClassificationMode | undefined;
setSelectedMode: (value: ClassificationMode | undefined) => void;
modeOptions: ClassificationMode[];
}
const ModeSelectRow: React.FC<IModeSelectRowProps> = ({
numberOfShades,
Expand All @@ -21,8 +23,11 @@ const ModeSelectRow: React.FC<IModeSelectRowProps> = ({
className="jp-mod-styled"
name="class-number-input"
type="number"
value={selectedMode === 'continuous' ? 52 : numberOfShades}
onChange={event => setNumberOfShades(event.target.value)}
value={selectedMode === 'continuous' ? 52 : (numberOfShades ?? '')}
onChange={event => {
const value = event.target.value;
setNumberOfShades(value === '' ? undefined : Number(value));
}}
disabled={selectedMode === 'continuous'}
/>
</div>
Expand All @@ -34,10 +39,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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ 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';

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

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

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

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

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

const buildColorInfoFromClassification = async (
selectedMode: string,
numberOfShades: string,
selectedMode: ClassificationMode | undefined,
numberOfShades: number | undefined,
selectedRamp: string,
setIsLoading: (isLoading: boolean) => void,
) => {
Expand All @@ -299,6 +304,10 @@ const SingleBandPseudoColor: React.FC<ISymbologyDialogProps> = ({
const currentBand = bandRows[selectedBand - 1];
const source = model.getSource(layer?.parameters?.source);
const sourceInfo = source?.parameters?.urls[0];
if (!numberOfShades) {
console.warn('No number of shades provided');
return;
}
const nClasses = selectedMode === 'continuous' ? 52 : +numberOfShades;

setIsLoading(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ 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';

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

const buildColorInfoFromClassification = (
selectedMode: string,
numberOfShades: string,
selectedMode: ClassificationMode | undefined,
numberOfShades: number | undefined,
selectedRamp: string,
setIsLoading: (isLoading: boolean) => void,
) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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';

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

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

const buildColorInfoFromClassification = (
selectedMode: string,
numberOfShades: string,
selectedMode: ClassificationMode | undefined,
numberOfShades: number | undefined,
Copy link
Member

@mfisher87 mfisher87 Oct 21, 2025

Choose a reason for hiding this comment

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

Suggested change
selectedMode: ClassificationMode | undefined,
numberOfShades: number | undefined,
selectedMode: ClassificationMode,
numberOfShades: number,

Let's make these required and check that these are defined before allowing the user to trigger this function to be called (e.g. don't allow the "Classify" button to be clicked unless these are defined). Then we can remove the casts below!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

safely done

selectedRamp: string,
) => {
setColorRampOptions({
Expand All @@ -229,6 +230,11 @@ const Graduated: React.FC<ISymbologyTabbedDialogWithAttributesProps> = ({

const values = Array.from(selectableAttributesAndValues[selectedAttribute]);

if (!numberOfShades) {
console.warn('No number of shades provided');
return;
}

switch (selectedMode) {
case 'quantile':
stops = VectorClassifications.calculateQuantileBreaks(
Expand Down
11 changes: 11 additions & 0 deletions packages/base/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,14 @@ declare global {
jupytergisMaps: { [name: string]: Map };
}
}

const classificationModes = [
'quantile',
'equal interval',
'jenks',
'pretty',
'logarithmic',
'continuous',
] as const;

export type ClassificationMode = (typeof classificationModes)[number];
Loading