private getHandleTarget(point: Point): RectangleHandleKey | null {
const geometry = this.getGeometry();
if (!geometry) {
return null;
}
const handleOrder: RectangleHandleKey[] = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
let nearestHandle: RectangleHandleKey | null = null;
let nearestDistance = Number.POSITIVE_INFINITY;
for (const handleName of handleOrder) {
const handle = geometry.handles[handleName];
const dx = point.x - handle.x;
const dy = point.y - handle.y;
if (Math.abs(dx) > HANDLE_HIT_TOLERANCE || Math.abs(dy) > HANDLE_HIT_TOLERANCE) {
continue;
}
const distance = dx * dx + dy * dy;
if (distance < nearestDistance) {
nearestDistance = distance;
nearestHandle = handleName;
}
}
return nearestHandle;
}
function getNearestLogicalFromTime(series: SeriesApi, time: Time): number | null {
const targetTime = getNumericTime(time);
if (targetTime === null) {
return null;
}
const points = getSeriesTimePoints(series);
if (!points.length) {
return null;
}
if (points.length === 1) {
return points[0].logical;
}
const lastIndex = points.length - 1;
if (targetTime === points[0].time) {
return points[0].logical;
}
if (targetTime === points[lastIndex].time) {
return points[lastIndex].logical;
}
if (targetTime < points[0].time) {
return projectLogicalByTime(points[0], points[1], targetTime);
}
if (targetTime > points[lastIndex].time) {
return projectLogicalByTime(points[lastIndex - 1], points[lastIndex], targetTime);
}
let left = 0;
let right = lastIndex;
while (left <= right) {
const middleIndex = Math.floor((left + right) / 2);
const middleTime = points[middleIndex].time;
if (middleTime === targetTime) {
return points[middleIndex].logical;
}
if (middleTime < targetTime) {
left = middleIndex + 1;
} else {
right = middleIndex - 1;
}
}
const previousPoint = points[right] ?? null;
const nextPoint = points[left] ?? null;
return getNearestLogicalByTime(targetTime, previousPoint, nextPoint);
}
function projectLogicalByTime(leftPoint: TimePoint, rightPoint: TimePoint, targetTime: number): number | null {
const timeRange = rightPoint.time - leftPoint.time;
if (timeRange === 0) {
return leftPoint.logical;
}
const ratio = (targetTime - leftPoint.time) / timeRange;
return leftPoint.logical + (rightPoint.logical - leftPoint.logical) * ratio;
}