Skip to content

Commit b477ed2

Browse files
authored
Improve map filters (#712)
1 parent f84a4e4 commit b477ed2

File tree

31 files changed

+659
-320
lines changed

31 files changed

+659
-320
lines changed

src/app/component.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,11 @@ const App = (props) => {
104104
const isResultsPage = pathname === '/observed-outcomes';
105105

106106
// Home page needs predictions and sparse data for the map
107-
// First fetch available years to ensure predictionYear is valid (e.g., 2024 not 2025)
108-
// The reducer will auto-correct predictionYear, which triggers the other useEffect to fetch predictions
107+
// Fetch years, predictions, and available states immediately
109108
if (isHomePage) {
110109
getAvailableYears();
110+
getPredictions(predictionYear);
111+
getAvailableStates({ predictionYear });
111112
}
112113

113114
// Blog pages need blog posts
@@ -124,6 +125,7 @@ const App = (props) => {
124125
getAggregateStateData,
125126
getAggregateYearData,
126127
getAllBlogPosts,
128+
getAvailableStates,
127129
getAvailableYears,
128130
getPredictions,
129131
getSparseData,

src/components/filter-bar/index.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -52,28 +52,28 @@ const mapStateToProps = (state, ownProps) => {
5252
};
5353
};
5454

55-
const mapDispatchToProps = (dispatch) => ({
56-
clearAllSelections: () => {
55+
const mapDispatchToProps = (dispatch, ownProps) => ({
56+
clearAllSelections: ownProps.onClearSelections || (() => {
5757
dispatch(clearSelections());
58-
},
59-
setPredictionYear: (year) => {
58+
}),
59+
setPredictionYear: ownProps.onSetPredictionYear || ((year) => {
6060
dispatch(setPredictionYear(year));
61-
},
61+
}),
6262
setStartYear: (year) => {
6363
dispatch(setStartYear(year));
6464
},
6565
setEndYear: (year) => {
6666
dispatch(setEndYear(year));
6767
},
68-
setCounty: (county) => {
68+
setCounty: ownProps.onSetCounty || ((county) => {
6969
dispatch(setCounty(county));
70-
},
71-
setRangerDistrict: (rangerDistrict) => {
70+
}),
71+
setRangerDistrict: ownProps.onSetRangerDistrict || ((rangerDistrict) => {
7272
dispatch(setRangerDistrict(rangerDistrict));
73-
},
74-
setState: (state) => {
73+
}),
74+
setState: ownProps.onSetState || ((state) => {
7575
dispatch(setState(state));
76-
},
76+
}),
7777
setDataMode: (mode) => {
7878
dispatch(setDataMode(mode));
7979
},

src/components/historical-data/line-chart/components/avg-prob-chart/component.js

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@ const AvgProbChart = (props) => {
103103
],
104104
};
105105

106-
const updatedChartOptions = {
107-
...avgProbChartOptions,
108-
};
109-
110106
const yearRange = getYearRange(startYear, endYear);
111107
if (yearRange.length === 0) {
112108
return;
@@ -124,12 +120,24 @@ const AvgProbChart = (props) => {
124120
const maxValue = Math.max(
125121
...updatedAvgProbChartData.datasets[0].data.filter((v) => v !== null && v !== undefined)
126122
);
127-
if (!Number.isNaN(maxValue) && maxValue > 0) {
128-
updatedChartOptions.scales.yAxes[0].ticks.max = maxValue;
129-
}
130123

131124
setAvgProbChartData(updatedAvgProbChartData);
132-
setAvgProbChartOptions(updatedChartOptions);
125+
126+
if (!Number.isNaN(maxValue) && maxValue > 0) {
127+
setAvgProbChartOptions((prevOptions) => ({
128+
...prevOptions,
129+
scales: {
130+
...prevOptions.scales,
131+
yAxes: [{
132+
...prevOptions.scales.yAxes[0],
133+
ticks: {
134+
...prevOptions.scales.yAxes[0].ticks,
135+
max: maxValue,
136+
},
137+
}],
138+
},
139+
}));
140+
}
133141
}, [yearData, startYear, endYear]);
134142

135143
return <Line data={avgProbChartData} height={400} options={avgProbChartOptions} />;

src/components/historical-data/trapping-data-map/component.js

Lines changed: 104 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
addMapLayer,
3030
createBaseExpressions,
3131
removeVectorLayer,
32+
waitForStyleLoad,
3233
} from '../../../utils/map-coloring';
3334
import { isMapRemoved as checkMapRemoved, markMapAsRemoved as markMapRemoved } from '../../../utils/map-instance-tracker';
3435
import Map from '../../map';
@@ -43,20 +44,24 @@ const HistoricalMap = (props) => {
4344
const {
4445
availableStates,
4546
availableSublocations,
47+
county,
4648
dataMode,
4749
predictionYear,
50+
rangerDistrict,
4851
selectedState,
4952
setCounty,
5053
setRangerDistrict,
5154
setState,
55+
setCountyFilter,
56+
setRangerDistrictFilter,
57+
setStateFilter,
58+
clearAllSelections,
5259
sublocationData: rawData,
5360
} = props;
5461

5562
const {
5663
map,
5764
setMap,
58-
initialFill,
59-
setInitialFill,
6065
hover: trappingHover,
6166
setHover: setTrappingHover,
6267
isDownloadingMap,
@@ -65,6 +70,26 @@ const HistoricalMap = (props) => {
6570

6671
const allRangerDistricts = useRangerDistricts(dataMode);
6772

73+
const colorFillTimeoutRef = useRef(null);
74+
const isMountedRef = useRef(true);
75+
const styleRetryCountRef = useRef(0);
76+
const containerCheckTimeoutRef = useRef(null);
77+
78+
useEffect(() => {
79+
isMountedRef.current = true;
80+
return () => {
81+
isMountedRef.current = false;
82+
if (colorFillTimeoutRef.current) {
83+
clearTimeout(colorFillTimeoutRef.current);
84+
colorFillTimeoutRef.current = null;
85+
}
86+
if (containerCheckTimeoutRef.current) {
87+
clearTimeout(containerCheckTimeoutRef.current);
88+
containerCheckTimeoutRef.current = null;
89+
}
90+
};
91+
}, []);
92+
6893
const createMapHoverCallback = useCallback((allData, rangerDistricts, mode, state, availStates) => {
6994
const callback = (hoverState, location, x, y, counties) => {
7095
const sublocation = mode === DATA_MODES.COUNTY ? 'county' : 'rangerDistrict';
@@ -96,14 +121,11 @@ const HistoricalMap = (props) => {
96121
return createHoverCallback(map, rangerDistricts, dataMode, callback);
97122
}, [map, dataMode, setTrappingHover]);
98123

99-
const colorFill = (d) => {
100-
if (!map) return;
124+
const colorFill = useCallback((d) => {
125+
if (!isMountedRef.current || !map) return false;
101126

102-
if (!map.isStyleLoaded()) {
103-
setTimeout(() => {
104-
colorFill(d);
105-
}, 1000);
106-
return;
127+
if (!waitForStyleLoad(map, colorFill, [d], colorFillTimeoutRef, isMountedRef, styleRetryCountRef)) {
128+
return false;
107129
}
108130

109131
removeVectorLayer(map);
@@ -114,14 +136,27 @@ const HistoricalMap = (props) => {
114136
const filteredData = d.filter((item) => {
115137
const itemYear = parseYearFromItem(item);
116138
if (itemYear === null) return false;
117-
return itemYear === selectedYear;
139+
if (itemYear !== selectedYear) return false;
140+
141+
// Apply visual filtering based on selected state, county, and rangerDistrict
142+
if (selectedState && item.state !== selectedState) return false;
143+
144+
if (dataMode === DATA_MODES.COUNTY && county && county.length > 0) {
145+
if (!county.includes(item.county)) return false;
146+
}
147+
148+
if (dataMode === DATA_MODES.RANGER_DISTRICT && rangerDistrict && rangerDistrict.length > 0) {
149+
if (!rangerDistrict.includes(item.rangerDistrict)) return false;
150+
}
151+
152+
return true;
118153
});
119154

120155
const trappingsByLocality = filteredData.reduce((acc, curr) => {
121156
const {
122-
county,
123-
rangerDistrict,
124-
state,
157+
county: itemCounty,
158+
rangerDistrict: itemRangerDistrict,
159+
state: itemState,
125160
sumSpotst0,
126161
spotst0,
127162
spots,
@@ -136,8 +171,8 @@ const HistoricalMap = (props) => {
136171
spotsValue = spots;
137172
}
138173

139-
const countyFormatName = county && state ? `${county} ${state}`.toUpperCase() : '';
140-
const rangerDistrictFormatName = rangerDistrict ? getMapboxRDNameFormat(rangerDistrict)?.toUpperCase() : '';
174+
const countyFormatName = itemCounty && itemState ? `${itemCounty} ${itemState}`.toUpperCase() : '';
175+
const rangerDistrictFormatName = itemRangerDistrict ? getMapboxRDNameFormat(itemRangerDistrict)?.toUpperCase() : '';
141176

142177
const localityDescription = dataMode === DATA_MODES.COUNTY ? countyFormatName : rangerDistrictFormatName;
143178

@@ -178,14 +213,20 @@ const HistoricalMap = (props) => {
178213

179214
addDefaultExpressions(fillExpression, strokeExpression);
180215

181-
addMapLayer(map, fillExpression, strokeExpression, getSourceLayer(dataMode));
182-
};
216+
addMapLayer(map, fillExpression, strokeExpression, getSourceLayer(dataMode), dataMode);
217+
218+
return true;
219+
}, [map, dataMode, selectedState, county, rangerDistrict, predictionYear]);
183220

184221
const mapInitializedRef = useRef(false);
185222
const lastDataModeRef = useRef(dataMode);
186223
const initTimeoutRef = useRef(null);
187224
const containerRetryCountRef = useRef(0);
188225

226+
useEffect(() => {
227+
mapInitializedRef.current = false;
228+
}, []);
229+
189230
useEffect(() => {
190231
const shouldRegenerate = !map || lastDataModeRef.current !== dataMode;
191232

@@ -203,7 +244,13 @@ const HistoricalMap = (props) => {
203244
const currentMap = map;
204245

205246
initTimeoutRef.current = setTimeout(() => {
247+
initTimeoutRef.current = null;
248+
249+
if (!isMountedRef.current) return;
250+
206251
const checkContainer = () => {
252+
if (!isMountedRef.current) return;
253+
207254
if (containerRetryCountRef.current >= MAP_INIT_CONSTANTS.MAX_CONTAINER_CHECK_RETRIES) {
208255
logError('Map container not found after maximum retries', null, { component: 'TrappingDataMap' });
209256
return;
@@ -220,10 +267,14 @@ const HistoricalMap = (props) => {
220267
});
221268
mapInitializedRef.current = true;
222269
lastDataModeRef.current = dataMode;
223-
initTimeoutRef.current = null;
224270
} else {
225271
containerRetryCountRef.current += 1;
226-
setTimeout(checkContainer, MAP_INIT_CONSTANTS.CONTAINER_CHECK_INTERVAL);
272+
containerCheckTimeoutRef.current = setTimeout(() => {
273+
containerCheckTimeoutRef.current = null;
274+
if (isMountedRef.current) {
275+
checkContainer();
276+
}
277+
}, MAP_INIT_CONSTANTS.CONTAINER_CHECK_INTERVAL);
227278
}
228279
};
229280
checkContainer();
@@ -235,6 +286,10 @@ const HistoricalMap = (props) => {
235286
clearTimeout(initTimeoutRef.current);
236287
initTimeoutRef.current = null;
237288
}
289+
if (containerCheckTimeoutRef.current) {
290+
clearTimeout(containerCheckTimeoutRef.current);
291+
containerCheckTimeoutRef.current = null;
292+
}
238293
if (map && typeof map.remove === 'function' && map.getContainer && !checkMapRemoved(map)) {
239294
try {
240295
const container = map.getContainer();
@@ -251,23 +306,36 @@ const HistoricalMap = (props) => {
251306
}
252307
mapInitializedRef.current = false;
253308
};
309+
// eslint-disable-next-line react-hooks/exhaustive-deps
254310
}, [dataMode]);
255311

256312
useEffect(() => {
257-
if (!map) return;
313+
if (!map || rawData.length === 0 || predictionYear.toString().length !== 4) {
314+
return undefined;
315+
}
258316

259-
if (predictionYear.toString().length === 4) colorFill(rawData);
317+
const attemptColoring = () => {
318+
if (map.isStyleLoaded && map.isStyleLoaded()) {
319+
const didColor = colorFill(rawData);
320+
if (didColor) {
321+
zoomToSelectedState(selectedState, map);
322+
}
323+
} else {
324+
map.once('styledata', () => {
325+
const didColor = colorFill(rawData);
326+
if (didColor) {
327+
zoomToSelectedState(selectedState, map);
328+
}
329+
});
330+
}
331+
};
260332

261-
zoomToSelectedState(selectedState, map);
333+
const timer = setTimeout(attemptColoring, 50);
334+
return () => {
335+
clearTimeout(timer);
336+
};
262337
// eslint-disable-next-line react-hooks/exhaustive-deps
263-
}, [rawData, selectedState, map, predictionYear]);
264-
265-
useEffect(() => {
266-
if (!initialFill && map && rawData.length > 0) {
267-
colorFill(rawData);
268-
setInitialFill(true);
269-
}
270-
}, [initialFill, map, rawData, setInitialFill]);
338+
}, [rawData, selectedState, map, predictionYear, county, rangerDistrict]);
271339

272340
const hoverCallback = useMemo(() => {
273341
if (!map || !rawData) return null;
@@ -321,27 +389,6 @@ const HistoricalMap = (props) => {
321389
}
322390
}, [rawData, map]);
323391

324-
useEffect(() => {
325-
const handleDownloadClick = (event) => {
326-
if (!event.target.matches('.download-button') && !event.target.matches('.download-button p')) return;
327-
downloadMap(
328-
map,
329-
predictionYear,
330-
isDownloadingMap,
331-
setIsDownloadingMap,
332-
selectedState,
333-
MAP_TITLES.HISTORICAL,
334-
{ titleDetails: { selectedState, period: predictionYear }, thresholds, colors }
335-
);
336-
};
337-
338-
document.addEventListener('click', handleDownloadClick, false);
339-
340-
return () => {
341-
document.removeEventListener('click', handleDownloadClick, false);
342-
};
343-
}, [map, predictionYear, isDownloadingMap, setIsDownloadingMap, selectedState]);
344-
345392
const getRiskLevel = (index) => {
346393
const riskLevels = ['No Data', '0-9', '10-19', '20-49', '50-99', '100-249', '250+'];
347394
return riskLevels[index] || 'Unknown';
@@ -361,16 +408,16 @@ const HistoricalMap = (props) => {
361408
availableStates={availableStates}
362409
availableYears={[]}
363410
availableSublocations={availableSublocations}
364-
county={props.county}
411+
county={county}
365412
dataMode={dataMode}
366413
predictionYear={predictionYear}
367-
rangerDistrict={props.rangerDistrict}
414+
rangerDistrict={rangerDistrict}
368415
selectedState={selectedState}
369-
setCounty={setCounty}
416+
setCounty={setCountyFilter}
370417
setPredictionYear={() => {}}
371-
setRangerDistrict={setRangerDistrict}
372-
setState={setState}
373-
clearAllSelections={props.clearAllSelections}
418+
setRangerDistrict={setRangerDistrictFilter}
419+
setState={setStateFilter}
420+
clearAllSelections={clearAllSelections}
374421
legendItems={legendItems}
375422
legendTitle="Total Number of Spots"
376423
downloadCallback={() => downloadMap(

0 commit comments

Comments
 (0)