feat: enhance SidebarInfoTab with detailed map layers and add timeline layer functionality to CrimeMap; update TimelineLegend and TimelinePopup styles
This commit is contained in:
parent
4cc01babf1
commit
05ba4ab920
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Layers, Info, Eye, Filter, MapPin, AlertTriangle, AlertCircle } from 'lucide-react'
|
import { Layers, Info, Eye, Filter, MapPin, AlertTriangle, AlertCircle, Clock, Flame, MapPinned, Users, Map, Box, Thermometer } from 'lucide-react'
|
||||||
import { Card, CardContent } from "@/app/_components/ui/card"
|
import { Card, CardContent } from "@/app/_components/ui/card"
|
||||||
import { Separator } from "@/app/_components/ui/separator"
|
import { Separator } from "@/app/_components/ui/separator"
|
||||||
import { CRIME_RATE_COLORS } from "@/app/_utils/const/map"
|
import { CRIME_RATE_COLORS } from "@/app/_utils/const/map"
|
||||||
|
@ -44,6 +44,112 @@ export function SidebarInfoTab() {
|
||||||
</Card>
|
</Card>
|
||||||
</SidebarSection>
|
</SidebarSection>
|
||||||
|
|
||||||
|
<SidebarSection title="Map Layers" icon={<Map className="h-4 w-4 text-blue-400" />}>
|
||||||
|
<Card className="bg-gradient-to-r from-zinc-800/80 to-zinc-900/80 border border-white/10">
|
||||||
|
<CardContent className="p-4 text-xs space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="flex items-center gap-2 font-medium text-sm">
|
||||||
|
<AlertCircle className="h-4 w-4 text-cyan-400" />
|
||||||
|
<span>Incidents Layer</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-white/70 pl-6">
|
||||||
|
Shows individual crime incidents as map markers. Each marker represents a single crime report and is color-coded by category.
|
||||||
|
Click on any marker to see detailed information about the incident.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="flex items-center gap-2 font-medium text-sm">
|
||||||
|
<Box className="h-4 w-4 text-pink-400" />
|
||||||
|
<span>Clusters Layer</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-white/70 pl-6">
|
||||||
|
Groups nearby incidents into clusters for better visibility at lower zoom levels. Numbers show incident count in each cluster.
|
||||||
|
Clusters are color-coded by size: blue (small), yellow (medium), pink (large).
|
||||||
|
Click on a cluster to zoom in and see individual incidents.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-6 pl-6 pt-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-[#51bbd6]"></div>
|
||||||
|
<span>1-5</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-[#f1f075]"></div>
|
||||||
|
<span>6-15</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-4 h-4 rounded-full bg-[#f28cb1]"></div>
|
||||||
|
<span>15+</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="flex items-center gap-2 font-medium text-sm">
|
||||||
|
<Thermometer className="h-4 w-4 text-orange-400" />
|
||||||
|
<span>Heatmap Layer</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-white/70 pl-6">
|
||||||
|
Shows crime density across regions, with warmer colors (red, orange) indicating higher crime concentration
|
||||||
|
and cooler colors (blue) showing lower concentration. Useful for identifying crime hotspots.
|
||||||
|
</p>
|
||||||
|
<div className="pl-6 pt-1">
|
||||||
|
<div className="h-2 w-full rounded-full bg-gradient-to-r from-blue-600 via-yellow-400 to-red-600"></div>
|
||||||
|
<div className="flex justify-between text-[10px] mt-1 text-white/70">
|
||||||
|
<span>Low</span>
|
||||||
|
<span>Density</span>
|
||||||
|
<span>High</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="flex items-center gap-2 font-medium text-sm">
|
||||||
|
<Users className="h-4 w-4 text-blue-400" />
|
||||||
|
<span>Units Layer</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-white/70 pl-6">
|
||||||
|
Displays police and security units as blue circles with connecting lines to nearby incidents.
|
||||||
|
Units are labeled and can be clicked to show more information and related incident details.
|
||||||
|
</p>
|
||||||
|
<div className="flex items-center gap-2 pl-6 pt-1">
|
||||||
|
<div className="w-4 h-4 rounded-full border-2 border-white bg-[#1e40af]"></div>
|
||||||
|
<span>Police/Security Unit</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h4 className="flex items-center gap-2 font-medium text-sm">
|
||||||
|
<Clock className="h-4 w-4 text-yellow-400" />
|
||||||
|
<span>Timeline Layer</span>
|
||||||
|
</h4>
|
||||||
|
<p className="text-white/70 pl-6">
|
||||||
|
Shows time patterns of crime incidents with color-coded circles representing average times of day when
|
||||||
|
incidents occur in each district. Click for detailed time distribution analysis.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-wrap gap-x-5 gap-y-2 pl-6 pt-1">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FFEB3B]"></div>
|
||||||
|
<span>Morning</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#FF9800]"></div>
|
||||||
|
<span>Afternoon</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#3F51B5]"></div>
|
||||||
|
<span>Evening</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-3 h-3 rounded-full bg-[#263238]"></div>
|
||||||
|
<span>Night</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</SidebarSection>
|
||||||
|
|
||||||
<SidebarSection title="About" icon={<Info className="h-4 w-4 text-green-400" />}>
|
<SidebarSection title="About" icon={<Info className="h-4 w-4 text-green-400" />}>
|
||||||
<Card className="bg-gradient-to-r from-zinc-800/80 to-zinc-900/80 border border-white/10">
|
<Card className="bg-gradient-to-r from-zinc-800/80 to-zinc-900/80 border border-white/10">
|
||||||
<CardContent className="p-4 text-xs">
|
<CardContent className="p-4 text-xs">
|
||||||
|
|
|
@ -37,6 +37,9 @@ export default function CrimeMap() {
|
||||||
const [isTimelapsePlaying, setisTimelapsePlaying] = useState(false)
|
const [isTimelapsePlaying, setisTimelapsePlaying] = useState(false)
|
||||||
const [isSearchActive, setIsSearchActive] = useState(false)
|
const [isSearchActive, setIsSearchActive] = useState(false)
|
||||||
const [showUnitsLayer, setShowUnitsLayer] = useState(false)
|
const [showUnitsLayer, setShowUnitsLayer] = useState(false)
|
||||||
|
const [showClusters, setShowClusters] = useState(false)
|
||||||
|
const [showHeatmap, setShowHeatmap] = useState(false)
|
||||||
|
const [showUnclustered, setShowUnclustered] = useState(true)
|
||||||
const [useAllYears, setUseAllYears] = useState<boolean>(false)
|
const [useAllYears, setUseAllYears] = useState<boolean>(false)
|
||||||
const [useAllMonths, setUseAllMonths] = useState<boolean>(false)
|
const [useAllMonths, setUseAllMonths] = useState<boolean>(false)
|
||||||
|
|
||||||
|
@ -153,6 +156,18 @@ export default function CrimeMap() {
|
||||||
const handleControlChange = (controlId: ITooltips) => {
|
const handleControlChange = (controlId: ITooltips) => {
|
||||||
setActiveControl(controlId);
|
setActiveControl(controlId);
|
||||||
|
|
||||||
|
if (controlId === "clusters") {
|
||||||
|
setShowClusters(true)
|
||||||
|
} else {
|
||||||
|
setShowClusters(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (controlId === "incidents") {
|
||||||
|
setShowUnclustered(true)
|
||||||
|
} else {
|
||||||
|
setShowUnclustered(false)
|
||||||
|
}
|
||||||
|
|
||||||
if (controlId === "search") {
|
if (controlId === "search") {
|
||||||
setIsSearchActive(prev => !prev);
|
setIsSearchActive(prev => !prev);
|
||||||
}
|
}
|
||||||
|
@ -244,12 +259,19 @@ export default function CrimeMap() {
|
||||||
selectedYear={selectedYear}
|
selectedYear={selectedYear}
|
||||||
selectedMonth={selectedMonth}
|
selectedMonth={selectedMonth}
|
||||||
/>
|
/>
|
||||||
<div className="absolute bottom-20 right-0 z-10 p-2">
|
{isFullscreen && (
|
||||||
<MapLegend position="bottom-right" />
|
<div className="absolute bottom-20 right-0 z-20 p-2">
|
||||||
</div>
|
{showClusters && (
|
||||||
|
<MapLegend position="bottom-right" />
|
||||||
|
)}
|
||||||
|
{showUnclustered && !showClusters && (
|
||||||
|
<MapLegend position="bottom-right" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{isFullscreen && showUnitsLayer && (
|
{isFullscreen && showUnitsLayer && (
|
||||||
<div className="absolute bottom-40 right-0 z-10 p-2">
|
<div className="absolute bottom-20 right-0 z-10 p-2">
|
||||||
<UnitsLegend
|
<UnitsLegend
|
||||||
categories={categories}
|
categories={categories}
|
||||||
position="bottom-right"
|
position="bottom-right"
|
||||||
|
@ -258,7 +280,7 @@ export default function CrimeMap() {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{isFullscreen && showTimelineLayer && (
|
{isFullscreen && showTimelineLayer && (
|
||||||
<div className="absolute bottom-40 right-0 z-10 p-2">
|
<div className="absolute flex bottom-20 right-0 z-10 p-2">
|
||||||
<TimelineLegend position="bottom-right" />
|
<TimelineLegend position="bottom-right" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { Card } from "@/app/_components/ui/card"
|
import { Card } from "@/app/_components/ui/card"
|
||||||
import { Clock, Moon, Sun } from "lucide-react"
|
import { Clock, Moon, Sun, Sunset } from "lucide-react"
|
||||||
|
|
||||||
interface TimelineLegendProps {
|
interface TimelineLegendProps {
|
||||||
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left"
|
position?: "top-right" | "top-left" | "bottom-right" | "bottom-left"
|
||||||
|
@ -18,36 +18,40 @@ export default function TimelineLegend({
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card className={`absolute z-10 bg-black/80 border-gray-700 shadow-lg p-3 ${positionClasses[position]}`}>
|
<Card className={`flex z-10 bg-black/80 border-gray-700 shadow-lg p-4 ${positionClasses[position]}`}>
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-3">
|
||||||
<h3 className="text-sm font-medium text-white mb-2 flex items-center gap-2">
|
<h3 className="text-sm font-medium text-white flex items-center gap-2">
|
||||||
<Clock className="h-4 w-4" />
|
<Clock className="h-4 w-4" />
|
||||||
<span>Incident Time Patterns</span>
|
<span>Incident Time Patterns</span>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-2">
|
<div className="grid grid-cols-2 gap-y-3">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Sun className="h-4 w-4 text-[#FFEB3B]" />
|
||||||
<div className="w-3 h-3 rounded-full bg-[#FFEB3B]"></div>
|
<div className="w-3 h-3 rounded-full bg-[#FFEB3B]"></div>
|
||||||
<span className="text-xs text-white">Morning (5am-12pm)</span>
|
<span className="text-xs text-white">Morning (5am-12pm)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Sun className="h-4 w-4 text-[#FF9800]" />
|
||||||
<div className="w-3 h-3 rounded-full bg-[#FF9800]"></div>
|
<div className="w-3 h-3 rounded-full bg-[#FF9800]"></div>
|
||||||
<span className="text-xs text-white">Afternoon (12pm-5pm)</span>
|
<span className="text-xs text-white">Afternoon (12pm-5pm)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Sunset className="h-4 w-4 text-[#3F51B5]" />
|
||||||
<div className="w-3 h-3 rounded-full bg-[#3F51B5]"></div>
|
<div className="w-3 h-3 rounded-full bg-[#3F51B5]"></div>
|
||||||
<span className="text-xs text-white">Evening (5pm-9pm)</span>
|
<span className="text-xs text-white">Evening (5pm-9pm)</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
|
<Moon className="h-4 w-4 text-gray-400" />
|
||||||
<div className="w-3 h-3 rounded-full bg-[#263238]"></div>
|
<div className="w-3 h-3 rounded-full bg-[#263238]"></div>
|
||||||
<span className="text-xs text-white">Night (9pm-5am)</span>
|
<span className="text-xs text-white">Night (9pm-5am)</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-xs text-gray-400 mt-1">
|
<div className="text-xs text-gray-300 mt-1 border-t border-gray-700 pt-2">
|
||||||
Circles show average incident time. Click for details.
|
Circles show average incident time. Click for details.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -59,7 +59,7 @@ export default function TimelinePopup({
|
||||||
anchor="bottom"
|
anchor="bottom"
|
||||||
closeOnClick={false}
|
closeOnClick={false}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
className="z-10"
|
className="timeline-popup z-10"
|
||||||
maxWidth="300px"
|
maxWidth="300px"
|
||||||
>
|
>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
|
|
Loading…
Reference in New Issue