Skip to content

Commit 4a670c0

Browse files
authored
Merge pull request #15 from SwiftUIExtensions/stacked-column-chart
Stacked column chart style
2 parents 6c597fa + c8f936e commit 4a670c0

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

Examples/ChartsExamples/ChartsView.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ struct ChartsView: View {
1111
@State var data5: [CGFloat] = (0...100).map { _ in .random(in: 0.1...0.3) }
1212
@State var data6: [CGFloat] = (0...100).map { _ in .random(in: 0.3...0.4) }
1313

14+
@State var matrixData1: [[CGFloat]] = (0..<50).map { _ in (0..<3).map { _ in CGFloat.random(in: 0.00...0.33) } }
15+
1416
var body: some View {
1517
ScrollView {
1618
Chart(data: data1)
@@ -54,6 +56,16 @@ struct ChartsView: View {
5456
.frame(height: 300)
5557
.padding()
5658

59+
Chart(data: matrixData1)
60+
.chartStyle(
61+
StackedColumnChartStyle(spacing: 2, colors: [.yellow, .orange, .red])
62+
)
63+
.padding()
64+
.background(Color.gray.opacity(0.1))
65+
.cornerRadius(16)
66+
.frame(height: 300)
67+
.padding()
68+
5769
ZStack {
5870
Chart(data: data4)
5971
.chartStyle(
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import SwiftUI
2+
import Shapes
3+
4+
public struct StackedColumnChartStyle<Column: View>: ChartStyle {
5+
private let spacing: CGFloat
6+
private let column: ([CGFloat]) -> Column
7+
8+
public func makeBody(configuration: Self.Configuration) -> some View {
9+
GeometryReader { geometry in
10+
self.columnChart(in: geometry, data: configuration.dataMatrix)
11+
}
12+
}
13+
14+
private func columnChart(in geometry: GeometryProxy, data: [[CGFloat]]) -> some View {
15+
let columnWidth = (geometry.size.width - (CGFloat(data.count - 1) * spacing)) / CGFloat(data.count)
16+
17+
return ZStack(alignment: .bottomLeading) {
18+
ForEach(Array(data.enumerated()), id: \.self.offset) { enumeratedData in
19+
self.column(enumeratedData.element)
20+
.alignmentGuide(.leading, computeValue: { _ in self.leadingAlignmentGuide(for: enumeratedData.offset, in: geometry.size.width, dataCount: data.count) })
21+
.alignmentGuide(.bottom, computeValue: { _ in self.columnHeight(data: enumeratedData.element, in: geometry.size.height) })
22+
.frame(width: columnWidth, height: self.columnHeight(data: enumeratedData.element, in: geometry.size.height))
23+
}
24+
}
25+
.frame(width: geometry.size.width, height: geometry.size.height, alignment: .bottom)
26+
}
27+
28+
private func columnHeight(data: [CGFloat], in availableHeight: CGFloat) -> CGFloat {
29+
availableHeight * data.reduce(0, +)
30+
}
31+
32+
private func leadingAlignmentGuide(for index: Int, in availableWidth: CGFloat, dataCount: Int) -> CGFloat {
33+
let columnWidth = (availableWidth - (CGFloat(dataCount - 1) * spacing)) / CGFloat(dataCount)
34+
return (CGFloat(index) * columnWidth) + (CGFloat(index - 1) * spacing)
35+
}
36+
37+
init(spacing: CGFloat = 8, column: @escaping ([CGFloat]) -> Column) {
38+
self.spacing = spacing
39+
self.column = column
40+
}
41+
}
42+
43+
public struct DefaultStackedColumnView: View {
44+
let data: [CGFloat]
45+
let colors: [Color]
46+
47+
public var body: some View {
48+
GeometryReader { geometry in
49+
self.column(for: self.data, in: geometry)
50+
}
51+
}
52+
53+
private func column(for data: [CGFloat], in geometry: GeometryProxy) -> some View {
54+
let height = geometry.size.height
55+
let dataUnit = data.reduce(0, +)
56+
57+
return VStack(spacing: 0) {
58+
ForEach(Array(self.data.reversed().enumerated()), id: \.self.offset) { enumeratedData in
59+
Rectangle()
60+
.foregroundColor(self.color(at: enumeratedData.offset))
61+
.frame(height: height * (enumeratedData.element / dataUnit))
62+
}
63+
}
64+
}
65+
66+
private func color(at rollingIndex: Int) -> Color {
67+
self.colors.prefix(self.data.count).reversed()[rollingIndex % self.colors.count]
68+
}
69+
}
70+
71+
public extension StackedColumnChartStyle where Column == DefaultStackedColumnView {
72+
init(spacing: CGFloat = 8, colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple]) {
73+
self.init(spacing: spacing, column: { DefaultStackedColumnView(data: $0, colors: colors) })
74+
}
75+
}

0 commit comments

Comments
 (0)