【Swift】SwiftUIでグラフを実装する
初めに
今回はSwiftUIでグラフを実装していきたいと思います。
記事の対象者
- Swift, SwiftUI 学習者
- SwiftUIでグラフを実装したい方
目的
今回は上記の通りSwiftUIでグラフを実装することを目的とします。
最終的には以下の画像のようなグラフを実装します。
実装
実装は以下の手順で進めていきます。
- グラフに使用するデータ構造とサンプルデータの定義
- グラフのUIの実装
- ContentViewに反映
1. グラフに使用するデータ構造とサンプルデータの定義
まずはグラフに使用するデータ構造と表示させるサンプルデータを定義していきます。
今回は東京都の数年間の人口の推移を折れ線グラフで表示させたいと思います。
なお、人口データは e-Statの統計ダッシュボード から取得しました。
コードは以下の通りです。
import Foundation
struct PopulationTrendsLineData: Identifiable, Equatable, Hashable {
var id = UUID()
var year: Int
var population: Int
}
let tokyoLineData: [PopulationTrendsLineData] = [
.init(year: 1975, population: 11673554),
.init(year: 1976, population: 11674000),
.init(year: 1977, population: 11669000),
.init(year: 1978, population: 11659000),
.init(year: 1979, population: 11637000),
.init(year: 1980, population: 11618281),
// ~ 2022年までのデータ 長いので省略 ~
]
PopulationTrendsLineData
では id
として UUID を指定しており、自動的にIDが生成されるようになっています。また、year
, population
として特定の年の人口を格納しています。
そして、tokyoLineData
にリストの PopulationTrendsLineData
をサンプルデータとして格納しています。
全データを含むコード
import Foundation
struct PopulationTrendsLineData: Identifiable, Equatable, Hashable {
var id = UUID()
var year: Int
var population: Int
}
let tokyoLineData: [PopulationTrendsLineData] = [
.init(year: 1975, population: 11673554),
.init(year: 1976, population: 11674000),
.init(year: 1977, population: 11669000),
.init(year: 1978, population: 11659000),
.init(year: 1979, population: 11637000),
.init(year: 1980, population: 11618281),
.init(year: 1981, population: 11626000),
.init(year: 1982, population: 11650000),
.init(year: 1983, population: 11700000),
.init(year: 1984, population: 11759000),
.init(year: 1985, population: 11829363),
.init(year: 1986, population: 11888000),
.init(year: 1987, population: 11887000),
.init(year: 1988, population: 11873000),
.init(year: 1989, population: 11863000),
.init(year: 1990, population: 11855563),
.init(year: 1991, population: 11894000),
.init(year: 1992, population: 11887000),
.init(year: 1993, population: 11849000),
.init(year: 1994, population: 11796000),
.init(year: 1995, population: 11773605),
.init(year: 1996, population: 11808000),
.init(year: 1997, population: 11881000),
.init(year: 1998, population: 11939000),
.init(year: 1999, population: 11983000),
.init(year: 2000, population: 12064101),
.init(year: 2001, population: 12165000),
.init(year: 2002, population: 12271000),
.init(year: 2003, population: 12388000),
.init(year: 2004, population: 12482000),
.init(year: 2005, population: 12576601),
.init(year: 2006, population: 12704000),
.init(year: 2007, population: 12848000),
.init(year: 2008, population: 12973000),
.init(year: 2009, population: 13048000),
.init(year: 2010, population: 13159388),
.init(year: 2011, population: 13198000),
.init(year: 2012, population: 13234000),
.init(year: 2013, population: 13307000),
.init(year: 2014, population: 13399000),
.init(year: 2015, population: 13515271),
.init(year: 2016, population: 13646000),
.init(year: 2017, population: 13768000),
.init(year: 2018, population: 13887000),
.init(year: 2019, population: 14007000),
.init(year: 2020, population: 14047594),
.init(year: 2021, population: 14010000),
.init(year: 2022, population: 14038000),
]
2. グラフのUIの実装
次にグラフのUIを実装していきます。
コードは以下の通りです。
import SwiftUI
import Charts
struct PopulationTrendView: View {
let populationTrendData: [PopulationTrendsLineData]
@State private var minYearXValue: Int = 0
@State private var maxYearXValue: Int = 0
@State private var minPopulationYValue: Int = 0
@State private var maxPopulationYValue: Int = 0
var body: some View {
VStack {
Text("\(minYearXValue + 1) ~ \(maxYearXValue - 1)年の人口推移")
.font(.title)
Chart(populationTrendData) { dataRow in
LineMark(x: .value("Year", dataRow.year), y: .value("Population", dataRow.population))
}
.chartXScale(domain: minYearXValue...maxYearXValue)
.chartYScale(domain: minPopulationYValue...maxPopulationYValue)
.onAppear {
minYearXValue = (populationTrendData.min { $0.year < $1.year }?.year ?? 0) - 1
maxYearXValue = (populationTrendData.max { $0.year < $1.year }?.year ?? 0) + 1
minPopulationYValue = (populationTrendData.min { $0.population < $1.population }?.population ?? 0) - 100000
maxPopulationYValue = (populationTrendData.max {$0.population < $1.population }?.population ?? 0) + 100000
}
}
.padding(50)
}
}
それぞれ詳しくみていきます。
以下では populationTrendData
として PopulationTrendsLineData
のリストを受け取るようにしています。
また、グラフを表示させる際のグラフの縦軸、横軸の最小値、最大値の数値を計算するための値を State として保持しています。これに関しては後述します。
let populationTrendData: [PopulationTrendsLineData]
@State private var minYearXValue: Int = 0
@State private var maxYearXValue: Int = 0
@State private var minPopulationYValue: Int = 0
@State private var maxPopulationYValue: Int = 0
以下ではデータの year
の最小値、最大値をもとにタイトルを表示させています。
Text("\(minYearXValue + 1) ~ \(maxYearXValue - 1)年の人口推移")
.font(.title)
以下ではデータをもとに折れ線グラフの実装を行なっています。
引数として受け取った populationTrendData
を dataRow
として扱い、x軸のラベルを Year
, y軸のラベルを Population
としてそれぞれのデータを表示しています。
Chart(populationTrendData) { dataRow in
LineMark(
x: .value("Year", dataRow.year),
y: .value("Population", dataRow.population)
)
}
以下ではグラフの設定を行なっています。
具体的にはx軸、y軸のデータの範囲を絞っています。
コメントにもある通り、Viewが構築される段階でデータに含まれる year, population の最小値、最大値を求め、それを chartXScale
, chartYScale
の domain
にそれぞれ代入することで、グラフの範囲を限定しています。
なお、表示領域に余裕を持たせるため、year
は1年、population
は10万ずつ足し引きをしています。
.chartXScale(domain: minYearXValue...maxYearXValue)
.chartYScale(domain: minPopulationYValue...maxPopulationYValue)
.onAppear {
// データに含まれる year の最小値を計算
minYearXValue = (populationTrendData.min { $0.year < $1.year }?.year ?? 0) - 1
// データに含まれる year の最大値を計算
maxYearXValue = (populationTrendData.max { $0.year < $1.year }?.year ?? 0) + 1
// データに含まれる population の最小値を計算
minPopulationYValue = (populationTrendData.min { $0.population < $1.population }?.population ?? 0) - 100000
// データに含まれる population の最大値を計算
maxPopulationYValue = (populationTrendData.max {$0.population < $1.population }?.population ?? 0) + 100000
}
これでグラフのUIの作成は完了です。
ちなみに... 仮にグラフのx軸、y軸のデータの範囲を絞らなかった場合は、以下の画像のように表示範囲が限定されず、見づらいグラフになります。(本来であればよしなに表示してくれるようなのですが、手元では以下のようになりました。)
3. ContentViewに反映
最後に ContentView に反映させます。
コードは以下の通りです。
import SwiftUI
struct ContentView: View {
var body: some View {
PopulationTrendView(populationTrendData: tokyoLineData)
.glassBackgroundEffect()
}
}
これで実行すると以下の画像のように折れ線グラフが表示されるかと思います。
今回のコード全文
import Foundation
import SwiftUI
import Charts
// content view で表示
struct ContentView: View {
var body: some View {
PopulationTrendView(populationTrendData: tokyoLineData)
.glassBackgroundEffect()
}
}
// グラフのUI
struct PopulationTrendView: View {
let populationTrendData: [PopulationTrendsLineData]
@State private var minYearXValue: Int = 0
@State private var maxYearXValue: Int = 0
@State private var minPopulationYValue: Int = 0
@State private var maxPopulationYValue: Int = 0
var body: some View {
VStack {
Text("\(minYearXValue + 1) ~ \(maxYearXValue - 1)年の人口推移")
.font(.title)
Chart(populationTrendData) { dataRow in
LineMark(x: .value("Year", dataRow.year), y: .value("Population", dataRow.population))
}
.chartXScale(domain: minYearXValue...maxYearXValue)
.chartYScale(domain: minPopulationYValue...maxPopulationYValue)
.onAppear {
minYearXValue = (populationTrendData.min { $0.year < $1.year }?.year ?? 0) - 1
maxYearXValue = (populationTrendData.max { $0.year < $1.year }?.year ?? 0) + 1
minPopulationYValue = (populationTrendData.min { $0.population < $1.population }?.population ?? 0) - 100000
maxPopulationYValue = (populationTrendData.max {$0.population < $1.population }?.population ?? 0) + 100000
}
}
.padding(50)
}
}
// グラフに使用するデータのデータ構造
struct PopulationTrendsLineData: Identifiable, Equatable, Hashable {
var id = UUID()
var year: Int
var population: Int
}
// サンプルデータ
let tokyoLineData: [PopulationTrendsLineData] = [
.init(year: 1975, population: 11673554),
.init(year: 1976, population: 11674000),
.init(year: 1977, population: 11669000),
.init(year: 1978, population: 11659000),
.init(year: 1979, population: 11637000),
.init(year: 1980, population: 11618281),
.init(year: 1981, population: 11626000),
.init(year: 1982, population: 11650000),
.init(year: 1983, population: 11700000),
.init(year: 1984, population: 11759000),
.init(year: 1985, population: 11829363),
.init(year: 1986, population: 11888000),
.init(year: 1987, population: 11887000),
.init(year: 1988, population: 11873000),
.init(year: 1989, population: 11863000),
.init(year: 1990, population: 11855563),
.init(year: 1991, population: 11894000),
.init(year: 1992, population: 11887000),
.init(year: 1993, population: 11849000),
.init(year: 1994, population: 11796000),
.init(year: 1995, population: 11773605),
.init(year: 1996, population: 11808000),
.init(year: 1997, population: 11881000),
.init(year: 1998, population: 11939000),
.init(year: 1999, population: 11983000),
.init(year: 2000, population: 12064101),
.init(year: 2001, population: 12165000),
.init(year: 2002, population: 12271000),
.init(year: 2003, population: 12388000),
.init(year: 2004, population: 12482000),
.init(year: 2005, population: 12576601),
.init(year: 2006, population: 12704000),
.init(year: 2007, population: 12848000),
.init(year: 2008, population: 12973000),
.init(year: 2009, population: 13048000),
.init(year: 2010, population: 13159388),
.init(year: 2011, population: 13198000),
.init(year: 2012, population: 13234000),
.init(year: 2013, population: 13307000),
.init(year: 2014, population: 13399000),
.init(year: 2015, population: 13515271),
.init(year: 2016, population: 13646000),
.init(year: 2017, population: 13768000),
.init(year: 2018, population: 13887000),
.init(year: 2019, population: 14007000),
.init(year: 2020, population: 14047594),
.init(year: 2021, population: 14010000),
.init(year: 2022, population: 14038000),
]
まとめ
最後まで読んでいただいてありがとうございました。
今回は SwiftUI でグラフを実装する方法を簡単に共有しました。
他にも棒グラフや円グラフも実装できるようなので、機会があれば追加していきます。
誤っている点等あればご指摘いただければ幸いです。
参考
Discussion