Chart がきた
iOS には今まで標準ライブラリでチャートを表示させる方法がなく、外部ライブラリに依存しっぱなしだったのですがとうとう Swift にもチャートライブラリが実装されました。
正直、機能としてはまだまだ足りないところがあるのですが標準機能として実装されるのはありがたいですね。
今回はCreating a chart using Swift Chartsを読みながら、SwiftUI でチャートを実装させるための方法を学んでいこうと思います。
チャートの種類
チャートにはMarkと呼ばれるコンポーネントがあり、これでチャートを表現します。
- AreaMark
- LineMark
- PointMark
- RectangleMark
- RuleMark
- BarMark
実装されているのは上の六種類で、PieMark や DoughnutMark や RadarMark のようなものは存在しません。あればよかったんですけどね、残念です。
で、それぞれどんな実装が可能なのかを見ていきます。サンプルの画像は Apple のドキュメントから引っ張ってきました。
AreaMark
init<X, Y>( x: PlottableValue<X>, y: PlottableValue<Y>, stacking: MarkStackingMethod = .standard) where X : Plottable, Y : Plottable株価とかに使えそうなやつですね。
これも積み重ねることができます。
積み重ね方を変えるとこういう表現も可能という例。
LineMark
init<X, Y>( x: PlottableValue<X>, y: PlottableValue<Y>) where X : Plottable, Y : Plottable標準的なラインチャートって感じがします。
線を増やすこともできます。
PointMark
init<X, Y>( x: PlottableValue<X>, y: PlottableValue<Y>) where X : Plottable, Y : Plottable散布図とかに使えるやつです。
RectangleMark
init<X, Y>( x: PlottableValue<X>, yStart: PlottableValue<Y>, yEnd: PlottableValue<Y>, width: MarkDimension = .automatic) where X : Plottable, Y : Plottable使い所がよくわからないグラフの一つ。
RuleMark
init<X, Y>( x: PlottableValue<X>, yStart: PlottableValue<Y>, yEnd: PlottableValue<Y>) where X : Plottable, Y : Plottableこれもいまいち使い所がわかっていないです。幅をもたせたいときに使う感じでしょうか。
BarMark
init<X, Y>( x: PlottableValue<X>, yStart: PlottableValue<Y>, yEnd: PlottableValue<Y>, width: MarkDimension = .automatic) where X : Plottable, Y : Plottable標準的なバーチャートです。使い所は多いはず。サンプルはこの向きですが、X 軸と Y 軸の反転や積み重ねなどもできます。
PlottableValue
で、チャートを表現するために必要なのがこのPlottableValueという謎の構造体。
struct PlottableValue<Value> where Value: Plottable {}定義を見てみるとこんな感じで、要はPlottableに適合すれば良いらしいので、Plottableを調べると、
You can plot Plottable data values with marks with
.value(label, keyPath):
とでてきます。なんでこんなややこしいことになっているのかとも思ったのですが、よくよく考えれば理にかなっている気もします。
例えばラインチャートを使って以下の画像のようなものを表示させることを考えます。
このときに必要なのはxの値をyの値を持つ構造体の配列です。なので以下のようなコードを使ってEntryを定義して、その配列を渡せばチャートの表示に必要なデータとしては十分なわけです。
struct Entry<T: BinaryFloatingPoint> { let x: T let y: T}
let entries: [Entry] = []
var body: some View { Chart(entries, content: { LineMark( x: .value(x)), y: .value(y)) ) })}あれ、じゃあデータが一つ増えて二本の線を引くことになったらどうすればいいんだとなりますね。
バカ正直に考えるとentriesを二次元配列にして、二回描画するとかなるわけですがそれだと無駄にコードが複雑化してしまいます。
じゃあどうするのかというとentries自体は弄らずに、線を区別するための情報をEntryにもたせます。例として、以下のコードではtypeで区別するようにしました。
struct Entry<T: BinaryFloatingPoint> { let x: T let y: T let type: Int}
let entries: [Entry] = []
var body: some View { Chart(entries, content: { entry in LineMark( x: .value(entry.x)), y: .value(entry.y)) ) .foregroundStyle(by: .value(entry.type)) })}foregroundStyleというのはまあ簡単に言えばカテゴリ分類みたいな感じです。上のコードでは簡略化のためにラベルを省略していますが、本来のPlottableはラベルと値を組み合わせたプロトコルなので、ラベルのデータも必要になります。
とはいえ、使う場面があまりないのでなくてもいいのかもしれません。いや、多分要るけど理解ができてなさすぎて使う場面がわからないだけなんですけれど。