📊

SwiftUI ChartsのPointMarkで3要素を表現する方法

2022/10/23に公開

Swift Charts 、皆さん使っていますか?
私は筋トレの記録をつけるアプリを作っていて、日時・重さ・回数を2次元のグラフに落とし込みたいモチベーションがありました。
現状の Swift Charts では3次元のグラフが提供されていません。
そこで、2次元のグラフで3要素を表現する方法をいくつか試したので、それをまとめています。

データ構造

以下のようなデータ構造をグラフとして表示することを考えました。

struct Record: Identifiable {
    var id: UUID = UUID()
    let dateString: String
    let reps: Int
    let weight: Int
}

単純に2次元のグラフを使う

まずは以下のようなコードで2次元のグラフとして表示してみます。


var records: [Record] = [
    Record(dateString: "2022/10/01", reps: 10, weight: 60),
    Record(dateString: "2022/10/02", reps: 20, weight: 40),
    Record(dateString: "2022/10/03", reps: 5, weight: 100)
]

struct ContentView: View {
    var body: some View {
        Chart(records) {
            PointMark(
                x: .value("Date", $0.dateString),
                y: .value("Reps", $0.reps)
            )
        }
        .frame(height: 200)
        .padding(.horizontal)
    }
}

表示した結果は以下のようになります。

当たり前ですが、 weight の情報が表示できていないことが分かります。
以下では weight をどのようにこのグラフに追加するかを検討します。

.foregroundStyle(by:)

.foregroundStyle(by:) を使うパターンを考えてみます。
https://developer.apple.com/documentation/charts/chartcontent/foregroundstyle(by:)
このAPIを使うと、設定した値に応じて自動的にグラフの点のスタイルを変更してくれます。
以下のコードに変更してみます。

Chart(records) {
    PointMark(
        x: .value("Date", $0.dateString),
	y: .value("Reps", $0.reps)
    )
    .foregroundStyle(by: .value("Weight", $0.weight))
}

表示した結果は以下のようになります。

色の濃淡によって weight を表現することができました。
大体の値が分かれば大丈夫なケースでは .foregroundStyle(by:) が使えそうです。

.symbolSize(by:)

続いて .symbolSize(by:) を使うパターンを考えてみます
https://developer.apple.com/documentation/charts/chartcontent/symbolsize(by:)

このAPIを使うと、設定した値に応じて自動的にグラフの点の大きさを変更してくれます。
以下のコードに変更してみます。

Chart(records) {
    PointMark(
        x: .value("Date", $0.dateString),
        y: .value("Reps", $0.reps)
     )
     .symbolSize(by: .value("Weight", $0.weight))
}

表示した結果は以下のようになります。

点の大きさによって weight を表現することができました。
.foregroundStyle(by:) より正確な値が表現できますが、量を表現しない場合はやや違和感があります。

.annotation()

上記の2つの方法は大体の値を表現するのに使えそうでしたが、今回の場合は正確な値が表現したかったので他の方法を検討しました。
.annotation() を使うとグラフの点に対して装飾を出来ることが出来るので、これを使います。
https://developer.apple.com/documentation/charts/chartcontent/annotation(position:alignment:spacing:content:)-65emh

.annotation()weight をテキストで表示し、グラフの点を消すという指針で以下のようにコードを修正します。

Chart(records) { record in
    PointMark(
        x: .value("Date", record.dateString),
        y: .value("Reps", record.reps)
    )
    .annotation(position: .overlay, alignment: .center) {
        Text(String(record.weight))
    }
    .symbolSize(0)
}

表示した結果は以下のようになります。

点の位置に weight をテキストで表現することができました。
この方法が正確な値を表示したい場合は適切そうですが、グラフとしては違和感があります。

まとめ

上記3つの方法を紹介しました。
私のアプリでは3つめの方法を使おうと考えていますが、グラフとしてはやや違和感があるので、別の方法も探していきたいと思います。
もし何かいい方法をご存知の方はコメントで教えていただけると嬉しいです。

Discussion