Skip to main content

Dynamische Infografiken

Diese View zeigt eine dynamische Infografik, die in Echtzeit auf Benutzereingaben oder externe Datenquellen reagiert. Durch animierte Grafikelemente werden Veränderungen optisch hervorgehoben und der Fortschritt anschaulich visualisiert.

🔍 Zweck

  • Darstellung des Fitnessfortschritts (z.B. Schritte, Kalorien, Trainingsziele)
  • Visualisierung von Finanzdaten (z.B. Ausgaben, Budgetplanung)
  • Überwachung von Gesundheitsparametern (z.B. Herzfrequenz, Schlafdauer)
  • Fortschrittsanzeige bei Lern- oder Sprach-Apps
  • Live-Datenvisualisierung für IoT-Geräte oder Sensoren

📄 Codebeispiel

import SwiftUI
import Charts

// Model for representing progress data points
struct ProgressData: Identifiable, Equatable {
    let id = UUID()
    let date: Date
    let value: Double
}

// Sample data generator for preview and simulation
extension Array where Element == ProgressData {
    static func sampleData(days: Int = 14, range: ClosedRange<Double> = 20...100) -> [ProgressData] {
        let calendar = Calendar.current
        let today = calendar.startOfDay(for: Date())
        return (0..<days).map { offset in
            ProgressData(
                date: calendar.date(byAdding: .day, value: -offset, to: today)!,
                value: Double.random(in: range)
            )
        }.reversed()
    }
}

struct DynamicInfographicView: View {
    // Simulated data source; in a real app this could be bound to external data.
    @State private var progressData: [ProgressData] = .sampleData()

    // User input for dynamic updates
    @State private var newValue: Double = 50

    // Animation for chart changes
    @Namespace private var animation

    var body: some View {
        VStack(spacing: 24) {
            Text("Weekly Fitness Progress")
                .font(.title2.bold())
                .accessibilityAddTraits(.isHeader)

            // Animated line chart of user's progress
            Chart(progressData) { dataPoint in
                LineMark(
                    x: .value("Date", dataPoint.date),
                    y: .value("Value", dataPoint.value)
                )
                .interpolationMethod(.catmullRom)
                .foregroundStyle(Color.accentColor.gradient)
                .lineStyle(StrokeStyle(lineWidth: 4))
                
                PointMark(
                    x: .value("Date", dataPoint.date),
                    y: .value("Value", dataPoint.value)
                )
                .foregroundStyle(Color.blue)
                .symbolSize(50)
            }
            .chartYAxis(.visible)
            .frame(height: 220)
            .animation(.easeInOut(duration: 0.6), value: progressData)

            HStack {
                VStack(alignment: .leading) {
                    Text("Today's Value")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                    Text("\(Int(progressData.last?.value ?? 0)) Points")
                        .font(.title3.bold())
                        .foregroundStyle(Color.accentColor)
                        .accessibilityLabel("Today's value is \(Int(progressData.last?.value ?? 0)) points")
                }
                Spacer()
                // Circular progress bar with smooth animation
                ZStack {
                    Circle()
                        .stroke(lineWidth: 11)
                        .opacity(0.12)
                        .foregroundColor(.accentColor)

                    Circle()
                        .trim(from: 0, to: CGFloat(min((progressData.last?.value ?? 0) / 100, 1)))
                        .stroke(
                            AngularGradient(gradient: Gradient(colors: [.accentColor, .green]), center: .center),
                            style: StrokeStyle(lineWidth: 11, lineCap: .round, lineJoin: .round)
                        )
                        .rotationEffect(.degrees(-90))
                        .animation(.spring(response: 0.5), value: progressData.last?.value)

                    Text("\(Int((progressData.last?.value ?? 0)))%")
                        .font(.headline.monospacedDigit())
                        .bold()
                        .foregroundColor(.primary)
                }
                .frame(width: 70, height: 70)
            }
            .padding(.horizontal)

            Divider().padding(.vertical, 8)

            // User input slider to simulate real-time updates
            VStack(alignment: .leading) {
                Text("Update Today's Progress")
                    .font(.footnote).bold()
                Slider(value: $newValue, in: 0...100, step: 1) {
                    Text("Progress Value")
                }
                Button(action: addNewValue) {
                    Label("Set Value", systemImage: "waveform.path.ecg")
                        .fontWeight(.medium)
                        .padding(.vertical, 7)
                        .padding(.horizontal, 22)
                        .background(Capsule().foregroundColor(Color.accentColor.opacity(0.15)))
                }
            }
            Spacer(minLength: 12)
        }
        .padding()
        // Accessibility improvements
        .accessibilityElement(children: .contain)
        // Optional refresh button for simulated "external" update
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Button(action: randomizeToday) {
                    Image(systemName: "arrow.triangle.2.circlepath")
                        .imageScale(.large)
                        .accessibilityLabel("Randomize today's value")
                }
            }
        }
    }

    // Update today's value in the progress data array with user input.
    private func addNewValue() {
        guard !progressData.isEmpty else { return }
        withAnimation {
            progressData[progressData.count - 1] = ProgressData(date: progressData.last!.date, value: newValue)
        }
    }

    // Simulate an external data update (randomizes today's value).
    private func randomizeToday() {
        guard !progressData.isEmpty else { return }
        withAnimation {
            let randomValue = Double.random(in: 10...100)
            progressData[progressData.count - 1] = ProgressData(date: progressData.last!.date, value: randomValue)
            newValue = randomValue
        }
    }
}

#Preview {
    DynamicInfographicView()
}