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);
|
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) => {
|
const paths = languages.map((_, l) => {
|
||||||
if (weeks.length === 0) return '';
|
if (weeks.length === 0) return '';
|
||||||
// Top edge left-to-right
|
const topPts = weeks.map((_, w) => [xFor(w), y0s[w][l]] as [number, number]);
|
||||||
const top = weeks.map((_, w) => {
|
const bottomPts = weeks
|
||||||
const x = weeks.length > 1 ? (w / (weeks.length - 1)) * 100 : 50;
|
.map((_, w) => [xFor(w), y1s[w][l]] as [number, number])
|
||||||
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]}`;
|
|
||||||
})
|
|
||||||
.reverse();
|
.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
|
// Default colors for "Other" and fallback
|
||||||
@@ -176,3 +171,28 @@ export function LanguageStreamGraph() {
|
|||||||
function fmt(d: Date): string {
|
function fmt(d: Date): string {
|
||||||
return d.toISOString().slice(0, 10);
|
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