SwiftUI Chart: how can I overlay multiple graph datas?

Viewed 452

here are two charts. and I would like to show the data of object and object2 together in one chart.

Would it be possible?

Chart(db.objects, id: \.self) { object in
    LineMark(
        x: .value("name", object.name),
        y: .value("value", Int(object.value) ?? 0)
    )
}

Chart(db.objects2, id: \.self) { object2 in
    LineMark(
        x: .value("name", object2.name),
        y: .value("value", Int(object2.value) ?? 0)
    )
}
3 Answers

I just found the way from apple developer website

https://developer.apple.com/documentation/charts/chart/

struct ProfitOverTime {
    var date: Date
    var profit: Double
}

let departmentAProfit: [ProfitOverTime] = [] // ...
let departmentBProfit: [ProfitOverTime] = [] // ...

var body: some View {
    Chart {
        ForEach(departmentAProfit) {
            LineMark(
                x: .value("Date", $0.date),
                y: .value("Profit A", $0.profit)
            )
            .foregroundStyle(.blue)
        }
        ForEach(departmentBProfit) {
            LineMark(
                x: .value("Date", $0.date),
                y: .value("Profit B", $0.profit)
            )
            .foregroundStyle(.green)
        }
        RuleMark(
            y: .value("Threshold", 500.0)
        )
        .foregroundStyle(.red)
    }
}

chart showing overlaid data

This is an alternative solution. Since the data structure is the same, the data could be one source and displayed using one LineMark.

As an example, dept was added to the model to identify each group of data that represents a single line in the chart:

    struct ProfitOverTime {
        var dept: String
        var date: Date
        var profit: Double
    }

The data combined into one array:

    let data: [ProfitOverTime] = [] // ...

The foreground style is based on the department, that is, each line in the chart.

struct DepartmentChart: View {
    var body: some View {
        Chart(data) {
            LineMark(
                x: .value("Date", $0.date),
                y: .value("Profit", $0.profit)
                series: .value("Department", $0.dept)
            )
            .foregroundStyle(by: .value("Department", $0.dept))
    
            RuleMark(
                y: .value("Threshold", 500.0)
            )
            .foregroundStyle(.red)
        }
        .chartForegroundStyleScale(["Profit A": .green, "Profit B": .blue])
    }
}

Series was added to identify each line by its color:

enter image description here

The chartForegroundStyleScale is optional since each line will automatically be colored differently. But chartForegroundStyleScale can be used to customize the line colors.

Here I am showing the data for a week. So if you want to show more than data then you can increase the number of entries. So that’s it from my side hope you understand.

Now use MultiLineChartView like this.

Complete Code:

import SwiftUI
import Charts

struct ContentView: View {
    let days = ["S", "M", "T", "W", "T", "F", "S"]
    let entries1 = [
        ChartDataEntry(x: 1, y: 1),
        ChartDataEntry(x: 2, y: 2),
        ChartDataEntry(x: 3, y: 0),
        ChartDataEntry(x: 4, y: 0),
        ChartDataEntry(x: 5, y: 0),
        ChartDataEntry(x: 6, y: 0),
        ChartDataEntry(x: 7, y: 1),
        
    ]
    let entries2 = [
        ChartDataEntry(x: 1, y: 2),
        ChartDataEntry(x: 2, y: 3),
        ChartDataEntry(x: 3, y: 0),
        ChartDataEntry(x: 4, y: 0),
        ChartDataEntry(x: 5, y: 0),
        ChartDataEntry(x: 6, y: 0),
        ChartDataEntry(x: 7, y: 2)
    ]
    var body: some View {
        VStack{
            Spacer()
            MultiLineChartView(entries1: entries1, entries2: entries2, days: days)
            .frame(height: 220)
            Spacer()
        }
    }
}

struct MultiLineChartView : UIViewRepresentable {
    
    var entries1 : [ChartDataEntry]
    var entries2 : [ChartDataEntry]
    var days: [String]
    
    func makeUIView(context: Context) -> LineChartView {
        let chart = LineChartView()
        return createChart(chart: chart)
    }
    
    func updateUIView(_ uiView: LineChartView, context: Context) {
        uiView.data = addData()
    }
    
    func createChart(chart: LineChartView) -> LineChartView{
        chart.chartDescription?.enabled = false
        chart.xAxis.drawGridLinesEnabled = false
        chart.xAxis.drawLabelsEnabled = true
        chart.xAxis.drawAxisLineEnabled = false
        chart.xAxis.labelPosition = .bottom
        chart.rightAxis.enabled = false
        chart.leftAxis.enabled = false
        chart.drawBordersEnabled = false
        chart.legend.form = .none
        chart.xAxis.labelCount = 7
        chart.xAxis.forceLabelsEnabled = true
        chart.xAxis.granularityEnabled = true
        chart.xAxis.granularity = 1
        chart.xAxis.valueFormatter = CustomChartFormatter(days: days)
        
        chart.data = addData()
        return chart
    }
    
    func addData() -> LineChartData{
        let data = LineChartData(dataSets: [
            //Schedule Trips Line
            generateLineChartDataSet(dataSetEntries: entries1, color: UIColor(Color(#colorLiteral(red: 0.6235294118, green: 0.7333333333, blue: 0.3568627451, alpha: 1))), fillColor: UIColor(Color(#colorLiteral(red: 0, green: 0.8134518862, blue: 0.9959517121, alpha: 1)))),
            //Unloadings Line
            generateLineChartDataSet(dataSetEntries: entries2, color: UIColor(Color(#colorLiteral(red: 0.003921568627, green: 0.231372549, blue: 0.431372549, alpha: 1))), fillColor: UIColor(Color(#colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1))))
        ])
        return data
    }
    
    func generateLineChartDataSet(dataSetEntries: [ChartDataEntry], color: UIColor, fillColor: UIColor) -> LineChartDataSet{
        let dataSet = LineChartDataSet(entries: dataSetEntries, label: "")
        dataSet.colors = [color]
        dataSet.mode = .cubicBezier
        dataSet.circleRadius = 5
        dataSet.circleHoleColor = UIColor(Color(#colorLiteral(red: 0.003921568627, green: 0.231372549, blue: 0.431372549, alpha: 1)))
        dataSet.fill = Fill.fillWithColor(fillColor)
        dataSet.drawFilledEnabled = true
        dataSet.setCircleColor(UIColor.clear)
        dataSet.lineWidth = 2
        dataSet.valueTextColor = color
        dataSet.valueFont = UIFont(name: "Avenir", size: 12)!
        return dataSet
    }
    
}

class CustomChartFormatter: NSObject, IAxisValueFormatter {
    var days: [String]
    
    init(days: [String]) {
        self.days = days
    }
    
    public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
        return days[Int(value-1)]
    }
}
Related