{{ theme.skipToContentLabel || 'Skip to content' }}

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:

typescript
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

bash
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:

typescript
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

bash
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:

typescript
// 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:

typescript
const stats = useMemo(() => calculateStats(rows), [rows]);

To:

typescript
const stats = useMemo(() => calculateStats(filteredRows), [filteredRows]);

Step 3: Add filteredMarkers useMemo

After the filteredRows memo, add:

typescript
// 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:

typescript
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:

typescript
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

bash
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:

typescript
markers.length === 0 ||

To:

typescript
filteredMarkers.length === 0 ||

And change:

typescript
canvasOverlayRef.current.updateMarkers(markers);

To:

typescript
canvasOverlayRef.current.updateMarkers(filteredMarkers);

And in the overlay creation:

typescript
const overlay = new (OverlayClass as unknown as new (
  markers: MarkerData[],
  ...
) => CanvasOverlayInstance)(
  filteredMarkers,  // Changed from markers
  ...
);

And update the dependency array:

typescript
}, [
  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:

typescript
if (!initialBoundsFitted.current && markers.length > 0) {

To:

typescript
if (!initialBoundsFitted.current && filteredMarkers.length > 0) {

And change:

typescript
for (const marker of markers) {

To:

typescript
for (const marker of filteredMarkers) {

And update the dependency array:

typescript
}, [map, mapsStatus, rows, filteredMarkers, selectedMineSite, mineSites]);

Step 3: Verify TypeScript compiles

Run: pnpm build:check Expected: PASS

Step 4: Commit rendering updates

bash
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:

tsx
{/* 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

bash
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

  1. Navigate to the Heatmap page
  2. Select a mine site with data
  3. Verify the "Hide Zero Values" toggle appears in the Data Filters section
  4. 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
  5. Toggle it OFF and verify:
    • All data points reappear
    • Stats return to original values

Step 3: Test edge cases

  1. Test with no data loaded - toggle should still work without errors
  2. Test switching mine sites - filter state should persist
  3. Test with all zero values - should show empty map gracefully

Step 4: Final commit

bash
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:

  1. Type-safe: Adds the new control to the existing type system
  2. Performant: Uses useMemo to only recompute filtered data when needed
  3. Non-breaking: Defaults to false (show all data) to maintain existing behavior
  4. User-friendly: Shows count of filtered points when enabled
  5. Consistent: Uses the same toggle switch pattern as existing controls (showOverlay, showPoints)

Total estimated time: 15-20 minutes