Creating Dynamic Line and Bar Graphs in SwiftUI with Charts and Combine

|

SwiftUI makes it simple to create stunning, interactive data visualizations with its Charts framework, introduced in iOS 16. This article explores how to build dynamic line and bar graphs using Charts and Combine to simulate real-time data updates.


Overview of the Components

1. LineGraphSample View

This view displays a dynamic line chart that updates every two seconds with new data points. It uses the LineMark API from the Charts framework to render the graph.

Key Features:

  • Dynamic Updates: The chart dynamically reflects real-time data changes.
  • Custom Styling: The line graph includes blue-colored lines and circular markers at each data point.

Code:

struct LineGraphSample: View {
    @StateObject private var viewModel = GraphSampleViewModel()
    
    var body: some View {
        VStack {
            Chart(viewModel.data) { item in
                LineMark(
                    x: .value("Month", item.month),
                    y: .value("Sales", item.sales)
                )
                .foregroundStyle(.blue)
                .symbol(Circle())
            }
            .padding(.top)
        }
        .navigationTitle("Dynamic Line Chart")
    }
}

2. BarGraphSample View

The BarGraphSample view uses the BarMark API to create a bar chart that also dynamically updates every two seconds.

Key Features:

  • Bar Visualization: Each bar represents sales for a specific month.
  • Dynamic Data: Updates are reflected in real-time, making it ideal for representing temporal data trends.

Code:

struct BarGraphSample: View {
    @ObservedObject var viewModel = GraphSampleViewModel()
    
    var body: some View {
        VStack {
            Chart(viewModel.data) { item in
                BarMark(
                    x: .value("Month", item.month),
                    y: .value("Sales", item.sales)
                )
                .foregroundStyle(.blue)
            }
            .navigationTitle("Bar Sales Charts")
            .padding(.top)
        }
    }
}

3. GraphSampleViewModel

The GraphSampleViewModel manages the data for both graphs. It uses Combine to generate random sales data for each month and updates the chart every two seconds.

Key Features:

  • Randomized Data Generation: Simulates real-world scenarios with dynamic, ever-changing data.
  • Timer with Combine: Uses Combine’s Timer publisher to add new data points periodically.
  • Rolling Data Window: Keeps only the latest 12 months of data for a clean visualization.

Code:

class GraphSampleViewModel: ObservableObject {
    @Published var data: [GraphSampleSalesData] = []
    private var cancellables = Set<AnyCancellable>()
    private var timer: AnyCancellable?
    private var currentMonth: Int = 0
    
    init() {
        startGeneratingData()
    }
    
    func startGeneratingData() {
        addRandomData()
        timer = Timer.publish(every: 2, on: .main, in: .common)
            .autoconnect()
            .sink { [weak self] _ in
                self?.addRandomData()
            }
    }
    
    private func addRandomData() {
        if currentMonth > 11 {
            currentMonth = 0
        }
        
        let months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
        let randomMonth = months[currentMonth]
        let randomSales = Double.random(in: 100...300)
        
        currentMonth += 1
        
        let newEntry = GraphSampleSalesData(month: randomMonth, sales: randomSales)
        
        DispatchQueue.main.async {
            if self.data.count >= 12 {
                self.data.removeFirst()
            }
            self.data.append(newEntry)
        }
    }
}

4. GraphSampleSalesData

This simple struct represents each data point, including the month and corresponding sales value.

Code:

struct GraphSampleSalesData: Identifiable {
    let id = UUID()
    let month: String
    let sales: Double
}

How It Works

  1. Dynamic Data Generation:
    • The GraphSampleViewModel generates random sales data every two seconds.
    • It updates the data array, which is observed by the SwiftUI views.
  2. Charts Framework:
    • The LineMark and BarMark APIs render the data as line and bar graphs, respectively.
    • SwiftUI automatically re-renders the chart when the data changes.
  3. Combine Integration:
    • A Timer publisher creates a periodic trigger to update the data array.
    • Combine’s .sink handles appending new data points to the array.

Benefits of This Approach

  1. Real-Time Updates:
    • Perfect for use cases like dashboards, stock monitoring, or performance metrics.
  2. Reusable Components:
    • The GraphSampleViewModel can support multiple chart types with minimal adjustments.
  3. Declarative and Dynamic:
    • SwiftUI’s declarative nature makes it easy to build responsive, dynamic visualizations.
  4. Visual Simplicity:
    • The Charts framework simplifies creating professional-quality graphs without third-party libraries.

Conclusion

Using SwiftUI’s Charts framework with Combine allows you to create dynamic and visually appealing graphs in minimal code. The LineGraphSample and BarGraphSample views demonstrate how to visualize time-series data effectively, while the GraphSampleViewModel showcases a scalable way to manage and update data in real-time. These examples are perfect starting points for integrating dynamic data visualizations into your apps.

The Source Code Repository

Check out the source Github Repository:

https://github.com/san0suke/swift-ui-combine2/tree/main/SwiftUICombine/SwiftUICombine/GraphSample