{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "liquid-path", "title": "Liquid Path", "description": "Scale, smoothing and arc helpers shared by the liquid chart components.", "files": [ { "path": "src/lib/liquid-path.ts", "content": "/**\n * Path helpers shared by the liquid chart components. Deliberately dependency\n * free — these charts are small enough that a plotting library would cost more\n * than it saves, and the reference set's charts are decorative-but-honest\n * glyphs rather than analytical plots.\n */\n\nexport type Point = { x: number; y: number }\n\n/** Maps raw values into a box, with an optional padded domain. */\nexport function scale(\n values: number[],\n width: number,\n height: number,\n opts: { pad?: number; min?: number; max?: number } = {},\n): Point[] {\n const { pad = 0 } = opts\n const lo = opts.min ?? Math.min(...values)\n const hi = opts.max ?? Math.max(...values)\n const span = hi - lo || 1\n const inner = height - pad * 2\n const step = values.length > 1 ? width / (values.length - 1) : 0\n\n return values.map((v, i) => ({\n x: i * step,\n y: pad + inner - ((v - lo) / span) * inner,\n }))\n}\n\n/** Straight polyline. */\nexport function linePath(points: Point[]): string {\n if (!points.length) return \"\"\n return points.map((p, i) => `${i ? \"L\" : \"M\"}${p.x.toFixed(2)} ${p.y.toFixed(2)}`).join(\" \")\n}\n\n/**\n * Catmull-Rom converted to cubic beziers. `tension` at 0 is a straight join;\n * higher values round the shoulders. The reference sparklines sit near 0.5.\n */\nexport function smoothPath(points: Point[], tension = 0.5): string {\n if (points.length < 3) return linePath(points)\n const t = tension / 6\n let d = `M${points[0].x.toFixed(2)} ${points[0].y.toFixed(2)}`\n\n for (let i = 0; i < points.length - 1; i++) {\n const p0 = points[i - 1] ?? points[i]\n const p1 = points[i]\n const p2 = points[i + 1]\n const p3 = points[i + 2] ?? p2\n const c1 = { x: p1.x + (p2.x - p0.x) * t, y: p1.y + (p2.y - p0.y) * t }\n const c2 = { x: p2.x - (p3.x - p1.x) * t, y: p2.y - (p3.y - p1.y) * t }\n d += ` C${c1.x.toFixed(2)} ${c1.y.toFixed(2)} ${c2.x.toFixed(2)} ${c2.y.toFixed(2)} ${p2.x.toFixed(2)} ${p2.y.toFixed(2)}`\n }\n return d\n}\n\n/** Closes a line down to the baseline so it can be filled. */\nexport function areaPath(points: Point[], height: number, d: string): string {\n if (!points.length) return \"\"\n const first = points[0]\n const last = points[points.length - 1]\n return `${d} L${last.x.toFixed(2)} ${height} L${first.x.toFixed(2)} ${height} Z`\n}\n\n/** Arc along a circle, in degrees, clockwise from 3 o'clock. */\nexport function arcPath(\n cx: number,\n cy: number,\n r: number,\n startDeg: number,\n endDeg: number,\n): string {\n const rad = (d: number) => (d * Math.PI) / 180\n const x1 = cx + r * Math.cos(rad(startDeg))\n const y1 = cy + r * Math.sin(rad(startDeg))\n const x2 = cx + r * Math.cos(rad(endDeg))\n const y2 = cy + r * Math.sin(rad(endDeg))\n const large = Math.abs(endDeg - startDeg) > 180 ? 1 : 0\n const sweep = endDeg > startDeg ? 1 : 0\n return `M${x1.toFixed(2)} ${y1.toFixed(2)} A${r} ${r} 0 ${large} ${sweep} ${x2.toFixed(2)} ${y2.toFixed(2)}`\n}\n\nexport const clamp = (v: number, lo: number, hi: number) => Math.min(hi, Math.max(lo, v))\n", "type": "registry:lib" } ], "type": "registry:lib" }