import SwiftUI struct SprayChartHit: Identifiable { let id: String let coordX: Double let coordY: Double let event: String? var color: Color { switch event?.lowercased() { case "single": return .blue case "double": return .green case "triple": return .orange case "home_run", "home run": return .red default: return .white.opacity(0.4) } } var label: String { switch event?.lowercased() { case "single": return "1B" case "double": return "2B" case "triple": return "3B" case "home_run", "home run": return "HR" default: return "Out" } } } struct SprayChartView: View { let hits: [SprayChartHit] var size: CGFloat = 280 // MLB API coordinate system: home plate ~(125, 200), field extends upward // coordX: 0-250 (left to right), coordY: 0-250 (top to bottom, lower Y = deeper) private let coordRange: ClosedRange = 0...250 private let homePlate = CGPoint(x: 125, y: 200) var body: some View { Canvas { context, canvasSize in let w = canvasSize.width let h = canvasSize.height let scale = min(w, h) / 250.0 let homeX = homePlate.x * scale let homeY = homePlate.y * scale // Draw foul lines from home plate let foulLineLength: CGFloat = 200 * scale // Left field foul line (~135 degrees from horizontal) let leftEnd = CGPoint( x: homeX - foulLineLength * cos(.pi / 4), y: homeY - foulLineLength * sin(.pi / 4) ) var leftLine = Path() leftLine.move(to: CGPoint(x: homeX, y: homeY)) leftLine.addLine(to: leftEnd) context.stroke(leftLine, with: .color(.white.opacity(0.15)), lineWidth: 1) // Right field foul line let rightEnd = CGPoint( x: homeX + foulLineLength * cos(.pi / 4), y: homeY - foulLineLength * sin(.pi / 4) ) var rightLine = Path() rightLine.move(to: CGPoint(x: homeX, y: homeY)) rightLine.addLine(to: rightEnd) context.stroke(rightLine, with: .color(.white.opacity(0.15)), lineWidth: 1) // Outfield arc var arc = Path() arc.addArc( center: CGPoint(x: homeX, y: homeY), radius: foulLineLength, startAngle: .degrees(-135), endAngle: .degrees(-45), clockwise: false ) context.stroke(arc, with: .color(.white.opacity(0.1)), lineWidth: 1) // Infield arc (smaller) var infieldArc = Path() let infieldRadius: CGFloat = 60 * scale infieldArc.addArc( center: CGPoint(x: homeX, y: homeY), radius: infieldRadius, startAngle: .degrees(-135), endAngle: .degrees(-45), clockwise: false ) context.stroke(infieldArc, with: .color(.white.opacity(0.08)), lineWidth: 0.5) // Infield diamond let baseDistance: CGFloat = 40 * scale let first = CGPoint(x: homeX + baseDistance * cos(.pi / 4), y: homeY - baseDistance * sin(.pi / 4)) let second = CGPoint(x: homeX, y: homeY - baseDistance * sqrt(2)) let third = CGPoint(x: homeX - baseDistance * cos(.pi / 4), y: homeY - baseDistance * sin(.pi / 4)) var diamond = Path() diamond.move(to: CGPoint(x: homeX, y: homeY)) diamond.addLine(to: first) diamond.addLine(to: second) diamond.addLine(to: third) diamond.closeSubpath() context.stroke(diamond, with: .color(.white.opacity(0.12)), lineWidth: 0.5) // Home plate marker let plateSize: CGFloat = 4 let plateRect = CGRect(x: homeX - plateSize, y: homeY - plateSize, width: plateSize * 2, height: plateSize * 2) context.fill(Path(plateRect), with: .color(.white.opacity(0.3))) // Draw hit dots for hit in hits { let x = hit.coordX * scale let y = hit.coordY * scale let dotRadius: CGFloat = 5 let dotRect = CGRect(x: x - dotRadius, y: y - dotRadius, width: dotRadius * 2, height: dotRadius * 2) context.fill(Circle().path(in: dotRect), with: .color(hit.color)) context.stroke(Circle().path(in: dotRect), with: .color(.white.opacity(0.2)), lineWidth: 0.5) } } .frame(width: size, height: size) } }