SwiftUIでサクッとレーダーチャートを作りたいな
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()
}
メモリあり
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()
}