diff --git a/src/data/selectors/index.js b/src/data/selectors/index.js index 4bfb776c..bbb9fa09 100644 --- a/src/data/selectors/index.js +++ b/src/data/selectors/index.js @@ -43,6 +43,7 @@ import { adjustArcPath, constructFetchedGeoJson, constructGeoJson, + ALLOWED_RANGE_KEYS, } from '../../utils' const workerManager = new ThreadMaxWorkers() @@ -1180,6 +1181,42 @@ export const selectMergedGeos = createSelector( [selectLocalizedGeoTypes, selectCurrentTime], (geos, time) => getMergedAllProps(getTimeValue(time, geos), 'geo') ) + +const selectEffectiveMapFeaturesBy = createSelector( + selectLegendTypesFn, + (legendObjectsFunc) => + R.curry((mapId, layerKey, featuresByType, type) => { + const legendFeatures = legendObjectsFunc({ mapId, layerKey }) + const features = R.propOr([], type)(featuresByType) + const effectiveMapFeatures = R.map( + // R.over(R.lensProp('props'), R.mergeDeepLeft(legendFeatures[type].props)) + R.mergeDeepLeft(legendFeatures[type]) + )(features) + return effectiveMapFeatures + }) +) +export const selectEffectiveArcsBy = createSelector( + [selectEffectiveMapFeaturesBy, selectMergedArcs], + (effectiveMapFeaturesBy, arcsByType) => + R.curry((type, mapId) => + effectiveMapFeaturesBy(mapId, 'arc', arcsByType, type) + ) +) +export const selectEffectiveNodesBy = createSelector( + [selectEffectiveMapFeaturesBy, selectMergedNodes], + (effectiveMapFeaturesBy, nodesByType) => + R.curry((type, mapId) => + effectiveMapFeaturesBy(mapId, 'node', nodesByType, type) + ) +) +export const selectEffectiveGeosBy = createSelector( + [selectEffectiveMapFeaturesBy, selectMergedGeos], + (effectiveMapFeaturesBy, geosByType) => + R.curry((type, mapId) => + effectiveMapFeaturesBy(mapId, 'geo', geosByType, type) + ) +) + // Map (Custom) export const selectLayerById = (state, id) => R.path(['local', 'map', 'mapLayers', id])(state) @@ -1624,14 +1661,14 @@ export const selectMultiLineDataFunc = createSelector( ) ) export const selectArcRange = createSelector( - [selectMergedArcs, selectLegendTypesFn], - (arcsByType, legendObjectsFunc) => + selectEffectiveArcsBy, + (effectiveArcsBy) => R.memoizeWith( - (type, prop, mapId, dimensionOptions) => - JSON.stringify([type, prop, dimensionOptions, mapId]), - (type, prop, mapId, dimensionOptions) => - R.pipe( - R.path([type, dimensionOptions, prop]), + (type, prop, mapId) => JSON.stringify([type, prop, mapId]), + (type, prop, mapId) => { + const effectiveArcs = effectiveArcsBy(type, mapId) + return R.pipe( + R.path([0, 'props', prop]), // Picking a specific index (in this case 0) is irrelevant as the elements of the array only vary by value and the props should remain the same R.when( (range) => R.isEmpty(range) || @@ -1646,11 +1683,13 @@ export const selectArcRange = createSelector( min: R.min(acc.min, R.path(['values', prop], value)), }), { min: Infinity, max: -Infinity } - )(R.propOr([], type, arcsByType)) + )(effectiveArcs) ) ), + R.unless(R.isNil, R.pick(ALLOWED_RANGE_KEYS)), R.unless(checkValidRange, R.always({ min: 0, max: 0 })) - )(legendObjectsFunc({ mapId, layerKey: 'arc' })) + )(effectiveArcs) + } ) ) export const selectGroupedEnabledGeosFunc = createSelector( @@ -1768,14 +1807,14 @@ export const selectGroupedNodesWithIdFunc = createSelector( ) export const selectNodeRange = createSelector( - [selectLegendTypesFn, selectMergedNodes], - (legendObjectsFunc, nodesByType) => + selectEffectiveNodesBy, + (effectiveNodesBy) => R.memoizeWith( - (type, prop, mapId, dimensionOptions) => - JSON.stringify([type, prop, dimensionOptions, mapId]), - (type, prop, mapId, dimensionOptions) => - R.pipe( - R.path([type, dimensionOptions, prop]), + (type, prop, mapId) => JSON.stringify([type, prop, mapId]), + (type, prop, mapId) => { + const effectiveNodes = effectiveNodesBy(type, mapId) + return R.pipe( + R.path([0, 'props', prop]), R.when( (range) => R.isEmpty(range) || @@ -1789,22 +1828,24 @@ export const selectNodeRange = createSelector( min: R.min(acc.min, R.path(['values', prop], value)), }), { min: Infinity, max: -Infinity } - )(R.propOr([], type, nodesByType)) + )(effectiveNodes) ) ), + R.unless(R.isNil, R.pick(ALLOWED_RANGE_KEYS)), R.unless(checkValidRange, R.always({ min: 0, max: 0 })) - )(legendObjectsFunc({ mapId, layerKey: 'node' })) + )(effectiveNodes) + } ) ) export const selectGeoRange = createSelector( - [selectLegendTypesFn, selectMergedGeos], - (legendObjectsFunc, geosByType) => + [selectEffectiveGeosBy], + (effectiveGeosBy) => R.memoizeWith( - (type, prop, mapId, dimensionOptions) => - JSON.stringify([type, prop, dimensionOptions, mapId]), - (type, prop, mapId, dimensionOptions) => - R.pipe( - R.path([type, dimensionOptions, prop]), + (type, prop, mapId) => JSON.stringify([type, prop, mapId]), + (type, prop, mapId) => { + const effectiveGeos = effectiveGeosBy(type, mapId) + return R.pipe( + R.path([0, 'props', prop]), R.when( (range) => R.isEmpty(range) || @@ -1818,11 +1859,13 @@ export const selectGeoRange = createSelector( min: R.min(acc.min, R.path(['values', prop], value)), }), { min: Infinity, max: -Infinity } - )(R.propOr([], type, geosByType)) + )(effectiveGeos) ) ), + R.unless(R.isNil, R.pick(ALLOWED_RANGE_KEYS)), R.unless(checkValidRange, R.always({ min: 0, max: 0 })) - )(legendObjectsFunc({ mapId, layerKey: 'geo' })) + )(effectiveGeos) + } ) ) @@ -1841,12 +1884,7 @@ export const selectLineMatchingKeysByTypeFunc = createSelector( [d.type, 'colorBy'], enabledArcsFunc(mapId) ) - const statRange = arcRange( - d.type, - colorProp, - mapId, - 'colorByOptions' - ) + const statRange = arcRange(d.type, colorProp, mapId) return !( R.has('nullColor', statRange) && R.isNil(R.prop('nullColor', statRange)) @@ -2112,49 +2150,44 @@ export const selectNodeGeoJsonObjectFunc = createSelector( } ) export const selectNodeClusterGeoJsonObjectFunc = createSelector( - [selectNodeClustersAtZoomFunc, selectEnabledNodesFunc], - (nodeClustersFunc, legendObjectsFunc) => + [ + selectNodeClustersAtZoomFunc, + selectEnabledNodesFunc, + selectEffectiveNodesBy, + ], + (nodeClustersFunc, legendObjectsFunc, effectiveNodesBy) => maxSizedMemoization( R.identity, (mapId) => R.map((group) => { const nodeType = group.properties.type const legendObj = legendObjectsFunc(mapId)[nodeType] + const effectiveNodes = effectiveNodesBy(nodeType, mapId)[0] const sizePropObj = R.path(['properties', 'sizeProp'], group) - const sizeProp = legendObj.sizeBy - const isSizeCategorical = !R.has('min')( - legendObj.sizeByOptions[sizeProp] - ) + + const sizeByProp = effectiveNodes.props[effectiveNodes.sizeBy] + const isSizeCategorical = !R.has('min')(sizeByProp) const sizeRange = isSizeCategorical - ? legendObj.sizeByOptions[sizeProp] + ? R.pluck('size')(sizeByProp.options) : nodeClustersFunc(mapId).range[group.properties.type].size const size = isSizeCategorical ? parseFloat(R.propOr('0', sizePropObj.value, sizeRange)) : getScaledValue( R.prop('min', sizeRange), R.prop('max', sizeRange), - parseFloat( - R.prop('startSize', legendObj.sizeByOptions[sizeProp]) - ), - parseFloat( - R.prop('endSize', legendObj.sizeByOptions[sizeProp]) - ), + parseFloat(R.prop('startSize', sizeByProp)), + parseFloat(R.prop('endSize', sizeByProp)), parseFloat(sizePropObj.value) ) - const colorProp = legendObj.colorBy + const colorByProp = effectiveNodes.props[effectiveNodes.colorBy] const colorObj = group.properties.colorProp const colorDomain = nodeClustersFunc(mapId).range[nodeType].color - const isColorCategorical = !R.has('min')( - legendObj.colorByOptions[colorProp] - ) + const isColorCategorical = !R.has('min')(colorByProp) const value = R.prop('value', colorObj) const colorRange = isColorCategorical - ? legendObj.colorByOptions[colorProp] - : R.map((prop) => legendObj.colorByOptions[colorProp][prop])([ - 'startGradientColor', - 'endGradientColor', - ]) + ? R.pluck('color')(colorByProp.options) + : R.props(['startGradientColor', 'endGradientColor'])(colorByProp) const color = isColorCategorical ? R.propOr('', value, colorRange) .replace(/[^\d,.]/g, '') diff --git a/src/ui/views/common/GridEditMultiSelectCell.js b/src/ui/views/common/GridEditMultiSelectCell.js index eb37ed84..f1d50fc6 100644 --- a/src/ui/views/common/GridEditMultiSelectCell.js +++ b/src/ui/views/common/GridEditMultiSelectCell.js @@ -25,7 +25,7 @@ const GridEditMultiSelectCell = ({ placeholder = 'Select values', value: defaultValue, options: optionsRaw, - colorByOptions = {}, + colorOptions = {}, readOnly, onChange, }) => { @@ -47,8 +47,8 @@ const GridEditMultiSelectCell = ({ R.applySpec({ main: R.identity, contrastText: R.unless(R.isNil, getContrastText), - })(colorByOptions[option]), - [colorByOptions] + })(colorOptions[option]), + [colorOptions] ) return ( diff --git a/src/ui/views/common/GridFilter.js b/src/ui/views/common/GridFilter.js index 96ef27a8..0a15b431 100644 --- a/src/ui/views/common/GridFilter.js +++ b/src/ui/views/common/GridFilter.js @@ -474,7 +474,7 @@ const GridFilter = ({ return ( @@ -512,7 +512,7 @@ const GridFilter = ({ R.always( { const { @@ -318,21 +316,33 @@ const CategoricalColorLegend = ({ showColorPicker, handleOpen, handleClose, - handleChange, + handleChange: handleChangeRaw, } = useColorPicker(onChangeColor) - const colorOptions = colorByOptions[colorBy] const getCategoryLabel = useCallback( (option) => { const label = - type === propId.SELECTOR - ? featureTypeProps[colorBy].options[option].name + type === propId.SELECTOR || type === propId.TOGGLE + ? colorByProps[colorBy].options[option].name : null return label || capitalize(option) }, - [colorBy, featureTypeProps, type] + [colorBy, colorByProps, type] + ) + const handleChange = useCallback( + (event, value) => { + handleChangeRaw(event, value, ['options', colorPickerProps.key, 'color']) + }, + [handleChangeRaw, colorPickerProps.key] + ) + const colorOptions = useMemo( + () => + Object.entries(colorByProps[colorBy].options).reduce( + (acc, [option, value]) => ({ ...acc, [option]: value.color }), + {} + ), + [colorBy, colorByProps] ) - return ( <> + Object.entries(featureTypeProps).reduce((acc, [propId, prop]) => { + const hasGradientColors = + 'startGradientColor' in prop && 'endGradientColor' in prop + const hasColorOptions = Object.values(prop.options || {}).some( + (value) => 'color' in value + ) + + if (hasGradientColors || hasColorOptions) { + return { ...acc, [propId]: prop } + } + return acc + }, {}), + [featureTypeProps] + ) return ( featureTypeProps[prop].name || prop} onSelect={onSelectProp} /> @@ -454,7 +480,7 @@ const ColorLegend = ({ {isCategorical ? ( ) : ( { @@ -671,20 +696,36 @@ const CategoricalSizeLegend = ({ handleOpen, handleClose, handleChange, - handleChangeComitted, + handleChangeComitted: handleChangeComittedRaw, } = useSizeSlider(onChangeSize) const getCategoryLabel = useCallback( (option) => { const label = - type === propId.SELECTOR - ? featureTypeProps[sizeBy].options[option].name + type === propId.SELECTOR || type === propId.TOGGLE + ? sizeByProps[sizeBy].options[option].name : null return label || capitalize(option) }, - [featureTypeProps, sizeBy, type] + [sizeBy, sizeByProps, type] ) - const sizeOptions = sizeByOptions[sizeBy] + const handleChangeComitted = useCallback( + (event, value) => { + handleChangeComittedRaw(event, value, [ + 'options', + sizeSliderProps.key, + 'size', + ]) + }, + [handleChangeComittedRaw, sizeSliderProps.key] + ) + + const sizeOptions = useMemo(() => { + return Object.entries(sizeByProps[sizeBy].options).reduce( + (acc, [option, value]) => ({ ...acc, [option]: value.size ?? '1px' }), + {} + ) + }, [sizeBy, sizeByProps]) return ( <> Object.keys(sizeByOptions), [sizeByOptions]) + // Find valid `sizeBy` props + const sizeByProps = useMemo( + () => + Object.entries(featureTypeProps).reduce((acc, [propId, prop]) => { + const hasSizeRange = 'startSize' in prop && 'endSize' in prop + const hasSizeOptions = Object.values(prop.options || {}).some( + (value) => 'size' in value + ) + if (hasSizeRange || hasSizeOptions) { + return { ...acc, [propId]: prop } + } + return acc + }, {}), + [featureTypeProps] + ) return ( featureTypeProps[option].name || option} onSelect={onSelectProp} /> @@ -774,7 +828,7 @@ const SizeLegend = ({ {isCategorical ? ( ) : ( getRange(id, colorBy, mapId, 'colorByOptions'), + () => getRange(id, colorBy, mapId), [colorBy, getRange, id, mapId] ) const sizeRange = useMemo( - () => getRange(id, sizeBy, mapId, 'sizeByOptions'), + () => getRange(id, sizeBy, mapId), [sizeBy, getRange, id, mapId] ) // Groupable nodes (only) @@ -1056,10 +1106,7 @@ const LegendRowDetails = ({ }, [getRangeOnZoom, mapId]) // Geos (only) const heightRange = useMemo( - () => - heightBy != null - ? getRange(id, heightBy, mapId, 'heightByOptions') - : null, + () => (heightBy != null ? getRange(id, heightBy, mapId) : null), [getRange, heightBy, id, mapId] ) @@ -1110,7 +1157,7 @@ const LegendRowDetails = ({ const handleChangeColor = useCallback( (pathEnd) => (value) => { - const path = [...basePath, 'colorByOptions', colorBy, pathEnd] + const path = [...basePath, 'props', colorBy, ...forceArray(pathEnd)] dispatch( mutateLocal({ path, @@ -1124,7 +1171,7 @@ const LegendRowDetails = ({ const handleChangeSize = useCallback( (pathEnd) => (value) => { - const path = [...basePath, 'sizeByOptions', sizeBy, pathEnd] + const path = [...basePath, 'props', sizeBy, ...forceArray(pathEnd)] dispatch( mutateLocal({ path, @@ -1286,7 +1333,6 @@ const LegendRowDetails = ({ mapId, group, colorBy, - colorByOptions, featureTypeProps, }} groupCalcValue={groupCalcByColor} @@ -1304,7 +1350,6 @@ const LegendRowDetails = ({ icon, group, sizeBy, - sizeByOptions, featureTypeProps, }} groupCalcValue={groupCalcBySize} @@ -1320,7 +1365,6 @@ const LegendRowDetails = ({ legendGroupId, mapId, heightBy, - heightByOptions, featureTypeProps, }} icon={} @@ -1335,8 +1379,9 @@ const LegendRowDetails = ({ ) } -const LegendRow = ({ id, featureTypeData, settingsMode, ...props }) => { - const name = getLabelFn(featureTypeData, id) +const LegendRow = ({ id, mapFeaturesBy, settingsMode, ...props }) => { + const featureTypeData = mapFeaturesBy(id, props.mapId)[0] + const name = featureTypeData.name ?? id return ( { {!settingsMode && ( @@ -1374,13 +1419,13 @@ const LegendRow = ({ id, featureTypeData, settingsMode, ...props }) => { const LegendRowNode = (props) => { const [options, setOptions] = useState([]) - const nodeTypes = useSelector(selectLocalizedNodeTypes) + const effectiveNodesBy = useSelector(selectEffectiveNodesBy) const getRange = useSelector(selectNodeRange) const iconUrl = useSelector(selectSettingsIconUrl) useIconDataLoader(iconUrl, setOptions, console.error) return ( { } const LegendRowArc = (props) => { - const arcTypes = useSelector(selectLocalizedArcTypes) + const effectiveArcsBy = useSelector(selectEffectiveArcsBy) const getRange = useSelector(selectArcRange) const indexedOptions = { solid: { icon: 'ai/AiOutlineLine', label: 'Solid' }, @@ -1406,7 +1451,7 @@ const LegendRowArc = (props) => { } return ( { } const LegendRowGeo = (props) => { - const geoTypes = useSelector(selectLocalizedGeoTypes) + const effectiveGeosBy = useSelector(selectEffectiveGeosBy) const getRange = useSelector(selectGeoRange) - return + return ( + + ) } const MapFeature = ({ diff --git a/src/ui/views/map/useColorPicker.js b/src/ui/views/map/useColorPicker.js index 2a962b2b..135cc107 100644 --- a/src/ui/views/map/useColorPicker.js +++ b/src/ui/views/map/useColorPicker.js @@ -5,9 +5,9 @@ const useColorPicker = (onChangeColor) => { const [colorPickerProps, setColorPickerProps] = useState({}) const handleChange = useCallback( - (value) => { + (value, colorOutputs, pathEnd = colorPickerProps.key) => { setColorPickerProps(R.assoc('value', value)) - onChangeColor(colorPickerProps.key)(value) + onChangeColor(pathEnd)(value) }, [colorPickerProps, onChangeColor] ) diff --git a/src/ui/views/map/useMapFilter.js b/src/ui/views/map/useMapFilter.js index 7a79fe76..d0cbea5d 100644 --- a/src/ui/views/map/useMapFilter.js +++ b/src/ui/views/map/useMapFilter.js @@ -16,7 +16,6 @@ import { includesPath } from '../../../utils' const useMapFilter = ({ mapId, group, - colorByOptions, filtersPath, featureTypeProps, filters, @@ -47,12 +46,16 @@ const useMapFilter = ({ R.cond([ [ R.propEq('selector', 'type'), - R.always({ colorByOptions: colorByOptions[key] }), + R.always({ + colorOptions: R.pluck('color')( + featureTypeProps[key]?.options ?? [] + ), + }), ], // Others if needed ])(value) )(filterableProps), - [colorByOptions, filterableProps] + [featureTypeProps, filterableProps] ) const numActiveFilters = useMemo( diff --git a/src/ui/views/map/useSizeSlider.js b/src/ui/views/map/useSizeSlider.js index ce47612a..3c825681 100644 --- a/src/ui/views/map/useSizeSlider.js +++ b/src/ui/views/map/useSizeSlider.js @@ -18,8 +18,8 @@ const useSizeSlider = (onChangeSize) => { }, []) const handleChangeComitted = useCallback( - (event, value) => { - onChangeSize(sizeSliderProps.key)(value[0]) + (event, value, pathEnd = sizeSliderProps.key) => { + onChangeSize(pathEnd)(value[0]) }, [onChangeSize, sizeSliderProps.key] ) diff --git a/src/utils/index.js b/src/utils/index.js index a5966fbd..4f1c0e3e 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -616,7 +616,7 @@ export const getQuartiles = R.ifElse( R.pipe(R.sort(R.comparator(R.lt)), getQuantiles(5)) ) -const allowedRangeKeys = [ +export const ALLOWED_RANGE_KEYS = [ 'startGradientColor', 'endGradientColor', 'nullColor', @@ -624,6 +624,11 @@ const allowedRangeKeys = [ 'timeValues', 'startSize', 'endSize', + 'startHeight', + 'endHeight', + 'min', + 'max', + 'options', ] // checks that range is either min/max or list of strings @@ -631,7 +636,7 @@ export const checkValidRange = R.pipe( R.mapObjIndexed((value, key) => key === 'min' || key === 'max' ? R.is(Number, value) - : R.includes(key, allowedRangeKeys) || R.is(String, value) + : R.includes(key, ALLOWED_RANGE_KEYS) || R.is(String, value) ), R.values, R.all(R.identity) @@ -814,12 +819,7 @@ export const constructFetchedGeoJson = ( } else if (!filterMapFeature(filters, geoObj)) return false const colorProp = R.path([geoObj.type, 'colorBy'], enabledItems) - const colorRange = itemRange( - geoObj.type, - colorProp, - mapId, - 'colorByOptions' - ) + const colorRange = itemRange(geoObj.type, colorProp, mapId) const isCategorical = !R.has('min', colorRange) const propVal = R.pipe( R.path(['values', colorProp]), @@ -827,16 +827,19 @@ export const constructFetchedGeoJson = ( (s) => s.toString() )(geoObj) - const nullColor = R.propOr( + const nullColor = R.pathOr( 'rgba(0,0,0,255)', - 'nullColor', + isCategorical ? ['options', 'nullColor', 'color'] : ['color'], colorRange ) - const color = R.equals('', propVal) ? nullColor : isCategorical - ? R.propOr('rgba(0,0,0,255)', propVal, colorRange) + ? R.pathOr( + 'rgba(0,0,0,255)', + ['options', propVal, 'color'], + colorRange + ) : `rgba(${getScaledArray( R.prop('min', colorRange), R.prop('max', colorRange), @@ -852,19 +855,13 @@ export const constructFetchedGeoJson = ( ), parseFloat(R.path(['values', colorProp], geoObj)) ).join(',')})` - const id = R.prop('data_key')(geoObj) const heightProp = R.path( [geoObj.type, 'heightBy'], enabledItems ) - const heightRange = itemRange( - geoObj.type, - heightProp, - mapId, - 'heightByOptions' - ) + const heightRange = itemRange(geoObj.type, heightProp, mapId) const heightPropVal = parseFloat( R.path(['values', heightProp], geoObj) @@ -896,12 +893,7 @@ export const constructFetchedGeoJson = ( }) const sizeProp = R.path([geoObj.type, 'sizeBy'], enabledItems) - const sizeRange = itemRange( - geoObj.type, - sizeProp, - mapId, - 'sizeByOptions' - ) + const sizeRange = itemRange(geoObj.type, sizeProp, mapId) const sizePropVal = parseFloat( R.path(['values', sizeProp], geoObj) ) @@ -978,19 +970,22 @@ export const constructGeoJson = ( R.when(R.isNil, R.always('')), (s) => s.toString() )(item) - const colorRange = itemRange( - item.type, - colorProp, - mapId, - 'colorByOptions' - ) + const colorRange = itemRange(item.type, colorProp, mapId) + const isColorCategorical = !R.has('min', colorRange) - const nullColor = R.propOr('rgba(0,0,0,255)', 'nullColor', colorRange) + const nullColor = R.pathOr( + 'rgba(0,0,0,255)', + isColorCategorical ? ['options', 'nullColor', 'color'] : ['color'], + colorRange + ) - const isColorCategorical = !R.has('min', colorRange) const color = isColorCategorical ? R.map((val) => parseFloat(val))( - R.propOr('rgba(0,0,0,255)', colorPropVal, colorRange) + R.pathOr( + 'rgba(0,0,0,255)', + ['options', colorPropVal, 'color'], + colorRange + ) .replace(/[^\d,.]/g, '') .split(',') ) @@ -1017,12 +1012,7 @@ export const constructGeoJson = ( if (type === 'node' || type === 'arc') { const sizeProp = legendObj.sizeBy - const sizeRange = itemRange( - item.type, - sizeProp, - mapId, - 'sizeByOptions' - ) + const sizeRange = itemRange(item.type, sizeProp, mapId) const sizePropVal = R.path(['values', sizeProp], item) const isSizeCategorical = !R.has('min', sizeRange) size = R.isNil(sizePropVal) @@ -1045,12 +1035,7 @@ export const constructGeoJson = ( if (type === 'geo' || type === 'arc') { const heightProp = legendObj.heightBy - const heightRange = itemRange( - item.type, - heightProp, - mapId, - 'heightByOptions' - ) + const heightRange = itemRange(item.type, heightProp, mapId) const heightPropVal = parseFloat( R.path(['values', heightProp], item)