Skip to content

Commit

Permalink
Add step function to scale options (#507)
Browse files Browse the repository at this point in the history
  • Loading branch information
luisvasq committed Dec 11, 2024
1 parent 31eba8a commit 25e2f7e
Show file tree
Hide file tree
Showing 4 changed files with 67 additions and 58 deletions.
54 changes: 37 additions & 17 deletions src/ui/views/map/ColorLegend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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(() => {
Expand All @@ -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) => {
Expand Down
24 changes: 7 additions & 17 deletions src/ui/views/map/Legend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<Stack direction="row" spacing={1}>
<FormControl fullWidth>
Expand All @@ -378,18 +365,21 @@ export const ScaleSelector = ({
id="scale-fn"
labelId="scale-fn-label"
label="Gradient Scale Func."
getLabel={getScaleLabel}
optionsList={validScales}
startAdornment={
<InputAdornment position="start">
<IconClass size={24} />
<FetchedIcon
iconName={scaleIndexedOptions[scale]?.iconName}
size={24}
/>
</InputAdornment>
}
value={scale}
getLabel={(option) => scaleIndexedOptions[option]?.label}
{...{ onSelect }}
/>
</FormControl>
{scale !== scaleId.LINEAR && (
{scale !== scaleId.LINEAR && scale !== scaleId.STEP && (
<NumberInput
sx={{ width: 'auto' }}
label={getScaleParamLabel(scaleParamId)}
Expand Down
1 change: 1 addition & 0 deletions src/utils/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export const statFuncs = {

export const scaleId = {
LINEAR: 'linear',
STEP: 'step',
LOG: 'log',
POW: 'pow',
}
Expand Down
46 changes: 22 additions & 24 deletions src/utils/scales.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { scaleLinear, scaleLog, scalePow } from 'd3-scale'
import { scaleLinear, scaleLog, scalePow, scaleThreshold } from 'd3-scale'
import * as R from 'ramda'

import { scaleId, scaleParamId } from './enums'

export const getScaleLabel = R.cond([
[R.equals(scaleId.LINEAR), R.always('Linear')],
[R.equals(scaleId.LOG), R.always('Logarithmic')],
[R.equals(scaleId.POW), R.always('Power')],
[R.T, R.always(null)],
])
export const scaleIndexedOptions = {
[scaleId.LINEAR]: { label: 'Linear', iconName: 'pi/PiArrowUpRight' },
[scaleId.STEP]: { label: 'Step', iconName: 'pi/PiSteps' },
[scaleId.LOG]: { label: 'Logarithmic', iconName: 'pi/PiArrowBendUpRight' },
[scaleId.POW]: { label: 'Power', iconName: 'pi/PiArrowBendRightUp' },
}

export const getScaleParamLabel = R.cond([
[R.equals(scaleParamId.BASE), R.always('Base')],
Expand All @@ -28,7 +28,7 @@ export const getScaleParamDefaults = R.cond([
* @param {Array<number>} domain - The input domain as an array of numbers [min, max].
* @param {Array<any>} 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.
Expand All @@ -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
}
)

0 comments on commit 25e2f7e

Please sign in to comment.