{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ink/line-chart",
  "title": "Line Chart",
  "description": "ASCII line chart with axes and multi-series support",
  "dependencies": [
    "ink"
  ],
  "registryDependencies": [
    "https://termcn.dev/r/ink/accessibility.json",
    "https://termcn.dev/r/ink/terminal-symbols.json",
    "https://termcn.dev/r/ink/use-theme.json",
    "https://termcn.dev/r/ink/use-unicode.json"
  ],
  "files": [
    {
      "path": "registry/ui/line-chart.tsx",
      "content": "import { Box, Text } from \"ink\";\n\nimport { useTheme } from \"@/hooks/use-theme\";\nimport { useUnicode } from \"@/hooks/use-unicode\";\nimport { summarizeSeries } from \"@/lib/accessibility\";\nimport { resolveTerminalSymbol } from \"@/lib/terminal-symbols\";\n\nexport type LineChartDataPoint = number | { label?: string; value: number };\n\nexport interface LineChartProps {\n  data: LineChartDataPoint[];\n  width?: number;\n  height?: number;\n  title?: string;\n  color?: string;\n  showAxes?: boolean;\n  accessibleSummary?: string;\n  \"aria-label\"?: string;\n}\n\nconst getValue = (d: LineChartDataPoint): number =>\n  typeof d === \"number\" ? d : d.value;\n\nconst getLabel = (d: LineChartDataPoint): string =>\n  typeof d === \"number\" ? \"\" : (d.label ?? \"\");\n\nconst normalize = (\n  value: number,\n  min: number,\n  max: number,\n  rows: number\n): number => {\n  if (max === min) {\n    return Math.floor(rows / 2);\n  }\n  return Math.round(((value - min) / (max - min)) * (rows - 1));\n};\n\nconst PLOT_CHAR = \"●\";\nconst _CONNECT_H = \"─\";\nconst CONNECT_UP = \"╱\";\nconst CONNECT_DOWN = \"╲\";\nconst _CONNECT_FLAT = \"─\";\nconst AXIS_V = \"│\";\nconst AXIS_H = \"─\";\nconst AXIS_CORNER = \"└\";\nconst AXIS_TICK_V = \"┤\";\nconst _AXIS_TICK_H = \"┬\";\n\nexport const LineChart = ({\n  data,\n  width = 40,\n  height = 10,\n  title,\n  color,\n  showAxes = true,\n  accessibleSummary,\n  \"aria-label\": ariaLabel,\n}: LineChartProps) => {\n  const theme = useTheme();\n  const unicode = useUnicode();\n  const resolvedColor = color ?? theme.colors.primary;\n  const plotChar = resolveTerminalSymbol(unicode, PLOT_CHAR, \"*\");\n  const connectUp = resolveTerminalSymbol(unicode, CONNECT_UP, \"/\");\n  const connectDown = resolveTerminalSymbol(unicode, CONNECT_DOWN, \"\\\\\");\n  const axisVertical = resolveTerminalSymbol(unicode, AXIS_V, \"|\");\n  const axisHorizontal = resolveTerminalSymbol(unicode, AXIS_H, \"-\");\n  const axisCorner = resolveTerminalSymbol(unicode, AXIS_CORNER, \"+\");\n  const axisTickVertical = resolveTerminalSymbol(unicode, AXIS_TICK_V, \"+\");\n\n  if (data.length === 0) {\n    return <Text color={theme.colors.mutedForeground}>No data</Text>;\n  }\n\n  const values = data.map(getValue);\n  const minVal = Math.min(...values);\n  const maxVal = Math.max(...values);\n  const chartSummary =\n    accessibleSummary ??\n    summarizeSeries(\n      title ?? \"Line chart\",\n      data.map((point, index) => ({\n        label: getLabel(point) || `Point ${index + 1}`,\n        value: getValue(point),\n      })),\n      12\n    );\n\n  const yAxisWidth = showAxes ? String(Math.round(maxVal)).length + 2 : 0;\n  const chartWidth = Math.max(4, width - yAxisWidth);\n\n  const numPoints = data.length;\n  const sampledIndices = Array.from({ length: chartWidth }, (_, i) =>\n    Math.round((i / (chartWidth - 1)) * (numPoints - 1))\n  );\n  const sampledValues = sampledIndices.map((si) => values[si] ?? 0);\n  const sampledData = sampledIndices.map((si) => data[si]);\n\n  const grid: string[][] = Array.from({ length: height }, () =>\n    Array.from({ length: chartWidth }, () => \" \")\n  );\n\n  const normalizedRows = sampledValues.map(\n    (v) => height - 1 - normalize(v, minVal, maxVal, height)\n  );\n\n  for (let col = 0; col < chartWidth; col += 1) {\n    const row = normalizedRows[col];\n    grid[row][col] = plotChar;\n\n    if (col < chartWidth - 1) {\n      const nextRow = normalizedRows[col + 1] ?? row;\n      if (nextRow < row) {\n        let r = row - 1;\n        while (r > nextRow) {\n          grid[r][col] = axisVertical;\n          r -= 1;\n        }\n        if (grid[nextRow][col] === \" \") {\n          grid[nextRow][col] = connectUp;\n        }\n      } else {\n        let r = row + 1;\n        while (r < nextRow) {\n          grid[r][col] = axisVertical;\n          r += 1;\n        }\n        if (grid[nextRow][col] === \" \") {\n          grid[nextRow][col] = connectDown;\n        }\n      }\n    }\n  }\n\n  const yLabels = Array.from({ length: height }, (_, i) => {\n    const rowVal =\n      minVal + ((height - 1 - i) / (height - 1)) * (maxVal - minVal);\n    if (i === 0 || i === Math.floor(height / 2) || i === height - 1) {\n      return String(Math.round(rowVal));\n    }\n    return \"\";\n  });\n\n  return (\n    <Box flexDirection=\"column\" aria-label={ariaLabel ?? chartSummary}>\n      {title && (\n        <Text bold color={theme.colors.primary}>\n          {title}\n        </Text>\n      )}\n      {grid.map((row, rowIdx) => (\n        <Box key={rowIdx} flexDirection=\"row\">\n          {showAxes && (\n            <Text color={theme.colors.mutedForeground}>\n              {String(yLabels[rowIdx] ?? \"\").padStart(yAxisWidth - 1)}\n              {axisTickVertical}\n            </Text>\n          )}\n          {row.map((cell, colIdx) => {\n            const isPlot = cell === plotChar;\n            return (\n              <Text\n                key={colIdx}\n                color={isPlot ? resolvedColor : theme.colors.mutedForeground}\n              >\n                {cell}\n              </Text>\n            );\n          })}\n        </Box>\n      ))}\n      {showAxes && (\n        <Box flexDirection=\"row\">\n          <Text color={theme.colors.mutedForeground}>\n            {\" \".repeat(yAxisWidth - 1)}\n            {axisCorner}\n            {axisHorizontal.repeat(chartWidth)}\n          </Text>\n        </Box>\n      )}\n      {showAxes && (\n        <Box flexDirection=\"row\">\n          <Text color={theme.colors.mutedForeground}>\n            {\" \".repeat(yAxisWidth)}\n          </Text>\n          {sampledData.map((d, idx) => {\n            const lbl = getLabel(d);\n            if (\n              idx === 0 ||\n              idx === Math.floor(chartWidth / 2) ||\n              idx === chartWidth - 1\n            ) {\n              return (\n                <Text key={idx} color={theme.colors.mutedForeground}>\n                  {lbl || String(idx)}\n                </Text>\n              );\n            }\n            return <Text key={idx}> </Text>;\n          })}\n        </Box>\n      )}\n    </Box>\n  );\n};\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "charts"
  ],
  "type": "registry:ui"
}