2023.06.29 - [iyOmSd/Title: SwiftUI] - [SwiftUI] Charts 이론편 (feat. iOS16+ apple framework)
을 통해서 기본적인 사용법을 익혔으니 이젠 직접 구현해보면서
다양한 케이스를 확인하려고해요!
기본적인것들만 알아볼게요
BarMark
간단한 BarChart부터 뜯어볼까요
struct Case1: Identifiable {
let name: String
let age: Int
var id: String { name }
}
struct NSBarChart: View {
let case1: [Case1] = [
Case1(name: "김씨", age: 10),
Case1(name: "이씨", age: 20),
Case1(name: "박씨", age: 30),
Case1(name: "정씨", age: 40),
Case1(name: "남씨", age: 45),
]
var body: some View {
Chart(case1) {
BarMark(
x: .value("name", $0.name),
y: .value("age", $0.age),
width: .automatic,
height: .automatic,
stacking: .center
)
}
}
}
Chart타입에 배열을 넣을 수도있고 안넣을 수도 있어요
넣으면 각 데이터를 바로 사용해서 Mark를 그릴 수 있고
배열을 안넣어준다면 ForEach를 사용해서 반복되는 작업으로 Mark를 그려줘야해요
stacking이라는 프로퍼티를 이용해서 차트의 정렬을 바꿀수 있어요
struct NSBarChart: View {
enum Stacking {
case center // center offset 사용(가운데를 기준으로 위아래로 표시)
case normalized // 누적막대형
case standard // 0에서 시작
case unstacked // 스택사용안함
}
@State private var stacking: Stacking = .center
...
// 변환해주는 역할 Stacking -> MarkStackingMethod
var stackingMethod: MarkStackingMethod { ... }
var body: some View {
VStack {
Picker("stacking", selection: $stacking) { ... }
.pickerStyle(.segmented)
Chart(case1) {
BarMark(
x: .value("name", $0.name),
y: .value("age", $0.age),
width: .automatic,
height: .automatic,
stacking: stackingMethod
)
BarMark(
x: .value("name", $0.name),
y: .value("age", $0.age),
width: .automatic,
height: .automatic,
stacking: stackingMethod
)
.foregroundStyle(.brown)
}
}
}
}
같은데이터의 차트를 구분하기위해 색만 변경했어요
위에 주석으로 써있듯
center - center offset을 사용하여 중앙부터 시작하는 차트를 그림
normalized - 차트영역을 모두 사용하여 차트를 그림
standard - 0부터 시작하는 차트를 그림
unstacked - 스택을 사용하지않음(겹쳐져서 처음에그린 차트가 가려져요)
를 고려해서 그리면 좋을 것같아요
다음으론
chartYScale() 을 사용해볼게요
Chart {
}
.chartYScale(range: 50...200) // 차트 그려지는영역의 위치 변경
.chartYScale(domain: 50...200) // 차트 영역에서 값 범위만 변경
range매개변수는 차트가 그려지는 영역에 대한 위치를 변경하는 함수이고
domain매개변수는 차트의 최대,최솟값을 변경하는 함수에요
어떻게 구성하느냐에 따라 다양하게 그려볼 수 있을 것같네요
단순모델이아니라 차트데이터처럼 사용하고 비교를 하기위해서 모델을 아래와같이 변경했습니다
struct Case2 {
let date: Int
let value: Int
static var dummy = [
(
name: "김씨",
data: [
Case2(date: 1, value: 1),
Case2(date: 2, value: 10),
Case2(date: 3, value: 100),
Case2(date: 4, value: 50),
Case2(date: 5, value: 120)
]
),
(
name: "이씨",
data: [
Case2(date: 1, value: 3),
Case2(date: 2, value: 30),
Case2(date: 3, value: 300),
Case2(date: 4, value: 30),
Case2(date: 5, value: 130)
]
)
]
}
범주는 name으로 `김씨`와 `이씨`를 사용하고
data로 date와 value가 있습니다
각날에 해당하는 매출? 이라고 생각하면 편할거같아요
차트색을 구분하기위해서 위에서는 .foregroundStyle를 Mark에 직접 지정하는 식이였어요
하지만 데이터가 많아지거나 범주별로 색을 주길 원한다면 하나하나 지정해주는게 어려울 수도 있어요
Chart(Case2.dummy, id: \.name) { element in
ForEach(element.data, id: \.date) {
BarMark(
x: .value("date", $0.date),
y: .value("value", $0.value),
width: 20,
height: .automatic,
stacking: stackingMethod
)
}
.foregroundStyle(by: .value("name", element.name))
}
.chartForegroundStyleScale([
"김씨": .brown,
"이씨": .purple
])
.chartForegroundStyleScale([
"김씨": .brown,
"이씨": .purple
])
두개의 이미지차이는 위의 코드 차이에요
왼쪽은 .foregroundStyle(by: .value("name", element.name)) 만적용했고
오른쪽은 chartForegroundStyleScale 까지 적용해준 차트에요
foregroundStyle(by:)를 이용해서 색을 구분하는 기준 값을 정해줘요
foregroundStyle만 사용하면 디폴트로 시스템에서 자동으로 색을 구분해줘요
범주색을 커스텀하고싶다면 chartForegroundStyleScale를 이용해서 foregroundStyle에서 기준으로 준 값들에 대한 색을 지정할 수 있어요
저같은경우는 name 즉 김씨, 이씨에대한 색을 구분했기때문에
두개의 범주색을 지정할 수 있어요
chartLegend
를 이용해서 밑에 범주표시를 숨길수 있어요
BarMark말고 다른차트도 보고싶다..!
변경하는 방법은 아주 쉽습니다
나름 호환성좋게 디자인 돼서 나온것같아요
같은데이터를 이용해서
LineMark, PointMark, AreaMark, RuleMark, RectangleMark, SectorMark
다양한 차트를 그려봤어요
struct NSLineChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
LineMark(
x: .value("date", $0.date),
y: .value("value", $0.value)
)
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
struct NSPointChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
PointMark(
x: .value("date", $0.date),
y: .value("value", $0.value)
)
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
struct NSAreaChart: View {
enum Stacking {
case center // center offset 사용(가운데를 기준으로 위아래로 표시)
case normalized // 누적막대형
case standard // 0에서 시작
case unstacked // 스택사용안함
}
@State private var stacking: Stacking = .center
var stackingMethod: MarkStackingMethod {
switch stacking {
case .center: return .center
case .normalized: return .normalized
case .standard: return .standard
case .unstacked: return .unstacked
}
}
let case2: [ChartData]
var body: some View {
VStack {
Picker("stacking", selection: $stacking) {
Text("center").tag(Stacking.center)
Text("normalized").tag(Stacking.normalized)
Text("standard").tag(Stacking.standard)
Text("unstacked").tag(Stacking.unstacked)
}
.pickerStyle(.segmented)
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
AreaMark(
x: .value("date", $0.date),
y: .value("value", $0.value),
stacking: stackingMethod
)
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
}
struct NSRuleChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
RuleMark(y: .value("value", $0.value))
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
struct NSRectangleChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
RectangleMark(x: .value("date", $0.date), y: .value("value", $0.value))
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
struct NSPieChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
SectorMark(angle: .value("value", $0.value))
}
.foregroundStyle(by: .value("name", element.name))
}
}
}
Mark의 생성자만 약간식 다를 뿐 사용하는 방법이나 뷰모디파이어는 동일하게 적용시킬 수 있어요
예외적으로 몇몇 차트에는 적용안돼는것도 있으니 잘 선택해야해요
예를들어
[iOS17] chartScrollableAxes(.horizontal) 을 적용해줬다면 가로로 스크롤이 가능하겠구나를 기대할거에요
여기에 이제 [iOS17] chartScrollPosition 를 추가하면 스크롤포지션도 자동으로 받을 수 있는데
SectorMark에서는 원으로 크게뜨는거라 스크롤할일이 없기때문에 동작하지않아요
이모든건 iOS17... 입니다
따라서 16에선..
chartOverlay를 이용해서 커스텀으로 계산하고 만들어 줘야합니다.. ㅠ
struct NSLineChart: View {
@State private var scroll: Double = 0
let case2: [ChartData]
var body: some View {
VStack {
Text("Scroll: \(scroll)")
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
LineMark(
x: .value("date", $0.date),
y: .value("value", $0.value)
)
}
.foregroundStyle(by: .value("name", element.name))
}
.chartScrollableAxes(.horizontal)
.chartScrollPosition(x: $scroll)
}
}
}
잘동작하죠 하지만 이 함수를 SectorMark에 적용하면 아무런 동작이 일어나지않아요
이렇듯 연산자는 모든 차트에 적용할 수 있지만 안되는것도 있다아~ 알아두시면 됩니다!
또한 클릭된 위치를 알고싶다면
struct NSLineChart: View {
...
@State private var select: Int?
var body: some View {
VStack {
...
Text("select: \(select ?? 0)")
Chart(case2, id: \.name) { element in
...
if let select {
RuleMark(x: .value("select", select))
}
}
.chartScrollableAxes(.horizontal)
.chartScrollPosition(x: $scroll)
.chartPlotStyle { content in
content.background(Color.red.opacity(0.3))
}
.chartXSelection(value: $select)
}
}
}
chartPlotStyle을 통해서 차트영역의 뷰를 받아와서 빨간색영역을 준것처럼 커스텀할 수 있고
[iOS17]chartXSelection(value: ) 를통해서 값을 받거나 chartXSelection(range: )를 통해서 범위를 받아 올 수 있어요
하지만 iOS16에선 못쓰겠죠...? ㅠ
커스텀하기 쉽지않을것같네요
chartOverlay를 사용하면
ChartProxy를 받아서 사용할 수 있어요
struct NSRectangleChart: View {
let case2: [ChartData]
var body: some View {
Chart(case2, id: \.name) { element in
ForEach(element.data, id: \.date) {
RectangleMark(x: .value("date", $0.date), y: .value("value", $0.value))
}
.foregroundStyle(by: .value("name", element.name))
}
.chartOverlay { proxy in
Color.clear.onAppear {
let xPos: CGFloat = proxy.position(forX: 4) ?? 0
print(xPos)
if let xValue: Int = proxy.value(atX: xPos) {
print(xValue)
}
}
}
}
}
proxy를 이용하면 차트의 x, y값에 접근할 수 있어요
또한 position, value 함수를 통해서
좌표 -> 값
값 -> 좌표
로 변환해서 이용할 수도 있어요
위의코드와 차트를 보면
position(forX: 4)를 이용해서
x값이 4인곳의 위치는 어디인가를 가져올 수 있었고
반대로 x위치가
value(atX: 245.33333333333331)(x = 4의 위치입니다)을 이용해서
해당 좌표의 X값은 4라는걸 얻을 수 있습니다
annotation을 이용해서 주석도 달아줄수있습니다
위치관련한 매개변수가 2개가있는데
position - 그려질 곳 좌표계기준에서의 위치
alignment- 위치가 지정된뒤 어느부분을 정렬로 맞출것인지
이렇듯 차트타입과 차트에 부가적인 기능사용법을 조금알아봤는데요
생각보다 사용하기 쉽게 간단해서
OS버전만 맞으면 차트도이제 금방 구현할 수 있을 것 같다는 생각이 드네요
또한...! 차트는 iOS17을 타겟으로 해야 쓸만할것같은 느낌이 듭니다..!
단순히 차트만 보여주는거라면 16이면 충분할것같은데...
부가적인 기능을 추가하려면 골치아파질것같네요 🥲
'iyOmSd > Title: SwiftUI' 카테고리의 다른 글
[SwiftUI] WWDC23 Demystify SwiftUI Performance (0) | 2023.11.30 |
---|---|
[SwiftUI] StateObject init 생성자 (1) | 2023.08.28 |
[SwiftUI] Charts 이론편 (feat. iOS16+ apple framework) (0) | 2023.06.29 |
[SwiftUI] NavigationStack (0) | 2023.05.29 |
[SwiftUI] 분리된 프레임워크(모듈) 리소스(Font, Color, Image)접근 (0) | 2023.02.24 |