Skip to content

Latest commit

Β 

History

History
458 lines (397 loc) Β· 32.9 KB

File metadata and controls

458 lines (397 loc) Β· 32.9 KB

Architecture

Quiet Tiger β€” an adaptive space-time cube prototype for exploring crime patterns in Chicago. It keeps a 3D cube, a 2D map, and a dual timeline synchronized so that users can brush time, inspect individual crime points, and watch bursty intervals expand or compress as the time resolution adapts.


System Overview

The application is a desktop-first Next.js 16 prototype with a client-heavy visualization architecture and server-side data processing. Its primary input is an ~8.5M-record crime dataset (Chicago, 2001–2026) stored as CSV and queried through a local DuckDB in-process OLAP database. The system streams crime records and computed density maps to three coordinated view panels β€” a MapLibre GL 2D map, a Three.js 3D space-time cube, and an @visx dual timeline β€” all synchronized through Zustand stores with a cross-panel coordination pattern. Heavy computation (adaptive time scaling, STKDE hotspot analysis) runs in Web Workers to keep the UI responsive.


Tech Stack Summary

Layer Technology Purpose
Framework Next.js 16.1.6 (App Router) Pages, API routes, SSR/SSG
UI Runtime React 19.2.3 + React DOM Component rendering
State Management Zustand 5.0.10 (~35 stores) Client state, cross-view coordination
Server State TanStack Query 5.90.21 Data fetching, caching, refetching
3D Rendering Three.js 0.182.0 + React Three Fiber 9.5.0 + Drei 10.7.7 Space-time cube visualization
Map Rendering MapLibre GL 5.17.0 + React Map GL 8.1.0 2D crime map
SVG/Chart @visx 3.12.0 (axis, brush, scale, shape) Dual timeline, density charts
Data Processing DuckDB 1.4.4 In-process OLAP queries over CSV
Data Transport Apache Arrow 21.1.0 + @loaders.gl/arrow 4.3.4 Columnar streaming from API
Styling Tailwind CSS 4 + shadcn/ui + Radix UI 1.4.3 Utility-first CSS, accessible primitives
Testing Vitest 4.0.18 + jsdom 28.0.0 + React Test Renderer 19.1.0 Unit and component tests
Language TypeScript 5.9.3 (strict mode) All application code

Directory Structure

src/
β”œβ”€β”€ app/                    # Next.js App Router pages and API routes
β”‚   β”œβ”€β”€ page.tsx            # Landing page (Quiet Tiger home)
β”‚   β”œβ”€β”€ layout.tsx          # Root layout (ThemeProvider, QueryProvider, Toaster, OnboardingTour)
β”‚   β”œβ”€β”€ dashboard/          # Main visualization dashboard (map + cube + timeline)
β”‚   β”œβ”€β”€ dashboard-demo/     # Guided demo shell with step-by-step workflow panels
β”‚   β”œβ”€β”€ stkde/              # STKDE hotspot analysis page
β”‚   β”œβ”€β”€ stkde-3d/           # 3D STKDE spatial-temporal visualization
β”‚   β”œβ”€β”€ timeline-test/      # Timeline testing interface
β”‚   β”œβ”€β”€ timeline-test-3d/   # 3D timeline visualization
β”‚   β”œβ”€β”€ timeslicing/        # Time slicing controls page
β”‚   β”œβ”€β”€ timeslicing-algos/  # Algorithm comparison for time slicing
β”‚   β”œβ”€β”€ stats/              # Statistical dashboard
β”‚   β”œβ”€β”€ algorithms/         # Algorithm documentation page
β”‚   β”œβ”€β”€ cube-sandbox/       # Isolated 3D cube testing
β”‚   β”œβ”€β”€ demo/               # Demo pages
β”‚   β”œβ”€β”€ docs/               # Documentation pages
β”‚   └── api/                # Route Handlers
β”‚       β”œβ”€β”€ crime/          # Crime data endpoints (stream, bins, facets, meta, overview, stats-summary)
β”‚       β”œβ”€β”€ crimes/range/   # Viewport-based crime range query
β”‚       β”œβ”€β”€ adaptive/       # Adaptive scaling (global maps, bursts)
β”‚       β”œβ”€β”€ stkde/hotspots/ # STKDE hotspot computation
β”‚       β”œβ”€β”€ neighbourhood/poi/ # Neighbourhood points of interest
β”‚       └── study/log/      # Study session logging
β”‚
β”œβ”€β”€ components/             # React components
β”‚   β”œβ”€β”€ dashboard/          # Dashboard header
β”‚   β”œβ”€β”€ dashboard-demo/     # Demo workflow panels (Configure, Detect, Inspect) and shell
β”‚   β”œβ”€β”€ layout/             # DashboardLayout (resizable panels), ThemeProvider, TopBar
β”‚   β”œβ”€β”€ map/                # MapBase, MapVisualization, overlay layers (heatmap, STKDE, trajectory, cluster, POI)
β”‚   β”œβ”€β”€ timeline/           # DualTimeline, TimelinePanel, DensityAreaChart, DensityHeatStrip, etc.
β”‚   β”œβ”€β”€ viz/                # CubeVisualization, MainScene, Scene (Three.js), data points, slice planes, grids
β”‚   β”œβ”€β”€ ui/                 # shadcn/ui primitives (button, card, slider, select, dialog, etc.)
β”‚   β”œβ”€β”€ onboarding/         # OnboardingTour (driver.js)
β”‚   β”œβ”€β”€ study/              # StudyControls
β”‚   β”œβ”€β”€ settings/           # Feature flags, settings panel
β”‚   β”œβ”€β”€ binning/            # Binning strategy display components
β”‚   β”œβ”€β”€ stkde/              # STKDE-specific visualization components
β”‚   └── timeslicing/        # Time slicing controls UI
β”‚
β”œβ”€β”€ store/                  # Zustand state stores (~35 stores)
β”‚   β”œβ”€β”€ slice-domain/       # Slice state slices (core, creation, selection, adjustment)
β”‚   β”œβ”€β”€ useCoordinationStore.ts  # Cross-panel coordination (selection, sync, brush)
β”‚   β”œβ”€β”€ useAdaptiveStore.ts      # Adaptive time scaling parameters
β”‚   β”œβ”€β”€ useFilterStore.ts        # Crime type, district, time, spatial filters
β”‚   β”œβ”€β”€ useTimeStore.ts          # Playback time, range, resolution, scale mode
β”‚   β”œβ”€β”€ useSliceDomainStore.ts   # Time slice CRUD (aliased as useSliceStore)
β”‚   β”œβ”€β”€ useAggregationStore.ts   # Aggregated data caching
β”‚   β”œβ”€β”€ useClusterStore.ts       # DBSCAN cluster analysis state
β”‚   β”œβ”€β”€ useStkdeStore.ts         # STKDE hotspot computation state
β”‚   β”œβ”€β”€ useIntervalProposalStore.ts  # Auto-proposal interval management
β”‚   β”œβ”€β”€ useSuggestionStore.ts    # Interactive suggestion state
β”‚   β”œβ”€β”€ useWarpProposalStore.ts  # Warp proposal management
β”‚   β”œβ”€β”€ useTimelineDataStore.ts  # Timeline series data
β”‚   β”œβ”€β”€ useLayoutStore.ts        # Panel layout persistence
β”‚   β”œβ”€β”€ useStatsStore.ts         # Statistical summary state
β”‚   β”œβ”€β”€ useMapLayerStore.ts      # Map overlay layer toggles
β”‚   β”œβ”€β”€ useTimeslicingModeStore.ts # Time slicing mode controls
β”‚   └── ...                 # Additional stores (study, trajectory, heatmap, suggestions, etc.)
β”‚
β”œβ”€β”€ lib/                    # Business logic and data layer
β”‚   β”œβ”€β”€ db.ts               # DuckDB initialization, CSV path resolution, mock data detection
β”‚   β”œβ”€β”€ queries/            # DuckDB query builders with SQL sanitization
β”‚   β”œβ”€β”€ binning/            # Time binning engine (strategies, rules, burst taxonomy, warp scaling)
β”‚   β”œβ”€β”€ stkde/              # STKDE computation (grid config, heatmap, hotspots, burst evolution, contracts)
β”‚   β”œβ”€β”€ kde/                # Slice-level KDE computation
β”‚   β”œβ”€β”€ adaptive/           # Adaptive binning mode logic (route-binning-mode)
β”‚   β”œβ”€β”€ clustering/         # DBSCAN cluster analysis
β”‚   β”œβ”€β”€ neighbourhood/      # Chicago neighbourhood data, OSM integration
β”‚   β”œβ”€β”€ context-diagnostics/ # Spatial/temporal profile comparison
β”‚   β”œβ”€β”€ stats/              # Temporal pulse series, aggregation helpers
β”‚   β”œβ”€β”€ suggestion/         # Suggestion event types
β”‚   β”œβ”€β”€ evolution/          # Evolution flow computation
β”‚   β”œβ”€β”€ motion/             # Easing functions, animation aging
β”‚   β”œβ”€β”€ data/               # Data selector types
β”‚   β”œβ”€β”€ stores/             # Viewport store
β”‚   β”œβ”€β”€ adaptive-scale.ts   # Adaptive Y-scale computation (d3-compatible)
β”‚   β”œβ”€β”€ burst-detection.ts  # Temporal, spatial, and combined burst detection
β”‚   β”œβ”€β”€ interval-detection.ts # Natural breakpoint boundary detection
β”‚   β”œβ”€β”€ confidence-scoring.ts  # Confidence scoring for proposed intervals
β”‚   β”œβ”€β”€ coordinate-normalization.ts  # Chicago ↔ normalized coordinate mapping
β”‚   β”œβ”€β”€ date-normalization.ts        # Date/time formatting utilities
β”‚   β”œβ”€β”€ duckdb-aggregator.ts         # 3D spatial bin aggregation
β”‚   β”œβ”€β”€ full-auto-orchestrator.ts    # Interval auto-proposal orchestration
β”‚   β”œβ”€β”€ logger.ts           # LoggerService (batch/flush via sendBeacon)
β”‚   β”œβ”€β”€ time-range.ts       # Time range utilities and validation
β”‚   β”œβ”€β”€ time-domain.ts      # Domain bounds computation
β”‚   β”œβ”€β”€ warp-generation.ts  # Warp map boundary generation
β”‚   β”œβ”€β”€ slice-utils.ts      # Slice math helpers (range matching, tolerance)
β”‚   β”œβ”€β”€ slice-allocator.ts  # Automatic slice positioning
β”‚   β”œβ”€β”€ slice-geometry.ts   # Slice geometry computation
β”‚   β”œβ”€β”€ projection.ts       # Coordinate system bridging
β”‚   β”œβ”€β”€ selection.ts        # Point selection utilities
β”‚   β”œβ”€β”€ trajectories.ts     # Trajectory pillar construction
β”‚   β”œβ”€β”€ downsample.ts       # Spatial/temporal downsampling
β”‚   β”œβ”€β”€ formatting.ts       # Display formatting utilities
β”‚   β”œβ”€β”€ mockData.ts         # Synthetic crime data generation
β”‚   β”œβ”€β”€ bounds.ts           # Geographic bounds computation
β”‚   β”œβ”€β”€ stats.ts            # Statistical helpers
β”‚   β”œβ”€β”€ math.ts             # Shared math utilities
β”‚   β”œβ”€β”€ palettes.ts         # Color palette definitions
β”‚   β”œβ”€β”€ constants.ts        # Shared constants
β”‚   β”œβ”€β”€ feature-flags.ts    # Feature flag definitions
β”‚   β”œβ”€β”€ category-maps.ts    # Crime category color/shape maps
β”‚   β”œβ”€β”€ category-legend.ts  # Legend label generation
β”‚   β”œβ”€β”€ category-shapes.ts  # Category shape definitions
β”‚   β”œβ”€β”€ state-machine.ts    # Workflow state machine
β”‚   └── utils.ts            # General-purpose utilities
β”‚
β”œβ”€β”€ hooks/                  # Custom React hooks
β”‚   β”œβ”€β”€ useCrimeData.ts     # TanStack Query wrapper for /api/crimes/range
β”‚   β”œβ”€β”€ useCrimeStream.ts   # Arrow IPC streaming hook
β”‚   β”œβ”€β”€ useViewportCrimeData.ts # Viewport-aware crime data fetching
β”‚   β”œβ”€β”€ useCrimePointCloud.ts   # 3D point cloud data preparation
β”‚   β”œβ”€β”€ useAdaptiveScale.ts # Adaptive scale computation
β”‚   β”œβ”€β”€ useDualTimelineScales.ts # Timeline d3 scale computation
β”‚   β”œβ”€β”€ useDebouncedDensity.ts  # Debounced density computation
β”‚   β”œβ”€β”€ useSelectionSync.ts # Cross-panel selection synchronization
β”‚   β”œβ”€β”€ useSuggestionGenerator.ts  # Interval proposal generation
β”‚   β”œβ”€β”€ useSmartProfiles.ts # Smart profile data hooks
β”‚   β”œβ”€β”€ useContextExtractor.ts   # Context extraction for selections
β”‚   β”œβ”€β”€ useSliceStats.ts    # Per-slice statistics
β”‚   β”œβ”€β”€ useDraggable.ts     # Drag interaction hook
β”‚   β”œβ”€β”€ useDebounce.ts      # Generic debounce hook
β”‚   β”œβ”€β”€ useMeasure.ts       # Element measurement hook
β”‚   β”œβ”€β”€ useLogger.ts        # LoggerService wrapper
β”‚   └── useURLFeatureFlags.ts   # URL-based feature flag overrides
β”‚
β”œβ”€β”€ types/                  # TypeScript type definitions
β”‚   β”œβ”€β”€ crime.ts            # CrimeRecord (canonical), CrimeViewport, UseCrimeDataOptions
β”‚   β”œβ”€β”€ autoProposalSet.ts  # Auto-proposal interval set types
β”‚   β”œβ”€β”€ adaptive.ts         # AdaptiveBinningMode
β”‚   β”œβ”€β”€ data.ts             # ColumnarData
β”‚   β”œβ”€β”€ suggestion.ts       # Suggestion-related types
β”‚   └── index.ts            # Re-exports
β”‚
β”œβ”€β”€ workers/                # Web Workers
β”‚   β”œβ”€β”€ adaptiveTime.worker.ts   # Density, burstiness, warp map computation
β”‚   β”œβ”€β”€ stkdeHotspot.worker.ts   # STKDE hotspot filtering/sorting
β”‚   └── kdeSlice.worker.ts       # Slice-level KDE computation
β”‚
β”œβ”€β”€ providers/              # React context providers
β”‚   └── QueryProvider.tsx   # TanStack QueryClient provider
β”‚
└── utils/                  # Utilities
    └── binning.ts          # Binning utility functions

Data Flow

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        DATA FLOW PIPELINE                          β”‚
β”‚                                                                     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”            β”‚
β”‚  β”‚  CSV Files    β”‚   β”‚  DuckDB  β”‚   β”‚  Next.js API     β”‚            β”‚
β”‚  β”‚  data/sources/│──▢│  (in-    │──▢│  Route Handlers  β”‚            β”‚
β”‚  β”‚  Crimes_...csvβ”‚   β”‚  process β”‚   β”‚  /api/*          β”‚            β”‚
β”‚  β”‚  8.5M records β”‚   β”‚  OLAP)   β”‚   β”‚                  β”‚            β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜            β”‚
β”‚                                              β”‚                      β”‚
β”‚                                     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”           β”‚
β”‚                                     β”‚  TanStack React   β”‚           β”‚
β”‚                                     β”‚  Query Hooks      β”‚           β”‚
β”‚                                     β”‚  useCrimeData()   β”‚           β”‚
β”‚                                     β”‚  useCrimeStream() β”‚           β”‚
β”‚                                     β”‚  useViewportData()β”‚           β”‚
β”‚                                     β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜           β”‚
β”‚                                              β”‚                      β”‚
β”‚                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚                              β”‚        Zustand Stores          β”‚    β”‚
β”‚                              β”‚  (filter, time, coordination,  β”‚    β”‚
β”‚                              β”‚   adaptive, slice, STKDE, etc.)β”‚    β”‚
β”‚                              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                              β”‚                      β”‚
β”‚         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”‚
β”‚         β”‚                                    β”‚                β”‚     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚     β”‚
β”‚  β”‚ Map View    β”‚  β”‚ 3D Cube View       β”‚  β”‚ Dual Timeline β”‚  β”‚     β”‚
β”‚  β”‚ (MapLibre   β”‚  β”‚ (Three.js / R3F)  β”‚  β”‚ (@visx SVG)  β”‚  β”‚     β”‚
β”‚  β”‚  + overlays)β”‚  β”‚                    β”‚  β”‚              β”‚  β”‚     β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚
                                                                     β”‚
  β–Ά All API routes serve JSON (default) or Apache Arrow streams
  β–Ά Mock data fallback: every API route detects DuckDB availability
    and returns plausible synthetic data if the database is disabled.
  β–Ά Web Workers (adaptiveTime, stkdeHotspot, kdeSlice) run
    computations off the main thread via postMessage.

DuckDB β†’ API Routes

DuckDB loads the raw CSV file (data/sources/Crimes_-_2001_to_Present_20260114.csv) in-process. A zone-map-optimized sorted table (crimes_sorted) is created on startup for efficient time-range queries. All API routes use force-dynamic and runtime: 'nodejs' to ensure DuckDB compatibility.

Key API endpoints:

Endpoint Method Purpose
/api/crime/stream GET Arrow-streaming crime data with filters
/api/crime/bins GET 3D spatial bin aggregation
/api/crime/facets GET Crime type and district facet counts
/api/crime/meta GET Dataset metadata (time range, bounds, types)
/api/crime/overview GET Sampled timeline overview data
/api/crime/stats-summary GET Statistical summaries (hour, day, month, etc.)
/api/crimes/range GET Viewport-based crime data with buffering
/api/adaptive/global GET Precomputed global density/burstiness/warp maps
/api/adaptive/bursts POST Burst detection computation
/api/stkde/hotspots POST STKDE hotspot computation (sampled or full-population)
/api/neighbourhood/poi GET Neighbourhood points of interest
/api/study/log POST Study session log ingestion (NDJSON)

API Routes β†’ React Query β†’ Components

The client uses TanStack React Query hooks (primarily useCrimeData) to fetch from API routes. Hooks pass viewport bounds and filters as query parameters, and the API applies buffering (default 30 days) for smooth panning. React Query provides caching, deduplication, and stale-while-revalidate behavior (5-minute stale time, no refetch on window focus).


Cross-View Coordination

The three primary visualization panels β€” map, cube, and timeline β€” are synchronized through a coordination architecture built on Zustand stores.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              COORDINATION ARCHITECTURE           β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚         useCoordinationStore                 β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
β”‚  β”‚  β”‚ selectedIndex / selectedSource         β”‚  β”‚ β”‚
β”‚  β”‚  β”‚ brushRange                             β”‚  β”‚ β”‚
│  │  │ workflowPhase (generate→review→applied)│  │ │
β”‚  β”‚  β”‚ syncStatus (syncing/synced/partial)   β”‚  β”‚ β”‚
β”‚  β”‚  β”‚ selectedBurstWindows                   β”‚  β”‚ β”‚
β”‚  β”‚  β”‚ panelNoMatch (per-panel error state)   β”‚  β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚
β”‚  β”‚ useFilterStoreβ”‚  β”‚ useTimeStore β”‚  β”‚ useSlice β”‚β”‚
β”‚  β”‚              β”‚  β”‚              β”‚  β”‚ Store    β”‚β”‚
β”‚  β”‚ selectedTypesβ”‚  β”‚ currentTime  β”‚  β”‚ slices[] β”‚β”‚
β”‚  β”‚ districts    β”‚  β”‚ timeRange    β”‚  β”‚ (time    β”‚β”‚
β”‚  β”‚ timeRange    β”‚  β”‚ isPlaying    β”‚  β”‚  ranges) β”‚β”‚
β”‚  β”‚ spatialBoundsβ”‚  β”‚ resolution   β”‚  β”‚          β”‚β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜β”‚
β”‚         β”‚                 β”‚                 β”‚     β”‚
β”‚         β–Ό                 β–Ό                 β–Ό     β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚         useAdaptiveStore                 β”‚    β”‚
β”‚  β”‚  warpFactor, densityMap, burstinessMap,  β”‚    β”‚
β”‚  β”‚  warpMap, mapDomain, burstThreshold,     β”‚    β”‚
β”‚  β”‚  binningMode (uniform-time/events)       β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚                                                   β”‚
β”‚  Selection flows:                                  β”‚
β”‚  1. User clicks a point in any panel               β”‚
β”‚  2. setSelectedIndex(source) fires                 β”‚
β”‚  3. commitSelection() notifies other panels        β”‚
β”‚  4. Each panel reconciles via reconcileSelection() β”‚
β”‚  5. syncStatus tracks synchronization state        β”‚
β”‚  6. panelNoMatch records failed lookups            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Register-based selection pattern: When a user selects a crime point in any panel (map click, cube raycast, timeline hover), the store records the index and source. Other panels listen to selectedIndex and reconcile β€” if a panel cannot find the matching point (e.g., due to different filter scope), it records a panelNoMatch entry and the coordination store sets syncStatus: 'partial' with a descriptive reason.

Burst window selection: The useCoordinationStore maintains selectedBurstWindows[] (max 3). The useAutoBurstSlices hook (in useSliceStore) automatically creates time slices from detected burst intervals, and useSliceStore normalizes burst slice ranges from epoch timestamps to the store's 0–100 normalized coordinate system.


Key Abstractions

1. CrimeRecord (Canonical Data Type)

  • File: src/types/crime.ts
  • Purpose: Single source of truth for all crime data across components, hooks, and API responses. Includes timestamp (epoch seconds), lat/lon (geographic), x/z (normalized to -50..+50 for the 3D cube), type, district, year, iucr.

2. Time Slice (useSliceDomainStore)

  • File: src/store/slice-domain/
  • Purpose: Represents a time selection (point or range) with isLocked, isVisible flags. Slices are stored in a normalized 0–100 coordinate system and are created manually, from bursts, or from proposals.

3. Adaptive Time Scaling

  • Files: src/store/useAdaptiveStore.ts, src/lib/adaptive-scale.ts, src/workers/adaptiveTime.worker.ts
  • Purpose: Computes density, burstiness, and warp maps from timestamp arrays. The warp map redistributes visual space so dense time intervals get more screen space and sparse intervals compress. Supports two binning modes: uniform-time (equal-width bins) and uniform-events (equal-count bins). Computation runs in a Web Worker for large datasets.

4. STKDE (Space-Time Kernel Density Estimation)

  • Files: src/lib/stkde/, src/workers/stkdeHotspot.worker.ts, src/app/api/stkde/hotspots/route.ts
  • Purpose: Detects crime hotspots across space and time. Supports two computation modes: sampled (optimized for viewport-scale data) and full-population (scans all rows via DuckDB aggregation). Falls back gracefully between modes. Full-population mode has configurable timeouts and span caps.

5. DuckDB Query Builders

  • Files: src/lib/queries/
  • Pattern: Fluent API with parameterized SQL and sanitization. buildCrimeRangeFilters() constructs WHERE clauses from filter state. buildCrimesInRangeQuery() produces complete SELECT statements with zone-map-optimized table references. All dynamic values use parameterized ? placeholders to prevent injection.

6. LoggerService

  • File: src/lib/logger.ts
  • Purpose: Batches log events and flushes periodically (every 5 seconds or every 50 events). Uses navigator.sendBeacon for reliability during page unload, with a fetch POST fallback. Logs are sent to /api/study/log and persisted as NDJSON.

7. Dashboard Layout (Resizable Panels)

  • File: src/components/layout/DashboardLayout.tsx
  • Purpose: Three-panel layout using react-resizable-panels. Left panel (map), top-right panel (3D cube), bottom panel (timeline). Layout state persisted in useLayoutStore.

Web Worker Integration

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   WEB WORKERS                        β”‚
β”‚                                                      β”‚
β”‚  adaptiveTime.worker.ts                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Input: timestamps (Float32Array), domain,    β”‚   β”‚
β”‚  β”‚        config (binCount, kernelWidth, mode)  β”‚   β”‚
β”‚  β”‚ Output: densityMap, burstinessMap,           β”‚   β”‚
β”‚  β”‚         warpMap, countMap                    β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                     β”‚                                β”‚
β”‚  stkdeHotspot.worker.ts                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Input: hotspots array + filters              β”‚   β”‚
β”‚  β”‚        (minIntensity, minSupport,            β”‚   β”‚
β”‚  β”‚         temporalWindow, spatialBbox)         β”‚   β”‚
β”‚  β”‚ Output: filtered + sorted hotspots           β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                      β”‚
β”‚  kdeSlice.worker.ts                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚ Slice-level KDE computation                   β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                      β”‚
β”‚  Workers are instantiated by stores and hooks        β”‚
β”‚  when heavy computation is needed. Results are       β”‚
β”‚  returned via postMessage and committed to stores.   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

MapLibre + Three.js Integration

The dashboard combines a 2D MapLibre GL map and a Three.js 3D space-time cube in a synchronized layout:

  • Map (left panel): MapVisualization renders a MapLibre base map with optional overlay layers: heatmap (MapHeatmapOverlay), STKDE heatmap (MapStkdeHeatmapLayer), trajectory lines (MapTrajectoryLayer), event points (MapEventLayer), cluster highlights (MapClusterHighlights), district boundaries (MapDistrictLayer), and POI markers (MapPoiLayer). A Deck.gl heatmap overlay (DeckGlHeatmapOverlay) provides GPU-accelerated heatmap rendering.

  • Cube (top-right panel): CubeVisualization renders a Three.js 3D scene via React Three Fiber (MainScene, Scene). It visualizes crime points as a 3D point cloud (DataPoints, SimpleCrimePoints, SliceCrimePoints) with time on the Y-axis, spatial coordinates on X/Z axes. The cube supports slice planes (SlicePlane), animated time planes (TimePlane), burst visualization, cluster highlights, STKDE overlays, and point inspection via raycasting.

  • Coordination: When a user selects a point or brushes a time range in either panel, the useCoordinationStore propagates the selection to the other panel. The cube uses normalized coordinates (x, z) mapped from geographic (lon, lat) via coordinate-normalization.ts, while the map uses raw geographic coordinates β€” the projection.ts utility bridges the two coordinate systems.


Adaptive Time Scaling Pipeline

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  ADAPTIVE TIME SCALING PIPELINE                  β”‚
β”‚                                                                  β”‚
β”‚  1. Raw timestamps (all crime events in viewport or globally)    β”‚
β”‚     β”‚                                                            β”‚
β”‚     β–Ό                                                            β”‚
β”‚  2. Binning (uniform-time or uniform-events)                     β”‚
β”‚     β”‚  β†’ countMap: event count per bin                          β”‚
β”‚     β–Ό                                                            β”‚
β”‚  3. Density smoothing (kernel width = 3 by default)              β”‚
β”‚     β”‚  β†’ densityMap: normalized 0..1 density per bin             β”‚
β”‚     β–Ό                                                            β”‚
β”‚  4. Burstiness computation (coefficient of variation of          β”‚
β”‚     inter-event intervals per bin)                               β”‚
β”‚     β”‚  β†’ burstinessMap: normalized 0..1 burstiness per bin       β”‚
β”‚     β–Ό                                                            β”‚
β”‚  5. Warp map (weight = 1 + density * 5, then cumulative          β”‚
β”‚     distribution β†’ redistributes screen space)                   β”‚
β”‚     β”‚  β†’ warpMap: boundary timestamps for each bin               β”‚
β”‚     β–Ό                                                            β”‚
β”‚  6. Application: d3.scaleLinear uses adaptive domain/range       β”‚
β”‚     arrays to warp the timeline axis                             β”‚
β”‚                                                                  β”‚
β”‚  Steps 1–5 run in adaptiveTime.worker.ts for large datasets.     β”‚
β”‚  Step 6 is applied in the DualTimeline component via             β”‚
β”‚  useAdaptiveScale() and useDualTimelineScales() hooks.           β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Users control adaptive behavior through useAdaptiveStore:

  • warpFactor: 0 (fully linear) to 1 (fully adaptive)
  • binningMode: uniform-time (equal-width bins) or uniform-events (equal-count bins)
  • densityScope: viewport (compute from visible data) or global (use precomputed global maps from /api/adaptive/global)
  • burstMetric: density or burstiness
  • warpGranularity: Controls the number of warp segments

DuckDB Optimization

The application uses DuckDB as a local, in-process OLAP database to query ~8.5M crime records from a CSV file:

  1. Zone map optimization: On first launch, the data is loaded into a sorted table (crimes_sorted) ordered by the Date column. DuckDB's zone maps allow it to skip irrelevant row groups when querying a time range, reducing scan overhead by up to 90% for narrow time windows.

  2. Runtime configuration: DuckDB runs in Node.js (next.config.ts β†’ serverExternalPackages: ["duckdb"]). The database path defaults to data/cache/crime.duckdb and can be overridden via DUCKDB_PATH environment variable.

  3. Mock data fallback: If DuckDB is unavailable (controlled by USE_MOCK_DATA, DISABLE_DUCKDB env vars), every API route returns plausible synthetic crime data. This allows frontend development without the full dataset.

  4. Apache Arrow streaming: The /api/crime/stream endpoint serializes query results to Apache Arrow IPC format using tableFromJSON / tableToIPC, enabling efficient columnar data transfer for large result sets. Other API endpoints use standard JSON.


Error Handling

  • API routes: Catch errors and return mock data with X-Data-Warning response header indicating the fallback reason.
  • API routes: Use NextResponse.json with appropriate status codes (400 for invalid input, 500 for server errors).
  • Stores: Handle loading and error states for async operations (isLoading, isFetching, error in hook results).
  • Coordination: The coordination store tracks panelNoMatch states per panel when a selection cannot be resolved, with descriptive reason strings.
  • STKDE pipeline: Full-population mode has timeout handling (configurable via fullPopulationTimeoutMs) with graceful fallback to sampled mode.
  • Logging: Centralized LoggerService in src/lib/logger.ts with batch-and-flush pattern and sendBeacon for page unload reliability.

References