feat(ui): smooth language stream graph with Catmull-Rom splines
Replace straight line segments with cubic bezier curves using Catmull-Rom to bezier conversion (tension 0.5) for a smoother stream graph visualization. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -99,22 +99,17 @@ export function LanguageStreamGraph() {
|
||||
y1s.push(wy1);
|
||||
}
|
||||
|
||||
// Build SVG paths for each language layer
|
||||
// Build SVG paths for each language layer using smooth curves
|
||||
const xFor = (w: number) =>
|
||||
weeks.length > 1 ? (w / (weeks.length - 1)) * 100 : 50;
|
||||
|
||||
const paths = languages.map((_, l) => {
|
||||
if (weeks.length === 0) return '';
|
||||
// Top edge left-to-right
|
||||
const top = weeks.map((_, w) => {
|
||||
const x = weeks.length > 1 ? (w / (weeks.length - 1)) * 100 : 50;
|
||||
return `${x},${y0s[w][l]}`;
|
||||
});
|
||||
// Bottom edge right-to-left
|
||||
const bottom = weeks
|
||||
.map((_, w) => {
|
||||
const x = weeks.length > 1 ? (w / (weeks.length - 1)) * 100 : 50;
|
||||
return `${x},${y1s[w][l]}`;
|
||||
})
|
||||
const topPts = weeks.map((_, w) => [xFor(w), y0s[w][l]] as [number, number]);
|
||||
const bottomPts = weeks
|
||||
.map((_, w) => [xFor(w), y1s[w][l]] as [number, number])
|
||||
.reverse();
|
||||
return `M${top.join(' L')} L${bottom.join(' L')} Z`;
|
||||
return `M${topPts[0][0]},${topPts[0][1]} ${smoothLine(topPts)} L${bottomPts[0][0]},${bottomPts[0][1]} ${smoothLine(bottomPts)} Z`;
|
||||
});
|
||||
|
||||
// Default colors for "Other" and fallback
|
||||
@@ -176,3 +171,28 @@ export function LanguageStreamGraph() {
|
||||
function fmt(d: Date): string {
|
||||
return d.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/** Convert a series of points into smooth cubic bezier curve commands.
|
||||
* Uses Catmull-Rom to Bezier conversion with tension 0.5. */
|
||||
function smoothLine(pts: [number, number][]): string {
|
||||
if (pts.length < 2) return '';
|
||||
if (pts.length === 2)
|
||||
return `L${pts[1][0]},${pts[1][1]}`;
|
||||
|
||||
const commands: string[] = [];
|
||||
for (let i = 1; i < pts.length; i++) {
|
||||
const p0 = pts[Math.max(i - 2, 0)];
|
||||
const p1 = pts[i - 1];
|
||||
const p2 = pts[i];
|
||||
const p3 = pts[Math.min(i + 1, pts.length - 1)];
|
||||
|
||||
const t = 0.5;
|
||||
const cp1x = p1[0] + (p2[0] - p0[0]) * t / 3;
|
||||
const cp1y = p1[1] + (p2[1] - p0[1]) * t / 3;
|
||||
const cp2x = p2[0] - (p3[0] - p1[0]) * t / 3;
|
||||
const cp2y = p2[1] - (p3[1] - p1[1]) * t / 3;
|
||||
|
||||
commands.push(`C${cp1x},${cp1y} ${cp2x},${cp2y} ${p2[0]},${p2[1]}`);
|
||||
}
|
||||
return commands.join(' ');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user