diff --git a/app/hooks/map/constants.tsx b/app/hooks/map/constants.tsx index e264d7869c..595b6ee6a1 100644 --- a/app/hooks/map/constants.tsx +++ b/app/hooks/map/constants.tsx @@ -252,7 +252,7 @@ export const LEGEND_LAYERS = { })); }, - 'features-preview-new': (options: { + 'binary-features': (options: { items: { id: string; name: string; color: string }[]; onChangeVisibility: (featureId: Feature['id']) => void; }) => { @@ -314,7 +314,7 @@ export const LEGEND_LAYERS = { }, }), // ANALYSIS - ['features-abundance']: (options: { + ['continuous-features']: (options: { items: { id: Feature['id']; amountRange: { min: number; max: number }; @@ -326,7 +326,7 @@ export const LEGEND_LAYERS = { const { items, onChangeVisibility } = options; return items?.map(({ id, name, amountRange, color }) => ({ - id: `feature-abundance-${id}`, + id, name, type: 'gradient' as LegendItemType, settingsManager: { diff --git a/app/hooks/map/index.ts b/app/hooks/map/index.ts index cd483bf043..32484156ce 100644 --- a/app/hooks/map/index.ts +++ b/app/hooks/map/index.ts @@ -770,7 +770,8 @@ export function usePUGridLayer({ const { visibility = true, opacity = 1, - amountRange = { min: 50000, max: 1000000 }, + amountRange, + color, } = restLayerSettings[featureId] || {}; return { @@ -804,10 +805,9 @@ export function usePUGridLayer({ ['linear'], ['var', 'amount'], amountRange.min, - 'white', // ! use COLORS.abundance.default instead when is available + COLORS.abundance.default, amountRange.max, - 'green', - // color, // ! enable the color variable when we receive it + color, ], ], 'fill-opacity': opacity, diff --git a/app/hooks/map/types.ts b/app/hooks/map/types.ts index dd73357bb3..ed3a762857 100644 --- a/app/hooks/map/types.ts +++ b/app/hooks/map/types.ts @@ -177,6 +177,7 @@ export interface UsePUGridLayer { min: number; max: number; }; + color?: string; }; }; }; diff --git a/app/layout/projects/show/map/index.tsx b/app/layout/projects/show/map/index.tsx index 24a369e10c..de52ed4f67 100644 --- a/app/layout/projects/show/map/index.tsx +++ b/app/layout/projects/show/map/index.tsx @@ -142,6 +142,7 @@ export const ProjectMap = (): JSX.Element => { sublayers: [ ...(sid1 && !sid2 ? ['frequency'] : []), ...(!!selectedCostSurface ? ['cost'] : []), + ...(!!selectedFeaturesIds.length ? ['features'] : []), ], options: { cost: { min: 1, max: 100 }, diff --git a/app/layout/projects/show/map/legend/hooks/index.ts b/app/layout/projects/show/map/legend/hooks/index.ts index 1c98b63e73..efed717765 100644 --- a/app/layout/projects/show/map/legend/hooks/index.ts +++ b/app/layout/projects/show/map/legend/hooks/index.ts @@ -116,7 +116,8 @@ export const useConservationAreasLegend = () => { }); }; -export const useFeatureAbundanceLegend = () => { +export const useFeaturesLegend = () => { + const { selectedFeatures } = useAppSelector((state) => state['/projects/[id]']); const { query } = useRouter(); const { pid } = query as { pid: string }; @@ -125,18 +126,42 @@ export const useFeatureAbundanceLegend = () => { pid, { sort: 'feature_class_name' }, { - select: ({ data }) => data, + select: ({ data }) => ({ + binaryFeatures: + data?.filter( + (feature) => + !Object.hasOwn(feature.amountRange, 'min') && + !Object.hasOwn(feature.amountRange, 'max') + ) || [], + continuousFeatures: + data?.filter( + (feature) => + Object.hasOwn(feature.amountRange, 'min') && Object.hasOwn(feature.amountRange, 'max') + ) || [], + }), } ); - const { layerSettings, selectedFeatures: visibleFeatures } = useAppSelector( - (state) => state['/projects/[id]'] - ); + const totalItems = + projectFeaturesQuery.data?.binaryFeatures.length + + projectFeaturesQuery.data?.continuousFeatures.length || 0; - const totalItems = projectFeaturesQuery.data?.length || 0; + const binaryFeaturesItems = + projectFeaturesQuery.data?.binaryFeatures?.map(({ id, featureClassName }, index) => { + const color = + totalItems > COLORS['features-preview'].ramp.length + ? chroma.scale(COLORS['features-preview'].ramp).colors(totalItems)[index] + : COLORS['features-preview'].ramp[index]; - const items = - projectFeaturesQuery.data?.map( + return { + id, + name: featureClassName, + color, + }; + }) || []; + + const continuousFeaturesItems = + projectFeaturesQuery.data?.continuousFeatures.map( ({ id, featureClassName, amountRange = { min: 5000, max: 100000 } }, index) => { const color = totalItems > COLORS['features-preview'].ramp.length @@ -152,91 +177,62 @@ export const useFeatureAbundanceLegend = () => { } ) || []; - return LEGEND_LAYERS['features-abundance']({ - items, - onChangeVisibility: (featureId: Feature['id']) => { - const { color, amountRange } = items.find(({ id }) => id === featureId) || {}; - - const newSelectedFeatures = [...visibleFeatures]; - const isIncluded = newSelectedFeatures.includes(featureId); - if (!isIncluded) { - newSelectedFeatures.push(featureId); - } else { - const i = newSelectedFeatures.indexOf(featureId); - newSelectedFeatures.splice(i, 1); - } - dispatch(setSelectedFeatures(newSelectedFeatures)); - - dispatch( - setLayerSettings({ - id: featureId, - settings: { - visibility: !layerSettings[featureId]?.visibility, - amountRange, - color, - }, - }) - ); - }, - }); -}; - -export const useFeaturesLegend = () => { - const { selectedFeatures } = useAppSelector((state) => state['/projects/[id]']); - const { query } = useRouter(); - const { pid } = query as { pid: string }; - - const dispatch = useAppDispatch(); - const projectFeaturesQuery = useAllFeatures( - pid, - { sort: 'feature_class_name' }, - { - select: ({ data }) => data, - } - ); - - const totalItems = projectFeaturesQuery.data?.length || 0; - - const items = - projectFeaturesQuery.data?.map(({ id, featureClassName }, index) => { - const color = - totalItems > COLORS['features-preview'].ramp.length - ? chroma.scale(COLORS['features-preview'].ramp).colors(totalItems)[index] - : COLORS['features-preview'].ramp[index]; - - return { - id, - name: featureClassName, - color, - }; - }) || []; - - return LEGEND_LAYERS['features-preview-new']({ - items, - onChangeVisibility: (featureId: Feature['id']) => { - const newSelectedFeatures = [...selectedFeatures]; - const isIncluded = newSelectedFeatures.includes(featureId); - if (!isIncluded) { - newSelectedFeatures.push(featureId); - } else { - const i = newSelectedFeatures.indexOf(featureId); - newSelectedFeatures.splice(i, 1); - } - dispatch(setSelectedFeatures(newSelectedFeatures)); + return [ + ...LEGEND_LAYERS['binary-features']({ + items: binaryFeaturesItems, + onChangeVisibility: (featureId: Feature['id']) => { + const newSelectedFeatures = [...selectedFeatures]; + const isIncluded = newSelectedFeatures.includes(featureId); + if (!isIncluded) { + newSelectedFeatures.push(featureId); + } else { + const i = newSelectedFeatures.indexOf(featureId); + newSelectedFeatures.splice(i, 1); + } + dispatch(setSelectedFeatures(newSelectedFeatures)); + + const { color } = binaryFeaturesItems.find(({ id }) => id === featureId) || {}; - const { color } = items.find(({ id }) => id === featureId) || {}; + dispatch( + setLayerSettings({ + id: featureId, + settings: { + visibility: !isIncluded, + color, + }, + }) + ); + }, + }), + ...LEGEND_LAYERS['continuous-features']({ + items: continuousFeaturesItems, + onChangeVisibility: (featureId: Feature['id']) => { + const { color, amountRange } = + continuousFeaturesItems.find(({ id }) => id === featureId) || {}; + + const newSelectedFeatures = [...selectedFeatures]; + const isIncluded = newSelectedFeatures.includes(featureId); + if (!isIncluded) { + newSelectedFeatures.push(featureId); + } else { + const i = newSelectedFeatures.indexOf(featureId); + newSelectedFeatures.splice(i, 1); + } + dispatch(setSelectedFeatures(newSelectedFeatures)); - dispatch( - setLayerSettings({ - id: featureId, - settings: { - visibility: !isIncluded, - color, - }, - }) - ); - }, - }); + dispatch( + setLayerSettings({ + id: featureId, + settings: { + visibility: !isIncluded, + amountRange, + color, + }, + }) + ); + }, + }), + ]; }; export const useComparisonScenariosLegend = ({ @@ -306,11 +302,7 @@ export const useInventoryLegend = ({ layers: useConservationAreasLegend(), }, { - name: 'Features (Continuous)', - layers: useFeatureAbundanceLegend(), - }, - { - name: 'Features (Binary)', + name: 'Features', layers: useFeaturesLegend(), }, ]; diff --git a/app/layout/scenarios/edit/map/component.tsx b/app/layout/scenarios/edit/map/component.tsx index 111e3a2c05..cdc0a24819 100644 --- a/app/layout/scenarios/edit/map/component.tsx +++ b/app/layout/scenarios/edit/map/component.tsx @@ -156,7 +156,7 @@ export const ScenariosEditMap = (): JSX.Element => { const sublayers = useMemo(() => { return [ ...(layerSettings['wdpa-percentage']?.visibility ? ['wdpa-percentage'] : []), - ...(layerSettings['features']?.visibility ? ['features'] : []), + ...(layerSettings['features']?.visibility || selectedFeatures?.length ? ['features'] : []), ...(preHighlightFeatures?.length || postHighlightFeatures?.length ? ['features'] : []), ...(selectedCostSurface ? ['cost'] : []), ...(layerSettings['lock-in']?.visibility ? ['lock-in'] : []), @@ -165,7 +165,13 @@ export const ScenariosEditMap = (): JSX.Element => { ...(layerSettings['frequency']?.visibility ? ['frequency'] : []), ...(layerSettings['solution']?.visibility ? ['solution'] : []), ]; - }, [layerSettings, selectedCostSurface, preHighlightFeatures, postHighlightFeatures]); + }, [ + layerSettings, + selectedCostSurface, + preHighlightFeatures, + postHighlightFeatures, + selectedFeatures, + ]); const featuresIds = useMemo(() => { if (allGapAnalysisData) { diff --git a/app/layout/scenarios/edit/map/legend/hooks/index.ts b/app/layout/scenarios/edit/map/legend/hooks/index.ts index a1b53329c5..51d8063178 100644 --- a/app/layout/scenarios/edit/map/legend/hooks/index.ts +++ b/app/layout/scenarios/edit/map/legend/hooks/index.ts @@ -7,7 +7,7 @@ import chroma from 'chroma-js'; import { orderBy, sortBy } from 'lodash'; import { useProjectCostSurfaces } from 'hooks/cost-surface'; -import { useSelectedFeatures } from 'hooks/features'; +import { useAllFeatures } from 'hooks/features'; import { useAllGapAnalysis } from 'hooks/gap-analysis'; import { COLORS, LEGEND_LAYERS } from 'hooks/map/constants'; import { useProject } from 'hooks/projects'; @@ -119,69 +119,42 @@ export const useConservationAreasLegend = () => { }); }; -export const useFeatureAbundanceLegend = () => { - const { query } = useRouter(); - const { sid } = query as { sid: string }; - - const dispatch = useAppDispatch(); - const { data: features } = useSelectedFeatures(sid); - const scenarioSlice = getScenarioEditSlice(sid); - const { setLayerSettings } = scenarioSlice.actions; - - const { layerSettings } = useAppSelector((state) => state[`/scenarios/${sid}/edit`]); - - const totalItems = features?.length || 0; - - const items = - features?.map(({ id, name, amountRange = { min: 5000, max: 100000 } }, index) => { - const color = - totalItems > COLORS['features-preview'].ramp.length - ? chroma.scale(COLORS['features-preview'].ramp).colors(totalItems)[index] - : COLORS['features-preview'].ramp[index]; - - return { - id, - name, - amountRange, - color, - }; - }) || []; - - return LEGEND_LAYERS['features-abundance']({ - items, - onChangeVisibility: (featureId: Feature['id']) => { - const { color, amountRange } = items.find(({ id }) => id === featureId) || {}; - - dispatch( - setLayerSettings({ - id: featureId, - settings: { - visibility: !layerSettings[featureId]?.visibility, - amountRange, - color, - }, - }) - ); - }, - }); -}; - export const useFeaturesLegend = () => { const { query } = useRouter(); - const { sid } = query as { sid: string }; - - const dispatch = useAppDispatch(); - const { data: features } = useSelectedFeatures(sid); + const { pid, sid } = query as { pid: string; sid: string }; const scenarioSlice = getScenarioEditSlice(sid); const { setSelectedFeatures, setLayerSettings } = scenarioSlice.actions; const { selectedFeatures }: { selectedFeatures: Feature['id'][] } = useAppSelector( (state) => state[`/scenarios/${sid}/edit`] ); - const totalItems = features?.length || 0; + const dispatch = useAppDispatch(); + const projectFeaturesQuery = useAllFeatures( + pid, + { sort: 'feature_class_name' }, + { + select: ({ data }) => ({ + binaryFeatures: + data?.filter( + (feature) => + !Object.hasOwn(feature.amountRange, 'min') && + !Object.hasOwn(feature.amountRange, 'max') + ) || [], + continuousFeatures: + data?.filter( + (feature) => + Object.hasOwn(feature.amountRange, 'min') && Object.hasOwn(feature.amountRange, 'max') + ) || [], + }), + } + ); - const items = - features?.map(({ id, name }, index) => { + const totalItems = + projectFeaturesQuery.data?.binaryFeatures.length + + projectFeaturesQuery.data?.continuousFeatures.length || 0; + + const binaryFeaturesItems = + projectFeaturesQuery.data?.binaryFeatures?.map(({ id, featureClassName }, index) => { const color = totalItems > COLORS['features-preview'].ramp.length ? chroma.scale(COLORS['features-preview'].ramp).colors(totalItems)[index] @@ -189,37 +162,84 @@ export const useFeaturesLegend = () => { return { id, - name, + name: featureClassName, color, }; }) || []; - return LEGEND_LAYERS['features-preview-new']({ - items, - onChangeVisibility: (featureId: Feature['id']) => { - const newSelectedFeatures = [...selectedFeatures]; - const isIncluded = newSelectedFeatures.includes(featureId); - if (!isIncluded) { - newSelectedFeatures.push(featureId); - } else { - const i = newSelectedFeatures.indexOf(featureId); - newSelectedFeatures.splice(i, 1); - } - dispatch(setSelectedFeatures(newSelectedFeatures)); + const continuousFeaturesItems = + projectFeaturesQuery.data?.continuousFeatures.map( + ({ id, featureClassName, amountRange = { min: 5000, max: 100000 } }, index) => { + const color = + totalItems > COLORS['features-preview'].ramp.length + ? chroma.scale(COLORS['features-preview'].ramp).colors(totalItems)[index] + : COLORS['features-preview'].ramp[index]; - const { color } = items.find(({ id }) => id === featureId) || {}; + return { + id, + name: featureClassName, + amountRange, + color, + }; + } + ) || []; - dispatch( - setLayerSettings({ - id: featureId, - settings: { - visibility: !isIncluded, - color, - }, - }) - ); - }, - }); + return [ + ...LEGEND_LAYERS['binary-features']({ + items: binaryFeaturesItems, + onChangeVisibility: (featureId: Feature['id']) => { + const newSelectedFeatures = [...selectedFeatures]; + const isIncluded = newSelectedFeatures.includes(featureId); + if (!isIncluded) { + newSelectedFeatures.push(featureId); + } else { + const i = newSelectedFeatures.indexOf(featureId); + newSelectedFeatures.splice(i, 1); + } + dispatch(setSelectedFeatures(newSelectedFeatures)); + + const { color } = binaryFeaturesItems.find(({ id }) => id === featureId) || {}; + + dispatch( + setLayerSettings({ + id: featureId, + settings: { + visibility: !isIncluded, + color, + }, + }) + ); + }, + }), + ...LEGEND_LAYERS['continuous-features']({ + items: continuousFeaturesItems, + onChangeVisibility: (featureId: Feature['id']) => { + const { color, amountRange } = + continuousFeaturesItems.find(({ id }) => id === featureId) || {}; + + const newSelectedFeatures = [...selectedFeatures]; + const isIncluded = newSelectedFeatures.includes(featureId); + if (!isIncluded) { + newSelectedFeatures.push(featureId); + } else { + const i = newSelectedFeatures.indexOf(featureId); + newSelectedFeatures.splice(i, 1); + } + dispatch(setSelectedFeatures(newSelectedFeatures)); + + dispatch( + setLayerSettings({ + id: featureId, + settings: { + visibility: !isIncluded, + amountRange, + color, + }, + }) + ); + }, + }), + ]; }; export const useLockInLegend = () => { @@ -463,11 +483,7 @@ export const useScenarioLegend = () => { layers: useConservationAreasLegend(), }, { - name: 'Features (Continuous)', - layers: useFeatureAbundanceLegend(), - }, - { - name: 'Features (Binary)', + name: 'Features', layers: useFeaturesLegend(), }, ];