Search code examples
swiftuiswiftui-charts

How to properly adjust the X and Y axis in Swift Charts line chart


I am making a Mac app that displays data using a Swift Charts line chart. Once the data is displayed I want to be able to "zoom" the X and Y axis using a drag gesture. For example if fully displaying all of the data required a X axis that went from 0 to 100 I want the X axis to zoom as I drag the mouse from left to right. So for example maybe when I am done dragging the X scale might display from 25 to 50 and only display the data in that range.

I have been trying to use.

.chartXScale(domain:) .chartYScale(domain:)

These modifiers do change the scale however it causes the line to go off of the chart.

Before Zoom

After Zoom

Here is the code I am using. There is some debug code here and I have not implemented the zoom on the Y axis yet but it should be a working example for the X axis that corresponds to the examples I have shown.

struct MainView: View {`
       
    @ObservedObject var globalData = GlobalData.shared
    @State var multiSelection = Set<UUID>()
    
    @State var currentXAxisScale = 0
    @State var xAxisChangeAmount = 0
    
    @State var xMin = 0.0
    @State var xMax = 20.0
    @State var yMin = 0.0
    @State var yMax = 15.0
    
    @State var startingXPostion = 0.0
    @State var startingYPositon = 0.0
    @State var currentXPositon = 0.0
    @State var currentYPosition = 0.0
    
    @State var xMinString = ""
    @State var xMaxString = ""
    @State var yMinString = ""
    @State var yMaxString = ""
    
    @State var isDataLoaded = false
    @State var firstPoint = true
    
    
    
    
    
    let amountFormatter: NumberFormatter = {
              let formatter = NumberFormatter()
              formatter.zeroSymbol = ""
              return formatter
         }()
    

    
    var body: some View {
        
        HStack {
            
            VStack {
                
                HStack {
                    Button("Open File") {
                        let readData: ReadInFile = ReadInFile()
                        
                        let fileName = readData.openFileDialog()
                        
                        
                    }
                    .padding()
                    GroupBox(label: Text("Axis Scale")) {
                        HStack {
                            Button("Update") {
                                
                                xMin = Double(xMinString)!
                                xMax = Double(xMaxString)!
                                yMin = Double(yMinString)!
                                yMax = Double(yMaxString)!
                                
                                
                            }
                            
                            VStack {
                                TextField("X Min", text: $xMinString)
                                TextField("X Max", text: $xMaxString)
                                TextField("Y Min", text: $yMinString)
                                TextField("Y Max", text: $yMaxString)
                                
                            }
                        }
                    }

                   
                }
                .padding()
                
                    
                List(selection: $multiSelection) {
                    
                    ForEach(globalData.dataSeriesGroup , id: \.id) { file in
                        Section(header: Text(file.fileName)){
                            
                            ForEach(file.dataSeries,  id: \.id){ series in
                                HStack{
                                    
                                    Text(series.Name)
                                    
                                    Spacer()
                                    
                                }
                                .contentShape(Rectangle())
                                .onTapGesture {
                                    print("touched item \(series.Name)  \(file.fileName)")
                                    
                                    isDataLoaded = false
                                    
                                    globalData.displayDataChannle(fileName: file.fileName, channleName: series.Name)
                                    
                                    setFullScale()
                                    
                                    isDataLoaded = true
                                }
                                
                            
                            
                        }
                    }
                }
                
            }
                .padding([.leading, .bottom])
            
            
            
            }
            .frame(width: 300.0)
            
            
            
            //Just a test to see a chart
            Chart(globalData.displayChannle) {
                LineMark(
                    x: .value("Time", $0.time),
                    y: .value("Value", $0.value)
                )
                
            }
            .chartXScale(domain: xMin...xMax)
            .chartYScale(domain: yMin...yMax)
            
                .chartOverlay{ displayChart in
                
                        GeometryReader { geometry in
                            Rectangle().fill(.clear).contentShape(Rectangle())
                                .gesture(
                                    DragGesture()
                                        .onChanged { value in
                                            let origin = geometry[displayChart.plotAreaFrame].origin
                                            let location = CGPoint(
                                                x: value.location.x - origin.x,
                                                y: value.location.y - origin.y
                                            )
                                            if(firstPoint == false){
                                                let (currentXposition1, currentYpostion1) = displayChart.value(at: location, as: (Double, Double).self)!
                                                
                                                currentXPositon = currentXposition1
                                                currentYPosition = currentYpostion1
                                                
                                                print("Current Location: \(currentXposition1), \(currentYpostion1)")
                                                
                                                zoom()
                                            }
                                            
                                            if(firstPoint == true){
                                                
                                                firstPoint = false
                                                
                                                 let(startXPosition1, startYPosition1) = displayChart.value(at: location, as: (Double, Double).self)!
                                                
                                                startingXPostion = startXPosition1
                                                startingYPositon = startYPosition1
                                                
                                                print("Starting Location: \(startXPosition1), \(startYPosition1)")
                                            }
                                        }
                                        .onEnded { value in
                                            
                                            firstPoint = true
                                            
                                            print("STOPPED")
                                            
                                        }
                                )
                        }
                    
                    
                }
                
                
            
            .padding()
            
        }
        
    }
    
    func setFullScale()
    {
        
        let count = globalData.displayChannle.count
        
        yMin = 0
        yMax = 0
        
        xMax = 0
        xMin = 0
        
        xMax = globalData.displayChannle[count - 1].time
        
        for point in globalData.displayChannle
        {
            if(point.value > yMax)
            {
                
                yMax = point.value
                
            }
            
            if(point.value < yMin)
            {
                
                yMin = point.value
                
            }
            
        }
        
    }
     
    func zoom()
    {
        if(currentXPositon > startingXPostion)
        {
            if(abs(xMax - xMin) > 4)
            {
                
                 xMin = xMin + 2
                 xMax = xMax - 2
            }
        }
        
        if(currentXPositon < startingXPostion)
        {
         
            if(abs(xMax - xMin) > 4)
            {
                
                xMin = xMin - 2
                xMax = xMax + 2
            }
            
            
        }
        
        
    }
    

}


Solution

  • Use the .clipped() view modifier on your Chart