Heatmap Filter Zero Values Implementation Plan
For Claude: REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
Goal: Add a toggle control to filter out data points with zero PM10 values from the heatmap visualization.
Architecture: Add a new boolean state hideZeroValues to HeatmapControlsState, add a toggle switch in the Data Filters section of the control panel, and filter the data in the useMemo hooks that process rows and markers before rendering.
Tech Stack: React, TypeScript, existing UI components (toggle switch pattern from showOverlay/showPoints)
Task 1: Add Type Definition
Files:
- Modify:
src/features/heatmap/types.ts:22-39
Step 1: Add hideZeroValues to HeatmapControlsState type
In src/features/heatmap/types.ts, add the new property to HeatmapControlsState:
export type HeatmapControlsState = {
gradient: HeatmapGradientId;
radius: number;
opacity: number;
dissipating: boolean;
weightScale: number;
maxIntensity: number;
renderMode: RenderMode;
// Canvas renderer specific controls
pointSize: number;
blurRadius: number;
gridSize: number;
showPoints: boolean;
canvasColorScheme: CanvasColorScheme;
// Map overlay controls
showOverlay: boolean;
overlayOpacity: number;
// Data filtering
hideZeroValues: boolean;
};Step 2: Verify TypeScript compiles
Run: pnpm build:check Expected: Build fails with error about missing hideZeroValues in DEFAULT_CONTROLS (this is expected, we'll fix it in Task 2)
Step 3: Commit type change
git add src/features/heatmap/types.ts
git commit -m "feat(heatmap): add hideZeroValues to HeatmapControlsState type"Task 2: Add Default Value in Constants
Files:
- Modify:
src/features/heatmap/constants.ts:95-112
Step 1: Add hideZeroValues default to DEFAULT_CONTROLS
In src/features/heatmap/constants.ts, add the default value:
export const DEFAULT_CONTROLS: HeatmapControlsState = {
gradient: "blueToRed",
radius: 10,
opacity: 0.8,
dissipating: true,
weightScale: 1,
maxIntensity: 250,
renderMode: "canvas",
// Canvas renderer specific controls
pointSize: 3,
blurRadius: 3,
gridSize: 2,
showPoints: true,
canvasColorScheme: "greenToRed", // Default for most sites
// Map overlay controls
showOverlay: true,
overlayOpacity: 0.75,
// Data filtering
hideZeroValues: false, // Show all data by default
};Step 2: Verify TypeScript compiles
Run: pnpm build:check Expected: PASS - no type errors
Step 3: Commit constants change
git add src/features/heatmap/constants.ts
git commit -m "feat(heatmap): add hideZeroValues default to DEFAULT_CONTROLS"Task 3: Add Filtered Data Memos
Files:
- Modify:
src/app/(admin)/(pages)/heatmap/index.tsx
Step 1: Add filteredRows useMemo after rows state
Find the line with const stats = useMemo(() => calculateStats(rows), [rows]); (around line 148) and add a new memo BEFORE it:
// Filter out zero values if hideZeroValues is enabled
const filteredRows = useMemo(() => {
if (!controls.hideZeroValues) {
return rows;
}
return rows.filter((row) => {
const value = parseFloat(row.Location_AssetValue);
return !isNaN(value) && value > 0;
});
}, [rows, controls.hideZeroValues]);Step 2: Update stats memo to use filteredRows
Change:
const stats = useMemo(() => calculateStats(rows), [rows]);To:
const stats = useMemo(() => calculateStats(filteredRows), [filteredRows]);Step 3: Add filteredMarkers useMemo
After the filteredRows memo, add:
// Filter markers based on hideZeroValues
const filteredMarkers = useMemo(() => {
if (!controls.hideZeroValues) {
return markers;
}
return markers.filter((marker) => marker.value > 0);
}, [markers, controls.hideZeroValues]);Step 4: Update weightedLocations memo to use filteredRows
Change:
const weightedLocations = useMemo(() => {
if (mapsStatus !== "ready" || !window.google?.maps) {
return [] as google.maps.visualization.WeightedLocation[];
}
return toWeightedLocations(rows, controls.weightScale, window.google.maps);
}, [controls.weightScale, mapsStatus, rows]);To:
const weightedLocations = useMemo(() => {
if (mapsStatus !== "ready" || !window.google?.maps) {
return [] as google.maps.visualization.WeightedLocation[];
}
return toWeightedLocations(filteredRows, controls.weightScale, window.google.maps);
}, [controls.weightScale, mapsStatus, filteredRows]);Step 5: Verify TypeScript compiles
Run: pnpm build:check Expected: PASS
Step 6: Commit filtered data memos
git add src/app/\(admin\)/\(pages\)/heatmap/index.tsx
git commit -m "feat(heatmap): add filteredRows and filteredMarkers memos for zero value filtering"Task 4: Update Rendering Effects to Use Filtered Data
Files:
- Modify:
src/app/(admin)/(pages)/heatmap/index.tsx
Step 1: Update Canvas mode rendering effect
Find the Canvas mode rendering effect (around line 476-546) that checks markers.length === 0. Update it to use filteredMarkers:
Change:
markers.length === 0 ||To:
filteredMarkers.length === 0 ||And change:
canvasOverlayRef.current.updateMarkers(markers);To:
canvasOverlayRef.current.updateMarkers(filteredMarkers);And in the overlay creation:
const overlay = new (OverlayClass as unknown as new (
markers: MarkerData[],
...
) => CanvasOverlayInstance)(
filteredMarkers, // Changed from markers
...
);And update the dependency array:
}, [
mapsStatus,
map,
controls.renderMode,
controls.showPoints,
filteredMarkers, // Changed from markers
heatmapLayer,
]);Step 2: Update fit bounds effect
Find the fit bounds effect (around line 634-724) and update it to use filteredMarkers:
Change:
if (!initialBoundsFitted.current && markers.length > 0) {To:
if (!initialBoundsFitted.current && filteredMarkers.length > 0) {And change:
for (const marker of markers) {To:
for (const marker of filteredMarkers) {And update the dependency array:
}, [map, mapsStatus, rows, filteredMarkers, selectedMineSite, mineSites]);Step 3: Verify TypeScript compiles
Run: pnpm build:check Expected: PASS
Step 4: Commit rendering updates
git add src/app/\(admin\)/\(pages\)/heatmap/index.tsx
git commit -m "feat(heatmap): update rendering effects to use filtered data"Task 5: Add Toggle UI Control
Files:
- Modify:
src/app/(admin)/(pages)/heatmap/index.tsx
Step 1: Add toggle switch in Data Filters section
Find the Data Filters section (around line 857-995). After the Date Range Selector section (after the closing </div> of the date range selector, before the closing </div> of the Data Filters section), add the new toggle:
{/* Hide Zero Values Toggle */}
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Icon
className="w-4 h-4 text-slate-600 dark:text-slate-400"
icon="solar:filter-bold-duotone"
/>
<label className="text-sm font-medium text-slate-700 dark:text-slate-300">
Hide Zero Values
</label>
</div>
<button
type="button"
role="switch"
aria-checked={controls.hideZeroValues}
onClick={() => updateControls({ hideZeroValues: !controls.hideZeroValues })}
className={`relative inline-flex h-6 w-11 items-center rounded-full transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 ${
controls.hideZeroValues
? "bg-blue-600"
: "bg-slate-300 dark:bg-slate-600"
}`}
>
<span
className={`inline-block h-4 w-4 transform rounded-full bg-white transition-transform ${
controls.hideZeroValues ? "translate-x-6" : "translate-x-1"
}`}
/>
</button>
</div>
{controls.hideZeroValues && (
<p className="text-xs text-slate-500 dark:text-slate-400 mt-1">
Filtering out {rows.length - filteredRows.length} zero-value data points
</p>
)}Step 2: Verify TypeScript compiles
Run: pnpm build:check Expected: PASS
Step 3: Commit UI toggle
git add src/app/\(admin\)/\(pages\)/heatmap/index.tsx
git commit -m "feat(heatmap): add Hide Zero Values toggle in control panel"Task 6: Manual Testing
Step 1: Start development server
Run: pnpm dev
Step 2: Test the feature
- Navigate to the Heatmap page
- Select a mine site with data
- Verify the "Hide Zero Values" toggle appears in the Data Filters section
- Toggle it ON and verify:
- Zero-value points are removed from the visualization
- The stats (data point count) updates to reflect filtered data
- The info text shows how many points were filtered
- Toggle it OFF and verify:
- All data points reappear
- Stats return to original values
Step 3: Test edge cases
- Test with no data loaded - toggle should still work without errors
- Test switching mine sites - filter state should persist
- Test with all zero values - should show empty map gracefully
Step 4: Final commit
git add -A
git commit -m "feat(heatmap): complete Hide Zero Values filter feature"Summary
This implementation adds a simple but effective filter to hide zero-value PM10 readings from the heatmap. The approach:
- Type-safe: Adds the new control to the existing type system
- Performant: Uses
useMemoto only recompute filtered data when needed - Non-breaking: Defaults to
false(show all data) to maintain existing behavior - User-friendly: Shows count of filtered points when enabled
- Consistent: Uses the same toggle switch pattern as existing controls (showOverlay, showPoints)
Total estimated time: 15-20 minutes