Chartsフレームワークの実践的な使用例
Chartsフレームワークをさらに使いこなすために、よく利用されるY軸領域の調整とチャートの描画時のアニメーションとX軸の期間を変更する例を挙げます。それぞれについて前節で作成したサンプルに機能を追加してみます。
グラフを見やすくする
既定のままグラフを表示すると、前節のサンプルのようにチャートのY軸の上部が詰まったように少し窮屈な感じで表示されます。そこで、Y軸に少し余裕を持たせて表示してみます。同時に棒グラフの色もグラデーションにしてみます。
func SampleChart() -> some View {
// サンプルデータ内のamount の最大値を取得
let max = sampleData.max(by: { $1.amount > $0.amount })?.amount ?? 0 // -------- ①
Chart {
ForEach(sampleData){ item in
BarMark( // 棒グラフを表示
x: .value("date", item.date, unit: .day),
y: .value("amount", item.amount)
).foregroundStyle(Color.blue.gradient) // -------- ③
}
}
.frame(height: 400)
.chartYScale(domain: 0...(max + 5000)) // -------- ②
}
チャートを表示する前に、サンプルデータ内のY軸の最大値を取得しておきます(①)。サンプルデータは配列なので、maxメソッドでamountの最も大きいものを取得します。maxメソッドで取得できない場合も考慮してオプショナル型で取得し、取得できない場合の最大値は0としています。
取得した最大値に5000を加算した値を、chartYScaleメソッドでY軸の範囲の最大に指定します(②)。この処理で、Y軸の最大値をmax+5000としたチャートを表示できます。棒グラフの色は、foregroundStyleメソッドで指定します(③)。foregroundStyleメソッドに関しては、本連載第2回で確認できます。
アニメーション効果を実装する
グラフを表示するときに、アニメーション効果をつけることも可能です。作成したサンプルにアニメーションを実装してみます。最初に次のようにチャートで表示する構造体にアニメーション用のプロパティを設けます。
struct DailyAmount: Identifiable {
var id = UUID()
var date: Date
var amount: Double
var animate: Bool = false // アニメーション用のプロパティ
}
サンプルでは、Bool型のanimateプロパティを定義しました。次に、animateプロパティがfalseからtrueに変わるときにアニメーションを行えるようにshowAnimateのメソッド名で処理を定義します。
func showAnimate() {
// animateプロパティをfalseに
sampleData = sampleData.map({ DailyAmount(date: $0.date, amount: $0.amount, animate: false) }) // -------- ①
for (index,_) in sampleData.enumerated(){ // -------- ②
// 各データを index * 0.05秒遅延して処理を行う
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.05){
// 順番にeaseInOutアニメーションを0.5秒かけて実行
withAnimation(.easeInOut(duration: 0.5)){
sampleData[index].animate = true
}
}
}
}
メソッドの最初では、animateプロパティをfalseにしておきます(①)。その後で、チャートに表示するsampleData内の要素である各DailyAmount内のanimateプロパティを、順番に時間差でtrueにしていきます(②)。これによって、チャートが左から順にアニメーションしていく効果を実装できます。
作成したshowAnimate()メソッドは、チャートを表示、または折れ線グラフに切り替えるタイミングで実行できるようにします。
var body: some View {
NavigationStack {
VStack {
VStack(alignment: .leading, spacing: 8) {
Toggle("Line Graph", isOn: $showLineGraph)
.padding(.top)
.onChange(of: showLineGraph) { newValue in // -------- ②
showAnimate() // アニメーション実行
}
SampleChart()
# 略
}
.onAppear { // -------- ①
showAnimate() // アニメーション実行
}
}
NavigationStackの表示時(①)、Toggleの切り替え時(②)にshowAnimate()メソッドを実行します。最後に、チャートを表示するときに、animetaプロパティがtrueであればamoutプロパティの値/falseであれば0を表示するようにします(③)。
Chart {
ForEach(sampleData){ item in
if showLineGraph {
LineMark(
x: .value("date", item.date, unit: .day),
// animetaプロパティがtrueであればamoutプロパティの値/falseであれば0を表示
y: .value("amount", item.animate ? item.amount : 0) // -------- ③
)
.foregroundStyle(Color.blue)
AreaMark(
x: .value("date", item.date ,unit: .day),
y: .value("amount", item.animate ? item.amount : 0) // -------- ③
)
.foregroundStyle(Color.blue).opacity(0.1)
} else {
BarMark(
x: .value("date", item.date, unit: .day),
y: .value("amount", item.animate ? item.amount : 0) // -------- ③
).foregroundStyle(Color.blue.gradient)
}
サンプルを実行すると、チャートを描画する際にアニメーションが適用されることを確認できます。
アニメーションの動きを変更したい場合には、showAnimateメソッド内のanimeteプロパティの値をtrueにするタイミング(間隔)を調整してみましょう。
週ごとに集計してチャートを表示する
日単位で表示していたチャートを週単位や月単位に切り替えて表示することも、Swiftのコレクションを扱う機能を利用して比較的短いコードで実装できます。最初に週単位のデータを定義する構造体を設けます。
struct WeeklyAmount: Identifiable {
var id = UUID()
var index: Int // 第何週というインデックス
var amount: Double // 週単位のamount
var animate: Bool = false // アニメーション用のプロパティ
}
次に、日単位(days)と週単位(week)を区別するためのPickerを設けます。ピッカーで週単位が選択された場合に、sampleDataを週単位のsampleDataWeeklyに分ける処理を行います。
struct ContentView: View {
# 略
@State var sampleDataWeekly: [WeeklyAmount] = [] // 週単位の構造体の配列
@State var selectedTab: String = "days" // 日単位か週単位かの選択
var isWeek: Bool { get { eturn selectedTab == "week" } } // 週単位の選択か?
# 略
Picker("", selection: $selectedTab) { // 日単位/週単位の選択ピッカー
Text("days").tag("days")
Text("week").tag("week")
}
.pickerStyle(.segmented)
.onChange(of: selectedTab) { newValue in // -------- ①
if isWeek {
// 日単位のデータを第○週という単位で辞書に分割
let calendar = Calendar(identifier: .gregorian)
let dic = Dictionary(grouping: sampleData, by: { calendar.component(.weekOfMonth, from: $0.date) }).sorted(by: { $0.0 < $1.0 })
// 週単位のWeeklyAmount構造体の配列にまとめる
sampleDataWeekly = dic.map({ (key,value) in
return WeeklyAmount(index: key, amount: value.map{ $0.amount }.reduce(0, +))
})
}
showAnimate()
# 略
ピッカーで週単位が選択された場合には、データを週単位で分割したあとに集計し直しています。配列をクロージャで指定した条件で分割する処理については、「Swift 4で最初に知っておきたい3つのポイント」連載の第2回で確認できます。
グラフ表示やアニメーションの処理は、sampleDataに対して行った処理をsampleDataWeeklyに対しても行う内容なのでコードは割愛します。
Chartで表示するデータは配列や辞書といったコレクションで用意することが多いです。コレクションの加工が容易というSwiftの特徴を利用することで、違う期間でのグラフの表示など、データをグループ化したチャートの切り替えも比較的容易に行うことができます。
まとめ
今回はSwiftUI 4で追加Swift Chartsフレームワークの使い方の例について説明しました。次回は、WeatherKitについて説明します。
