Swift Charts: Chart, BarMark, LineMark, PointMark y personalización en SwiftUI

Swift Charts es el framework nativo de Apple para visualización de datos en SwiftUI, disponible desde iOS 16. Sustituye a las librerías de terceros para la mayoría de casos de uso y se integra de forma natural con el sistema de diseño de SwiftUI, incluyendo modo oscuro, Dynamic Type y accesibilidad sin configuración adicional.

La estructura básica: Chart y Mark

Un gráfico en Swift Charts se compone de un contenedor Chart y uno o más tipos de marca (mark). Cada marca representa un elemento visual: barra, línea, punto, área o regla:

import Charts
import SwiftUI

struct VentasMensuales: View {
    let datos = [
        (mes: "Ene", ventas: 120),
        (mes: "Feb", ventas: 95),
        (mes: "Mar", ventas: 180),
        (mes: "Abr", ventas: 145),
        (mes: "May", ventas: 210),
    ]

    var body: some View {
        Chart(datos, id: .mes) { item in
            BarMark(
                x: .value("Mes", item.mes),
                y: .value("Ventas", item.ventas)
            )
        }
        .frame(height: 300)
        .padding()
    }
}

BarMark, LineMark, PointMark y AreaMark

Cada tipo de marca tiene su propio uso. BarMark para barras, LineMark para líneas temporales, PointMark para dispersión y AreaMark para resaltar áreas bajo una curva:

struct GraficoLineas: View {
    let temperaturas = [
        (dia: 1, ciudad: "Madrid", temp: 24.0),
        (dia: 2, ciudad: "Madrid", temp: 27.0),
        (dia: 3, ciudad: "Madrid", temp: 22.0),
        (dia: 1, ciudad: "Barcelona", temp: 26.0),
        (dia: 2, ciudad: "Barcelona", temp: 28.0),
        (dia: 3, ciudad: "Barcelona", temp: 25.0),
    ]

    var body: some View {
        Chart(temperaturas, id: .dia) { item in
            LineMark(
                x: .value("Día", item.dia),
                y: .value("Temperatura", item.temp)
            )
            .foregroundStyle(by: .value("Ciudad", item.ciudad))

            PointMark(
                x: .value("Día", item.dia),
                y: .value("Temperatura", item.temp)
            )
            .foregroundStyle(by: .value("Ciudad", item.ciudad))
        }
    }
}

foregroundStyle(by:) asigna colores automáticamente a cada serie usando la paleta del sistema. La leyenda se genera sola.

AreaMark es especialmente útil para rangos:

Chart(datos, id: .dia) { item in
    AreaMark(
        x: .value("Día", item.dia),
        yMin: .value("Mínima", item.minTemp),
        yMax: .value("Máxima", item.maxTemp)
    )
    .opacity(0.3)

    LineMark(
        x: .value("Día", item.dia),
        y: .value("Media", item.mediaTemp)
    )
}

RuleMark: líneas de referencia

RuleMark dibuja una línea horizontal o vertical fija, ideal para marcar umbrales o medias:

Chart(datos, id: .mes) { item in
    BarMark(
        x: .value("Mes", item.mes),
        y: .value("Ventas", item.ventas)
    )
}
.chartOverlay { _ in }
// Línea de objetivo horizontal en 150
Chart {
    ForEach(datos, id: .mes) { item in
        BarMark(
            x: .value("Mes", item.mes),
            y: .value("Ventas", item.ventas)
        )
    }
    RuleMark(y: .value("Objetivo", 150))
        .lineStyle(StrokeStyle(lineWidth: 2, dash: [5]))
        .foregroundStyle(.red)
        .annotation(position: .top, alignment: .trailing) {
            Text("Objetivo").font(.caption).foregroundStyle(.red)
        }
}

Escalas personalizadas y ejes

Las escalas de los ejes se personalizan con los modificadores chartXScale, chartYScale, chartXAxis y chartYAxis:

Chart(datos, id: .mes) { item in
    BarMark(
        x: .value("Mes", item.mes),
        y: .value("Ventas", item.ventas)
    )
    .foregroundStyle(item.ventas > 150 ? Color.green : Color.blue)
}
.chartYScale(domain: 0...300)
.chartYAxis {
    AxisMarks(values: .stride(by: 50)) { valor in
        AxisGridLine()
        AxisValueLabel { Text("(valor.as(Int.self) ?? 0)€") }
    }
}
.chartXAxis {
    AxisMarks { valor in
        AxisValueLabel(orientation: .vertical)
    }
}

Interactividad con chartOverlay

Para mostrar un tooltip o resaltar el punto seleccionado al tocar la gráfica, se usa chartOverlay para interceptar gestos y chartProxy para convertir coordenadas de pantalla a valores del dominio:

struct GraficoInteractivo: View {
    let datos: [(mes: String, ventas: Int)]
    @State private var seleccionado: String?

    var body: some View {
        Chart(datos, id: .mes) { item in
            BarMark(
                x: .value("Mes", item.mes),
                y: .value("Ventas", item.ventas)
            )
            .opacity(seleccionado == nil || seleccionado == item.mes ? 1 : 0.4)
        }
        .chartOverlay { proxy in
            GeometryReader { geo in
                Rectangle()
                    .fill(.clear)
                    .contentShape(Rectangle())
                    .gesture(
                        DragGesture(minimumDistance: 0)
                            .onChanged { value in
                                let xPos = value.location.x - geo[proxy.plotAreaFrame].origin.x
                                if let mes: String = proxy.value(atX: xPos) {
                                    seleccionado = mes
                                }
                            }
                            .onEnded { _ in seleccionado = nil }
                    )
            }
        }
    }
}

Resumen

Swift Charts elimina la dependencia de librerías externas para visualización de datos en apps Apple. BarMark, LineMark, PointMark, AreaMark y RuleMark cubren la mayoría de tipos de gráfico; foregroundStyle(by:) gestiona series múltiples automáticamente; y chartOverlay con chartProxy añade interactividad sin código de geometría complejo. El resultado es un framework declarativo que se adapta al sistema de diseño de Apple sin configuración adicional.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP