findNearestPoint static method

ChartInteractionResult? findNearestPoint(
  1. Offset tapPosition,
  2. List<ChartDataSet> dataSets,
  3. Size chartSize,
  4. double minX,
  5. double maxX,
  6. double minY,
  7. double maxY,
  8. double tapRadius,
)

Find nearest point to tap location (optimized with early exit and squared distance).

Searches through all data sets to find the point closest to the tap position within the specified tap radius. Returns null if no point is found.

Parameters:

  • tapPosition - The position where the user tapped
  • dataSets - List of chart data sets to search through
  • chartSize - Size of the chart area
  • minX, maxX, minY, maxY - Data bounds for coordinate conversion
  • tapRadius - Maximum distance from tap position to consider a hit

Returns a ChartInteractionResult if a point is found, null otherwise.

Implementation

static ChartInteractionResult? findNearestPoint(
  Offset tapPosition,
  List<ChartDataSet> dataSets,
  Size chartSize,
  double minX,
  double maxX,
  double minY,
  double maxY,
  double tapRadius,
) {
  // Early exit if no data
  if (dataSets.isEmpty) return null;

  // Validate bounds (check for NaN or Infinity)
  if (!minX.isFinite || !maxX.isFinite || !minY.isFinite || !maxY.isFinite) {
    return null;
  }

  final xRange = maxX - minX;
  final yRange = maxY - minY;
  if (xRange == 0 || yRange == 0 || !xRange.isFinite || !yRange.isFinite) {
    return null;
  }

  // Validate chart size
  if (!chartSize.width.isFinite ||
      !chartSize.height.isFinite ||
      chartSize.width <= 0 ||
      chartSize.height <= 0) {
    return null;
  }

  // Validate tap position
  if (!tapPosition.dx.isFinite || !tapPosition.dy.isFinite) {
    return null;
  }

  // Use squared distance to avoid expensive sqrt calculation
  final tapRadiusSquared = tapRadius * tapRadius;
  double minDistanceSquared = double.infinity;
  ChartInteractionResult? nearestResult;

  for (int dsIndex = 0; dsIndex < dataSets.length; dsIndex++) {
    final dataSet = dataSets[dsIndex];
    if (dataSet.dataPoints.isEmpty) continue;

    for (int ptIndex = 0; ptIndex < dataSet.dataPoints.length; ptIndex++) {
      final point = dataSet.dataPoints[ptIndex];

      // Validate point values
      if (!point.x.isFinite || !point.y.isFinite) continue;

      // Convert to canvas coordinates with NaN protection
      final canvasX = ((point.x - minX) / xRange) * chartSize.width;
      final canvasY =
          chartSize.height - ((point.y - minY) / yRange) * chartSize.height;

      // Validate calculated coordinates
      if (!canvasX.isFinite || !canvasY.isFinite) continue;

      // Quick bounds check before distance calculation
      final dx = tapPosition.dx - canvasX;
      final dy = tapPosition.dy - canvasY;

      // Validate dx and dy
      if (!dx.isFinite || !dy.isFinite) continue;

      // Early exit if point is clearly outside radius
      if (dx.abs() > tapRadius || dy.abs() > tapRadius) continue;

      // Calculate squared distance (faster than distance)
      final distanceSquared = dx * dx + dy * dy;

      // Validate distance
      if (!distanceSquared.isFinite) continue;

      if (distanceSquared < tapRadiusSquared &&
          distanceSquared < minDistanceSquared) {
        minDistanceSquared = distanceSquared;
        nearestResult = ChartInteractionResult(
          point: point,
          datasetIndex: dsIndex,
          elementIndex: ptIndex,
          isHit: true,
        );
      }
    }
  }

  return nearestResult;
}