Pie Charts 추가
전체 값에서 각 카테고리가 차지하는 비중을 나타낸다.
축이 없고, 정확도가 크게 중요하지 않다.
전체와 부분의 관계를 직관적으로 보여준다.
기존 Mark 조합으로 Chart를 만들던 것 대로 만들수 있다.
ex. 기존 stacked barMark 기반 chart를 pie chart로 바꾸기
Chart(data, id: \\.name) { element in
BarMark(
x: .value("Sales", element.sales),
stacking: .normalized
)
.foregroundStyle(by: .value("Name", element.name))
}
.chartXAxis(.hidden)
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales)
)
.foregroundStyle(by: .value("Name", element.name))
}
조각 사이의 간격주기
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
angularInset: 1.5
)
.foregroundStyle(by: .value("Name", element.name))
}
각 조각에 cornerradius주기
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}
donut chart로 바꾸기
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}
도넛 차트 가운데 텍스트 넣기
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
}
.chartBackground { chartProxy in
GeometryReader { geometry in
let frame = geometry[chartProxy.plotAreaFrame]
VStack {
Text("Most Sold Style")
.font(.callout)
.foregroundStyle(.secondary)
Text(mostSold)
.font(.title2.bold())
.foregroundColor(.primary)
}
.position(x: frame.midX, y: frame.midY)
}
}
Selection
상호작용이 가능해지면 사용자가 자연스럽게 점진적으로 상세 정보를 탐색할 수 있게 된다.
selection 핸들링하기
struct LocationDetailsChart: View {
@Binding var rawSelectedDate: Date?
var selectedDate: Date? {
guard let rawSelectedDate else { return nil }
return data.first?.sales.first(where: {
let endOfDay = endOfDay(for: $0.day)
return ($0.day ... endOfDay).contains(rawSelectedDate)
})?.day
}
var body: some View {
// Chart 구현
.chartXSelection(value: $rawSelectedDate)
}
}
chart body
Chart {
ForEach(data) { series in
ForEach(series.sales, id: \\.day) { element in
LineMark(
x: .value("Day", element.day, unit: .day),
y: .value("Sales", element.sales)
)
}
}
if let selectedDate {
RuleMark(
x: .value("Selected", selectedDate, unit: .day)
)
.foregroundStyle(Color.gray.opacity(0.3))
.offset(yStart: -10)
.zIndex(-1) // lineMark보다 뒤에
.annotation(
position: .top, spacing: 0,
overflowResolution: .init( // chart 바깥에 그리기 위해서
x: .fit(to: .chart), // chart의 가로범위를 벗어나서 그려지지는 않게
y: .disabled // chart를 침범하지 않기를
)
) {
valueSelectionPopover
}
}
}
.chartXSelection(value: $rawSelectedDate)
범위 선택
Chart(data) { series in
ForEach(series.sales, id: \\.day) { element in
LineMark(
x: .value("Day", element.day, unit: .day),
y: .value("Sales", element.sales)
)
}
...
}
.chartXSelection(value: $rawSelectedDate)
.chartXSelection(range: $rawSelectedRange)
커스텀 제스쳐
Chart(data) { series in
ForEach(series.sales, id: \\.day) { element in
LineMark(
x: .value("Day", element.day, unit: .day),
y: .value("Sales", element.sales)
)
}
...
}
.chartXSelection(value: $rawSelectedDate)
.chartGesture { proxy in
DragGesture(minimumDistance: 0)
.onChanged { proxy.selectXValue(at: $0.location.x) }
.onEnded { _ in selectedDate = nil }
}
pieChart에서의 selection
Chart(data, id: \\.name) { element in
SectorMark(
angle: .value("Sales", element.sales),
innerRadius: .ratio(0.618),
angularInset: 1.5
)
.cornerRadius(5)
.foregroundStyle(by: .value("Name", element.name))
.opacity(element.name == selectedName ? 1.0 : 0.3)
}
.chartAngleSelection(value: $selectedAngle)
Scrolling
Chart {
ForEach(SalesData.last365Days, id: \\.day) {
BarMark(
x: .value("Day", $0.day, unit: .day),
y: .value("Sales", $0.sales)
)
}
.foregroundStyle(.blue)
}
.chartScrollableAxes(.horizontal) // 가로 스크롤 가능하게
.chartXVisibleDomain(length: 3600 * 24 * 30) // 전체 도메인 크기 설정
.chartScrollPosition(x: $scrollPosition) // 현재 스크롤 위치 설정
.chartScrollTargetBehavior( // 스크롤 동작 커스텀
.valueAligned(
matching: DateComponents(hour: 0),
majorAlignment: .matching(DateComponents(day: 1))))