UNPKG

@mui/x-charts

Version:

The community edition of MUI X Charts components.

129 lines (119 loc) 3.05 kB
import { getCurveFactory } from "../../internals/getCurve.mjs"; /** * A straight line segment. */ /** * A cubic bezier segment with control points. */ function isBezierSegment(segment) { return 'cpx1' in segment; } /** * A minimal d3 path context that captures line/bezier segments * instead of producing an SVG path string. */ class SegmentCapture { segments = []; cx = 0; cy = 0; moveTo(x, y) { this.cx = x; this.cy = y; } lineTo(x, y) { this.segments.push({ x0: this.cx, y0: this.cy, x1: x, y1: y }); this.cx = x; this.cy = y; } bezierCurveTo(cpx1, cpy1, cpx2, cpy2, x, y) { this.segments.push({ x0: this.cx, y0: this.cy, cpx1, cpy1, cpx2, cpy2, x1: x, y1: y }); this.cx = x; this.cy = y; } closePath() {} } /** Evaluate a cubic Bezier at parameter t. */ function cubicBezier(t, p0, p1, p2, p3) { const mt = 1 - t; return mt * mt * mt * p0 + 3 * mt * mt * t * p1 + 3 * mt * t * t * p2 + t * t * t * p3; } /** * Find parameter t such that the segment's x(t) ≈ targetX using bisection. * 20 iterations gives ~1e-6 precision relative to the segment's x range. */ function findTForX(segment, targetX) { if (!isBezierSegment(segment)) { // Linear segment. const dx = segment.x1 - segment.x0; return dx === 0 ? 0 : (targetX - segment.x0) / dx; } // Cubic bezier — bisect. let lo = 0; let hi = 1; for (let iter = 0; iter < 20; iter += 1) { const mid = (lo + hi) / 2; const x = cubicBezier(mid, segment.x0, segment.cpx1, segment.cpx2, segment.x1); if (x < targetX) { lo = mid; } else { hi = mid; } if (Math.abs(x - targetX) < 1) { return (lo + hi) / 2; } } return (lo + hi) / 2; } /** Evaluate the segment's y at parameter t. */ function evaluateSegmentY(segment, t) { if (!isBezierSegment(segment)) { return segment.y0 + t * (segment.y1 - segment.y0); } return cubicBezier(t, segment.y0, segment.cpy1, segment.cpy2, segment.y1); } /** * Build the curve segments for a set of pixel-coordinate points * using d3's curve factory, then evaluate y at the given pixel x. * * Returns null if targetX is outside the curve's x range. */ export function evaluateCurveY(points, targetX, curveType) { if (points.length === 0) { return null; } if (points.length === 1) { return points[0].y; } const capture = new SegmentCapture(); const factory = getCurveFactory(curveType); const curveInstance = factory(capture); curveInstance.lineStart(); for (const p of points) { curveInstance.point(p.x, p.y); } curveInstance.lineEnd(); // Find the segment containing targetX. for (const segment of capture.segments) { const xMin = Math.min(segment.x0, segment.x1); const xMax = Math.max(segment.x0, segment.x1); if (targetX >= xMin - 0.5 && targetX <= xMax + 0.5) { const t = findTForX(segment, targetX); return evaluateSegmentY(segment, t); } } return null; }