@mui/x-charts
Version:
The community edition of MUI X Charts components.
129 lines (119 loc) • 3.05 kB
JavaScript
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;
}