LoginSignup
1
3

SwiftUIでRader Chart

Last updated at Posted at 2023-11-30

SwiftUIでサクッとレーダーチャートを作りたいな

image.png

RadarChartView.swift
//
//  RadarChartView.swift
//
import SwiftUI

struct RadarChart: View {
    var keys: [String]
    var values: [Double]
    var strokeColor: Color = Color.blue
    let maxValue: Double = 5.0 // 上限を5に設定

    var body: some View {
        GeometryReader { geometry in
            self.makeRadarChart(geometry: geometry)
        }
    }

    private func makeRadarChart(geometry: GeometryProxy) -> some View {
        let center = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
        let radius = min(geometry.size.width, geometry.size.height) / 2
        let angleAdjustment = -CGFloat.pi / 2 // 上向きに開始

        return ZStack {
            // レーダーチャートの各線とラベルを描画
            ForEach(Array(keys.enumerated()), id: \.offset) { index, label in
                self.lineAndLabel(at: index, in: keys.count, radius: radius, center: center, label: label, angleAdjustment: angleAdjustment)
            }

            // データポイントを結ぶ線と内部を塗りつぶし
            Path { path in
                for (index, value) in values.enumerated() {
                    let angle = 2 * .pi / CGFloat(values.count) * CGFloat(index) + angleAdjustment
                    let pointRadius = radius * (value / maxValue) // 上限を5として正規化
                    let point = CGPoint(x: center.x + cos(angle) * pointRadius, y: center.y + sin(angle) * pointRadius)

                    if index == 0 {
                        path.move(to: point)
                    } else {
                        path.addLine(to: point)
                    }
                }
                path.closeSubpath()
            }
            .fill(strokeColor.opacity(0.3))
            .overlay(
                Path { path in
                    for (index, value) in values.enumerated() {
                        let angle = 2 * .pi / CGFloat(values.count) * CGFloat(index) + angleAdjustment
                        let pointRadius = radius * (value / maxValue)
                        let point = CGPoint(x: center.x + cos(angle) * pointRadius, y: center.y + sin(angle) * pointRadius)

                        if index == 0 {
                            path.move(to: point)
                        } else {
                            path.addLine(to: point)
                        }
                    }
                    path.closeSubpath()
                }
                .stroke(strokeColor, lineWidth: 2)
            )
        }
    }

    private func lineAndLabel(at index: Int, in total: Int, radius: CGFloat, center: CGPoint, label: String, angleAdjustment: CGFloat) -> some View {
        let angle = 2 * .pi / CGFloat(total) * CGFloat(index) + angleAdjustment
        let endPoint = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)

        return ZStack {
            Path { path in
                path.move(to: center)
                path.addLine(to: endPoint)
            }
            .stroke(Color.gray, lineWidth: 1)

            Text(label)
                .position(x: center.x + cos(angle) * (radius + 20), y: center.y + sin(angle) * (radius + 20))
        }
    }
}


struct RadarChartView: View {
    var scoresKeys: [String] = [
        "性格", "見た目", "価値観", "金銭感", "コミュ力"
    ]
    var scoreValues: [Double] = [3, 3, 3, 3, 3]

    var body: some View {
        RadarChart(keys: scoresKeys, values: scoreValues)
            .frame(height: 300)
            .padding()
    }
}

#Preview {
    RadarChartView()
}



メモリあり

image.png

RaderChart.swift
//
//  RadarChartView.swift
//  matchingAppManager
//
//  Created by 高橋直希 on 2023/11/29.
//
import SwiftUI

struct RadarChart: View {
    var keys: [String]
    var values: [Double]
    var strokeColor: Color = .blue
    let maxValue: Double = 5.0 // 上限を5に設定

    var body: some View {
        GeometryReader { geometry in
            self.makeRadarChart(geometry: geometry)
        }
    }

    private func makeRadarChart(geometry: GeometryProxy) -> some View {
        let center = CGPoint(x: geometry.size.width / 2, y: geometry.size.height / 2)
        let radius = min(geometry.size.width, geometry.size.height) / 2
        let angleAdjustment = -CGFloat.pi / 2 // 上向きに開始
        let memoryLevels = 6 // メモリの数

        return ZStack {
            // メモリ(目盛り)を描画する円
            ForEach(0..<memoryLevels, id: \.self) { level in
                Circle()
                    .stroke(lineWidth: 1)
                    .opacity(0.5)
                    .frame(width: radius * 2 * CGFloat(level) / CGFloat(memoryLevels - 1), height: radius * 2 * CGFloat(level) / CGFloat(memoryLevels - 1))
                    .position(center)
            }

            // レーダーチャートの各線とラベルを描画
            ForEach(Array(keys.enumerated()), id: \.offset) { index, label in
                self.lineAndLabel(at: index, in: keys.count, radius: radius, center: center, label: label, angleAdjustment: angleAdjustment)
            }

            // 各軸に沿った目盛り
            ForEach(Array(keys.enumerated()), id: \.offset) { index, _ in
                ForEach(1..<memoryLevels, id: \.self) { level in
                    self.memoryLine(at: index, in: keys.count, radius: radius * CGFloat(level) / CGFloat(memoryLevels - 1), center: center, angleAdjustment: angleAdjustment)
                }
            }
            // データポイントを結ぶ線と内部を塗りつぶし
            Path { path in
                for (index, value) in values.enumerated() {
                    let angle = 2 * .pi / CGFloat(values.count) * CGFloat(index) + angleAdjustment
                    let pointRadius = radius * (value / maxValue) // 上限を5として正規化
                    let point = CGPoint(x: center.x + cos(angle) * pointRadius, y: center.y + sin(angle) * pointRadius)

                    if index == 0 {
                        path.move(to: point)
                    } else {
                        path.addLine(to: point)
                    }
                }
                path.closeSubpath()
            }
            .fill(strokeColor.opacity(0.3))
            .overlay(
                Path { path in
                    for (index, value) in values.enumerated() {
                        let angle = 2 * .pi / CGFloat(values.count) * CGFloat(index) + angleAdjustment
                        let pointRadius = radius * (value / maxValue)
                        let point = CGPoint(x: center.x + cos(angle) * pointRadius, y: center.y + sin(angle) * pointRadius)

                        if index == 0 {
                            path.move(to: point)
                        } else {
                            path.addLine(to: point)
                        }
                    }
                    path.closeSubpath()
                }
                .stroke(strokeColor, lineWidth: 2)
            )
        }
    }

    private func memoryLine(at index: Int, in total: Int, radius: CGFloat, center: CGPoint, angleAdjustment: CGFloat) -> some View {
        let angle = 2 * .pi / CGFloat(total) * CGFloat(index) + angleAdjustment
        let point = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)

        return Path { path in
            path.move(to: center)
            path.addLine(to: point)
        }
        .stroke(Color.gray, style: StrokeStyle(lineWidth: 1, dash: [4, 4]))
    }

    private func lineAndLabel(at index: Int, in total: Int, radius: CGFloat, center: CGPoint, label: String, angleAdjustment: CGFloat) -> some View {
        let angle = 2 * .pi / CGFloat(total) * CGFloat(index) + angleAdjustment
        let endPoint = CGPoint(x: center.x + cos(angle) * radius, y: center.y + sin(angle) * radius)

        var labelDistance = radius + 15

        if index == 1 || index == 4 {
            labelDistance += 20
        }
        let xPosition = center.x + cos(angle) * labelDistance
        let yPosition = center.y + sin(angle) * labelDistance

        return ZStack {
            Path { path in
                path.move(to: center)
                path.addLine(to: endPoint)
            }
            .stroke(Color.gray, lineWidth: 1)

            Text(label)
                .position(x: xPosition, y: yPosition)
        }
    }
}

struct RadarChartView: View {
    var scoresKeys: [String] = [
        "性格", "見た目", "価値観", "金銭感", "コミュ力"
    ]
    var scoreValues: [Double] = [3, 3, 3, 3, 3]

    var body: some View {
        RadarChart(keys: scoresKeys, values: scoreValues)
            .frame(height: 300)
            .padding()
    }
}

#Preview {
    RadarChartView()
}
1
3
0

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
  3. You can use dark theme
What you can do with signing up
1
3