Changing the domain of the drag line in swift?

23 Views Asked by At

I have been using the drag lines to view the values of the plotted points in the chart. However, the does not show the value of the plotted point plotted on the right hand side of the chart. I have been trying to fix it but I can't get that last point. I want the drag line to only be able to move within the domain of the charts x axis.

@ViewBuilder
func TargetTempChart() -> some View {
    if viewModel.filteredEvents.isEmpty {
        Text("No data is available")
            .foregroundColor(.gray)
            .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
    } else {
        GeometryReader { geometry in
            Chart {
                ForEach(viewModel.filteredEvents, id: \.id) { event in
                    LineMark(
                        x: .value("Time", event.timestamp, unit: .hour),
                        y: .value("Target Temperature", event.targetTemperature)
                    )
                    .symbol(by: .value("Attribution", event.attribution))
                    .foregroundStyle(colorForAttribution(event.attribution))
                }
            }
            .frame(height: 350)
            .overlay(
                Group {
                    if let dragLocation = dragLocation, let currentActiveItem = currentActiveItem {
                        Rectangle()
                            .fill(Color.blue.opacity(0.5))
                            .frame(width: 1, height: geometry.size.height - 50)
                            .position(x: dragLocation, y: (geometry.size.height - 50) / 2)
                        
                        Text("\(currentActiveItem.targetTemperature, specifier: "%.1f")°C")
                            .padding(5)
                            .background(Color.green.opacity(0.75))
                            .foregroundColor(Color.black)
                            .cornerRadius(5)
                            .position(x: dragLocation, y: 40)
                        
                        VStack {
                            HStack {
                                VStack {
                                    Text("Time")
                                        .font(.callout)
                                        .foregroundColor(Color.gray)
                                    
                                    Text("\(itemFormatter.string(from: currentActiveItem.timestamp))")
                                        .font(.caption)
                                        .foregroundColor(Color.blue)
                                }
                                .position(x: 70, y: 385)
                                
                                VStack {
                                    Text("Attribution")
                                        .font(.callout)
                                        .foregroundColor(Color.gray)
                                    
                                    Text("\(currentActiveItem.attribution)")
                                        .font(.caption)
                                        .foregroundColor(Color.blue)
                                }
                                .position(x: 80, y: 385)
                            }
                        }
                    }
                }
            )
            .gesture(
                DragGesture()
                    .onChanged { value in
                        let totalWidth = geometry.size.width
                        let estimatedEndX = 0.98 * totalWidth
                        let locationX = value.location.x
                        let clampedX = max(min(locationX, estimatedEndX), 0)
                        self.dragLocation = clampedX
                        self.currentActiveItem = self.closestEvent(to: clampedX, in: geometry.size)
                    }
                    .onEnded { _ in
                        self.dragLocation = nil
                        self.currentActiveItem = nil
                    }
            )
        }
        
        
    }
}

func colorForAttribution(_ attribution: String) -> Color {
    switch attribution {
    case "User Changed":
        return .blue
    case "System Changed":
        return .green
    default:
        return .gray
    }
}

private var itemFormatter: DateFormatter {
    let formatter = DateFormatter()
    formatter.dateStyle = .short
    formatter.timeStyle = .short
    return formatter
}

private func closestEvent(to position: CGFloat, in size: CGSize) -> TargetTempEventData? {
    guard !viewModel.filteredEvents.isEmpty, let firstTimestamp = viewModel.filteredEvents.first?.timestamp, let lastTimestamp = viewModel.filteredEvents.last?.timestamp else {
        return nil
    }
    
    let totalDuration = lastTimestamp.timeIntervalSince(firstTimestamp)
    let chartWidth = size.width
    
    let positions = viewModel.filteredEvents.map { event -> (event: TargetTempEventData, position: CGFloat) in
        let durationSinceFirst = event.timestamp.timeIntervalSince(firstTimestamp)
        let proportionOfTotal = CGFloat(durationSinceFirst / totalDuration)
        let estimatedXPosition = proportionOfTotal * chartWidth
        return (event, estimatedXPosition)
    }
    
    let closest = positions.min(by: { abs($0.position - position) < abs($1.position - position) })?.event
    
    return closest
}
0

There are 0 best solutions below