How to Add Labels on a LineMark Chart in SwiftUI
Asked Answered
M

1

6

I'm creating a chart in SwiftUI. It's weather related and I have the data successfully displaying. Now, I'm attempting to display the temperature on each symbol in the chart, but it only displays on the first symbol.

Here's what I've done:

  Chart {
     ForEach(temperatures, id: \.tempType) { series in
      ForEach(series.data) { element in
         LineMark(x: .value("Day", element.day), y: .value("Temps", element.temp))
             .interpolationMethod(.catmullRom)
             .foregroundStyle(by: .value("TemperatureType", series.tempType))
              .symbol {
                    Circle()
                       .fill(.yellow)
                       .frame(width: 10)
                       .shadow(radius: 2)
                       }
                       .lineStyle(.init(lineWidth: 5))
                                
        } // end for each
      } // end for each
 }

SwiftUI chart with LineMark

This works. Then, I attempt to add text using this modifier on the LineMark:

.annotation(position: .overlay, alignment: .bottom) {
                                    let text = "\(element.temp)"
                                    Text(text)
                                 }

It only displays the text on the first symbol's data point:

SwiftUI chart with LineMark with text at first data point

Since the annotation modifier is within the ForEach loop, I thought it would display the temperature at each data point, but it doesn't. What's the best way to have the temperature displayed at each symbol instead of only the first?

Muzhik answered 7/2, 2023 at 16:23 Comment(0)
C
6

The short answer is that the .annotation refers to the type of "Mark" that you attach it to - and you are attaching it to a LineMark, so it is the entire line you are "annotating", not the individual points.

Had you used BarMarks or PointMarks, the annotation will attach to the individual bar or point. So try this instead:

    Chart {
        ForEach(Array(zip(numbers, numbers.indices)), id: \.0) { number, index in
            LineMark(
                x: .value("Index", index),
                y: .value("Value", number)
            )
            .lineStyle(.init(lineWidth: 5))

            PointMark(
                x: .value("Index", index),
                y: .value("Value", number)
                )
            .annotation(position: .overlay,
                        alignment: .bottom,
                        spacing: 10) {
                Text("\(number)")
                    .font(.largeTitle)
            }
        }
    }

enter image description here

To make it compatible with your nice symbols, we need to add a couple of extra steps:

    Chart {
        ForEach(Array(zip(numbers, numbers.indices)), id: \.0) { number, index in
             
            LineMark(
                x: .value("Index", index),
                y: .value("Value", number)
            )
            .interpolationMethod(.catmullRom)
            .lineStyle(.init(lineWidth: 5))
            .symbol {
                // This still needs to be associated
                // with the LineMark
                Circle()
                    .fill(.yellow)
                    .frame(width: 10)
                    .shadow(radius: 2)
            }
            
            PointMark(
                x: .value("Index", index),
                y: .value("Value", number)
                )
            // We need .opacity(0) or it will
            // overlay your `.symbol`
            .opacity(0)
            .annotation(position: .overlay,
                        alignment: .bottom,
                        spacing: 10) {
                Text("\(number)")
                    .font(.largeTitle)
            }
        }
    }

enter image description here

Cimah answered 15/2, 2023 at 15:20 Comment(1)
Thanks for the explanation, and the code. Your answer worked indeed.Muzhik

© 2022 - 2024 — McMap. All rights reserved.