Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 14 additions & 74 deletions app/components/widgets/MapStory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,20 @@ import type { ViewStateChangeParameters } from '@deck.gl/core';
import { setBarChartData, setColorScaleValues, setSelectedCounty } from '@/lib/features/map/mapSlice';
import { Progress } from '@/app/components/ui/progress';
import { MapControls, MapLegend, MapTooltip } from './map';
import type { EnhancedFeature } from '@/app/types/map';
import {
LOAD_DELAY_MS,
FLY_TO_ZOOM,
TRANSITION_DURATION_MS,
UNSELECTED_COUNTY_COLOR,
MAP_BOUNDS,
MIN_ZOOM,
} from '@/lib/constants/mapConfig';
import { dataDescriptions } from '@/lib/constants/dataDescriptions';

// Mapbox access token for using Mapbox services.
const MAP_BOX_TOKEN = process.env.NEXT_PUBLIC_MAPBOX_TOKEN || '';

/**
* Define the geographic boundaries for map constraint (Roughly North/South America).
* [ [minLongitude, minLatitude], [maxLongitude, maxLatitude] ]
*/
const MAP_BOUNDS: [[number, number], [number, number]] = [
[-117.595944, 33.386416], // Southwest corner
[-120.999866, 42.183974], // Northeast corner
];

/**
* Minimum zoom level allowed. Prevents zooming out too far.
*/
const MIN_ZOOM = 5;

/**
* Interface defining the structure for the map's view state.
*/
Expand Down Expand Up @@ -70,65 +66,9 @@ const INITIAL_COORDINATES = {
latitude: 37.7853,
};

/**
* Extends the standard GeoJSON Feature to include calculated properties
* based on the selected metric and data source.
*/
interface EnhancedFeature extends Feature {
properties: {
name: string;
[metricKey: string]: any; // Allows indexing by selectedMetric string
rawValue: number; // Original aggregated value from the data
perCapitaValue?: number; // Value calculated per capita if applicable
rowCount: number; // Number of data rows contributing to the aggregation
totalCostValue?: number; // Specifically calculated total cost for county_prison data
avgCostPerPrisonerValue?: number; // Specifically calculated average cost per prisoner
};
}


// Re-export for backward compatibility with components that import from here
export { COUNTY_POPULATION } from '@/lib/constants/countyPopulation';

/**
* Static descriptions for different data sources and their metrics.
* Used to display information in the description box on the map.
*/
const dataDescriptions = {
arrest: {
name: 'Arrest Data',
description:
'Explore how California counties use arrests as a response to crime, revealing justice system practices and community impacts across demographics and offense types.',
metrics: {
Arrest_rate: 'Rate of arrests per population for the selected filters and county.',
Total_Arrests: 'Total number of arrests recorded for the selected filters and county.',
},
},
county_prison: {
name: 'County Prison Data',
description:
'Examine imprisonment patterns and associated costs to understand how local sentencing practices impact state resources and community safety outcomes.',
metrics: {
Imprisonments: 'Total number of imprisonments recorded for the county.',
Cost_per_prisoner: 'Average cost per prisoner for the county.',
Total_Cost: 'Calculated total cost based on imprisonments and cost per prisoner.',
},
},
jail: {
name: 'Jail Data',
description: 'Understand how counties rely on local incarceration and the extent of pretrial detention in California\'s justice system.',
metrics: {
ADPtotrate: 'Average Daily Population total rate per capita for the county.',
ADPtotal: 'Total Average Daily Population count for the county.',
Felony: 'Total count of felony-related jail population for the county (Juvenile only).',
Misd: 'Total count of misdemeanor-related jail population for the county (Juvenile only).',
Postdisp: 'Post-disposition jail population count for the county.',
Predisp: 'Pre-disposition jail population count for the county.',
},
},
// Add other data sources as needed
};

/**
* Main component for displaying the interactive map story.
* It fetches GeoJSON data, merges it with filtered data from Redux state,
Expand Down Expand Up @@ -269,7 +209,7 @@ export default function MapStory() {
setShowLoading(true);
}
loadingTimerRef.current = null;
}, 500);
}, LOAD_DELAY_MS);

// Calculate dynamic population data for the selected year
const populationData = getPopulationByCountyAndYear(censusData, selectedYear);
Expand Down Expand Up @@ -444,8 +384,8 @@ export default function MapStory() {
...prevState,
longitude: polygonCentroid.longitude,
latitude: polygonCentroid.latitude,
zoom: 10, // Zoom in closer
transitionDuration: 1000, // Animation duration
zoom: FLY_TO_ZOOM, // Zoom in closer
transitionDuration: TRANSITION_DURATION_MS, // Animation duration
transitionInterpolator: new FlyToInterpolator(), // Smooth fly-to animation
}));
}
Expand All @@ -471,7 +411,7 @@ export default function MapStory() {
!selectedCounties.includes(feature.properties.name)
) {
// Return grey color for unselected counties
return [200, 200, 200, 150]; // Light grey with some transparency
return UNSELECTED_COUNTY_COLOR; // Light grey with some transparency
}

// Default coloring based on metric value for selected counties
Expand Down
14 changes: 1 addition & 13 deletions app/components/widgets/map/MapTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,9 @@
'use client';

import React from 'react';
import type { Feature } from 'geojson';
import { COUNTY_POPULATION } from '@/lib/constants/countyPopulation';
import { formatMetricLabel } from '@/lib/utils/metricFormatters';

interface EnhancedFeature extends Feature {
properties: {
name: string;
[metricKey: string]: any;
rawValue: number;
perCapitaValue?: number;
rowCount: number;
totalCostValue?: number;
avgCostPerPrisonerValue?: number;
};
}
import type { EnhancedFeature } from '@/app/types/map';

interface MapTooltipProps {
hoverInfo: {
Expand Down
14 changes: 1 addition & 13 deletions app/components/workers/dataProcessor.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,7 @@ import type { Feature } from 'geojson';
import type { CsvRow } from '@/app/types/shared';
import type { DataSourceType } from '@/lib/features/filters/filterSlice';
import { COUNTY_POPULATION } from '@/lib/constants/countyPopulation';

// --- Define EnhancedFeature interface here ---
interface EnhancedFeature extends Feature {
properties: {
name: string;
[metricKey: string]: any; // Allows indexing by selectedMetric string
rawValue: number; // Original aggregated value from the data
perCapitaValue?: number; // Value calculated per capita if applicable
rowCount: number; // Number of data rows contributing to the aggregation
totalCostValue?: number; // Specifically calculated total cost for county_prison data
avgCostPerPrisonerValue?: number; // Specifically calculated average cost per prisoner
};
}
import type { EnhancedFeature } from '@/app/types/map';


// --- Paste the enhanceGeoJsonWithData function here ---
Expand Down
17 changes: 17 additions & 0 deletions app/types/map.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import type { Feature } from 'geojson';

/**
* Extends the standard GeoJSON Feature to include calculated properties
* based on the selected metric and data source.
*/
export interface EnhancedFeature extends Feature {
properties: {
name: string;
[metricKey: string]: any; // Allows indexing by selectedMetric string
rawValue: number; // Original aggregated value from the data
perCapitaValue?: number; // Value calculated per capita if applicable
rowCount: number; // Number of data rows contributing to the aggregation
totalCostValue?: number; // Specifically calculated total cost for county_prison data
avgCostPerPrisonerValue?: number; // Specifically calculated average cost per prisoner
};
}
38 changes: 38 additions & 0 deletions lib/constants/dataDescriptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/**
* Static descriptions for different data sources and their metrics.
* Used to display information in the description box on the map.
*/
export const dataDescriptions = {
arrest: {
name: 'Arrest Data',
description:
'Explore how California counties use arrests as a response to crime, revealing justice system practices and community impacts across demographics and offense types.',
metrics: {
Arrest_rate: 'Rate of arrests per population for the selected filters and county.',
Total_Arrests: 'Total number of arrests recorded for the selected filters and county.',
},
},
county_prison: {
name: 'County Prison Data',
description:
'Examine imprisonment patterns and associated costs to understand how local sentencing practices impact state resources and community safety outcomes.',
metrics: {
Imprisonments: 'Total number of imprisonments recorded for the county.',
Cost_per_prisoner: 'Average cost per prisoner for the county.',
Total_Cost: 'Calculated total cost based on imprisonments and cost per prisoner.',
},
},
jail: {
name: 'Jail Data',
description: 'Understand how counties rely on local incarceration and the extent of pretrial detention in California\'s justice system.',
metrics: {
ADPtotrate: 'Average Daily Population total rate per capita for the county.',
ADPtotal: 'Total Average Daily Population count for the county.',
Felony: 'Total count of felony-related jail population for the county (Juvenile only).',
Misd: 'Total count of misdemeanor-related jail population for the county (Juvenile only).',
Postdisp: 'Post-disposition jail population count for the county.',
Predisp: 'Pre-disposition jail population count for the county.',
},
},
// Add other data sources as needed
};
23 changes: 23 additions & 0 deletions lib/constants/mapConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/** Delay in ms before showing the loading indicator after worker starts */
export const LOAD_DELAY_MS = 500;

/** Zoom level used when flying to a selected county */
export const FLY_TO_ZOOM = 10;

/** Animation duration in ms for fly-to transitions */
export const TRANSITION_DURATION_MS = 1000;

/** Fill color for counties not in the active selection [R, G, B, A] */
export const UNSELECTED_COUNTY_COLOR: [number, number, number, number] = [200, 200, 200, 150];

/**
* Geographic bounds constraining map panning (roughly California).
* [ [minLongitude, minLatitude], [maxLongitude, maxLatitude] ]
*/
export const MAP_BOUNDS: [[number, number], [number, number]] = [
[-117.595944, 33.386416], // Southwest corner
[-120.999866, 42.183974], // Northeast corner
Comment on lines +17 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): MAP_BOUNDS longitudes appear reversed (west > east), which may break consumers expecting [west, south], [east, north].

These bounds are [-117.595944, 33.386416] to [-120.999866, 42.183974]. Since -120.999866 < -117.595944, this encodes [east, south][west, north] instead of [west, south][east, north]. Many mapping APIs (Mapbox, deck.gl) assume west < east and may fail or behave oddly with reversed longitudes, especially now that this constant is shared. If this isn’t intentional, please swap the longitudes and align the comments with the correct convention.

];

/** Minimum zoom level — prevents zooming out too far */
export const MIN_ZOOM = 5;