【Swift】半円グラフの作り方

# はじめに
Swiftのグラフ描写ライブラリと言えば「[Charts](https://github.com/danielgindi/Charts)」ですよね。
そんなChartsで半円グラフを作る機会があったのでアウトプットとして記事にしておきます。

# 作るもの
GitHubリポジトリの使用言語グラフ
(今回は[こちら](https://github.com/apple/swift)のリポジトリ)

![スクリーンショット 2022-05-23 22.52.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/7fbe9330-f4ff-da45-7ce3-5d736512a584.png)

これを半円グラフで描写します。

# インストール
私はCocoaPodsでインストールしました。
通信にはAlamofireを使います
```Ruby
pod 'Charts'
pod 'Alamofire'
```


# 実装
### Storyboardの設定
Viewを配置します
今回はわかりやすいように背景色を茶色に変更しました
![スクリーンショット 2022-05-23 23.00.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/a6212402-69cb-58a2-3c30-5da7756c2788.png)

制約を追加してViewをいい感じに配置します
![スクリーンショット 2022-05-23 23.34.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/6c9ad76e-3171-4567-392a-1af2a107d213.png)



先ほど追加したViewを選択して以下のように入力します
![スクリーンショット 2022-05-23 23.03.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/c6a3514e-47d6-edb6-3a8e-c8c6bcd9d3b0.png)

|欄名|入力文字|
|:-:|:-:|
|Class|PieChartView|
|Module|Charts|

### Storyboardとコードの紐付け
Storyboardを開いた状態でoptionキーを押しながらViewControllerを選択します
そうするとこのようにに画面表示になります。
![スクリーンショット 2022-05-23 23.10.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/9cfbe541-18f6-4b45-4cbf-86399235b49d.png)

Controlキーを押しながらViewをコードにドラッグします。
![スクリーンショット 2022-05-23 23.12.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/895d50a5-2250-76f7-cee6-759adda59c89.png)

chartView(任意)と入力してConnectを選択しましょう
![スクリーンショット 2022-05-23 23.13.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/c695003d-3d1d-df53-ec98-004451dec664.png)

紐付けができたら`Charts`をインポートしましょう
![スクリーンショット 2022-05-23 23.15.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/a276d77e-5467-0616-0173-74000ce452a0.png)



### コードの実装
コードの説明はコード内のコメントを見てください
```ViewController.swift
import UIKit
import Charts
import Alamofire

class ViewController: UIViewController {

    @IBOutlet var chartView: PieChartView!

    public let viewModel = ViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()

        // 言語の使用割合グラフを表示
        self.setChart()
    }
}

extension ViewController: ChartViewDelegate {
    /// 言語の使用割合グラフを表示
    /// グラフの設定
    private func setChart() {
        chartView.drawEntryLabelsEnabled = false // グラフラのラベルを非表示
        chartView.chartDescription.enabled = false // グラフの説明文を非表示
        chartView.holeColor = .clear // 中央のくり抜き円の色
        chartView.holeRadiusPercent = 0.58 // 中央のくり抜き円の大きさ

        chartView.rotationEnabled = false // 回転無効化
        chartView.highlightPerTapEnabled = false // タップを無効化
        chartView.noDataTextColor = .clear // データなしの場合のテキストを透明にする

        // 半円用グラフ(これがないと円になる)
        chartView.maxAngle = 180
        chartView.rotationAngle = 180
        chartView.centerTextOffset = CGPoint(x: 0, y: -20)

        // 凡例の設定
        let l = chartView.legend
        l.textColor = .black
        l.horizontalAlignment = .center
        l.verticalAlignment = .bottom
        l.orientation = .horizontal
        l.drawInside = false
        l.xEntrySpace = 5
        l.yEntrySpace = 0

        // 使用言語を取得
        self.viewModel.getLanguages(
            url: "https://api.github.com/repos/apple/swift/languages"
        ) { (languagesNameArray, languagesValueArray)  in
            self.setData(languagesNameArray, languagesValueArray)
        }

        chartView.animate(xAxisDuration: 1.4, easingOption: .easeInOutCubic) // グラフに表示アニメーションを設定
    }


    /// 言語の使用割合グラフ
    /// データの作成
    private func setData(_ languagesNameArray: [String], _ languagesValueArray: [Int]) {

        let languagesArray = self.viewModel.createLanguageArray(languagesNameArray: languagesNameArray, languagesValueArray: languagesValueArray)

        // PieChartデータを作成
        let entries = (0..<languagesArray.0.count).map { (i) -> PieChartDataEntry in
            return PieChartDataEntry(
                value: Double(languagesArray.1[i % languagesArray.1.count]),
                label: languagesArray.0[i % languagesArray.0.count]
            )
        }

        let set = PieChartDataSet(entries: entries, label: "")
        set.sliceSpace = 0 // 項目間のスペースを0にする
        set.selectionShift = 20 // 縮小

        // 使用言語割合グラフに適用する言語カラー配列を作成する
        let colors = self.viewModel.createLanguageColorArray(languagesArray: languagesArray.0)
        set.colors = colors // グラフの色

        let data = PieChartData(dataSet: set)
        chartView.data = data

        // 言語が多いとゴチャゴチャになるので値の非表示
        for set in chartView.data! {
            set.drawValuesEnabled = !set.drawValuesEnabled
        }

        chartView.setNeedsDisplay()
    }
}
```

```ViewModel.swift
import UIKit
import Alamofire

class ViewModel: NSObject {
    /// リポジトリで使用されている言語を取得
    /// ↓
    /// 使用割合の高い順に並び替え
    ///
    /// - parameters:
    ///  - url: 言語情報の取得可能なAPIをリポジトリ情報から指定
    ///  - completion: 言語リストと言語割合リストを返す
    ///
    /// EX) https://api.github.com/repos/apple/swift/languages
    ///
    public func getLanguages(
        url: String,
        completion: @escaping ([String], [Int]) -> Void
    ) {
        var languagesNameArray: [String] = []
        var languagesValueArray: [Int] = []

        AF.request(url, method: .get).responseData { response in
            do {
                guard let data = response.data else { return }
                let languages = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Int]
                guard let languagesDict = languages else { return }

                // 使用割合の高い言語順に並び替える
                let languagesSort = languagesDict.sorted { $0.1 > $1.1 } .map { $0 }

                for language in languagesSort {
                    languagesNameArray.append(language.key)
                    languagesValueArray.append(language.value)
                }
                completion(languagesNameArray, languagesValueArray)
            } catch {
                completion(languagesNameArray, languagesValueArray)
            }
        }
    }

    /// 使用言語割合グラフ用のデータを作成する(言語選別)
    ///
    /// - parameters:
    ///  - languagesNameArray: 全ての使用言語名
    ///  - languagesValueArray: 全ての使用言語割合
    ///
    /// - returns:
    ///  - newLanguagesNameArray: 使用割合が0.5%以上の言語名のみ
    ///  - newLanguagesValueArray: 使用割合が0.5%以上の言語割合のみ
    ///
    public func createLanguageArray(languagesNameArray: [String], languagesValueArray: [Int]) -> ([String], [Double]) {
        let languagesValueSum = languagesValueArray.reduce(0, +) // 配列合計

        var newLanguagesNameArray: [String] = []
        var newLanguagesValueArray: [Double] = []

        // 割合が0.5％以上の言語を配列に格納
        for i in 0..<languagesValueArray.count {
            let percent = floor((Double(languagesValueArray[i]) / Double(languagesValueSum)) * 1000) / 10
            if percent >= 0.5 {
                newLanguagesNameArray.append(languagesNameArray[i])
                newLanguagesValueArray.append(percent)
                print("\(languagesNameArray[i]): \(percent)%")
            }
        }

        var newLanguagesValueSum: Double = 0 // 割合合計

        for i in newLanguagesValueArray {
            newLanguagesValueSum += i
        }

        // 割合が0.5より小さい言語はOtherとしてまとめる & 言語がなかった場合、"No Language"を返す
        if (100 - newLanguagesValueSum) != 0.0 && (100 - newLanguagesValueSum) != 100.0 {
            newLanguagesNameArray.append("Other")
            newLanguagesValueArray.append(round((100 - newLanguagesValueSum) * 100) / 100)
            print("Other: \(floor((100 - newLanguagesValueSum) * 100) / 100)%")
        } else if (100 - newLanguagesValueSum) == 100.0 {
            newLanguagesNameArray.append("No Language")
            newLanguagesValueArray.append(100)
            print("No Language")
        }

        return (newLanguagesNameArray, newLanguagesValueArray)
    }

    /// 使用言語割合グラフに適用する言語カラー配列を作成
    ///
    /// - parameters:
    ///  - languagesArray: 言語配列
    ///
    /// - returns: 言語カラー配列
    ///
    public func createLanguageColorArray(languagesArray: [String]) -> [UIColor] {
        var colors: [UIColor] = []
        for i in languagesArray {
            colors.append(UIColor(language: i))
        }

        return colors
    }
}
```

```Color+.swift
// 長いので省略
// ↓ ここに乗ってます ↓
// https://qiita.com/SNQ-2001/items/56e5d0147f35e4bd0e13
```

# 完成
![IMG_0383.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1745371/90603051-12f4-08ed-e309-84f9fbb5c270.png)

# おわり
SwiftUIでの実装方法も紹介できたらなと思ってます。

今回作成したデモ置いときます。

https://github.com/SNQ-2001/Charts-HalfPieCharts-Demo