데이터를 보여주는 것을 넘어서 수학 함수를 플롯할 수 있게 되었다.
함수 플롯
ex. 히스토그램을 함수로 플롯하기
히스토그램
Chart {
ForEach(bins) { bin in
BarMark(
x: .value("Capacity density", bin.range),
y: .value("Probability", bin.probability)
)
}
}
라인 플롯 추가
Chart {
LinePlot(
x: "Capacity density", y: "Probability"
) { x in // (Double) -> Double
normalDistribution( // 정규분포 값을 구하는 커스텀 함수
x,
mean: mean,
standardDeviation: standardDeviation
)
}
ForEach(bins) { bin in
BarMark(
x: .value("Capacity density", bin.range),
y: .value("Probability", bin.probability)
)
}
}
히스토그램과 다른 색을 가지도록 커스텀
Chart {
LinePlot(
x: "Capacity density", y: "Probability"
) { x in
normalDistribution(x, ...)
}
.foregroundStyle(.gray)
.opacity(0.2)
}
ex. 두 함수간의 AreaPlot
Chart {
AreaPlot(
x: "x", yStart: "cos(x)", yEnd: "sin(x)"
) { x in
(yStart: cos(x / 180 * .pi),
yEnd: sin(x / 180 * .pi))
}
}
데이터와 다르는 함수는 정의역에 제한이 없다.
기본적으로 Swift Chart는 함수를 샘플링해서 도메인을 추론한다.
이것도 커스텀이 가능하다.
Chart {
AreaPlot(
x: "x", yStart: "cos(x)", yEnd: "sin(x)"
) { x in
(yStart: cos(x / 180 * .pi),
yEnd: sin(x / 180 * .pi))
}
}
.chartXScale(domain: -315...225)
.chartYScale(domain: -5...5)
아예 플롯되는 함수 범위도 조정이 가능하다.
Chart {
AreaPlot(
x: "x", yStart: "cos(x)", yEnd: "sin(x)",
domain: -135...45
) { x in
(yStart: cos(x / 180 * .pi),
yEnd: sin(x / 180 * .pi))
}
}
.chartXScale(domain: -315...225)
.chartYScale(domain: -5...5)
매개변수 방정식도 지원한다.
Chart {
LinePlot(
x: "x", y: "y", t: "t", domain: -.pi ... .pi
) { t in
let x = sqrt(2) * pow(sin(t), 3)
let y = cos(t) * (2 - cos(t) - pow(cos(t), 2))
return (x, y)
}
}
.chartXScale(domain: -3...3)
.chartYScale(domain: -4...2)
값이 정의되지 않는 범위를 표현하기 위해서는 nan을 반환하면 된다.
Chart {
LinePlot(x: "x", y: "1 / x") { x in
if x < 0 {
return .nan
}
return x + 1
}
}
.chartXScale(domain: -5...10)
.chartYScale(domain: -5...10)
Chart {
LinePlot(x: "x", y: "1 / x") { x in
guard x != 0 else {
return .nan
}
return 1 / x
}
}
.chartXScale(domain: -10...10)
.chartYScale(domain: -10...10)
플롯은 함수뿐 아니라 큰 데이터 셋을 시각화하는데도 좋다.
그래서 다른 마커 타입들에도 플롯 API를 추가 했다.
Vectorized Plot
Swift Chart는 자유로운 커스텀이 가능하다.
Chart {
ForEach(model.data) {
if $0.capacityDensity > 0.0001 {
RectangleMark(
x: .value("Longitude", $0.x),
y: .value("Latitude", $0.y)
)
.foregroundStyle(by: .value("Axis type", $0.axisType))
} else {
PointMark(
x: .value("Longitude", $0.x),
y: .value("Latitude", $0.y)
)
.opacity(0.5)
}
}
}
하지만 보통은 전체 데이터에 동일한 Mark를 적용한다.
Chart {
ForEach(model.data) {
RectangleMark(
x: .value("Longitude", $0.x),
y: .value("Latitude", $0.y)
)
.foregroundStyle(by: .value("Axis type", $0.panelAxisType))
.opacity($0.capacityDensity)
}
}
새로운 Vectorized 플롯 API는 전체 데이터를 인자로 받아서 더 효율적인 처리를 가능하게 한다.
Chart {
RectanglePlot(
model.data,
x: .value("Longitude", \\.x),
y: .value("Latitude", \\.y)
)
.foregroundStyle(by: .value("Axis type", \\.panelAxisType))
.opacity(\\.capacityDensity)
}
ex. 전미 태양광 설치위치 시각화
데이터모델 정의
struct DataPoint: Identifiable {
let id: Int
let capacity: Double
let panelAxisType: String
let xLongitude: Double
let yLatitude: Double
}
평면에 그리기 위해서는 Albers projection을 적용해야 한다.
계산 프로퍼티로 할 수도 있을 것이다. 이 경우는 getter가 매번 호출될 것이다.
extension DataPoint {
var x: Double { ... }
var y: Double { ... }
}
하지만 저장 프로퍼티로 하면 고정된 메모리 오프셋으로 Swift Chart가 접근할 수 있어서 성능을 최대화할 수 있다.
struct DataPoint: Identifiable {
let id: Int
let capacity: Double
let panelAxisType: String
let xLongitude: Double
let yLatitude: Double
// Albers projection
var x: Double
var y: Double
}
PointPlot 적용
Chart {
contiguousUSMap
RectanglePlot(
model.data,
x: .value("Longitude", \\.x),
y: .value("Latitude", \\.y)
)
}
modifier에서도 keyPath를 받는다.
Chart {
contiguousUSMap
RectanglePlot(
model.data,
x: .value("Longitude", \\.x),
y: .value("Latitude", \\.y)
)
.foregroundStyle(by: .value("Axis type", \\.panelAxisType))
.opacity(\\.capacityDensity)
}
언제 Vectorized Plot API를 써야 하는가?
기존 Mark API를 사용할 때
vectorized plot을 효과적으로 쓰기 위해서는