diff --git a/src/ui/views/map/ColorLegend.js b/src/ui/views/map/ColorLegend.js index ff5e7ba9..5d1a0228 100644 --- a/src/ui/views/map/ColorLegend.js +++ b/src/ui/views/map/ColorLegend.js @@ -20,7 +20,7 @@ import { } from './Legend' import { selectNumberFormatPropsFn } from '../../../data/selectors' -import { propId } from '../../../utils/enums' +import { propId, scaleId } from '../../../utils/enums' import { useToggle } from '../../../utils/hooks' import { getScaledValueAlt } from '../../../utils/scales' import ColorPicker, { useColorPicker } from '../../compound/ColorPicker' @@ -115,22 +115,33 @@ const NumericalColorLegend = ({ [group, labels, numberFormat, values] ) + const isStepScale = useMemo( + () => valueRange.colorGradient?.scale === scaleId.STEP, + [valueRange.colorGradient?.scale] + ) + const getColorLabel = useCallback( (index) => - index < 1 - ? 'Min' - : index < values.length - 1 - ? `"${getLabel(index)}"` - : 'Max', - [getLabel, values.length] + index > 0 && index < values.length - 1 // Within the bounds + ? isStepScale + ? `[${values[index - 1]}, ${values[index]}) "${getLabel(index)}"` + : `"${getLabel(index)}"` + : isStepScale + ? `${index < 1 ? `(-\u221E, ${values[0]})` : `[${values[index]}, \u221E)`}` + : `${index < 1 ? 'Min' : 'Max'}`, + [getLabel, isStepScale, values] ) const getValueLabel = useCallback( (index) => - index > 0 && index < values.length - 1 - ? `Value \u279D "${getLabel(index)}"` - : `${index < 1 ? 'Min' : 'Max'} (Read-Only)`, - [getLabel, values.length] + index > 0 && index < values.length - 1 // Within the bounds + ? isStepScale + ? `Threshold \u279D [${values[index - 1]}, \u2B07)${labels[index] != null ? ` "${getLabel(index)}"` : ''}` + : `Value${labels[index] != null ? ` \u279D "${getLabel(index)}"` : ''}` + : isStepScale + ? `Threshold (Read-Only) \u279D ${index < 1 ? `(-\u221E, ${values[0]})` : `[${values[index]}, \u221E)`}` + : `Value (Read-Only) \u279D ${index < 1 ? 'Min' : 'Max'}`, + [getLabel, isStepScale, labels, values] ) const gradientStyle = useMemo(() => { @@ -140,19 +151,28 @@ const NumericalColorLegend = ({ max: maxValue, } = valueRange - const gradientColors = R.zipWith((color, value) => { - const scaledValue = getScaledValueAlt( + const scaledValues = R.map((value) => + getScaledValueAlt( [minValue, maxValue], [0, 100], value, - scale, + isStepScale ? scaleId.LINEAR : scale, scaleParams ) - return `${color} ${scaledValue}%` - }, colors)(values) + )(values) + + const gradientColors = R.addIndex(R.zipWith)( + (color, scaledValue, idx) => + !isStepScale + ? `${color} ${scaledValue}%` + : idx > 0 + ? `${color} ${scaledValues[idx - 1]}% ${scaledValue}%` + : `${color} 1%`, + colors + )(scaledValues) return styles.getGradient(gradientColors.join(', ')) - }, [colors, valueRange, values]) + }, [colors, isStepScale, valueRange, values]) const handleChangeColorByIndex = useCallback( (index) => (value, colorOutputs) => { diff --git a/src/ui/views/map/Legend.js b/src/ui/views/map/Legend.js index 86e3dd3d..d803cda9 100644 --- a/src/ui/views/map/Legend.js +++ b/src/ui/views/map/Legend.js @@ -16,11 +16,6 @@ import * as R from 'ramda' import { useCallback, useMemo, useState } from 'react' import { LuShapes } from 'react-icons/lu' import { MdOutlineEdit } from 'react-icons/md' -import { - PiArrowBendRightUp, - PiArrowBendUpRight, - PiArrowUpRight, -} from 'react-icons/pi' import { RiSettings5Line } from 'react-icons/ri' import { TbLogicAnd, TbMathFunction } from 'react-icons/tb' import { useDispatch, useSelector } from 'react-redux' @@ -54,9 +49,9 @@ import { useToggle, } from '../../../utils/hooks' import { - getScaleLabel, getScaleParamDefaults, getScaleParamLabel, + scaleIndexedOptions, } from '../../../utils/scales' import { getStatFuncsByType, getStatLabel } from '../../../utils/stats' import { EnhancedListbox, useIconDataLoader } from '../../compound/ShapePicker' @@ -361,15 +356,7 @@ export const ScaleSelector = ({ )(scaleId), [minDomainValue] ) - const scaleParamId = scaleParamsById[scale] - const IconClass = - scale === scaleId.LINEAR - ? PiArrowUpRight - : scale === scaleId.LOG - ? PiArrowBendUpRight - : PiArrowBendRightUp - return ( @@ -378,18 +365,21 @@ export const ScaleSelector = ({ id="scale-fn" labelId="scale-fn-label" label="Gradient Scale Func." - getLabel={getScaleLabel} optionsList={validScales} startAdornment={ - + } value={scale} + getLabel={(option) => scaleIndexedOptions[option]?.label} {...{ onSelect }} /> - {scale !== scaleId.LINEAR && ( + {scale !== scaleId.LINEAR && scale !== scaleId.STEP && ( } domain - The input domain as an array of numbers [min, max]. * @param {Array} range - The output range corresponding to the domain (e.g., [start, end]). * @param {number} value - The input value to scale. - * @param {string} [scale='linear'] - The type of scale to apply. Supported values: 'linear', 'pow', 'log'. + * @param {string} [scale='linear'] - The type of scale to apply. Supported values: 'linear', 'pow', 'log', 'step'. * @param {number|{}} [scaleParams={}] - An optional parameter for 'pow' and 'log' scales (exponent for 'pow', base for 'log'). * @param {any} [fallback=null] - The fallback value to return if the input is invalid or unknown. * @returns {number|any} - The scaled value within the range, or the fallback if the input is invalid. @@ -47,23 +47,21 @@ export const getScaledValueAlt = R.curry( const scaleBuilder = scale === scaleId.LINEAR ? scaleLinear() - : scale === scaleId.POW - ? scalePow().exponent( - scaleParams.exponent || - getScaleParamDefaults(scaleParamId.EXPONENT) - ) - : scale === scaleId.LOG - ? scaleLog().base( - scaleParams.base || getScaleParamDefaults(scaleParamId.BASE) + : scale === scaleId.STEP + ? scaleThreshold() + : scale === scaleId.POW + ? scalePow().exponent( + scaleParams.exponent || + getScaleParamDefaults(scaleParamId.EXPONENT) ) - : () => { - throw new Error(`Invalid scale "${scale}"`) - } - const scaleFunc = scaleBuilder - .domain(domain) - .range(range) - .clamp(true) - .unknown(fallback) + : scale === scaleId.LOG + ? scaleLog().base( + scaleParams.base || getScaleParamDefaults(scaleParamId.BASE) + ) + : () => { + throw new Error(`Invalid scale "${scale}"`) + } + const scaleFunc = scaleBuilder.domain(domain).range(range).unknown(fallback) return scaleFunc(value) // Return the scaled value or the fallback } )