今度は外部ライブラリChartsを利用して、棒グラフを作成してみる。
- 値が最大のデータは色をオレンジにする
- アニメーションがある
- 棒グラフの上に値を表示する
- ページ切り替えができる棒グラフを作る
- タップしたらイベントを発生させる
1〜3、5は機能としてある。4だけ頑張って作る。思い通りのレイアウトにするためにはプロパティとかドキュメントとかを漁る必要があるが、どこにどのプロパティがあるのかは大体予想できる。
ChartDataSet.colors
で各棒の色を変更できる。BarChartView.animate(yAxisDuration:)
を利用。BarChartView.drawValueAboveBarEnabled = true
とする。表示形式を変更するためにはChartDataSet.valueFormatter
にフォーマット用のオブジェクトを指定する。- ScrollViewの中ににBarChartViewを複数配置。
ChartViewDelegate
を利用。
その他デフォルトの設定だと表示する情報量が多すぎるので、いくつかのプロパティをいじる。
Chartsのインストール#
まず、CocoaPodsがインストールされていることが前提。
プロジェクトフォルダで以下のコマンドを実行。
podfile
が作成されるので、それを編集する。use_frameworks!
の下に以下の記述を追加。
プロジェクトフォルダで以下のコマンドを実行。
以降、プロジェクトはプロジェクト名.xcodeproj
ではなくプロジェクト名.xcworkspace
から開く。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| import UIKit
import Charts
struct BarChartModel {
let value: Int
let name: String
}
class ViewController: UIViewController {
let barItems = [
(7, "太郎"), (1, "次郎"), (2, "三郎"), (6, "四郎"), (3, "五郎"),
(9, "六郎"), (2, "七郎"), (3, "八郎"), (1, "九郎"), (5, "十郎"),
(1, "十一郎"), (1, "十二郎"), (6, "十三郎")
]
override func viewDidLoad() {
super.viewDidLoad()
let barChartView = createBarChartView()
self.view.addSubview(barChartView)
barChartView.translatesAutoresizingMaskIntoConstraints = false
barChartView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 80).isActive = true
barChartView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -80).isActive = true
barChartView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
barChartView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
}
private func createBarChartView() -> BarChartView {
let barChartView = BarChartView()
barChartView.data = createBarChartData(of: barItems.map({BarChartModel(value: $0.0, name: $0.1)}))
return barChartView
}
private func createBarChartData(of items: [BarChartModel]) -> BarChartData {
let entries: [BarChartDataEntry] = items.enumerated().map {
let (i, item) = $0
return BarChartDataEntry(x: Double(i), y: Double(item.value))
}
let barChartDataSet = BarChartDataSet(entries: entries, label: "Label")
let barChartData = BarChartData(dataSet: barChartDataSet)
return barChartData
}
}
|
これだけの記述で以下の棒グラフが描ける。
棒グラフに限っては、以下の手順で作る。
BarChartEntry
にデータを詰めるBarChartEntry
の配列からBarChartDataSet
を作成BarChartDataSet
からBarChartData
を作成BarChartView
にBarChartData
を詰める
色をつける#
ViewController
に最大値のプロパティを持たせる。
1
| lazy var maxVal: Int = barItems.map({ $0.0 }).max()!
|
barChartDataSet
に色の配列をセットする。
1
| barChartDataSet.colors = items.map { $0.value == maxVal ? .systemOrange : .systemBlue }
|
アニメーション#
createBarChartView
関数に以下の記述を追加。
1
| barChartView.animate(yAxisDuration: 1)
|
棒グラフの下に名前を表示する#
これは、棒グラフのx軸を設定することで実現できる。
createBarChartView
関数に以下の記述を追加。
1
2
3
| barChartView.xAxis.labelCount = items.count
barChartView.xAxis.labelPosition = .bottom
barChartView.xAxis.valueFormatter = IndexAxisValueFormatter(values: items.map({$0.name}))
|
labelCount
を設定しておかないと、ラベルの表示が奇数番目のみにになるので注意。labelPosition
を設定しておかないと、ラベルの位置が上になるので注意。valueFormatter
には、軸の表示方法を管理するオブジェクトを定義する。
IndexAxisValueFormatter
の代わりに、IAxisValueFormatter
に準拠したオブジェクトを指定すると、x軸の書式をカスタマイズできる。
例えば、以下のようにXAxisFormatter
を定義すると、これはIndexAxisValueFormatter
と同じような振る舞いをする。
1
2
3
4
5
6
7
8
9
10
| class XAxisFormatter: IAxisValueFormatter {
let items: [BarChartModel]
init(of items: [BarChartModel]) {
self.items = items
}
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let index = Int(value)
return self.items[index].name
}
}
|
棒グラフの上にある数字の書式設定#
createBarChartData
関数に以下の記述を追加。
1
2
| barChartView.valueFont = .systemFont(ofSize: 20)
barChartDataSet.valueFormatter = ValueFormatter(of: items)
|
ValueFormatter
を定義する。これはIValueFormatter
に準拠したクラス。このstringForValue
で、x軸の値value
に対するラベルの値を返すように設定する。
1
2
3
4
5
6
7
8
9
| class ValueFormatter: IValueFormatter {
let items: [BarChartModel]
init(of items: [BarChartModel]) {
self.items = items
}
func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
return "\(Int(value))"
}
}
|
細かい設定#
グリッドとかy軸とかはいらないので、それを消す設定をする。
createBarChartView
関数に以下の記述を追加。
1
2
3
4
5
6
7
8
9
10
11
| // グリッドやy軸を非表示
barChartView.xAxis.drawGridLinesEnabled = false
barChartView.leftAxis.enabled = false
barChartView.rightAxis.enabled = false
// 凡例を非表示にする
barChartView.legend.enabled = false
// ズームできないようにする
barChartView.pinchZoomEnabled = false
barChartView.doubleTapToZoomEnabled = false
|
ページ分け#
1ページ内に13本のグラフが並んでいるのは見づらい。なのでScrollViewを使ってページ分けする。
Main.storyboard#
前回通りにやる。
ViewController.swift#
viewDidLoad
では主にscrollView
の設定をする。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| override func viewDidLoad() {
super.viewDidLoad()
scrollView.frame = CGRect(
x: 0,
y: 0,
width: scrollView.superview!.frame.width,
height: scrollView.superview!.frame.height
)
let contentsView = createContentsView(
of: barItems.map({ BarChartModel(value: $0.0, name: $0.1 ) }),
barsCountPerPage: 5
)
scrollView.addSubview(contentsView)
scrollView.contentSize = contentsView.frame.size
scrollView.isPagingEnabled = true
}
|
createContentsView
を定義する。こちらもやってること自体は前回とあまり変わっていない。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| private func createContentsView(of items: [BarChartModel], barsCountPerPage: Int) -> UIView {
let itemsPerPage = stride(from: 0, to: items.count, by: barsCountPerPage).map {
Array(items[$0 ..< min($0 + barsCountPerPage, items.count)])
}
let contentsView = UIView(frame: CGRect(
x: 0,
y: 0,
width: scrollView.frame.width * CGFloat(itemsPerPage.count),
height: scrollView.frame.height
))
for (i, items) in itemsPerPage.enumerated() {
let barChartView = createBarChartView(of: items)
let percent = CGFloat(items.count) / CGFloat(itemsPerPage[0].count)
barChartView.frame = CGRect(
x: scrollView.frame.width * CGFloat(i),
y: 0,
width: scrollView.frame.width * percent,
height: scrollView.frame.height
)
contentsView.addSubview(barChartView)
}
return contentsView
}
|
createBarChartView
に以下の記述を追加。これで全ページで同じ縮尺になる。
1
| barChartView.leftAxis.axisMaximum = Double(maxVal) + 1
|
タップイベント#
createBarChartView
に以下の記述を追加。
1
| barChartView.delegate = self
|
ViewController
のextension
を追加する。chartValueSelected
メソッドでタップ時の処理を指定する。例えば次のようにすると、棒グラフの名前と値を取得できる。
1
2
3
4
5
6
7
| extension ViewController: ChartViewDelegate {
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
let axisFormatter = chartView.xAxis.valueFormatter!
let label = axisFormatter.stringForValue(entry.x, axis: nil)
print(label, entry.y)
}
}
|
ちなみに、先ほどValueFormatter
にitems
を持たせていたため、次のようにしてitemの値を取得することも可能(あまり綺麗な方法ではないが)。
1
2
3
4
5
6
7
8
| extension ViewController: ChartViewDelegate {
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
let valueFormatter = chartView.data?.dataSets[0].valueFormatter as! ValueFormatter
let items = valueFormatter.items
let index = Int(entry.x)
print(items[index])
}
}
|