NRIネットコム社員が様々な視点で、日々の気づきやナレッジを発信するメディアです

注目のタグ

    【SwiftUI】Chartsフレームワークを使用してグラフを作成する方法

    概要

    iOS16から使用できるようになったChartsフレームワークについて使用方法と大まかな概要を説明した記事になります。

    https://developer.apple.com/documentation/charts/creating-a-chart-using-swift-charts

    はじめに

    Chartとグラフが混同して出てきますが、コードに関する部分ではChartと表現し、図として表現したい場合はグラフとしています。

    Chart

    まずはドキュメントを参考にした簡単な棒グラフのサンプルコードとプレビューです。

    import SwiftUI
    import Charts
    
    struct ChartsSampleView: View {
        var data: [ToyShape] = [
            .init(type: "Cube", count: 5),
            .init(type: "Sphere", count: 4),
            .init(type: "Pyramid", count: 4)
        ]
    
        var body: some View {
            Chart {
                BarMark(
                    x: .value("Shape Type", data[0].type),
                    y: .value("Total Count", data[0].count)
                )
                BarMark(
                     x: .value("Shape Type", data[1].type),
                     y: .value("Total Count", data[1].count)
                )
                BarMark(
                     x: .value("Shape Type", data[2].type),
                     y: .value("Total Count", data[2].count)
                )
            }
        }
    }
    
    struct ChartsSampleView_Previews: PreviewProvider {
        static var previews: some View {
            ChartsSampleView()
        }
    }
    
    struct ToyShape: Identifiable {
        var type: String
        var count: Double
        var id = UUID()
    }
    

    Chartを利用するにはChartsライブラリのImportが必要です。
    使用方法はChart内にMarkを使用することでChartを表示することができます。
    上記のサンプルではBarMarkを使用して棒グラフを表示していますが、棒グラフ一つ毎にBarMarkの呼び出しが必要となります。
    またx: .value()には横軸にStringを、y: .value( )には縦軸でDoubleの値を入れることで縦棒グラフができます。(横棒グラフに関してはBarMarkのセクションで説明します)
    基本的なChartの使用に関してはこれだけで、簡単にグラフを作成することができます。

    MarkとProperty

    このBarMarkについては各項目の視覚的要素の一種であり、それらの視覚要素を総称してMarkと呼びます。
    Markについては6種類ありますが、そちらに関しては後ほど説明します。
    またそれぞれのMarkに対して使用できるPropertyがあり、それらを効果的に使用することでより様々なグラフやChartを作成することができます。

    https://developer.apple.com/videos/play/wwdc2022/10136/

    上記サンプルでxやyが出てきましたがこれらはX PositionとY Positionにあたります。

    foregroundStyle

    foregroundStyleを使用すると棒に色をつけることができます。
    ただ色の指定はforegroundStyleではできないのでchartForegroundStyleScaleで定義する必要があります。
    以下積み上げ棒グラフのサンプルコードを参考にforegroundStyleを使用したサンプルです。

    import SwiftUI
    import Charts
    
    struct ChartsSampleView: View {
        var stackedBarData: [ToyShape] = [
            .init(color: "Green", type: "Cube", count: 2),
            .init(color: "Green", type: "Sphere", count: 0),
            .init(color: "Green", type: "Pyramid", count: 1),
            .init(color: "Purple", type: "Cube", count: 1),
            .init(color: "Purple", type: "Sphere", count: 1),
            .init(color: "Purple", type: "Pyramid", count: 1),
            .init(color: "Pink", type: "Cube", count: 1),
            .init(color: "Pink", type: "Sphere", count: 2),
            .init(color: "Pink", type: "Pyramid", count: 0),
            .init(color: "Yellow", type: "Cube", count: 1),
            .init(color: "Yellow", type: "Sphere", count: 1),
            .init(color: "Yellow", type: "Pyramid", count: 2)
        ]
        var body: some View {
            Chart {
                ForEach(stackedBarData) { shape in
                    BarMark(
                        x: .value("Shape Type", shape.type),
                        y: .value("Total Count", shape.count)
                    )
                    .foregroundStyle(by: .value("Shape Color", shape.color))
                }
            }
            .chartForegroundStyleScale ([
                "Green": .green, "Purple": .purple, "Pink": .pink, "Yellow": .yellow
            ])
            .padding()
        }
    }
    
    struct ChartsSampleView_Previews: PreviewProvider {
        static var previews: some View {
            ChartsSampleView()
        }
    }
    
    struct ToyShape: Identifiable {
        var color: String
        var type: String
        var count: Double
        var id = UUID()
    }
    

    stackedBarDataのようにデータの数が増えてくるとChart内で定義するBarMarkの数も比例して多くなります。
    そこでForEachを使用して繰り返し処理を行います。
    余談ですがForEachでstructを使用するにはIdentifiableに準拠させる必要があります。
    またcolorを追加することで凡例が追加されます。
    ForEachを使わず、Chartに直接引数としてstackedBarDataを渡して、クロージャーの引数としてToyShape型を定義することもできます。

    Chart(stackedBarData) { shape in
        BarMark(
            x: .value("Shape Type", shape.type),
            y: .value("Total Count", shape.count)
        )
        .foregroundStyle (by: .value("Shape Color", shape.color))
    }
    

    annotation

    ChartにTextやImageを注釈としてつけることができます。

    Chart(stackedBarData) { shape in
        BarMark(
            x: .value("Shape Type", shape.type),
            y: .value("Total Count", shape.count)
        )
        .foregroundStyle (by: .value("Shape Color", shape.color))
        .annotation(position: .top) {
            shape.type == "Cube" ? Text("★") : Text("☆")
        }
    }
    

    annotationの引数にpositionを指定することで配置を変えることもできます。

    SymbolやLine Styleに関しては以下の「Markの種類」セクション内で説明します。

    Markの種類

    https://developer.apple.com/videos/play/wwdc2022/10137

    Markの種類はBarを含めて以下6種類あります。

    • AreaMark
    • LineMark
    • PointMark
    • RectangleMark
    • RuleMark
    • BarMark

    以下それぞれの使用方法と特徴です。
    ※BarMark以外の各Markにて共通して使用するサンプルデータは以下の通りです。

    struct SampleData: Identifiable {
        var id: String { name }
        let name: String
        let amount: Double
        let from: String
    }
    
    let sampleData: [SampleData] = [
        .init(name: "NameA", amount: 2500, from: "PlaceA"),
        .init(name: "NameB", amount: 3500, from: "PlaceA"),
        .init(name: "NameC", amount: 2000, from: "PlaceA"),
        .init(name: "NameD", amount: 4500, from: "PlaceA"),
        .init(name: "NameE", amount: 5000,from: "PlaceA"),
        .init(name: "NameF", amount: 5500,from: "PlaceA"),
        .init(name: "NameA", amount: 360, from: "PlaceB"),
        .init(name: "NameB", amount: 640, from: "PlaceB"),
        .init(name: "NameC", amount: 680, from: "PlaceB"),
        .init(name: "NameD", amount: 760, from: "PlaceB"),
        .init(name: "NameE", amount: 780, from: "PlaceB"),
        .init(name: "NameF", amount: 800, from: "PlaceB")
    ]
    

    AreaMark

    面グラフを作成することができます。
    以下サンプルコードとプレビューです。

    struct AreaMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                AreaMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("From", data.from))
                .position(by: .value("From", data.from))
            }
            .chartForegroundStyleScale([
                "PlaceA": .green.opacity(0.4), "PlaceB": .purple.opacity(0.4)
            ])
            .frame(height: 300)
            .padding()
        }
    }
    

    RuleMark

    RuleMarkに関しては少し特殊で、単体で使用してグラフを表示するものではなく、単一の水平線または垂直線を表示します。
    以下サンプルコードとプレビューです。

    struct RuleMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                AreaMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("From", data.from))
                .position(by: .value("From", data.from))
                
                RuleMark(y: .value("Base", 2500))
                    .lineStyle(StrokeStyle(lineWidth: 2))
                    .annotation(position: .top, alignment: .leading) {
                        Text("Base Line 2500")
                            .font(.subheadline)
                            .foregroundColor(.blue)
                    }
            }
            .chartForegroundStyleScale([
                "PlaceA": .green.opacity(0.4), "PlaceB": .purple.opacity(0.4)
            ])
            .frame(height: 300)
            .padding()
        }
    }
    

    annotationを使用すると基準となるラインなどに使用できます。

    LineMark

    折れ線グラフを作成することができます。
    以下サンプルコードとプレビューです。

    struct LineMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                LineMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Form", data.from))
                .lineStyle(StrokeStyle(lineWidth: 1))
            }
            .frame(height: 300)
            .padding()
        }
    }
    

    lineStyle(StrokeStyle())で線の太さなどを変化させたり.interpolationMethod()で滑らかな線や直線的な線に変更することができます。

    左から順にstepCenter / catmullRom / linear

    symbol()を使用すると各値にマーカーのようなシンボルを表示することができます。

    struct LineMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                LineMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Name", data.from))
                .lineStyle(StrokeStyle(lineWidth: 1))
                .interpolationMethod(.linear)
                .symbol(by: .value("Form", data.from))
            }
            .frame(height: 300)
            .padding()
        }
    }
    

    PointMark

    点グラフを作成することができます。
    以下サンプルコードとプレビューです。

    struct RectangleMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                PointMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Form", data.from))
            }
            .frame(height: 300)
            .padding()
        }
    }
    

    LineMarkと併用して使用することでsymbolのように値にマーカーとして表示することも可能です。

    struct PointMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                LineMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Form", data.from))
                .lineStyle(StrokeStyle(lineWidth: 3))
                .interpolationMethod(.linear)
                PointMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Form", data.from))
            }
            .chartForegroundStyleScale([
                        "PlaceA": .green.opacity(0.4), "PlaceB": .purple.opacity(0.4)
                    ])
            .frame(height: 300)
            .padding()
        }
    }
    

    RectangleMark

    線表を作成することができます。
    以下サンプルコードとプレビューです。

    struct RectangleMarkView: View {
        var body: some View {
            Chart(sampleData) { data in
                RectangleMark(
                    x: .value("Name", data.name),
                    y: .value("Amount", data.amount)
                )
                .foregroundStyle(by: .value("Form", data.from))
            }
            .frame(height: 300)
            .padding()
        }
    }
    

    BarMark

    棒グラフを作成することができます。
    以下サンプルコードとプレビューです。

    struct BarMarkView: View {
        let barColor: [Color] = [.red, .purple, .blue, .green, .yellow]
        var body: some View {
            Chart(productData) { data in
                BarMark(
                    x: .value("ProductName", data.name),
                    y: .value("Count", data.count)
                )
                .foregroundStyle(by: .value("ProductName", data.name))
            }
            .chartForegroundStyleScale(
                domain: productData.compactMap({ product -> String? in
                    product.name
                }),
                range: barColor
            )
            .frame(height: 300)
            .padding()
        }
    }
    
    struct BarMarkView_Previews: PreviewProvider {
        static var previews: some View {
            BarMarkView()
        }
    }
    
    struct Products: Identifiable {
        var id: String { name }
        let name: String
        let count: Double
    }
    
    let productData: [Products] = [
        .init(name: "製品A", count: 960),
        .init(name: "製品B", count: 420),
        .init(name: "製品C", count: 660),
        .init(name: "製品D", count: 540),
        .init(name: "製品E", count: 1120)
    ]
    

    XとYの値を反転させると横棒グラフになります。

    BarMark(
        x: .value("Count", data.count),
        y: .value("ProductName", data.name)
    )
    

    幅を指定することもできます。

    BarMark(
        x: .value("Shape Type", data[0].type),
        y: .value("Total Count", data[0].count),
        width: 10
    )
    

    また積み上げ棒グラフではなく同じ項目内で比較したいグラフを表示したい時は.positionを使用します。

    struct BarMarkView: View {
        var body: some View {
            Chart(productData) { data in
                BarMark(
                    x: .value("Count", data.count),
                    y: .value("ProductName", data.name),
                )
                .foregroundStyle(by: .value("ProductName", data.factory))
                .position(by: .value("Factory", data.factory))
            }
            .chartForegroundStyleScale([
                "工場A": .green, "工場B": .purple
            ])
            .frame(height: 300)
            .padding()
        }
    }
    
    struct BarMarkView_Previews: PreviewProvider {
        static var previews: some View {
            BarMarkView()
        }
    }
    
    struct Products: Identifiable {
        var id: String { name }
        let name: String
        let count: Double
        let factory: String
    }
    
    let productData: [Products] = [
        .init(name: "製品A", count: 960, factory: "工場A"),
        .init(name: "製品B", count: 420, factory: "工場A"),
        .init(name: "製品C", count: 660, factory: "工場A"),
        .init(name: "製品D", count: 540, factory: "工場A"),
        .init(name: "製品E", count: 1120, factory: "工場A"),
        .init(name: "製品A", count: 360, factory: "工場B"),
        .init(name: "製品B", count: 640, factory: "工場B"),
        .init(name: "製品C", count: 780, factory: "工場B"),
        .init(name: "製品D", count: 960, factory: "工場B"),
        .init(name: "製品E", count: 880, factory: "工場B")
    ]
    

    最後に

    ざっとChartについての説明からかくMarkについてサンプルコードとプレビューで紹介させていただきました。
    次回はもっと複雑な使用方法について掘り下げたいと思います。

    参考記事

    https://developer.apple.com/documentation/charts/creating-a-chart-using-swift-charts

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

    https://developer.apple.com/documentation/charts/barmark/init(x:y:width:height:stacking:)

    執筆者岡優志

    iOSエンジニア
    iOSを専門とし、モバイルアプリの開発を行なっています。

    Twitter