From 0f46db2444ee2bffd5c5fceaa31274de9e6a1081 Mon Sep 17 00:00:00 2001 From: Reed Es Date: Sat, 6 Nov 2021 20:58:52 -0600 Subject: [PATCH 1/2] added y-axis mirror support for StackedAreaChart --- Sources/Charts/Chart/Chart.swift | 37 +++++++++++++++++++ .../Charts/Chart/Styles/Area/AreaChart.swift | 11 ++++-- .../Styles/Area/StackedAreaChartStyle.swift | 7 +++- 3 files changed, 50 insertions(+), 5 deletions(-) diff --git a/Sources/Charts/Chart/Chart.swift b/Sources/Charts/Chart/Chart.swift index 7701783..bc81c9f 100644 --- a/Sources/Charts/Chart/Chart.swift +++ b/Sources/Charts/Chart/Chart.swift @@ -35,6 +35,7 @@ struct Chart_Previews: PreviewProvider { ColumnChartDemo() BarChartDemo() StackedAreaChartDemo() + StackedAreaChartDualDemo() CompositeChartDemo() } } @@ -166,6 +167,42 @@ private struct StackedAreaChartDemo: View { } } +private struct StackedAreaChartDualDemo: View { + @State var matrixDataTop: [[CGFloat]] = (0..<20).map { _ in (0..<3).map { _ in CGFloat.random(in: 0.00...0.33) } } + @State var matrixDataBottom: [[CGFloat]] = (0..<20).map { _ in (0..<3).map { _ in CGFloat.random(in: 0.00...0.33) } } + + var body: some View { + VStack(spacing: 0) { + Chart(data: matrixDataTop) + .chartStyle( + StackedAreaChartStyle(.quadCurve, colors: [.yellow, .orange, .red]) + ) + .background( + Color.gray.opacity(0.1) + .overlay( + GridPattern(verticalLines: matrixDataTop.count) + .inset(by: 1) + .stroke(Color.red.opacity(0.2), style: .init(lineWidth: 1, lineCap: .round)) + ) + ) + Chart(data: matrixDataBottom) + .chartStyle( + StackedAreaChartStyle(.quadCurve, colors: [.gray, .green, .blue], yMirror: true) + ) + .background( + Color.gray.opacity(0.1) + .overlay( + GridPattern(verticalLines: matrixDataBottom.count) + .inset(by: 1) + .stroke(Color.red.opacity(0.2), style: .init(lineWidth: 1, lineCap: .round)) + ) + ) + } + .cornerRadius(16) + .padding() + } +} + private struct CompositeChartDemo: View { @State var data4: [CGFloat] = (0..<100).map { _ in .random(in: 0.4...1.0) } @State var data5: [CGFloat] = (0..<100).map { _ in .random(in: 0.1...0.3) } diff --git a/Sources/Charts/Chart/Styles/Area/AreaChart.swift b/Sources/Charts/Chart/Styles/Area/AreaChart.swift index 3d5bcb3..91ca785 100644 --- a/Sources/Charts/Chart/Styles/Area/AreaChart.swift +++ b/Sources/Charts/Chart/Styles/Area/AreaChart.swift @@ -5,6 +5,7 @@ struct AreaChart: Shape { private let lineType: LineType private let unitPoints: [UnitPoint] private let bottomUnitPoints: [UnitPoint]? + private let yMirror: Bool public func path(in rect: CGRect) -> Path { Path { path in @@ -24,6 +25,9 @@ struct AreaChart: Shape { case .quadCurve: path.addQuadCurves(bottomUnitPoints.reversed().points(in: rect)) } + } else if yMirror { + path.addLine(to: CGPoint(unitPoint: .bottomTrailing, in: rect)) + path.addLine(to: CGPoint(unitPoint: .bottomLeading, in: rect)) } else { path.addLine(to: CGPoint(unitPoint: .topTrailing, in: rect)) path.addLine(to: CGPoint(unitPoint: .topLeading, in: rect)) @@ -32,10 +36,11 @@ struct AreaChart: Shape { } } - init(unitData: Data, bottomUnitData: Data? = nil, lineType: LineType) where Data.Element : BinaryFloatingPoint { + init(unitData: Data, bottomUnitData: Data? = nil, lineType: LineType, yMirror: Bool = false) where Data.Element : BinaryFloatingPoint { self.lineType = lineType + self.yMirror = yMirror let step: CGFloat = unitData.count > 1 ? 1.0 / CGFloat(unitData.count - 1) : 1.0 - self.unitPoints = unitData.enumerated().map { (index, dataPoint) in UnitPoint(x: step * CGFloat(index), y: CGFloat(dataPoint)) } - self.bottomUnitPoints = bottomUnitData?.enumerated().map { (index, dataPoint) in UnitPoint(x: step * CGFloat(index), y: CGFloat(dataPoint)) } + self.unitPoints = unitData.enumerated().map { (index, dataPoint) in UnitPoint(x: step * CGFloat(index), y: yMirror ? 1 - CGFloat(dataPoint): CGFloat(dataPoint)) } + self.bottomUnitPoints = bottomUnitData?.enumerated().map { (index, dataPoint) in UnitPoint(x: step * CGFloat(index), y: yMirror ? 1 - CGFloat(dataPoint): CGFloat(dataPoint)) } } } diff --git a/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift b/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift index 4871b24..da93ae8 100644 --- a/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift +++ b/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift @@ -4,6 +4,7 @@ import Shapes public struct StackedAreaChartStyle: ChartStyle { private let lineType: LineType private let colors: [Color] + private let yMirror: Bool public func makeBody(configuration: Self.Configuration) -> some View { ZStack { @@ -11,7 +12,8 @@ public struct StackedAreaChartStyle: ChartStyle { colors[enumeratedData.offset % colors.count].clipShape( AreaChart( unitData: enumeratedData.element, - lineType: self.lineType + lineType: self.lineType, + yMirror: yMirror ) ) .zIndex(-Double(enumeratedData.offset)) @@ -20,9 +22,10 @@ public struct StackedAreaChartStyle: ChartStyle { .drawingGroup() } - public init(_ lineType: LineType = .quadCurve, colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple]) { + public init(_ lineType: LineType = .quadCurve, colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple], yMirror: Bool = false) { self.lineType = lineType self.colors = colors + self.yMirror = yMirror } } From d2082f73574bd696a2c2be1c49f2e4490c6f2f79 Mon Sep 17 00:00:00 2001 From: Reed Es Date: Fri, 26 Nov 2021 21:30:13 -0700 Subject: [PATCH 2/2] added fill support to stacked area, to support gradients --- README.md | 7 +++++- Sources/Charts/Chart/Chart.swift | 22 +++++++++++++++---- .../Chart/Styles/Area/AreaChartStyle.swift | 6 +++-- .../Styles/Area/StackedAreaChartStyle.swift | 11 +++++++--- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index c1f2668..c6dbff1 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,12 @@ Chart(data: [0.1, 0.3, 0.2, 0.5, 0.4, 0.9, 0.1]) ```swift Chart(data: matrix) .chartStyle( - StackedAreaChartStyle(.quadCurve, colors: [.yellow, .orange, .red]) + StackedAreaChartStyle(.quadCurve, colors: [.yellow, .orange, .red], yMirror: false) + ) + +Chart(data: matrix) + .chartStyle( + StackedAreaChartStyle(.quadCurve, fills: [AnyView(Color.yellow), AnyView(Color.orange), AnyView(Color.red)], yMirror: true) ) ``` diff --git a/Sources/Charts/Chart/Chart.swift b/Sources/Charts/Chart/Chart.swift index bc81c9f..8438421 100644 --- a/Sources/Charts/Chart/Chart.swift +++ b/Sources/Charts/Chart/Chart.swift @@ -101,7 +101,7 @@ private struct AreaChartDemo: View { var body: some View { Chart(data: data2) .chartStyle( - AreaChartStyle(.quadCurve, fill: LinearGradient(gradient: .init(colors: [Color.red.opacity(0.8), Color.red.opacity(0.2)]), startPoint: .top, endPoint: .bottom)) + AreaChartStyle(.quadCurve, fill: LinearGradient(gradient: .init(colors: [Color.red.opacity(0.8), Color.red.opacity(0.2)]), startPoint: .top, endPoint: .bottom), yMirror: false) ) .background( Color.gray.opacity(0.1) @@ -152,7 +152,7 @@ private struct StackedAreaChartDemo: View { var body: some View { Chart(data: matrixData) .chartStyle( - StackedAreaChartStyle(.quadCurve, colors: [.yellow, .orange, .red]) + StackedAreaChartStyle(.quadCurve, fills: fills, yMirror: false) ) .background( Color.gray.opacity(0.1) @@ -165,12 +165,26 @@ private struct StackedAreaChartDemo: View { .cornerRadius(16) .padding() } + + private var fills: [AnyView] { + [ + AnyView( + LinearGradient(gradient: .init(colors: [Color.yellow.opacity(0.8), Color.green.opacity(0.2)]), startPoint: .top, endPoint: .bottom) + ), + AnyView( + LinearGradient(gradient: .init(colors: [Color.orange.opacity(0.8), Color.green.opacity(0.2)]), startPoint: .top, endPoint: .bottom) + ), + AnyView( + LinearGradient(gradient: .init(colors: [Color.red.opacity(0.8), Color.green.opacity(0.2)]), startPoint: .top, endPoint: .bottom) + ) + ] + } } private struct StackedAreaChartDualDemo: View { @State var matrixDataTop: [[CGFloat]] = (0..<20).map { _ in (0..<3).map { _ in CGFloat.random(in: 0.00...0.33) } } @State var matrixDataBottom: [[CGFloat]] = (0..<20).map { _ in (0..<3).map { _ in CGFloat.random(in: 0.00...0.33) } } - + var body: some View { VStack(spacing: 0) { Chart(data: matrixDataTop) @@ -187,7 +201,7 @@ private struct StackedAreaChartDualDemo: View { ) Chart(data: matrixDataBottom) .chartStyle( - StackedAreaChartStyle(.quadCurve, colors: [.gray, .green, .blue], yMirror: true) + StackedAreaChartStyle(.quadCurve, colors: [.yellow, .green, .blue], yMirror: true) ) .background( Color.gray.opacity(0.1) diff --git a/Sources/Charts/Chart/Styles/Area/AreaChartStyle.swift b/Sources/Charts/Chart/Styles/Area/AreaChartStyle.swift index 7370d0e..2ee37b3 100644 --- a/Sources/Charts/Chart/Styles/Area/AreaChartStyle.swift +++ b/Sources/Charts/Chart/Styles/Area/AreaChartStyle.swift @@ -4,16 +4,18 @@ import Shapes public struct AreaChartStyle: ChartStyle { private let lineType: LineType private let fill: Fill + private let yMirror: Bool public func makeBody(configuration: Self.Configuration) -> some View { fill .clipShape( - AreaChart(unitData: configuration.dataMatrix.map { $0.reduce(0, +) }, lineType: self.lineType) + AreaChart(unitData: configuration.dataMatrix.map { $0.reduce(0, +) }, lineType: self.lineType, yMirror: yMirror) ) } - public init(_ lineType: LineType = .quadCurve, fill: Fill) { + public init(_ lineType: LineType = .quadCurve, fill: Fill, yMirror: Bool = false) { self.lineType = lineType self.fill = fill + self.yMirror = yMirror } } diff --git a/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift b/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift index da93ae8..f80c7c4 100644 --- a/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift +++ b/Sources/Charts/Chart/Styles/Area/StackedAreaChartStyle.swift @@ -3,13 +3,13 @@ import Shapes public struct StackedAreaChartStyle: ChartStyle { private let lineType: LineType - private let colors: [Color] + private let fills: [AnyView] private let yMirror: Bool public func makeBody(configuration: Self.Configuration) -> some View { ZStack { ForEach(Array(configuration.dataMatrix.transpose().stacked().enumerated()), id: \.self.offset) { enumeratedData in - colors[enumeratedData.offset % colors.count].clipShape( + fills[enumeratedData.offset % fills.count].clipShape( AreaChart( unitData: enumeratedData.element, lineType: self.lineType, @@ -23,8 +23,13 @@ public struct StackedAreaChartStyle: ChartStyle { } public init(_ lineType: LineType = .quadCurve, colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple], yMirror: Bool = false) { + let fills = colors.map { AnyView($0) } + self.init(lineType, fills: fills, yMirror: yMirror) + } + + public init(_ lineType: LineType = .quadCurve, fills: [AnyView] = [], yMirror: Bool = false) { self.lineType = lineType - self.colors = colors + self.fills = fills self.yMirror = yMirror } }