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.
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.
| 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 |
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 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 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) |
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).
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.
- 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.
- File:
src/store/slice-domain/ - Purpose: Represents a time selection (point or range) with
isLocked,isVisibleflags. Slices are stored in a normalized 0β100 coordinate system and are created manually, from bursts, or from proposals.
- 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) anduniform-events(equal-count bins). Computation runs in a Web Worker for large datasets.
- 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) andfull-population(scans all rows via DuckDB aggregation). Falls back gracefully between modes. Full-population mode has configurable timeouts and span caps.
- 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.
- File:
src/lib/logger.ts - Purpose: Batches log events and flushes periodically (every 5 seconds or every 50 events). Uses
navigator.sendBeaconfor reliability during page unload, with a fetch POST fallback. Logs are sent to/api/study/logand persisted as NDJSON.
- 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 inuseLayoutStore.
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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. β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The dashboard combines a 2D MapLibre GL map and a Three.js 3D space-time cube in a synchronized layout:
-
Map (left panel):
MapVisualizationrenders 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):
CubeVisualizationrenders 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
useCoordinationStorepropagates the selection to the other panel. The cube uses normalized coordinates (x, z) mapped from geographic (lon, lat) viacoordinate-normalization.ts, while the map uses raw geographic coordinates β theprojection.tsutility bridges the two coordinate systems.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β 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) oruniform-events(equal-count bins) - densityScope:
viewport(compute from visible data) orglobal(use precomputed global maps from/api/adaptive/global) - burstMetric:
densityorburstiness - warpGranularity: Controls the number of warp segments
The application uses DuckDB as a local, in-process OLAP database to query ~8.5M crime records from a CSV file:
-
Zone map optimization: On first launch, the data is loaded into a sorted table (
crimes_sorted) ordered by theDatecolumn. 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. -
Runtime configuration: DuckDB runs in Node.js (
next.config.tsβserverExternalPackages: ["duckdb"]). The database path defaults todata/cache/crime.duckdband can be overridden viaDUCKDB_PATHenvironment variable. -
Mock data fallback: If DuckDB is unavailable (controlled by
USE_MOCK_DATA,DISABLE_DUCKDBenv vars), every API route returns plausible synthetic crime data. This allows frontend development without the full dataset. -
Apache Arrow streaming: The
/api/crime/streamendpoint serializes query results to Apache Arrow IPC format usingtableFromJSON/tableToIPC, enabling efficient columnar data transfer for large result sets. Other API endpoints use standard JSON.
- API routes: Catch errors and return mock data with
X-Data-Warningresponse header indicating the fallback reason. - API routes: Use
NextResponse.jsonwith appropriate status codes (400 for invalid input, 500 for server errors). - Stores: Handle loading and error states for async operations (
isLoading,isFetching,errorin hook results). - Coordination: The coordination store tracks
panelNoMatchstates 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
LoggerServiceinsrc/lib/logger.tswith batch-and-flush pattern andsendBeaconfor page unload reliability.
- See GETTING-STARTED.md for prerequisites and first run.
- See DEVELOPMENT.md for local development setup, build commands, and code style.