search
LoginSignup
3

posted at

updated at

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

はじめに

Swiftのグラフ描写ライブラリと言えば「Charts」ですよね。
そんなChartsで半円グラフを作る機会があったのでアウトプットとして記事にしておきます。

作るもの

GitHubリポジトリの使用言語グラフ
(今回はこちらのリポジトリ)

スクリーンショット 2022-05-23 22.52.06.png

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

インストール

私はCocoaPodsでインストールしました。
通信にはAlamofireを使います

pod 'Charts'
pod 'Alamofire'

実装

Storyboardの設定

Viewを配置します
今回はわかりやすいように背景色を茶色に変更しました
スクリーンショット 2022-05-23 23.00.03.png

制約を追加してViewをいい感じに配置します
スクリーンショット 2022-05-23 23.34.06.png

先ほど追加したViewを選択して以下のように入力します
スクリーンショット 2022-05-23 23.03.40.png

欄名 入力文字
Class PieChartView
Module Charts

Storyboardとコードの紐付け

Storyboardを開いた状態でoptionキーを押しながらViewControllerを選択します
そうするとこのようにに画面表示になります。
スクリーンショット 2022-05-23 23.10.52.png

Controlキーを押しながらViewをコードにドラッグします。
スクリーンショット 2022-05-23 23.12.17.png

chartView(任意)と入力してConnectを選択しましょう
スクリーンショット 2022-05-23 23.13.37.png

紐付けができたらChartsをインポートしましょう
スクリーンショット 2022-05-23 23.15.33.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

おわり

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

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

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
3