Interaktive Diagramme
Diese SwiftUI-View zeigt ein interaktives Balkendiagramm, das Verkaufszahlen für verschiedene Produkte visualisiert. Nutzer können auf einzelne Balken tippen, um detaillierte Informationen zu den jeweiligen Verkaufswerten anzuzeigen.
🔍 Zweck
- Analyse von Verkaufsdaten in einer Vertriebs-App
- Darstellung von Umsatzzahlen im Management-Dashboard
- Auswertung von Produktvergleichen im Einzelhandel
- Visualisierung von Jahresstatistiken in Reporting-Tools
- Interaktive Präsentation von Ergebnissen in Schulungen oder Workshops
📄 Codebeispiel
import SwiftUI
// Model for sales data
struct ProductSales: Identifiable {
let id = UUID()
let productName: String
let sales: Double
let color: Color
}
// Main interactive bar chart view
struct InteractiveBarChartView: View {
let salesData: [ProductSales]
@State private var selectedProductID: UUID?
// Calculate max value for scaling bars
private var maxSales: Double {
salesData.map { $0.sales }.max() ?? 1
}
var body: some View {
VStack(spacing: 24) {
Text("Product Sales")
.font(.title.bold())
.padding(.top)
HStack(alignment: .bottom, spacing: 20) {
ForEach(salesData) { product in
BarView(
product: product,
isSelected: selectedProductID == product.id,
maxSales: maxSales
)
.onTapGesture {
withAnimation(.spring()) {
if selectedProductID == product.id {
selectedProductID = nil // Deselect if tapped again
} else {
selectedProductID = product.id
}
}
}
}
}
.frame(height: 220)
.padding(.horizontal, 16)
// Details overlay for selected bar
if let selected = salesData.first(where: { $0.id == selectedProductID }) {
VStack(spacing: 8) {
Text(selected.productName)
.font(.headline)
Text("Sales: \(Int(selected.sales)) units")
.font(.subheadline)
.foregroundStyle(.secondary)
}
.padding()
.background(RoundedRectangle(cornerRadius: 14).fill(Color(.systemBackground).opacity(0.95)))
.shadow(radius: 4)
.transition(.move(edge: .bottom).combined(with: .opacity))
}
Spacer()
}
.animation(.easeInOut, value: selectedProductID)
.padding()
.background(Color(.systemGroupedBackground))
}
}
// Single bar representation with interactive styling
struct BarView: View {
let product: ProductSales
let isSelected: Bool
let maxSales: Double
var body: some View {
VStack {
ZStack(alignment: .bottom) {
RoundedRectangle(cornerRadius: 6)
.fill(product.color.opacity(isSelected ? 0.7 : 0.35))
.frame(width: 38, height: 180)
RoundedRectangle(cornerRadius: isSelected ? 12 : 6)
.fill(product.color.opacity(isSelected ? 1.0 : 0.8))
.frame(
width: isSelected ? 48 : 38,
height: CGFloat(product.sales / maxSales) * 170 + (isSelected ? 14 : 0)
)
.overlay(
// Animated sales value label on top of the bar when selected
Group {
if isSelected {
Text("\(Int(product.sales))")
.font(.caption.bold())
.foregroundColor(.white)
.padding(6)
.background(Capsule().fill(product.color.opacity(0.9)))
.offset(y: -44)
.transition(.scale.combined(with: .opacity))
}
},
alignment: .top
)
}
Text(product.productName)
.font(.caption2)
.frame(width: 56)
.lineLimit(2)
.multilineTextAlignment(.center)
.foregroundStyle(isSelected ? product.color : Color.secondary)
}
.contentShape(Rectangle()) // Makes the whole area tappable
.accessibilityElement(children: .combine)
.accessibilityLabel("\(product.productName), \(Int(product.sales)) units sold")
}
}
#Preview {
InteractiveBarChartView(
salesData: [
ProductSales(productName: "iPhone", sales: 120, color: Color.indigo),
ProductSales(productName: "iPad", sales: 80, color: Color.purple),
ProductSales(productName: "MacBook", sales: 60, color: Color.green),
ProductSales(productName: "Apple Watch", sales: 45, color: Color.orange),
ProductSales(productName: "AirPods", sales: 95, color: Color.teal),
]
)
}