Search code examples
swiftuiswiftui-charts

How to make Text (or any View) ignore the edge of the parent view?


I am making a chart using Swift Charts. I have added pinch zoom and panning to the chart. The problem I have can be seen in this video I made. The annotations on the BarMarks collide with the edges of the chart's parent view, creating this "squeeze" effect. How can I disable the "collisions"? Here's the source code of the chart:

VStack {
    GeometryReader { proxy in
        Chart {
            ForEach(viewModel.motionData) { data in
                BarMark(x: .value("", data.x), y: .value("", viewModel.scaleMotionValue(data.y)), width: .fixed(barWidth))
                    .clipShape(RoundedRectangle(cornerRadius: 4))
                    .foregroundStyle(viewModel.getMotionBarAppearance(.init(rawValue: data.y)!))
                    .annotation(position: .top, alignment: .center, spacing: 0) {
                        Text(String(data.x))
                    }
            }
            
            ForEach(viewModel.dbData) { data in
                LineMark(x: .value("", data.x), y: .value("", data.y))
                    .foregroundStyle(HiddenCustomerName.brownish)
            }
            .interpolationMethod(.catmullRom)
            
            BarMark(xStart: .value("", viewModel.feelscapeData[0]), xEnd: .value("", viewModel.feelscapeData[1]), y: .value("", -15), height: 20)
                .clipShape(RoundedRectangle(cornerRadius: 4))
                .foregroundStyle(Appearance.theme.iconColor)
                .annotation(position: .overlay, alignment: .center, spacing: 2) {
                    Text("hello")
                }
        }
        .chartYScale(domain: [yStart, yEnd])
        .chartXScale(range: viewModel.domain ?? 0.0...1.0)
        .chartXAxis {
            AxisMarks(position: .bottom, values: Array(stride(from: 0, to: viewModel.motionData.count, by: 1))) { axis in
                AxisTick(length: viewModel.getAxisTickLength(axis), stroke: StrokeStyle(lineWidth: 2, lineCap: .round))
                    .foregroundStyle(.black)
                    .offset(y: 10)
            }
        }
        .chartYAxis {
            AxisMarks(preset: .automatic) { axis in
                AxisTick()
                AxisGridLine()
            }
        }
        .gesture(SimultaneousGesture(magnificationGesture, dragGesture))
        .onAppear {
            if viewModel.domain == nil {
                let lowerBound = (proxy.size.width - (CGFloat(viewModel.motionData.count) * (barWidth + 2))) - barWidth
                let upperBound = proxy.size.width - barWidth
                viewModel.domain = lowerBound...upperBound
            }
        }
        .onChange(of: viewModel.motionData.count) { newValue in
            viewModel.calculateDimensions(plotEnd: proxy.size.width, barWidth: barWidth)
        }
    }
}
.frame(height: 140)
.padding([.top, .bottom])

I have tried adding .edgesIgnoringSafeArea(.all) and .ignoresSafeArea(.all) to the Text inside the .annotation(..). The problem goes away if I just remove the annotations, but I need the annotation on the horizontal BarMark. The rest is there just to demo the problem.


Solution

  • Okay so the solution was to add the domain parameter to the .chartXScale(..) modifier.

    Before:

    .chartXScale(range: viewModel.domain ?? 0.0...1.0)
    

    After:

    .chartXScale(domain: [0, viewModel.motionData.count - 1], range: viewModel.domain ?? 0.0...1.0)
    

    Why does this affect how the mark annotations interact with the view bounds is beyond me.