35
Help us understand the problem. What are the problem?

posted at

updated at

Organization

WWDC22、iOS 16:SwiftUIでChartsフレームワークを使ってチャートを作成する

※一般公開されているSession/Documentation/Sample Codeページだけを使ってこの記事を執筆しました。
SessionビデオやDocumentationページからの画像を収録しています。
実際のチャートは、Xcode 14でコードを実行することで確認することができます。

新しくリリースされたチャートフレームワークにより、独自のチャートビューを設計したり、サードパーティライブラリを使用する必要がなくなりました。

charts_type.jpg

最も基本的な機能としては、棒グラフ、ラインポイント(折れ線グラフ)、ポイントチャートを表現することができます。

ここでは、1つのデータ点をデータエントリーと呼ぶことにします。

データ構造を設計する

各データエントリーを格納するデータ構造を用意する必要があります。最も単純なものは、名前と値を持つ構造体です。

struct ChartEntry: Identifiable {
    var title: String
    var value: Double
    var color: Color = .green
    var id: String {
        return title + String(value)
    }
}

colorも追加されているので、個々のデータ入力ごとにバー(棒グラフ)/ラインポイント/ポイントの色をカスタマイズすることができます。

また、これを Identifiable プロトコルに適合させる必要があります。ListやForEachに対して行うのと同じように。

Chartビュー要素

最初に、SwiftUIのビューファイル内でチャートフレームワークをインポートしていることを確認してください。import Charts

Chart ブロックは List と非常によく似ています。
すべてのデータエントリを含む配列を Chart ビュー要素のパラメータ内に直接置くことができます。

以下のコードでは、 dataはデータ配列 (データセット。 [ChartEntry] として定義されます) で、 dataPoint は各データ (ChartEntry やその他のカスタム構造体として定義されます) です。
例えば、気温を表示する場合、 data はすべての天気の気温のエントリを含む配列であり、 dataPoint は1つの気温のエントリである。

Chart(data) { dataPoint in
    // TODO
}
.frame(height: 200)

または、Chart ビュー要素内に ForEach を配置することもできます。

Chart {
    ForEach(data) { dataPoint in
        // TODO
    }
}
.frame(height: 200)

ForEach`ブロック内では、各データエントリに表示するビューを作成します。
例えば、棒グラフを表示したい場合は、各データエントリーに棒(バー)のビュー(BarMark)を作成します。

棒グラフ

ビュー要素 BarMark を使用し、X軸にタイトル、Y軸に値を表示します。

BarMark(
    x: .value("タイトル", dataPoint.title),
    y: .value("値", dataPoint.value)
)
.foregroundStyle(dataPoint.color)

上記のコードをChartまたはForEachブロック内に配置します。

let data: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]

Chart(data) { dataPoint in
    BarMark(
        x: .value("タイトル", dataPoint.title),
        y: .value("値", dataPoint.value)
    )
    .foregroundStyle(dataPoint.color)
}
.frame(height: 200)

そして、棒グラフが表示されます。

CreatingYourFirstChart-2@2x.png

同じカテゴリーに複数のバーを追加する。

Chartビューでは、X軸のタイトル(名前)でカテゴリを識別します。
同じX軸の名前を持つ要素が複数ある場合、Chartビューは既存のバーの上にバーを表示します。

let data_test: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "A", value: 10, color: .blue),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]
Chart {
    ForEach(data_test) { dataPoint in
        BarMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)

上記のコード例では、X軸の名前を "A"とし、色を青としたデータエントリーを追加定義しているのがわかります。これで、チャートは次の画像のようになります。

bar-chart-02.jpg

この場合、同じ名前 "A "を持つ2つのデータが統合され、同じ縦線に表示されています。

x軸とy軸を反転させる

x軸にラベル、y軸に値を表示する代わりに、x軸に値を表示することもできます(横線グラフ)。

horizontal_chart.jpg

let data: [ChartEntry] = [
    .init(title: "A", value: 5),
    .init(title: "B", value: 10),
    .init(title: "C", value: 8)
]

Chart(data) { dataPoint in
    BarMark(
        x: .value("Value", dataPoint.value),
        y: .value("Category", dataPoint.title)
    )
    .foregroundStyle(dataPoint.color)
}
.frame(height: 200)

xパラメータに値、yパラメータにタイトルを入れることができます。

表示されているチャートを更新する

チャートに入力するデータセットに @State 変数を使うこともできる。そうすれば、データを更新したり、別のデータセットを表示したりすることができる。

struct ContentView: View {
    
    static let demoData1: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    static let demoData2: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "A", value: 10, color: .blue),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    
    @State var data: [ChartEntry] = [
        .init(title: "A", value: 5),
        .init(title: "B", value: 10),
        .init(title: "C", value: 8)
    ]
    @State private var dataSetSelection: Int = 0
    
    var body: some View {
        
        Form {
            
            Picker("Dataset selection", selection: $dataSetSelection) {
                Text("Dataset 1")
                    .tag(0)
                Text("Dataset 2")
                    .tag(1)
            }
            .onChange(of: dataSetSelection) { newValue in
                withAnimation {
                    if newValue == 0 {
                        self.data = ContentView.demoData1
                    } else if newValue == 1 {
                        self.data = ContentView.demoData2
                    }
                }
            }
            
            Chart {
                ForEach(data) { dataPoint in
                    BarMark(
                        x: .value("Value", dataPoint.value),
                        y: .value("Category", dataPoint.title)
                    )
                    .foregroundStyle(dataPoint.color)
                }
            }
            .frame(height: 200)
            
        }
        
    }
    
}

データが更新されるとき、withAnimationブロック内にコードを置くと、チャートはアニメーションで更新されます。

ラインチャート(折れ線グラフ)

line_chart_video.jpg

BarMarkLineMark`に変更するだけで、ラインチャート(折れ線グラフ)が表示されるようになります。

Chart {
    ForEach(data) { dataPoint in
        LineMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)

注:折れ線グラフの場合、値は通常Y軸に、ラベルは通常X軸に表示されます。

ポイントチャート

BarMarkPointMark に変更すると、ポイントチャートが得られます。

point_chart.jpg

Chart {
    ForEach(data) { dataPoint in
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
    }
}
.frame(height: 200)

注:ポイントチャートの場合、値は通常Y軸に、ラベルは通常X軸に表示されます。

点で構成される折れ線グラフを表示する

line_point.jpg

上で説明したように、同じX軸のラベルを持つ複数のデータポイントがある場合、SwiftUIはそれらを結合します。そこで、これを利用して、ポイントを持つ折れ線グラフを表示することができます。

基本的に、ForEachループ内では、LineMark(折れ線グラフに使用)とPointMark(点線グラフに使用)の両方を返すことになります。

Chart {
    ForEach(Charts_PointLine.demoData1) { dataPoint in
        LineMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(dataPoint.color)
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
    }
}
.frame(height: 200)

1つのグラフに複数のデータセットを表示する

1つのグラフの中に複数のデータセットを表示することもできます。

point_chart.jpg

まず、データセット(データの集合体)を表す構造体を新規に作成します。

struct DataCollection: Identifiable {
    var collectionName: String
    var data: [ChartEntry]
    var id: String { return collectionName }
}

上記のデータセットオブジェクトには、データセットの名前と、データエントリーが含まれます。

そして、データエントリーを持つデータセットを作成することができます。

static let demoData1: [ChartEntry] = [
    .init(title: "Morning", value: 20),
    .init(title: "Noon", value: 27),
    .init(title: "Evening", value: 21)
]
static let demoData2: [ChartEntry] = [
    .init(title: "Morning", value: 13),
    .init(title: "Noon", value: 14),
    .init(title: "Evening", value: 8)
]

@State var dataCollections: [DataCollection] = [
    .init(collectionName: "Kyoto", data: demoData1),
    .init(collectionName: "Hokkaido", data: demoData2)
]

各データセットをループするために、すべてのデータセットを含む配列(dataCollections)を Chart のパラメータに渡します。
Chartのブロックの中で、一つのデータセット内のすべてのデータエントリー(collection.data)をループするために、 ForEach を追加します。

Chart(dataCollections) { collection in
    ForEach(collection.data) { dataPoint in
        PointMark(
            x: .value("Category", dataPoint.title),
            y: .value("Value", dataPoint.value)
        )
        .foregroundStyle(by: .value("City", collection.collectionName))
    }
}
.frame(height: 200)

チャートに都市の名前を表示するために、.foregroundStyle(by: .value("City", collection.collectionName)) を使って、チャートに都市の名前を表示しました。

SwiftUIは自動的に各データセットに異なる色を選択します。

水平線・垂直線を表示する (RuleMark)

チャートに水平線または垂直線を表示することができます。
例えば、チャート内の全データの平均値を示すのに使用できます。

水平線: RuleMark(y: <#T##PlottableValue<Plottable>#>)
垂直線: RuleMark(s: <#T##PlottableValue<Plottable>#>)

chart_rulemark.jpg

高度なチャートタイプ

他にもいくつかの高度なチャートタイプがあります。
例えば、AreaMarkを使えば、チャート上のある領域をマークすることができます。
例えば、温度範囲をマークすることができます。

struct ChartEntry_minMax: Identifiable {
    var title: String
    var minY: Int
    var maxY: Int
    var averageY: Int {
        return (minY + maxY) / 2
    }
    var id: String { return title }
}

struct Charts_AreaMark: View {
    
    @State var data: [ChartEntry_minMax] = [
        .init(title: "Today", minY: 15, maxY: 27),
        .init(title: "Tomorrow", minY: 17, maxY: 28),
        .init(title: "Saturday", minY: 18, maxY: 24),
        .init(title: "Sunday", minY: 16, maxY: 25),
        .init(title: "Monday", minY: 14, maxY: 26),
        .init(title: "Tuesday", minY: 17, maxY: 20),
        .init(title: "Wednesday", minY: 17, maxY: 23)
    ]
    
    var body: some View {
        
        Form {
            
            Chart {
                ForEach(data) { dataPoint in
                    AreaMark(
                        x: .value("Category", dataPoint.title),
                        yStart: .value("Lowest temperature", dataPoint.minY),
                        yEnd: .value("Highest temperature", dataPoint.maxY)
                    )
                    .opacity(0.3)
                    LineMark(
                        x: .value("Category", dataPoint.title),
                        y: .value("Average", dataPoint.averageY)
                    )
                }
            }
            .frame(height: 200)
            
        }
        .navigationTitle("Temperature")
        
    }
    
}

area_chart_1.jpg

他の種類のチャートについて知りたい方は、WWDCのビデオを見てください。


Enjoy the WWDC!


:relaxed: Twitter @MszPro

:relaxed: 個人ウェブサイト https://MszPro.com

:sunny: 私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます:

Written by MszPro~


関連記事

iPhone 14 ProのDynamic Islandにウィジェットを追加し、Live Activitiesを開始する(iOS16.1以降)
iOS 16:秘密値の保存、FaceID認証に基づく個人情報の表示/非表示(LARight)
iOS16 MapKitの新機能 : 地図から場所を選ぶ、通りを見回す、検索補完
SwiftUIアプリでバックグラウンドタスクの実行(ネットワーク、プッシュ通知) (BackgroundTasks, URLSession)
WWDC22、iOS16:iOSアプリに画像からテキストを選択する機能を追加(VisionKit)
WWDC22、iOS16:数行のコードで作成できるSwiftUIの新機能(26本)
WWDC22、iOS 16:SwiftUIでChartsフレームワークを使ってチャートを作成する
WWDC22, iOS 16: WeatherKitで気象データを取得
WWDC 2022の基調講演のまとめ記事


Github repo

このGitHubリポジトリには、この記事で参照したコードが含まれています。


Register as a new user and use Qiita more conveniently

  1. You can follow users and tags
  2. you can stock useful information
  3. You can make editorial suggestions for articles
What you can do with signing up
35
Help us understand the problem. What are the problem?