0

Is possible to animate the color transition between two colors defined to a LineMark on a SwiftUI Chart?

I would like to see gradual change and not an abrupt switch between the colors, as you can see in this sample code:

struct SwiftUIViewTest: View {

    @State private var numbers = Array((0...100))

    @State private var color = true

    var body: some View {
        VStack{
            Button("Change Color") {
                color.toggle()
            }
            .buttonStyle(.borderedProminent)
            .padding()

            Chart(numbers, id: \.self) { number in
                LineMark(
                    x: .value("Index", number),
                    y: .value("Number", number)
                )
                //Property to animate with a gradual transition
                .foregroundStyle(color ? Color.blue : Color.yellow)
            }
            .animation(.default, value: color)
        }
        .padding(.horizontal)
    }
}

Thanks and best regards.

João Colaço
  • 1,202
  • 1
  • 14
  • 38

1 Answers1

2

If the change in line color cannot be animated using the usual techniques then I was thinking it might at least be possible to use an AnimatableModifier. Unfortunately, this doesn't work either, because LineMark does not have a .modifier function.

A non-abrupt color change can however be achieved by switching between two Chart renderings.

EDIT My earlier answer used an opacity transition between two chart renderings, these being the before-and-after versions of the chart when the color change is applied. This works fine for the color itself, but if other changes are applied at the same time, such as a change to the values, then only one of the two charts is animated. Specifically, the chart fading out is not animated (although you probably need to slow down the animation to notice it).

Here is a better way to do it. This time, both versions of the chart are drawn at all times, but the opacity modifier controls the one you see. Changes to other properties, such as the values, are now fully animated:

struct SwiftUIViewTest: View {

    static let straightLine = Array((0...10))
    static let zigZagLine = [0, 4, 2, 8, 3, 9, 6, 1, 7, 5, 10]

    @State private var numbers = SwiftUIViewTest.straightLine
    @State private var color = true

    private func theChart(foregroundColor: Color) -> some View {
        Chart(Array(numbers.enumerated()), id: \.element) { index, number in
            LineMark(
                x: .value("Index", index),
                y: .value("Number", number)
            )
            .foregroundStyle(foregroundColor)
        }
    }

    var body: some View {
        VStack{
            Button("Change Color and Values") {
                withAnimation {
                    color.toggle()
                    numbers = color ? SwiftUIViewTest.straightLine : SwiftUIViewTest.zigZagLine
                }
            }
            .buttonStyle(.borderedProminent)
            .padding()

            ZStack {
                theChart(foregroundColor: .blue)
                    .opacity(color ? 1 : 0)
                theChart(foregroundColor: .yellow)
                    .opacity(color ? 0 : 1)
            }
        }
        .padding(.horizontal)
    }
}

For reference, here is the earlier solution (pre-edit):

    ZStack {
        Chart(numbers, id: \.self) { number in
            // as before
        }
        .id(color)
    }

or alternatively:

    ZStack {
        if color {
            theChart
        } else {
            theChart
        }
    }
Benzy Neez
  • 1,546
  • 2
  • 3
  • 10
  • It will, as is a new chart that's being drawn. As such all the other animations are suppressed, as you have guessed... I will wait a bit to see if anyone can give an answer that maintains those animations (or at least make appear to the user that is the same chart). – João Colaço Aug 24 '23 at 21:43
  • In fact it works better than you might expect, as long as the other changes are applied after toggling the flag and ```withAnimation``` is used to apply them. I tested with a different set of values and the change to the line path is animated nicely (for both both techniques of switching views). – Benzy Neez Aug 25 '23 at 07:22
  • Answer updated with a better approach that preserves animations fully. – Benzy Neez Aug 25 '23 at 09:22
  • Ok, this one for seems the right answer. A perfect answer would have only one chart drawn, but it seems that only Apple could help implementing that... – João Colaço Aug 25 '23 at 19:46