{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dotted-map",
  "type": "registry:ui",
  "title": "Dotted Map",
  "description": "A component with a dotted map.",
  "dependencies": [
    "svg-dotted-map"
  ],
  "files": [
    {
      "path": "registry/magicui/dotted-map.tsx",
      "content": "import * as React from \"react\"\nimport { createMap } from \"svg-dotted-map\"\n\nimport { cn } from \"@/lib/utils\"\n\nexport interface Marker {\n  lat: number\n  lng: number\n  size?: number\n  pulse?: boolean\n}\n\n/** addMarkers returns markers with lat/lng removed; only x, y and other props (e.g. size) remain */\ntype MapMarker<M extends Marker> = Omit<M, \"lat\" | \"lng\"> & {\n  x: number\n  y: number\n}\n\nexport interface DottedMapProps<\n  M extends Marker = Marker,\n> extends React.SVGProps<SVGSVGElement> {\n  width?: number\n  height?: number\n  mapSamples?: number\n  markers?: M[]\n  dotColor?: string\n  markerColor?: string\n  dotRadius?: number\n  stagger?: boolean\n  pulse?: boolean\n\n  renderMarkerOverlay?: (args: {\n    marker: MapMarker<M>\n    index: number\n    x: number\n    y: number\n    r: number\n  }) => React.ReactNode\n}\n\nexport function DottedMap<M extends Marker = Marker>({\n  width = 150,\n  height = 75,\n  mapSamples = 5000,\n  markers = [],\n  dotColor = \"currentColor\",\n  markerColor = \"#FF6900\",\n  dotRadius = 0.2,\n  stagger = true,\n  pulse = false,\n  renderMarkerOverlay,\n  className,\n  style,\n  ...svgProps\n}: DottedMapProps<M>) {\n  const { points, addMarkers } = createMap({\n    width,\n    height,\n    mapSamples,\n  })\n  const processedMarkers = addMarkers(markers)\n\n  // Compute stagger helpers in a single, simple pass\n  const { xStep, yToRowIndex } = React.useMemo(() => {\n    const sorted = [...points].sort((a, b) => a.y - b.y || a.x - b.x)\n    const rowMap = new Map<number, number>()\n    let step = 0\n    let prevY = Number.NaN\n    let prevXInRow = Number.NaN\n\n    for (const p of sorted) {\n      if (p.y !== prevY) {\n        // new row\n        prevY = p.y\n        prevXInRow = Number.NaN\n        if (!rowMap.has(p.y)) rowMap.set(p.y, rowMap.size)\n      }\n      if (!Number.isNaN(prevXInRow)) {\n        const delta = p.x - prevXInRow\n        if (delta > 0) step = step === 0 ? delta : Math.min(step, delta)\n      }\n      prevXInRow = p.x\n    }\n\n    return { xStep: step || 1, yToRowIndex: rowMap }\n  }, [points])\n\n  return (\n    <svg\n      viewBox={`0 0 ${width} ${height}`}\n      className={cn(\"text-gray-500 dark:text-gray-500\", className)}\n      style={{ width: \"100%\", height: \"100%\", ...style }}\n      {...svgProps}\n    >\n      {points.map((point, index) => {\n        const rowIndex = yToRowIndex.get(point.y) ?? 0\n        const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0\n        return (\n          <circle\n            cx={point.x + offsetX}\n            cy={point.y}\n            r={dotRadius}\n            fill={dotColor}\n            key={`${point.x}-${point.y}-${index}`}\n          />\n        )\n      })}\n\n      {processedMarkers.map((marker, index) => {\n        const rowIndex = yToRowIndex.get(marker.y) ?? 0\n        const offsetX = stagger && rowIndex % 2 === 1 ? xStep / 2 : 0\n\n        const x = marker.x + offsetX\n        const y = marker.y\n        const r = marker.size ?? dotRadius\n        const shouldPulse = pulse\n          ? marker.pulse !== false\n          : marker.pulse === true\n        const pulseTo = r * 2.8\n\n        return (\n          <g key={`${marker.x}-${marker.y}-${index}`}>\n            <circle cx={x} cy={y} r={r} fill={markerColor} />\n\n            {shouldPulse ? (\n              <g pointerEvents=\"none\">\n                <circle\n                  cx={x}\n                  cy={y}\n                  r={r}\n                  fill=\"none\"\n                  stroke={markerColor}\n                  strokeOpacity={1}\n                  strokeWidth={0.35}\n                >\n                  <animate\n                    attributeName=\"r\"\n                    values={`${r};${pulseTo}`}\n                    dur=\"1.4s\"\n                    repeatCount=\"indefinite\"\n                  />\n                  <animate\n                    attributeName=\"opacity\"\n                    values=\"1;0\"\n                    dur=\"1.4s\"\n                    repeatCount=\"indefinite\"\n                  />\n                </circle>\n                <circle\n                  cx={x}\n                  cy={y}\n                  r={r}\n                  fill=\"none\"\n                  stroke={markerColor}\n                  strokeOpacity={0.9}\n                  strokeWidth={0.3}\n                >\n                  <animate\n                    attributeName=\"r\"\n                    values={`${r};${pulseTo}`}\n                    dur=\"1.4s\"\n                    begin=\"0.7s\"\n                    repeatCount=\"indefinite\"\n                  />\n                  <animate\n                    attributeName=\"opacity\"\n                    values=\"0.9;0\"\n                    dur=\"1.4s\"\n                    begin=\"0.7s\"\n                    repeatCount=\"indefinite\"\n                  />\n                </circle>\n              </g>\n            ) : null}\n\n            {renderMarkerOverlay?.({\n              marker: { ...marker, x, y },\n              index,\n              x,\n              y,\n              r,\n            })}\n          </g>\n        )\n      })}\n    </svg>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}