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. Uses squared distance calculations for better performance and includes comprehensive input validation.

Algorithm

  1. Validates all inputs (bounds, size, position)
  2. Converts each data point to canvas coordinates
  3. Uses quick bounds check before distance calculation
  4. Calculates squared distance (avoids sqrt)
  5. Returns the nearest point within radius

Performance

  • O(n) where n is total number of points across all datasets
  • Early exit for points clearly outside radius
  • Squared distance avoids expensive sqrt operation

Parameters:

  • tapPosition - The position where the user tapped (in chart coordinates)
  • dataSets - List of chart data sets to search through (must not be empty)
  • chartSize - Size of the chart area (must be positive and finite)
  • minX, maxX, minY, maxY - Data bounds for coordinate conversion (must be finite)
  • tapRadius - Maximum distance from tap position to consider a hit (must be positive)

Returns a ChartInteractionResult if a point is found within tapRadius, null otherwise. The result includes the point, dataset index, and element index.

Example

final result = ChartInteractionHelper.findNearestPoint(
  Offset(100, 150),
  dataSets,
  Size(400, 300),
  0, 100, 0, 50,
  20.0,
);

if (result != null && result.isHit) {
  print('Found point: ${result.point!.y}');
}

Throws no exceptions, but returns null for invalid inputs.

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 inputs using helper methods
  if (!_isValidSize(chartSize) ||
      !_isValidPosition(tapPosition) ||
      !_isValidBounds(minX, maxX, minY, maxY) ||
      !_isValidPositiveValue(tapRadius)) {
    return null;
  }

  // Pre-calculate squared radius to avoid repeated multiplication
  final tapRadiusSquared = tapRadius * tapRadius;
  double minDistanceSquared = double.infinity;
  ChartInteractionResult? nearestResult;

  for (int dsIndex = 0; dsIndex < dataSets.length; dsIndex++) {
    final dataSet = dataSets[dsIndex];
    final point = dataSet.dataPoint;

    // Convert to canvas coordinates using helper
    final canvasPoint = _toCanvasCoordinates(
      point,
      chartSize,
      minX,
      maxX,
      minY,
      maxY,
    );
    if (canvasPoint == null) continue;

    // Quick bounds check before distance calculation
    if (!_isWithinQuickBounds(tapPosition, canvasPoint, tapRadius)) {
      continue;
    }

    // Calculate squared distance using helper
    final distanceSquared = _squaredDistance(tapPosition, canvasPoint);
    if (distanceSquared == null) continue;

    // Update nearest result if closer
    if (distanceSquared < tapRadiusSquared &&
        distanceSquared < minDistanceSquared) {
      minDistanceSquared = distanceSquared;
      nearestResult = ChartInteractionResult(
        point: point,
        datasetIndex: dsIndex,
        elementIndex: 0,
        isHit: true,
      );
    }
  }

  return nearestResult;
}