diff --git a/Resources/images/PieCharts/DoughnutChart.png b/Resources/images/PieCharts/DoughnutChart.png index bb0fd14e..3f98999e 100644 Binary files a/Resources/images/PieCharts/DoughnutChart.png and b/Resources/images/PieCharts/DoughnutChart.png differ diff --git a/Resources/images/PieCharts/PieChart.png b/Resources/images/PieCharts/PieChart.png index cfefbeb8..5ea45cc2 100644 Binary files a/Resources/images/PieCharts/PieChart.png and b/Resources/images/PieCharts/PieChart.png differ diff --git a/Sources/SwiftUICharts/BarChart/Extras/BarChartEnums.swift b/Sources/SwiftUICharts/BarChart/Extras/BarChartEnums.swift index 9953c620..a64447a8 100644 --- a/Sources/SwiftUICharts/BarChart/Extras/BarChartEnums.swift +++ b/Sources/SwiftUICharts/BarChart/Extras/BarChartEnums.swift @@ -15,7 +15,9 @@ import SwiftUI ``` */ public enum ColourFrom { + /// From BarStyle data model case barStyle + /// From each data point case dataPoints } diff --git a/Sources/SwiftUICharts/BarChart/Models/ChartData/BarChartData.swift b/Sources/SwiftUICharts/BarChart/Models/ChartData/BarChartData.swift index 5a6d2ffa..783b4a38 100644 --- a/Sources/SwiftUICharts/BarChart/Models/ChartData/BarChartData.swift +++ b/Sources/SwiftUICharts/BarChart/Models/ChartData/BarChartData.swift @@ -12,20 +12,20 @@ import SwiftUI */ public final class BarChartData: CTBarChartDataProtocol { // MARK: Properties - public let id : UUID = UUID() - - @Published public final var dataSets : BarDataSet - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var barStyle : BarStyle - @Published public final var chartStyle : BarChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() - - public final var noDataText : Text - public final let chartType : (chartType: ChartType, dataSetType: DataSetType) + public let id: UUID = UUID() + + @Published public final var dataSets: BarDataSet + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var barStyle: BarStyle + @Published public final var chartStyle: BarChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + + public final var noDataText: Text + public final let chartType: (chartType: ChartType, dataSetType: DataSetType) // MARK: Initializer /// Initialises a standard Bar Chart. @@ -38,34 +38,35 @@ public final class BarChartData: CTBarChartDataProtocol { /// - barStyle: Control for the aesthetic of the bar chart. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : BarDataSet, - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - barStyle : BarStyle = BarStyle(), - chartStyle : BarChartStyle = BarChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: BarDataSet, + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + barStyle: BarStyle = BarStyle(), + chartStyle: BarChartStyle = BarChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.barStyle = barStyle - self.chartStyle = chartStyle - self.noDataText = noDataText + self.dataSets = dataSets + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.barStyle = barStyle + self.chartStyle = chartStyle + self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (.bar, .single) + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (.bar, .single) self.setupLegends() } - + // MARK: Labels public final func getXAxisLabels() -> some View { Group { switch self.chartStyle.xAxisLabelsFrom { case .dataPoint(let angle): - + HStack(alignment: .top, spacing: 0) { ForEach(dataSets.dataPoints) { data in Spacer() @@ -78,7 +79,7 @@ public final class BarChartData: CTBarChartDataProtocol { .frame(minWidth: 0, maxWidth: 500) } } - + case .chartData(let angle): if let labelArray = self.xAxisLabels { @@ -86,7 +87,7 @@ public final class BarChartData: CTBarChartDataProtocol { ForEach(labelArray, id: \.self) { data in Spacer() .frame(minWidth: 0, maxWidth: 500) - XAxisDataPointCell(chartData: self, label: data, rotationAngle: angle) + XAxisChartDataCell(chartData: self, label: data, rotationAngle: angle) .foregroundColor(self.chartStyle.xAxisLabelColour) .accessibilityLabel(Text("X Axis Label")) .accessibilityValue(Text("\(data)")) @@ -103,29 +104,28 @@ public final class BarChartData: CTBarChartDataProtocol { public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView() } + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [BarChartDataPoint] = [] - let xSection : CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count) - let index : Int = Int((touchLocation.x) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count) + let index: Int = Int((touchLocation.x) / xSection) if index >= 0 && index < dataSets.dataPoints.count { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } - self.infoView.touchOverlayInfo = points } + public final func getPointLocation(dataSet: BarDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count) - let ySection : CGFloat = chartSize.height / CGFloat(self.maxValue) - let index : Int = Int((touchLocation.x) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count) + let ySection: CGFloat = chartSize.height / CGFloat(self.maxValue) + let index: Int = Int((touchLocation.x) / xSection) if index >= 0 && index < dataSet.dataPoints.count { return CGPoint(x: (CGFloat(index) * xSection) + (xSection / 2), y: (chartSize.size.height - CGFloat(dataSet.dataPoints[index].value) * ySection)) } return nil } - - public typealias Set = BarDataSet + + public typealias Set = BarDataSet public typealias DataPoint = BarChartDataPoint - public typealias CTStyle = BarChartStyle + public typealias CTStyle = BarChartStyle } diff --git a/Sources/SwiftUICharts/BarChart/Models/ChartData/GroupedBarChartData.swift b/Sources/SwiftUICharts/BarChart/Models/ChartData/GroupedBarChartData.swift index d37ced1f..a225d266 100644 --- a/Sources/SwiftUICharts/BarChart/Models/ChartData/GroupedBarChartData.swift +++ b/Sources/SwiftUICharts/BarChart/Models/ChartData/GroupedBarChartData.swift @@ -9,31 +9,31 @@ import SwiftUI /** Data model for drawing and styling a Grouped Bar Chart. - + The grouping data informs the model as to how the datapoints are linked. ``` */ public final class GroupedBarChartData: CTMultiBarChartDataProtocol { // MARK: Properties - public let id : UUID = UUID() - - @Published public final var dataSets : GroupedBarDataSets - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var barStyle : BarStyle - @Published public final var chartStyle : BarChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() - @Published public final var groups : [GroupingData] + public let id: UUID = UUID() - public final var noDataText : Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) + @Published public final var dataSets: GroupedBarDataSets + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var barStyle: BarStyle + @Published public final var chartStyle: BarChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + @Published public final var groups: [GroupingData] + + public final var noDataText: Text + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) + + final var groupSpacing: CGFloat = 0 - final var groupSpacing : CGFloat = 0 - // MARK: Initializer /// Initialises a Grouped Bar Chart. /// @@ -46,26 +46,27 @@ public final class GroupedBarChartData: CTMultiBarChartDataProtocol { /// - barStyle: Control for the aesthetic of the bar chart. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : GroupedBarDataSets, - groups : [GroupingData], - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - barStyle : BarStyle = BarStyle(), - chartStyle : BarChartStyle = BarChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: GroupedBarDataSets, + groups: [GroupingData], + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + barStyle: BarStyle = BarStyle(), + chartStyle: BarChartStyle = BarChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.groups = groups - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.barStyle = barStyle - self.chartStyle = chartStyle - self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (chartType: .bar, dataSetType: .multi) + self.dataSets = dataSets + self.groups = groups + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.barStyle = barStyle + self.chartStyle = chartStyle + self.noDataText = noDataText + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (chartType: .bar, dataSetType: .multi) self.setupLegends() } @@ -114,7 +115,7 @@ public final class GroupedBarChartData: CTMultiBarChartDataProtocol { HStack(spacing: 0) { Spacer() .frame(minWidth: 0, maxWidth: 500) - XAxisDataPointCell(chartData: self, label: dataSet.setTitle, rotationAngle: .degrees(0)) + XAxisChartDataCell(chartData: self, label: dataSet.setTitle, rotationAngle: .degrees(0)) .foregroundColor(self.chartStyle.xAxisLabelColour) .accessibilityLabel(Text("X Axis Label")) .accessibilityValue(Text("\(dataSet.setTitle)")) @@ -131,58 +132,51 @@ public final class GroupedBarChartData: CTMultiBarChartDataProtocol { public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView() } + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - - var points : [GroupedBarDataPoint] = [] - // Divide the chart into equal sections. - let superXSection : CGFloat = (chartSize.width / CGFloat(dataSets.dataSets.count)) - let superIndex : Int = Int((touchLocation.x) / superXSection) + let superXSection: CGFloat = (chartSize.width / CGFloat(dataSets.dataSets.count)) + let superIndex: Int = Int((touchLocation.x) / superXSection) // Work out how much to remove from xSection due to groupSpacing. - let compensation : CGFloat = ((groupSpacing * CGFloat(dataSets.dataSets.count - 1)) / CGFloat(dataSets.dataSets.count)) + let compensation: CGFloat = ((groupSpacing * CGFloat(dataSets.dataSets.count - 1)) / CGFloat(dataSets.dataSets.count)) // Make those sections take account of spacing between groups. - let xSection : CGFloat = (chartSize.width / CGFloat(dataSets.dataSets.count)) - compensation - let index : Int = Int((touchLocation.x - CGFloat((groupSpacing * CGFloat(superIndex)))) / xSection) - + let xSection: CGFloat = superXSection - compensation + let index: Int = Int((touchLocation.x - CGFloat((groupSpacing * CGFloat(superIndex)))) / xSection) + if index >= 0 && index < dataSets.dataSets.count && superIndex == index { - let dataSet = dataSets.dataSets[index] - let xSubSection : CGFloat = (xSection / CGFloat(dataSet.dataPoints.count)) - let subIndex : Int = Int((touchLocation.x - CGFloat((groupSpacing * CGFloat(superIndex)))) / xSubSection) - (dataSet.dataPoints.count * index) - if subIndex >= 0 && subIndex < dataSet.dataPoints.count { - var dataPoint = dataSet.dataPoints[subIndex] - dataPoint.legendTag = dataSet.setTitle - points.append(dataPoint) + let xSubSection: CGFloat = (xSection / CGFloat(dataSets.dataSets[index].dataPoints.count)) + let subIndex: Int = Int((touchLocation.x - CGFloat((groupSpacing * CGFloat(superIndex)))) / xSubSection) - (dataSets.dataSets[index].dataPoints.count * index) + if subIndex >= 0 && subIndex < dataSets.dataSets[index].dataPoints.count { + dataSets.dataSets[index].dataPoints[subIndex].legendTag = dataSets.dataSets[index].setTitle + self.infoView.touchOverlayInfo = [dataSets.dataSets[index].dataPoints[subIndex]] } } - self.infoView.touchOverlayInfo = points } + public final func getPointLocation(dataSet: GroupedBarDataSets, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - // Divide the chart into equal sections. - let superXSection : CGFloat = (chartSize.width / CGFloat(dataSet.dataSets.count)) - let superIndex : Int = Int((touchLocation.x) / superXSection) - + let superXSection: CGFloat = (chartSize.width / CGFloat(dataSet.dataSets.count)) + let superIndex: Int = Int((touchLocation.x) / superXSection) + // Work out how much to remove from xSection due to groupSpacing. - let compensation : CGFloat = ((groupSpacing * CGFloat(dataSet.dataSets.count - 1)) / CGFloat(dataSet.dataSets.count)) - + let compensation: CGFloat = ((groupSpacing * CGFloat(dataSet.dataSets.count - 1)) / CGFloat(dataSet.dataSets.count)) + // Make those sections take account of spacing between groups. - let xSection : CGFloat = (chartSize.width / CGFloat(dataSet.dataSets.count)) - compensation - let ySection : CGFloat = chartSize.height / CGFloat(self.maxValue) - - let index : Int = Int((touchLocation.x - CGFloat(groupSpacing * CGFloat(superIndex))) / xSection) - + let xSection: CGFloat = superXSection - compensation + let ySection: CGFloat = chartSize.height / CGFloat(self.maxValue) + + let index: Int = Int((touchLocation.x - CGFloat(groupSpacing * CGFloat(superIndex))) / xSection) + if index >= 0 && index < dataSet.dataSets.count && superIndex == index { - let subDataSet = dataSet.dataSets[index] - let xSubSection : CGFloat = (xSection / CGFloat(subDataSet.dataPoints.count)) - let subIndex : Int = Int((touchLocation.x - CGFloat(groupSpacing * CGFloat(index))) / xSubSection) - (subDataSet.dataPoints.count * index) - + let xSubSection: CGFloat = (xSection / CGFloat(subDataSet.dataPoints.count)) + let subIndex: Int = Int((touchLocation.x - CGFloat(groupSpacing * CGFloat(index))) / xSubSection) - (subDataSet.dataPoints.count * index) if subIndex >= 0 && subIndex < subDataSet.dataPoints.count { - let element : CGFloat = (CGFloat(subIndex) * xSubSection) + (xSubSection / 2) - let section : CGFloat = (superXSection * CGFloat(superIndex)) - let spacing : CGFloat = ((groupSpacing / CGFloat(dataSets.dataSets.count)) * CGFloat(superIndex)) + let element: CGFloat = (CGFloat(subIndex) * xSubSection) + (xSubSection / 2) + let section: CGFloat = (superXSection * CGFloat(superIndex)) + let spacing: CGFloat = ((groupSpacing / CGFloat(dataSets.dataSets.count)) * CGFloat(superIndex)) return CGPoint(x: element + section + spacing, y: (chartSize.height - CGFloat(subDataSet.dataPoints[subIndex].value) * ySection)) } @@ -190,7 +184,7 @@ public final class GroupedBarChartData: CTMultiBarChartDataProtocol { return nil } - public typealias Set = GroupedBarDataSets - public typealias DataPoint = GroupedBarDataPoint - public typealias CTStyle = BarChartStyle + public typealias Set = GroupedBarDataSets + public typealias DataPoint = GroupedBarDataPoint + public typealias CTStyle = BarChartStyle } diff --git a/Sources/SwiftUICharts/BarChart/Models/ChartData/RangedBarChartData.swift b/Sources/SwiftUICharts/BarChart/Models/ChartData/RangedBarChartData.swift index 3b017545..06d4f070 100644 --- a/Sources/SwiftUICharts/BarChart/Models/ChartData/RangedBarChartData.swift +++ b/Sources/SwiftUICharts/BarChart/Models/ChartData/RangedBarChartData.swift @@ -7,24 +7,27 @@ import SwiftUI +/** + Data for drawing and styling a ranged Bar Chart. + */ public final class RangedBarChartData: CTRangedBarChartDataProtocol { - + // MARK: Properties - public let id : UUID = UUID() - - @Published public final var dataSets : RangedBarDataSet - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var barStyle : BarStyle - @Published public final var chartStyle : BarChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() - - public final var noDataText : Text - public final let chartType : (chartType: ChartType, dataSetType: DataSetType) - + public let id: UUID = UUID() + + @Published public final var dataSets: RangedBarDataSet + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var barStyle: BarStyle + @Published public final var chartStyle: BarChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + + public final var noDataText: Text + public final let chartType: (chartType: ChartType, dataSetType: DataSetType) + // MARK: Initializer /// Initialises a Ranged Bar Chart. /// @@ -36,35 +39,38 @@ public final class RangedBarChartData: CTRangedBarChartDataProtocol { /// - barStyle: Control for the aesthetic of the bar chart. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : RangedBarDataSet, - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - barStyle : BarStyle = BarStyle(), - chartStyle : BarChartStyle = BarChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: RangedBarDataSet, + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + barStyle: BarStyle = BarStyle(), + chartStyle: BarChartStyle = BarChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.barStyle = barStyle - self.chartStyle = chartStyle - self.noDataText = noDataText - - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (.bar, .single) + self.dataSets = dataSets + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.barStyle = barStyle + self.chartStyle = chartStyle + self.noDataText = noDataText + + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (.bar, .single) self.setupLegends() } - public final var average : Double { - let upperSum = dataSets.dataPoints.reduce(0) { $0 + $1.upperValue } - let lowerSum = dataSets.dataPoints.reduce(0) { $0 + $1.lowerValue } - - let upperAverage = upperSum / Double(dataSets.dataPoints.count) - let lowerAverage = lowerSum / Double(dataSets.dataPoints.count) - + public final var average: Double { + let upperAverage = dataSets.dataPoints + .map(\.upperValue) + .reduce(0, +) + .divide(by: Double(dataSets.dataPoints.count)) + let lowerAverage = dataSets.dataPoints + .map(\.lowerValue) + .reduce(0, +) + .divide(by: Double(dataSets.dataPoints.count)) return (upperAverage + lowerAverage) / 2 } @@ -73,7 +79,7 @@ public final class RangedBarChartData: CTRangedBarChartDataProtocol { Group { switch self.chartStyle.xAxisLabelsFrom { case .dataPoint(let angle): - + HStack(spacing: 0) { ForEach(dataSets.dataPoints) { data in Spacer() @@ -86,9 +92,9 @@ public final class RangedBarChartData: CTRangedBarChartDataProtocol { .frame(minWidth: 0, maxWidth: 500) } } - + case .chartData(let angle): - + if let labelArray = self.xAxisLabels { HStack(spacing: 0) { ForEach(labelArray, id: \.self) { data in @@ -96,7 +102,7 @@ public final class RangedBarChartData: CTRangedBarChartDataProtocol { Spacer() .frame(minWidth: 0, maxWidth: 500) } - XAxisDataPointCell(chartData: self, label: data, rotationAngle: angle) + XAxisChartDataCell(chartData: self, label: data, rotationAngle: angle) .foregroundColor(self.chartStyle.xAxisLabelColour) .accessibilityLabel(Text("X Axis Label")) .accessibilityValue(Text("\(data)")) @@ -110,44 +116,41 @@ public final class RangedBarChartData: CTRangedBarChartDataProtocol { } } } - + // MARK: - Touch public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView() } + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [RangedBarDataPoint] = [] - let xSection : CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count) - let index : Int = Int((touchLocation.x) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count) + let index: Int = Int((touchLocation.x) / xSection) if index >= 0 && index < dataSets.dataPoints.count { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } - self.infoView.touchOverlayInfo = points } + public final func getPointLocation(dataSet: RangedBarDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count) - let index : Int = Int((touchLocation.x) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count) + let index: Int = Int((touchLocation.x) / xSection) if index >= 0 && index < dataSet.dataPoints.count { - let value = CGFloat((dataSet.dataPoints[index].upperValue + dataSet.dataPoints[index].lowerValue) / 2) - CGFloat(self.minValue) - return CGPoint(x: (CGFloat(index) * xSection) + (xSection / 2), - y: (chartSize.size.height - (value / CGFloat(self.range)) * chartSize.size.height)) + y: (chartSize.size.height - (value / CGFloat(self.range)) * chartSize.size.height)) } return nil } - - public typealias Set = RangedBarDataSet - public typealias DataPoint = RangedBarDataPoint - public typealias CTStyle = BarChartStyle + + public typealias Set = RangedBarDataSet + public typealias DataPoint = RangedBarDataPoint + public typealias CTStyle = BarChartStyle } extension RangedBarChartData { - final func getBarPositionX(dataPoint: RangedBarDataPoint, height: CGFloat) -> CGFloat { + final func getBarPositionX(dataPoint: RangedBarDataPoint, height: CGFloat) -> CGFloat { let value = CGFloat((dataPoint.upperValue + dataPoint.lowerValue) / 2) - CGFloat(self.minValue) return (height - (value / CGFloat(self.range)) * height) - } + } } diff --git a/Sources/SwiftUICharts/BarChart/Models/ChartData/StackedBarChartData.swift b/Sources/SwiftUICharts/BarChart/Models/ChartData/StackedBarChartData.swift index ece85404..50a4c67f 100644 --- a/Sources/SwiftUICharts/BarChart/Models/ChartData/StackedBarChartData.swift +++ b/Sources/SwiftUICharts/BarChart/Models/ChartData/StackedBarChartData.swift @@ -11,85 +11,25 @@ import SwiftUI Data model for drawing and styling a Stacked Bar Chart. The grouping data informs the model as to how the datapoints are linked. - - # Example - ``` - static func makeData() -> StackedBarChartData { - - enum Group { - case one - case two - case three - case four - - var data : GroupingData { - switch self { - case .one: - return GroupingData(title: "One" , colour: .blue) - case .two: - return GroupingData(title: "Two" , colour: .red) - case .three: - return GroupingData(title: "Three", colour: .yellow) - case .four: - return GroupingData(title: "Four" , colour: .green) - } - } - } - - let groups : [GroupingData] = [Group.one.data, Group.two.data, Group.three.data, Group.four.data] - - let data = StackedBarDataSets(dataSets: [ - StackedBarDataSet(dataPoints: [ - StackedBarDataPoint(value: 10, xAxisLabel: "1.1", pointLabel: "One One" , group: Group.one.data), - StackedBarDataPoint(value: 10, xAxisLabel: "1.2", pointLabel: "One Two" , group: Group.two.data), - StackedBarDataPoint(value: 30, xAxisLabel: "1.3", pointLabel: "One Three" , group: Group.three.data), - StackedBarDataPoint(value: 40, xAxisLabel: "1.4", pointLabel: "One Four" , group: Group.four.data) - ]), - StackedBarDataSet(dataPoints: [ - StackedBarDataPoint(value: 50, xAxisLabel: "2.1", pointLabel: "Two One" , group: Group.one.data), - StackedBarDataPoint(value: 10, xAxisLabel: "2.2", pointLabel: "Two Two" , group: Group.two.data), - StackedBarDataPoint(value: 40, xAxisLabel: "2.3", pointLabel: "Two Three" , group: Group.three.data), - StackedBarDataPoint(value: 60, xAxisLabel: "2.3", pointLabel: "Two Four" , group: Group.four.data) - ]), - StackedBarDataSet(dataPoints: [ - StackedBarDataPoint(value: 10, xAxisLabel: "3.1", pointLabel: "Three One" , group: Group.one.data), - StackedBarDataPoint(value: 50, xAxisLabel: "3.2", pointLabel: "Three Two" , group: Group.two.data), - StackedBarDataPoint(value: 30, xAxisLabel: "3.3", pointLabel: "Three Three", group: Group.three.data), - StackedBarDataPoint(value: 100, xAxisLabel: "3.4", pointLabel: "Three Four" , group: Group.four.data) - ]), - StackedBarDataSet(dataPoints: [ - StackedBarDataPoint(value: 80, xAxisLabel: "4.1", pointLabel: "Four One" , group: Group.one.data), - StackedBarDataPoint(value: 10, xAxisLabel: "4.2", pointLabel: "Four Two" , group: Group.two.data), - StackedBarDataPoint(value: 20, xAxisLabel: "4.3", pointLabel: "Four Three" , group: Group.three.data), - StackedBarDataPoint(value: 50, xAxisLabel: "4.3", pointLabel: "Four Four" , group: Group.four.data) - ]) - ]) - - - return StackedBarChartData(dataSets: data, - groups: groups, - metadata: ChartMetadata(title: "Hello", subtitle: "World"), - chartStyle: BarChartStyle(xAxisLabelsFrom: .dataPoint)) - ``` */ public final class StackedBarChartData: CTMultiBarChartDataProtocol { // MARK: Properties - public let id : UUID = UUID() + public let id: UUID = UUID() - @Published public final var dataSets : StackedBarDataSets - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var barStyle : BarStyle - @Published public final var chartStyle : BarChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() - @Published public final var groups : [GroupingData] + @Published public final var dataSets: StackedBarDataSets + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var barStyle: BarStyle + @Published public final var chartStyle: BarChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + @Published public final var groups: [GroupingData] - public final var noDataText : Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) + public final var noDataText: Text + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) // MARK: Initializer /// Initialises a Stacked Bar Chart. @@ -103,26 +43,27 @@ public final class StackedBarChartData: CTMultiBarChartDataProtocol { /// - barStyle: Control for the aesthetic of the bar chart. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : StackedBarDataSets, - groups : [GroupingData], - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - barStyle : BarStyle = BarStyle(), - chartStyle : BarChartStyle = BarChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: StackedBarDataSets, + groups: [GroupingData], + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + barStyle: BarStyle = BarStyle(), + chartStyle: BarChartStyle = BarChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.groups = groups - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.barStyle = barStyle - self.chartStyle = chartStyle - self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (chartType: .bar, dataSetType: .multi) + self.dataSets = dataSets + self.groups = groups + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.barStyle = barStyle + self.chartStyle = chartStyle + self.noDataText = noDataText + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (chartType: .bar, dataSetType: .multi) self.setupLegends() } // MARK: Labels @@ -150,7 +91,7 @@ public final class StackedBarChartData: CTMultiBarChartDataProtocol { ForEach(labelArray, id: \.self) { data in Spacer() .frame(minWidth: 0, maxWidth: 500) - XAxisDataPointCell(chartData: self, label: data, rotationAngle: angle) + XAxisChartDataCell(chartData: self, label: data, rotationAngle: angle) .foregroundColor(self.chartStyle.xAxisLabelColour) .accessibilityLabel(Text("X Axis Label")) .accessibilityValue(Text("\(data)")) @@ -162,107 +103,83 @@ public final class StackedBarChartData: CTMultiBarChartDataProtocol { } } } + // MARK: Touch public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView() } - public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - - var points : [StackedBarDataPoint] = [] - - // Filter to get the right dataset based on the x axis. - let superXSection : CGFloat = chartSize.width / CGFloat(dataSets.dataSets.count) - let superIndex : Int = Int((touchLocation.x) / superXSection) - - if superIndex >= 0 && superIndex < dataSets.dataSets.count { - - let dataSet = dataSets.dataSets[superIndex] - - // Get the max value of the dataset relative to max value of all datasets. - // This is used to set the height of the y axis filtering. - let setMaxValue = dataSet.maxValue() - let allMaxValue = self.maxValue - let fraction : CGFloat = CGFloat(setMaxValue / allMaxValue) - - // Gets the height of each datapoint - var heightOfElements : [CGFloat] = [] - let sum = dataSet.dataPoints.reduce(0) { $0 + $1.value } - dataSet.dataPoints.forEach { datapoint in - heightOfElements.append((chartSize.height * fraction) * CGFloat(datapoint.value / sum)) - } - - // Gets the highest point of each element. - var endPointOfElements : [CGFloat] = [] - heightOfElements.enumerated().forEach { element in - var returnValue : CGFloat = 0 - for index in 0...element.offset { - returnValue += heightOfElements[index] - } - endPointOfElements.append(returnValue) - } - - let yIndex = endPointOfElements.enumerated().first(where: { $0.element > abs(touchLocation.y - chartSize.height) }) - - if let index = yIndex?.offset { - if index >= 0 && index < dataSet.dataPoints.count { - var dataPoint = dataSet.dataPoints[index] - dataPoint.legendTag = dataSet.setTitle - points.append(dataPoint) - } - } - } - self.infoView.touchOverlayInfo = points - } - - public final func getPointLocation(dataSet: StackedBarDataSets, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - // Filter to get the right dataset based on the x axis. - let superXSection : CGFloat = chartSize.width / CGFloat(dataSet.dataSets.count) - let superIndex : Int = Int((touchLocation.x) / superXSection) - - if superIndex >= 0 && superIndex < dataSet.dataSets.count { - - let subDataSet = dataSet.dataSets[superIndex] - - // Get the max value of the dataset relative to max value of all datasets. - // This is used to set the height of the y axis filtering. - let setMaxValue = subDataSet.maxValue() - let allMaxValue = self.maxValue - let fraction : CGFloat = CGFloat(setMaxValue / allMaxValue) - - // Gets the height of each datapoint - var heightOfElements : [CGFloat] = [] - let sum = subDataSet.dataPoints.reduce(0) { $0 + $1.value } - subDataSet.dataPoints.forEach { datapoint in - heightOfElements.append((chartSize.height * fraction) * CGFloat(datapoint.value / sum)) - } - - // Gets the highest point of each element. - var endPointOfElements : [CGFloat] = [] - heightOfElements.enumerated().forEach { element in - var returnValue : CGFloat = 0 - for index in 0...element.offset { - returnValue += heightOfElements[index] - } - endPointOfElements.append(returnValue) - } - - let yIndex = endPointOfElements.enumerated().first(where: { - $0.element > abs(touchLocation.y - chartSize.height) - }) - - if let index = yIndex?.offset { - if index >= 0 && index < subDataSet.dataPoints.count { - - return CGPoint(x: (CGFloat(superIndex) * superXSection) + (superXSection / 2), - y: (chartSize.height - endPointOfElements[index])) - } - } - } - return nil - } - - public typealias Set = StackedBarDataSets - public typealias DataPoint = StackedBarDataPoint - public typealias CTStyle = BarChartStyle + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { + // Filter to get the right dataset based on the x axis. + let superXSection: CGFloat = chartSize.width / CGFloat(dataSets.dataSets.count) + let superIndex: Int = Int((touchLocation.x) / superXSection) + if superIndex >= 0 && superIndex < dataSets.dataSets.count { + // Filter to get the right dataset based on the y axis. + let calculatedIndex = calculateIndex(dataSet: dataSets.dataSets[superIndex], touchLocation: touchLocation, chartSize: chartSize) + if let index = calculatedIndex.yIndex?.offset { + if index >= 0 && index < dataSets.dataSets[superIndex].dataPoints.count { + dataSets.dataSets[superIndex].dataPoints[index].legendTag = dataSets.dataSets[superIndex].setTitle + self.infoView.touchOverlayInfo = [dataSets.dataSets[superIndex].dataPoints[index]] + } + } + } + } + + public final func getPointLocation(dataSet: StackedBarDataSets, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { + // Filter to get the right dataset based on the x axis. + let superXSection: CGFloat = chartSize.width / CGFloat(dataSet.dataSets.count) + let superIndex: Int = Int((touchLocation.x) / superXSection) + if superIndex >= 0 && superIndex < dataSet.dataSets.count { + // Filter to get the right dataset based on the y axis. + let subDataSet = dataSet.dataSets[superIndex] + let calculatedIndex = calculateIndex(dataSet: subDataSet, touchLocation: touchLocation, chartSize: chartSize) + if let index = calculatedIndex.yIndex?.offset { + if index >= 0 && index < subDataSet.dataPoints.count { + return CGPoint(x: (CGFloat(superIndex) * superXSection) + (superXSection / 2), + y: (chartSize.height - calculatedIndex.endPointOfElements[index])) + } + } + } + return nil + } + + private final func calculateIndex( + dataSet: StackedBarDataSet, + touchLocation: CGPoint, + chartSize: CGRect + ) -> (yIndex: EnumeratedSequence<[CGFloat]>.Element?, endPointOfElements: [CGFloat]) { + + // Get the max value of the dataset relative to max value of all datasets. + // This is used to set the height of the y axis filtering. + let setMaxValue = dataSet.maxValue() + let allMaxValue = self.maxValue + let fraction: CGFloat = CGFloat(setMaxValue / allMaxValue) + + // Gets the height of each datapoint + let sum: Double = dataSet.dataPoints + .map(\.value) + .reduce(0, +) + let heightOfElements: [CGFloat] = dataSet.dataPoints + .map(\.value) + .map { (chartSize.height * fraction) * CGFloat($0 / sum) } + + // Gets the highest point of each element. + let endPointOfElements: [CGFloat] = heightOfElements + .enumerated() + .map { + (0...$0.offset) + .map { heightOfElements[$0] } + .reduce(0, +) + } + + let yIndex = endPointOfElements + .enumerated() + .first(where:) { $0.element > abs(touchLocation.y - chartSize.height) } + + return (yIndex, endPointOfElements) + } + + public typealias Set = StackedBarDataSets + public typealias DataPoint = StackedBarDataPoint + public typealias CTStyle = BarChartStyle } diff --git a/Sources/SwiftUICharts/BarChart/Models/CornerRadius.swift b/Sources/SwiftUICharts/BarChart/Models/CornerRadius.swift index ffb080dd..e5f917dd 100644 --- a/Sources/SwiftUICharts/BarChart/Models/CornerRadius.swift +++ b/Sources/SwiftUICharts/BarChart/Models/CornerRadius.swift @@ -11,12 +11,15 @@ import SwiftUI Corner radius of the bar shape. */ public struct CornerRadius: Hashable { - - public let top : CGFloat - public let bottom : CGFloat + + public let top: CGFloat + public let bottom: CGFloat /// Set the coner radius for the bar shapes - public init(top: CGFloat = 15.0, bottom: CGFloat = 0.0) { + public init( + top: CGFloat = 15.0, + bottom: CGFloat = 0.0 + ) { self.top = top self.bottom = bottom } diff --git a/Sources/SwiftUICharts/BarChart/Models/DataSet/BarDataSet.swift b/Sources/SwiftUICharts/BarChart/Models/DataSet/BarDataSet.swift index 6191316c..fe7bed2b 100644 --- a/Sources/SwiftUICharts/BarChart/Models/DataSet/BarDataSet.swift +++ b/Sources/SwiftUICharts/BarChart/Models/DataSet/BarDataSet.swift @@ -9,38 +9,25 @@ import SwiftUI /** Data set for a bar chart. - - # Example - ``` - let data = BarDataSet(dataPoints: [ - BarChartDataPoint(value: 20, xAxisLabel: "M", pointLabel: "Monday"), - BarChartDataPoint(value: 90, xAxisLabel: "T", pointLabel: "Tuesday"), - BarChartDataPoint(value: 100, xAxisLabel: "W", pointLabel: "Wednesday"), - BarChartDataPoint(value: 75, xAxisLabel: "T", pointLabel: "Thursday"), - BarChartDataPoint(value: 160, xAxisLabel: "F", pointLabel: "Friday"), - BarChartDataPoint(value: 110, xAxisLabel: "S", pointLabel: "Saturday"), - BarChartDataPoint(value: 90, xAxisLabel: "S", pointLabel: "Sunday") - ], - legendTitle: "Data") - ``` */ public struct BarDataSet: CTStandardBarChartDataSet { - - public let id : UUID = UUID() - public var dataPoints : [BarChartDataPoint] - public var legendTitle : String + + public let id: UUID = UUID() + public var dataPoints: [BarChartDataPoint] + public var legendTitle: String /// Initialises a new data set for standard Bar Charts. /// - Parameters: /// - dataPoints: Array of elements. /// - legendTitle: label for the data in legend. - public init(dataPoints : [BarChartDataPoint], - legendTitle : String = "" + public init( + dataPoints: [BarChartDataPoint], + legendTitle: String = "" ) { - self.dataPoints = dataPoints - self.legendTitle = legendTitle + self.dataPoints = dataPoints + self.legendTitle = legendTitle } - - public typealias ID = UUID + + public typealias ID = UUID public typealias DataPoint = BarChartDataPoint } diff --git a/Sources/SwiftUICharts/BarChart/Models/DataSet/GroupedBarDataSets.swift b/Sources/SwiftUICharts/BarChart/Models/DataSet/GroupedBarDataSets.swift index 3eb14fab..1126751b 100644 --- a/Sources/SwiftUICharts/BarChart/Models/DataSet/GroupedBarDataSets.swift +++ b/Sources/SwiftUICharts/BarChart/Models/DataSet/GroupedBarDataSets.swift @@ -7,14 +7,13 @@ import SwiftUI -// MARK: - Grouped /** Main data set for a grouped bar charts. */ public struct GroupedBarDataSets: CTMultiDataSetProtocol { - public let id : UUID = UUID() - public var dataSets : [GroupedBarDataSet] + public let id: UUID = UUID() + public var dataSets: [GroupedBarDataSet] /// Initialises a new data set for Grouped Bar Chart. public init(dataSets: [GroupedBarDataSet]) { @@ -24,31 +23,22 @@ public struct GroupedBarDataSets: CTMultiDataSetProtocol { /** Individual data sets for grouped bars charts. - - # Example - ``` - GroupedBarDataSet(dataPoints: [ - GroupedBarDataPoint(value: 10, group: GroupingData(title: "One", colour: .blue)), - GroupedBarDataPoint(value: 50, group: GroupingData(title: "Two", colour: .red)) - ]) - ``` */ public struct GroupedBarDataSet: CTMultiBarChartDataSet { - - public let id : UUID = UUID() - public var dataPoints : [GroupedBarDataPoint] - public var setTitle : String - + + public let id: UUID = UUID() + public var dataPoints: [GroupedBarDataPoint] + public var setTitle: String + /// Initialises a new data set for a Bar Chart. - public init(dataPoints: [GroupedBarDataPoint], - setTitle : String = "" + public init( + dataPoints: [GroupedBarDataPoint], + setTitle: String = "" ) { self.dataPoints = dataPoints - self.setTitle = setTitle + self.setTitle = setTitle } - - public typealias ID = UUID + + public typealias ID = UUID public typealias DataPoint = GroupedBarDataPoint - public typealias Styling = BarStyle } - diff --git a/Sources/SwiftUICharts/BarChart/Models/DataSet/RangedBarDataSet.swift b/Sources/SwiftUICharts/BarChart/Models/DataSet/RangedBarDataSet.swift index 725c7c94..6de1abd3 100644 --- a/Sources/SwiftUICharts/BarChart/Models/DataSet/RangedBarDataSet.swift +++ b/Sources/SwiftUICharts/BarChart/Models/DataSet/RangedBarDataSet.swift @@ -10,24 +10,24 @@ import SwiftUI /** Data set for ranged bar charts. */ -public struct RangedBarDataSet : CTRangedBarChartDataSet { +public struct RangedBarDataSet: CTRangedBarChartDataSet { public var id: UUID = UUID() - public var dataPoints : [RangedBarDataPoint] - public var legendTitle : String + public var dataPoints: [RangedBarDataPoint] + public var legendTitle: String /// Initialises a new data set for ranged bar chart. /// - Parameters: /// - dataPoints: Array of elements. /// - legendTitle: Label for the data in legend. - public init(dataPoints : [RangedBarDataPoint], - legendTitle : String = "" + public init( + dataPoints: [RangedBarDataPoint], + legendTitle: String = "" ) { - self.dataPoints = dataPoints - self.legendTitle = legendTitle + self.dataPoints = dataPoints + self.legendTitle = legendTitle } - - public typealias ID = UUID + public typealias ID = UUID public typealias DataPoint = RangedBarDataPoint } diff --git a/Sources/SwiftUICharts/BarChart/Models/DataSet/StackedBarDataSet.swift b/Sources/SwiftUICharts/BarChart/Models/DataSet/StackedBarDataSet.swift index 5c4711c9..e1ebf8a0 100644 --- a/Sources/SwiftUICharts/BarChart/Models/DataSet/StackedBarDataSet.swift +++ b/Sources/SwiftUICharts/BarChart/Models/DataSet/StackedBarDataSet.swift @@ -12,8 +12,8 @@ import SwiftUI */ public struct StackedBarDataSets: CTMultiDataSetProtocol { - public let id : UUID = UUID() - public var dataSets : [StackedBarDataSet] + public let id: UUID = UUID() + public var dataSets: [StackedBarDataSet] /// Initialises a new data set for a Stacked Bar Chart. public init(dataSets: [StackedBarDataSet]) { @@ -23,30 +23,22 @@ public struct StackedBarDataSets: CTMultiDataSetProtocol { /** Individual data sets for stacked bars charts. - - # Example - ``` - StackedBarDataSet(dataPoints: [ - StackedBarDataPoint(value: 10, group: GroupingData(title: "One", colour: .blue)), - StackedBarDataPoint(value: 50, group: GroupingData(title: "Two", colour: .red)) - ]) - ``` */ public struct StackedBarDataSet: CTMultiBarChartDataSet { - - public let id : UUID = UUID() - public var dataPoints : [StackedBarDataPoint] - public var setTitle : String - + + public let id: UUID = UUID() + public var dataPoints: [StackedBarDataPoint] + public var setTitle: String + /// Initialises a new data set for a Stacked Bar Chart. - public init(dataPoints: [StackedBarDataPoint], - setTitle : String = "" + public init( + dataPoints: [StackedBarDataPoint], + setTitle: String = "" ) { self.dataPoints = dataPoints - self.setTitle = setTitle + self.setTitle = setTitle } - - public typealias ID = UUID + + public typealias ID = UUID public typealias DataPoint = StackedBarDataPoint - public typealias Styling = BarStyle } diff --git a/Sources/SwiftUICharts/BarChart/Models/Datapoints/BarChartDataPoint.swift b/Sources/SwiftUICharts/BarChart/Models/Datapoints/BarChartDataPoint.swift index 16a3e1f0..c4d465fd 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Datapoints/BarChartDataPoint.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Datapoints/BarChartDataPoint.swift @@ -11,26 +11,16 @@ import SwiftUI Data for a single bar chart data point. Colour can be solid or gradient. - - # Example - ``` - BarChartDataPoint(value: 90, - xAxisLabel: "T", - description: "Tuesday", - colour: ColourStyle(colour: .blue)) - ``` */ public struct BarChartDataPoint: CTStandardBarDataPoint { public let id = UUID() - - public var value : Double - public var xAxisLabel : String? + public var value: Double + public var xAxisLabel: String? public var description: String? - public var date : Date? - public var colour : ColourStyle - - public var legendTag : String = "" + public var date: Date? + public var colour: ColourStyle + public var legendTag: String = "" // MARK: - Single colour /// Data model for a single data point with colour for use with a bar chart. @@ -40,16 +30,19 @@ public struct BarChartDataPoint: CTStandardBarDataPoint { /// - description: A longer label that can be shown on touch input. /// - date: Date of the data point if any data based calculations are required. /// - colour: Colour styling for the fill. - public init(value : Double, - xAxisLabel : String? = nil, - description : String? = nil, - date : Date? = nil, - colour : ColourStyle = ColourStyle(colour: .red) + public init( + value: Double, + xAxisLabel: String? = nil, + description: String? = nil, + date: Date? = nil, + colour: ColourStyle = ColourStyle(colour: .red) ) { - self.value = value - self.xAxisLabel = xAxisLabel + self.value = value + self.xAxisLabel = xAxisLabel self.description = description - self.date = date - self.colour = colour + self.date = date + self.colour = colour } + + public typealias ID = UUID } diff --git a/Sources/SwiftUICharts/BarChart/Models/Datapoints/GroupedBarDataPoint.swift b/Sources/SwiftUICharts/BarChart/Models/Datapoints/GroupedBarDataPoint.swift index aa297208..9d33f49e 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Datapoints/GroupedBarDataPoint.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Datapoints/GroupedBarDataPoint.swift @@ -9,41 +9,34 @@ import SwiftUI /** Data for a single grouped bar chart data point. - - # Example - ``` - GroupedBarDataPoint( - value: 10, - description: "One One", - group: GroupingData( - title: "One", - colour: .blue - ) - ) - ``` */ public struct GroupedBarDataPoint: CTMultiBarDataPoint { - - public let id : UUID = UUID() - public var value : Double - public var xAxisLabel : String? = nil - public var description : String? - public var date : Date? - public var group : GroupingData - - public var legendTag : String = "" - - public init(value : Double, - description : String? = nil, - date : Date? = nil, - group : GroupingData + + public let id: UUID = UUID() + public var value: Double + public var xAxisLabel: String? = nil + public var description: String? + public var date: Date? + public var group: GroupingData + public var legendTag: String = "" + + /// Data model for a single data point with colour info for use with a grouped bar chart. + /// - Parameters: + /// - value: Value of the data point. + /// - description: A longer label that can be shown on touch input. + /// - date: Date of the data point if any data based calculations are required. + /// - group: Group data informs the data models how the data point are linked. + public init( + value: Double, + description: String? = nil, + date: Date? = nil, + group: GroupingData ) { - self.value = value + self.value = value self.description = description - self.date = date - self.group = group + self.date = date + self.group = group } - + public typealias ID = UUID } - diff --git a/Sources/SwiftUICharts/BarChart/Models/Datapoints/MultiBarChartDataPoint.swift b/Sources/SwiftUICharts/BarChart/Models/Datapoints/MultiBarChartDataPoint.swift deleted file mode 100644 index ec6e0f2a..00000000 --- a/Sources/SwiftUICharts/BarChart/Models/Datapoints/MultiBarChartDataPoint.swift +++ /dev/null @@ -1,49 +0,0 @@ -// -// StackedBarDataPoint.swift -// -// -// Created by Will Dale on 19/02/2021. -// - -import SwiftUI - -/** - Data for a single stacked chart data point. - - # Example - ``` - StackedBarChartDataPoint( - value: 10, - description: "One One", - group: GroupingData( - title: "One", - colour: .blue - ) - ) - ``` - */ -public struct StackedBarDataPoint: CTMultiBarDataPoint { - - public let id : UUID = UUID() - public var value : Double - public var xAxisLabel : String? = nil - public var description : String? - public var date : Date? - public var group : GroupingData - - public var legendTag : String = "" - - public init(value : Double, - description : String? = nil, - date : Date? = nil, - group : GroupingData - ) { - self.value = value - self.description = description - self.date = date - self.group = group - } - - public typealias ID = UUID -} - diff --git a/Sources/SwiftUICharts/BarChart/Models/Datapoints/RangedBarDataPoint.swift b/Sources/SwiftUICharts/BarChart/Models/Datapoints/RangedBarDataPoint.swift index b23d843a..409d8229 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Datapoints/RangedBarDataPoint.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Datapoints/RangedBarDataPoint.swift @@ -11,17 +11,16 @@ import SwiftUI Data for a single ranged bar chart data point. */ public struct RangedBarDataPoint: CTRangedBarDataPoint { - - public let id : UUID = UUID() - public var upperValue : Double - public var lowerValue : Double - public var xAxisLabel : String? - public var description : String? - public var date : Date? - public var colour : ColourStyle - public var legendTag : String = "" - + public let id: UUID = UUID() + public var upperValue: Double + public var lowerValue: Double + public var xAxisLabel: String? + public var description: String? + public var date: Date? + public var colour: ColourStyle + public var legendTag: String = "" + /// Data model for a single data point with colour for use with a ranged bar chart. /// - Parameters: /// - lowerValue: Value of the lower range of the data point. @@ -30,19 +29,20 @@ public struct RangedBarDataPoint: CTRangedBarDataPoint { /// - description: A longer label that can be shown on touch input. /// - date: Date of the data point if any data based calculations are required. /// - colour: Colour styling for the fill. - public init(lowerValue : Double, - upperValue : Double, - xAxisLabel : String? = nil, - description : String? = nil, - date : Date? = nil, - colour : ColourStyle = ColourStyle(colour: .red) + public init( + lowerValue: Double, + upperValue: Double, + xAxisLabel: String? = nil, + description: String? = nil, + date: Date? = nil, + colour: ColourStyle = ColourStyle(colour: .red) ) { - self.upperValue = upperValue - self.lowerValue = lowerValue - self.xAxisLabel = xAxisLabel + self.upperValue = upperValue + self.lowerValue = lowerValue + self.xAxisLabel = xAxisLabel self.description = description - self.date = date - self.colour = colour + self.date = date + self.colour = colour } public typealias ID = UUID diff --git a/Sources/SwiftUICharts/BarChart/Models/Datapoints/StackedBarDataPoint.swift b/Sources/SwiftUICharts/BarChart/Models/Datapoints/StackedBarDataPoint.swift new file mode 100644 index 00000000..16fc4ca6 --- /dev/null +++ b/Sources/SwiftUICharts/BarChart/Models/Datapoints/StackedBarDataPoint.swift @@ -0,0 +1,42 @@ +// +// StackedBarDataPoint.swift +// +// +// Created by Will Dale on 19/02/2021. +// + +import SwiftUI + +/** + Data for a single stacked chart data point. + */ +public struct StackedBarDataPoint: CTMultiBarDataPoint { + + public let id: UUID = UUID() + public var value: Double + public var xAxisLabel: String? = nil + public var description: String? + public var date: Date? + public var group: GroupingData + public var legendTag: String = "" + + /// Data model for a single data point with colour info for use with a stacked bar chart. + /// - Parameters: + /// - value: Value of the data point. + /// - description: A longer label that can be shown on touch input. + /// - date: Date of the data point if any data based calculations are required. + /// - group: Group data informs the data models how the data point are linked. + public init( + value: Double, + description: String? = nil, + date: Date? = nil, + group: GroupingData + ) { + self.value = value + self.description = description + self.date = date + self.group = group + } + + public typealias ID = UUID +} diff --git a/Sources/SwiftUICharts/BarChart/Models/GroupingData.swift b/Sources/SwiftUICharts/BarChart/Models/GroupingData.swift index a457c8b1..0b30bbcc 100644 --- a/Sources/SwiftUICharts/BarChart/Models/GroupingData.swift +++ b/Sources/SwiftUICharts/BarChart/Models/GroupingData.swift @@ -9,26 +9,22 @@ import SwiftUI /** Model for grouping data points together so they can be drawn in the correct groupings. - - # Example - ``` - GroupingData(title: "One", colour: ColourStyle(colour: .blue)) - ``` */ public struct GroupingData: CTBarColourProtocol, Hashable, Identifiable { - public let id : UUID = UUID() - public var title : String - public var colour : ColourStyle + public let id: UUID = UUID() + public var title: String + public var colour: ColourStyle /// Group with single colour /// - Parameters: /// - title: Title for legends /// - colour: Colour styling for the bars. - public init(title : String, - colour : ColourStyle + public init( + title: String, + colour: ColourStyle ) { - self.title = title + self.title = title self.colour = colour } } diff --git a/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocols.swift b/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocols.swift index b0f798eb..f51a3444 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocols.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocols.swift @@ -13,11 +13,11 @@ import SwiftUI */ public protocol CTBarChartDataProtocol: CTLineBarChartDataProtocol { - associatedtype BarStyle : CTBarStyle + associatedtype BarStyle: CTBarStyle /** Overall styling for the bars */ - var barStyle : BarStyle { get set } + var barStyle: BarStyle { get set } } @@ -30,7 +30,7 @@ public protocol CTMultiBarChartDataProtocol: CTBarChartDataProtocol { /** Grouping data to inform the chart about the relationship between the datapoints. */ - var groups : [GroupingData] { get set } + var groups: [GroupingData] { get set } } /** @@ -49,13 +49,13 @@ public protocol CTBarChartStyle: CTLineBarChartStyle {} public protocol CTBarStyle: CTBarColourProtocol, Hashable { /// How much of the available width to use. 0...1 - var barWidth : CGFloat { get set } + var barWidth: CGFloat { get set } /// Corner radius of the bar shape. var cornerRadius: CornerRadius { get set } /// Where to get the colour data from. - var colourFrom : ColourFrom { get set } + var colourFrom: ColourFrom { get set } /// Drawing style of the fill. - var colour : ColourStyle { get set } + var colour: ColourStyle { get set } } @@ -71,13 +71,20 @@ public protocol CTStandardBarChartDataSet: CTSingleDataSetProtocol { /** Label to display in the legend. */ - var legendTitle : String { get set } + var legendTitle: String { get set } } /** A protocol to extend functionality of `CTSingleDataSetProtocol` specifically for Multi Part Bar Charts. */ -public protocol CTMultiBarChartDataSet: CTSingleDataSetProtocol {} +public protocol CTMultiBarChartDataSet: CTSingleDataSetProtocol { + /** + Title of the data set. + + This is used as an x axis label. + */ + var setTitle: String { get set } +} /** A protocol to extend functionality of `CTSingleDataSetProtocol` specifically for Ranged Bar Charts. @@ -101,7 +108,7 @@ public protocol CTBarDataPointBaseProtocol: CTLineBarDataPointProtocol {} */ public protocol CTBarColourProtocol { /// Drawing style of the range fill. - var colour : ColourStyle { get set } + var colour: ColourStyle { get set } } /** @@ -123,7 +130,5 @@ public protocol CTMultiBarDataPoint: CTBarDataPointBaseProtocol, CTStandardDataP /** For grouping data points together so they can be drawn in the correct groupings. */ - var group : GroupingData { get set } + var group: GroupingData { get set } } - - diff --git a/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocolsExtensions.swift b/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocolsExtensions.swift index 27543140..f884d152 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocolsExtensions.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Protocols/BarChartProtocolsExtensions.swift @@ -18,35 +18,23 @@ extension CTBarChartDataProtocol where Self.CTStyle.Mark == BarMarkerType { case .none: EmptyView() case .vertical(let colour, let style): - MarkerFull(position: position) .stroke(colour, style: style) - case .full(let colour, let style): - MarkerFull(position: position) .stroke(colour, style: style) - case .bottomLeading(let colour, let style): - MarkerBottomLeading(position: position) .stroke(colour, style: style) - case .bottomTrailing(let colour, let style): - MarkerBottomTrailing(position: position) .stroke(colour, style: style) - case .topLeading(let colour, let style): - MarkerTopLeading(position: position) .stroke(colour, style: style) - case .topTrailing(let colour, let style): - MarkerTopTrailing(position: position) .stroke(colour, style: style) - } } } @@ -65,73 +53,71 @@ extension CTBarChartDataProtocol where Self.Set.ID == UUID, if self.barStyle.colour.colourType == .colour, let colour = self.barStyle.colour.colour { - self.legends.append(LegendData(id : dataSets.id, - legend : dataSets.legendTitle, - colour : ColourStyle(colour: colour), + self.legends.append(LegendData(id: dataSets.id, + legend: dataSets.legendTitle, + colour: ColourStyle(colour: colour), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if self.barStyle.colour.colourType == .gradientColour, let colours = self.barStyle.colour.colours { - self.legends.append(LegendData(id : dataSets.id, - legend : dataSets.legendTitle, - colour : ColourStyle(colours: colours, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: dataSets.id, + legend: dataSets.legendTitle, + colour: ColourStyle(colours: colours, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if self.barStyle.colour.colourType == .gradientStops, let stops = self.barStyle.colour.stops { - self.legends.append(LegendData(id : dataSets.id, - legend : dataSets.legendTitle, - colour : ColourStyle(stops: stops, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: dataSets.id, + legend: dataSets.legendTitle, + colour: ColourStyle(stops: stops, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } case .dataPoints: - for data in dataSets.dataPoints { - if data.colour.colourType == .colour, let colour = data.colour.colour, let legend = data.description { - self.legends.append(LegendData(id : data.id, - legend : legend, - colour : ColourStyle(colour: colour), + self.legends.append(LegendData(id: data.id, + legend: legend, + colour: ColourStyle(colour: colour), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if data.colour.colourType == .gradientColour, let colours = data.colour.colours, let legend = data.description { - self.legends.append(LegendData(id : data.id, - legend : legend, - colour : ColourStyle(colours: colours, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: data.id, + legend: legend, + colour: ColourStyle(colours: colours, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if data.colour.colourType == .gradientStops, let stops = data.colour.stops, let legend = data.description { - self.legends.append(LegendData(id : data.id, - legend : legend, - colour : ColourStyle(stops: stops, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: data.id, + legend: legend, + colour: ColourStyle(stops: stops, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } } } @@ -141,40 +127,38 @@ extension CTBarChartDataProtocol where Self.Set.ID == UUID, // MARK: Multi Bar extension CTMultiBarChartDataProtocol { internal func setupLegends() { - for group in self.groups { - if group.colour.colourType == .colour, let colour = group.colour.colour { - self.legends.append(LegendData(id : group.id, - legend : group.title, - colour : ColourStyle(colour: colour), + self.legends.append(LegendData(id: group.id, + legend: group.title, + colour: ColourStyle(colour: colour), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if group.colour.colourType == .gradientColour, let colours = group.colour.colours { - self.legends.append(LegendData(id : group.id, - legend : group.title, - colour : ColourStyle(colours: colours, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: group.id, + legend: group.title, + colour: ColourStyle(colours: colours, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } else if group.colour.colourType == .gradientStops, let stops = group.colour.stops { - self.legends.append(LegendData(id : group.id, - legend : group.title, - colour : ColourStyle(stops: stops, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: group.id, + legend: group.title, + colour: ColourStyle(stops: stops, + startPoint: .leading, + endPoint: .trailing), strokeStyle: nil, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } } } diff --git a/Sources/SwiftUICharts/BarChart/Models/Style/BarChartStyle.swift b/Sources/SwiftUICharts/BarChart/Models/Style/BarChartStyle.swift index cbc5eaf8..05ddb83f 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Style/BarChartStyle.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Style/BarChartStyle.swift @@ -16,47 +16,46 @@ import SwiftUI */ public struct BarChartStyle: CTBarChartStyle { - public var infoBoxPlacement : InfoBoxPlacement + public var infoBoxPlacement: InfoBoxPlacement + public var infoBoxContentAlignment: InfoBoxAlignment - public var infoBoxContentAlignment : InfoBoxAlignment + public var infoBoxValueFont: Font + public var infoBoxValueColour: Color - public var infoBoxValueFont : Font - public var infoBoxValueColour : Color + public var infoBoxDescriptionFont: Font + public var infoBoxDescriptionColour: Color - public var infoBoxDescriptionFont : Font - public var infoBoxDescriptionColour : Color + public var infoBoxBackgroundColour: Color + public var infoBoxBorderColour: Color + public var infoBoxBorderStyle: StrokeStyle - public var infoBoxBackgroundColour : Color - public var infoBoxBorderColour : Color - public var infoBoxBorderStyle : StrokeStyle + public var markerType: BarMarkerType - public var markerType : BarMarkerType + public var xAxisGridStyle: GridStyle - public var xAxisGridStyle : GridStyle + public var xAxisLabelPosition: XAxisLabelPosistion + public var xAxisLabelFont: Font + public var xAxisLabelColour: Color + public var xAxisLabelsFrom: LabelsFrom - public var xAxisLabelPosition : XAxisLabelPosistion - public var xAxisLabelFont : Font - public var xAxisLabelColour : Color - public var xAxisLabelsFrom : LabelsFrom + public var xAxisTitle: String? + public var xAxisTitleFont: Font - public var xAxisTitle : String? - public var xAxisTitleFont : Font + public var yAxisGridStyle: GridStyle - public var yAxisGridStyle : GridStyle + public var yAxisLabelPosition: YAxisLabelPosistion + public var yAxisLabelFont: Font + public var yAxisLabelColour: Color + public var yAxisNumberOfLabels: Int + public var yAxisLabelType: YAxisLabelType - public var yAxisLabelPosition : YAxisLabelPosistion - public var yAxisLabelFont : Font - public var yAxisLabelColour : Color - public var yAxisNumberOfLabels : Int - public var yAxisLabelType : YAxisLabelType + public var yAxisTitle: String? + public var yAxisTitleFont: Font - public var yAxisTitle : String? - public var yAxisTitleFont : Font + public var baseline: Baseline + public var topLine: Topline - public var baseline : Baseline - public var topLine : Topline - - public var globalAnimation : Animation + public var globalAnimation: Animation /// Model for controlling the overall aesthetic of the Bar Chart. /// @@ -91,7 +90,8 @@ public struct BarChartStyle: CTBarChartStyle { /// - yAxisLabelPosition: Location of the X axis labels - Leading or Trailing. /// - yAxisLabelFont: Font of the labels on the Y axis. /// - yAxisLabelColour: Text Colour for the labels on the Y axis. - /// - yAxisNumberOfLabel: Number Of Labels on Y Axis. + /// - yAxisNumberOfLabels: Number Of Labels on Y Axis. + /// - yAxisLabelType: Option to choose between auto generated, numeric labels or custum array of strings. /// /// - yAxisTitle: Label to display next to the chart giving info about the axis. /// - yAxisTitleFont: Font of the y axis title. @@ -100,86 +100,87 @@ public struct BarChartStyle: CTBarChartStyle { /// - topLine: Where to finish drawing the chart from. Data set maximum or custom. /// /// - globalAnimation: Global control of animations. - public init(infoBoxPlacement : InfoBoxPlacement = .floating, - infoBoxContentAlignment : InfoBoxAlignment = .vertical, - - infoBoxValueFont : Font = .title3, - infoBoxValueColour : Color = Color.primary, - - infoBoxDescriptionFont : Font = .caption, - infoBoxDescriptionColour: Color = Color.primary, - - infoBoxBackgroundColour : Color = Color.systemsBackground, - infoBoxBorderColour : Color = Color.clear, - infoBoxBorderStyle : StrokeStyle = StrokeStyle(lineWidth: 0), - - markerType : BarMarkerType = .full(), - - xAxisGridStyle : GridStyle = GridStyle(), - - xAxisLabelPosition : XAxisLabelPosistion = .bottom, - xAxisLabelFont : Font = .caption, - xAxisLabelColour : Color = Color.primary, - xAxisLabelsFrom : LabelsFrom = .dataPoint(rotation: .degrees(0)), - - xAxisTitle : String? = nil, - xAxisTitleFont : Font = .caption, - - yAxisGridStyle : GridStyle = GridStyle(), - - yAxisLabelPosition : YAxisLabelPosistion = .leading, - yAxisLabelFont : Font = .caption, - yAxisLabelColour : Color = Color.primary, - yAxisNumberOfLabels : Int = 10, - yAxisLabelType : YAxisLabelType = .numeric, - - yAxisTitle : String? = nil, - yAxisTitleFont : Font = .caption, - - baseline : Baseline = .minimumValue, - topLine : Topline = .maximumValue, - - globalAnimation : Animation = Animation.linear(duration: 1) + public init( + infoBoxPlacement: InfoBoxPlacement = .floating, + infoBoxContentAlignment: InfoBoxAlignment = .vertical, + + infoBoxValueFont: Font = .title3, + infoBoxValueColour: Color = Color.primary, + + infoBoxDescriptionFont: Font = .caption, + infoBoxDescriptionColour: Color = Color.primary, + + infoBoxBackgroundColour: Color = Color.systemsBackground, + infoBoxBorderColour: Color = Color.clear, + infoBoxBorderStyle: StrokeStyle = StrokeStyle(lineWidth: 0), + + markerType: BarMarkerType = .full(), + + xAxisGridStyle: GridStyle = GridStyle(), + + xAxisLabelPosition: XAxisLabelPosistion = .bottom, + xAxisLabelFont: Font = .caption, + xAxisLabelColour: Color = Color.primary, + xAxisLabelsFrom: LabelsFrom = .dataPoint(rotation: .degrees(0)), + + xAxisTitle: String? = nil, + xAxisTitleFont: Font = .caption, + + yAxisGridStyle: GridStyle = GridStyle(), + + yAxisLabelPosition: YAxisLabelPosistion = .leading, + yAxisLabelFont: Font = .caption, + yAxisLabelColour: Color = Color.primary, + yAxisNumberOfLabels: Int = 10, + yAxisLabelType: YAxisLabelType = .numeric, + + yAxisTitle: String? = nil, + yAxisTitleFont: Font = .caption, + + baseline: Baseline = .minimumValue, + topLine: Topline = .maximumValue, + + globalAnimation: Animation = Animation.linear(duration: 1) ) { - self.infoBoxPlacement = infoBoxPlacement - self.infoBoxContentAlignment = infoBoxContentAlignment + self.infoBoxPlacement = infoBoxPlacement + self.infoBoxContentAlignment = infoBoxContentAlignment - self.infoBoxValueFont = infoBoxValueFont - self.infoBoxValueColour = infoBoxValueColour + self.infoBoxValueFont = infoBoxValueFont + self.infoBoxValueColour = infoBoxValueColour - self.infoBoxDescriptionFont = infoBoxDescriptionFont + self.infoBoxDescriptionFont = infoBoxDescriptionFont self.infoBoxDescriptionColour = infoBoxDescriptionColour - self.infoBoxBackgroundColour = infoBoxBackgroundColour - self.infoBoxBorderColour = infoBoxBorderColour - self.infoBoxBorderStyle = infoBoxBorderStyle + self.infoBoxBackgroundColour = infoBoxBackgroundColour + self.infoBoxBorderColour = infoBoxBorderColour + self.infoBoxBorderStyle = infoBoxBorderStyle - self.markerType = markerType + self.markerType = markerType - self.xAxisGridStyle = xAxisGridStyle + self.xAxisGridStyle = xAxisGridStyle - self.xAxisLabelPosition = xAxisLabelPosition - self.xAxisLabelFont = xAxisLabelFont - self.xAxisLabelColour = xAxisLabelColour - self.xAxisLabelsFrom = xAxisLabelsFrom + self.xAxisLabelPosition = xAxisLabelPosition + self.xAxisLabelFont = xAxisLabelFont + self.xAxisLabelColour = xAxisLabelColour + self.xAxisLabelsFrom = xAxisLabelsFrom - self.xAxisTitle = xAxisTitle - self.xAxisTitleFont = xAxisTitleFont + self.xAxisTitle = xAxisTitle + self.xAxisTitleFont = xAxisTitleFont - self.yAxisGridStyle = yAxisGridStyle + self.yAxisGridStyle = yAxisGridStyle - self.yAxisLabelPosition = yAxisLabelPosition + self.yAxisLabelPosition = yAxisLabelPosition self.yAxisNumberOfLabels = yAxisNumberOfLabels - self.yAxisLabelFont = yAxisLabelFont - self.yAxisLabelColour = yAxisLabelColour - self.yAxisLabelType = yAxisLabelType + self.yAxisLabelFont = yAxisLabelFont + self.yAxisLabelColour = yAxisLabelColour + self.yAxisLabelType = yAxisLabelType - self.yAxisTitle = yAxisTitle - self.yAxisTitleFont = yAxisTitleFont + self.yAxisTitle = yAxisTitle + self.yAxisTitleFont = yAxisTitleFont - self.baseline = baseline - self.topLine = topLine + self.baseline = baseline + self.topLine = topLine - self.globalAnimation = globalAnimation + self.globalAnimation = globalAnimation } } diff --git a/Sources/SwiftUICharts/BarChart/Models/Style/BarStyle.swift b/Sources/SwiftUICharts/BarChart/Models/Style/BarStyle.swift index a0657d11..9a7ad25d 100644 --- a/Sources/SwiftUICharts/BarChart/Models/Style/BarStyle.swift +++ b/Sources/SwiftUICharts/BarChart/Models/Style/BarStyle.swift @@ -9,21 +9,13 @@ import SwiftUI /** Model for controlling the aesthetic of the bars. - - # Example - ``` - BarStyle(barWidth : 0.5, - cornerRadius: CornerRadius(top: 15), - colourFrom : .barStyle, - colour : ColourStyle(colour: .blue)) - ``` */ public struct BarStyle: CTBarStyle { - - public var barWidth : CGFloat + + public var barWidth: CGFloat public var cornerRadius: CornerRadius - public var colourFrom : ColourFrom - public var colour : ColourStyle + public var colourFrom: ColourFrom + public var colour: ColourStyle // MARK: - Single colour /// Bar Chart with single colour @@ -31,15 +23,16 @@ public struct BarStyle: CTBarStyle { /// - barWidth: How much of the available width to use. 0...1 /// - cornerRadius: Corner radius of the bar shape. /// - colourFrom: Where to get the colour data from. - /// - colour: Single Colour - public init(barWidth : CGFloat = 1, - cornerRadius: CornerRadius = CornerRadius(top: 5.0, bottom: 0.0), - colourFrom : ColourFrom = .barStyle, - colour : ColourStyle = ColourStyle(colour: .red) + /// - colour: Set up colour styling. + public init( + barWidth: CGFloat = 1, + cornerRadius: CornerRadius = CornerRadius(top: 5.0, bottom: 0.0), + colourFrom: ColourFrom = .barStyle, + colour: ColourStyle = ColourStyle(colour: .red) ) { - self.barWidth = barWidth - self.cornerRadius = cornerRadius - self.colourFrom = colourFrom - self.colour = colour + self.barWidth = barWidth + self.cornerRadius = cornerRadius + self.colourFrom = colourFrom + self.colour = colour } } diff --git a/Sources/SwiftUICharts/BarChart/Shapes/RoundedRectangleBarShape.swift b/Sources/SwiftUICharts/BarChart/Shapes/RoundedRectangleBarShape.swift index d1d5db95..4937f7b3 100644 --- a/Sources/SwiftUICharts/BarChart/Shapes/RoundedRectangleBarShape.swift +++ b/Sources/SwiftUICharts/BarChart/Shapes/RoundedRectangleBarShape.swift @@ -18,11 +18,12 @@ internal struct RoundedRectangleBarShape: Shape { private let tr: CGFloat private let bl: CGFloat private let br: CGFloat - - internal init(tl: CGFloat, - tr: CGFloat, - bl: CGFloat, - br: CGFloat + + internal init( + tl: CGFloat, + tr: CGFloat, + bl: CGFloat, + br: CGFloat ) { self.tl = tl self.tr = tr @@ -32,33 +33,33 @@ internal struct RoundedRectangleBarShape: Shape { internal func path(in rect: CGRect) -> Path { var path = Path() - + let w = rect.size.width let h = rect.size.height - + // Make sure we do not exceed the size of the rectangle let tr = min(min(self.tr, h/2), w/2) let tl = min(min(self.tl, h/2), w/2) let bl = min(min(self.bl, h/2), w/2) let br = min(min(self.br, h/2), w/2) - + path.move(to: CGPoint(x: tl, y: 0)) path.addLine(to: CGPoint(x: w - tr, y: 0)) path.addArc(center: CGPoint(x: w - tr, y: tr), radius: tr, startAngle: Angle(degrees: -90), endAngle: Angle(degrees: 0), clockwise: false) - + path.addLine(to: CGPoint(x: w, y: h - br)) path.addArc(center: CGPoint(x: w - br, y: h - br), radius: br, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 90), clockwise: false) - + path.addLine(to: CGPoint(x: bl, y: h)) path.addArc(center: CGPoint(x: bl, y: h - bl), radius: bl, startAngle: Angle(degrees: 90), endAngle: Angle(degrees: 180), clockwise: false) - + path.addLine(to: CGPoint(x: 0, y: tl)) path.addArc(center: CGPoint(x: tl, y: tl), radius: tl, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false) - + return path } } diff --git a/Sources/SwiftUICharts/BarChart/Views/BarChart.swift b/Sources/SwiftUICharts/BarChart/Views/BarChart.swift index 98a47aa5..ef2eed32 100644 --- a/Sources/SwiftUICharts/BarChart/Views/BarChart.swift +++ b/Sources/SwiftUICharts/BarChart/Views/BarChart.swift @@ -42,7 +42,7 @@ import SwiftUI */ public struct BarChart: View where ChartData: BarChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData /// Initialises a bar chart view. /// - Parameter chartData: Must be BarChartData model. @@ -53,18 +53,14 @@ public struct BarChart: View where ChartData: BarChartData { public var body: some View { if chartData.isGreaterThanTwo() { HStack(spacing: 0) { - - switch chartData.barStyle.colourFrom { - case .barStyle: - - BarChartBarStyleSubView(chartData: chartData) - .accessibilityLabel(Text("\(chartData.metadata.title)")) - - case .dataPoints: - - BarChartDataPointSubView(chartData: chartData) - .accessibilityLabel(Text("\(chartData.metadata.title)")) - } + switch chartData.barStyle.colourFrom { + case .barStyle: + BarChartBarStyleSubView(chartData: chartData) + .accessibilityLabel(Text("\(chartData.metadata.title)")) + case .dataPoints: + BarChartDataPointSubView(chartData: chartData) + .accessibilityLabel(Text("\(chartData.metadata.title)")) + } } } else { CustomNoDataView(chartData: chartData) } } diff --git a/Sources/SwiftUICharts/BarChart/Views/GroupedBarChart.swift b/Sources/SwiftUICharts/BarChart/Views/GroupedBarChart.swift index 79ef419d..126fc219 100644 --- a/Sources/SwiftUICharts/BarChart/Views/GroupedBarChart.swift +++ b/Sources/SwiftUICharts/BarChart/Views/GroupedBarChart.swift @@ -9,7 +9,7 @@ import SwiftUI /** View for creating a grouped bar chart. - + Uses `GroupedBarChartData` data model. # Declaration @@ -24,7 +24,7 @@ import SwiftUI ``` .touchOverlay(chartData: data) .averageLine(chartData: data, - strokeStyle: StrokeStyle(lineWidth: 3,dash: [5,10])) + strokeStyle: StrokeStyle(lineWidth: 3,dash: [5,10])) .yAxisPOI(chartData: data, markerName: "50", markerValue: 50, @@ -42,21 +42,23 @@ import SwiftUI */ public struct GroupedBarChart: View where ChartData: GroupedBarChartData { - @ObservedObject var chartData: ChartData - - private let groupSpacing : CGFloat + @ObservedObject private var chartData: ChartData + private let groupSpacing: CGFloat /// Initialises a grouped bar chart view. /// - Parameters: /// - chartData: Must be GroupedBarChartData model. /// - groupSpacing: Spacing between groups of bars. - public init(chartData: ChartData, groupSpacing: CGFloat) { - self.chartData = chartData + public init( + chartData: ChartData, + groupSpacing: CGFloat + ) { + self.chartData = chartData self.groupSpacing = groupSpacing self.chartData.groupSpacing = groupSpacing } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false public var body: some View { if chartData.isGreaterThanTwo() { @@ -64,45 +66,36 @@ public struct GroupedBarChart: View where ChartData: GroupedBarChartD ForEach(chartData.dataSets.dataSets) { dataSet in HStack(spacing: 0) { ForEach(dataSet.dataPoints) { dataPoint in - if dataPoint.group.colour.colourType == .colour, let colour = dataPoint.group.colour.colour { - - ColourBar(chartData : chartData, - dataPoint : dataPoint, - colour : colour) + ColourBar(chartData: chartData, + dataPoint: dataPoint, + colour: colour) .accessibilityLabel(Text("\(chartData.metadata.title)")) - } else if dataPoint.group.colour.colourType == .gradientColour, - let colours = dataPoint.group.colour.colours, + let colours = dataPoint.group.colour.colours, let startPoint = dataPoint.group.colour.startPoint, - let endPoint = dataPoint.group.colour.endPoint + let endPoint = dataPoint.group.colour.endPoint { - - GradientColoursBar(chartData : chartData, - dataPoint : dataPoint, - colours : colours, - startPoint : startPoint, - endPoint : endPoint) - .accessibilityLabel( Text("\(chartData.metadata.title)")) - + GradientColoursBar(chartData: chartData, + dataPoint: dataPoint, + colours: colours, + startPoint: startPoint, + endPoint: endPoint) + .accessibilityLabel(Text("\(chartData.metadata.title)")) } else if dataPoint.group.colour.colourType == .gradientStops, - let stops = dataPoint.group.colour.stops, + let stops = dataPoint.group.colour.stops, let startPoint = dataPoint.group.colour.startPoint, - let endPoint = dataPoint.group.colour.endPoint + let endPoint = dataPoint.group.colour.endPoint { - let safeStops = GradientStop.convertToGradientStopsArray(stops: stops) - - GradientStopsBar(chartData : chartData, - dataPoint : dataPoint, - stops : safeStops, - startPoint : startPoint, - endPoint : endPoint) - - .accessibilityLabel( Text("\(chartData.metadata.title)")) - + GradientStopsBar(chartData: chartData, + dataPoint: dataPoint, + stops: safeStops, + startPoint: startPoint, + endPoint: endPoint) + .accessibilityLabel(Text("\(chartData.metadata.title)")) } } } diff --git a/Sources/SwiftUICharts/BarChart/Views/RangedBarChart.swift b/Sources/SwiftUICharts/BarChart/Views/RangedBarChart.swift index 7ebd4a09..1b723309 100644 --- a/Sources/SwiftUICharts/BarChart/Views/RangedBarChart.swift +++ b/Sources/SwiftUICharts/BarChart/Views/RangedBarChart.swift @@ -9,7 +9,7 @@ import SwiftUI /** View for creating a grouped bar chart. - + Uses `RangedBarChartData` data model. # Declaration @@ -42,7 +42,7 @@ import SwiftUI */ public struct RangedBarChart: View where ChartData: RangedBarChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData /// Initialises a bar chart view. /// - Parameter chartData: Must be RangedBarChartData model. @@ -53,16 +53,13 @@ public struct RangedBarChart: View where ChartData: RangedBarChartDat public var body: some View { if chartData.isGreaterThanTwo() { HStack(spacing: 0) { - switch chartData.barStyle.colourFrom { case .barStyle: - RangedBarChartBarStyleSubView(chartData: chartData) - .accessibilityLabel( Text("\(chartData.metadata.title)")) + .accessibilityLabel(Text("\(chartData.metadata.title)")) case .dataPoints: - RangedBarChartDataPointSubView(chartData: chartData) - .accessibilityLabel( Text("\(chartData.metadata.title)")) + .accessibilityLabel(Text("\(chartData.metadata.title)")) } } } else { CustomNoDataView(chartData: chartData) } diff --git a/Sources/SwiftUICharts/BarChart/Views/StackedBarChart.swift b/Sources/SwiftUICharts/BarChart/Views/StackedBarChart.swift index b192948b..f7ba1642 100644 --- a/Sources/SwiftUICharts/BarChart/Views/StackedBarChart.swift +++ b/Sources/SwiftUICharts/BarChart/Views/StackedBarChart.swift @@ -9,7 +9,7 @@ import SwiftUI /** View for creating a stacked bar chart. - + Uses `StackedBarChartData` data model. # Declaration @@ -42,8 +42,8 @@ import SwiftUI */ public struct StackedBarChart: View where ChartData: StackedBarChartData { - @ObservedObject var chartData: ChartData - + @ObservedObject private var chartData: ChartData + /// Initialises a stacked bar chart view. /// - Parameters: /// - chartData: Must be StackedBarChartData model. @@ -51,15 +51,12 @@ public struct StackedBarChart: View where ChartData: StackedBarChartD self.chartData = chartData } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false public var body: some View { - if chartData.isGreaterThanTwo() { - HStack(alignment: .bottom, spacing: 0) { ForEach(chartData.dataSets.dataSets) { dataSet in - StackElementSubView(dataSet: dataSet, specifier: chartData.infoView.touchSpecifier) .scaleEffect(y: startAnimation ? CGFloat(dataSet.maxValue() / chartData.maxValue) : 0, anchor: .bottom) .scaleEffect(x: chartData.barStyle.barWidth, anchor: .center) diff --git a/Sources/SwiftUICharts/BarChart/Views/SubViews/BarChartSubViews.swift b/Sources/SwiftUICharts/BarChart/Views/SubViews/BarChartSubViews.swift index 808c03d4..d02b2eb8 100644 --- a/Sources/SwiftUICharts/BarChart/Views/SubViews/BarChartSubViews.swift +++ b/Sources/SwiftUICharts/BarChart/Views/SubViews/BarChartSubViews.swift @@ -13,7 +13,7 @@ import SwiftUI Bar segment where the colour information comes from chart style. */ internal struct BarChartBarStyleSubView: View { - + private let chartData: CD internal init(chartData: CD) { @@ -24,43 +24,36 @@ internal struct BarChartBarStyleSubView: View { if chartData.barStyle.colour.colourType == .colour, let colour = chartData.barStyle.colour.colour { - ForEach(chartData.dataSets.dataPoints) { dataPoint in - ColourBar(chartData : chartData, - dataPoint : dataPoint, - colour : colour) + ColourBar(chartData: chartData, + dataPoint: dataPoint, + colour: colour) } - } else if chartData.barStyle.colour.colourType == .gradientColour, - let colours = chartData.barStyle.colour.colours, + let colours = chartData.barStyle.colour.colours, let startPoint = chartData.barStyle.colour.startPoint, - let endPoint = chartData.barStyle.colour.endPoint + let endPoint = chartData.barStyle.colour.endPoint { - ForEach(chartData.dataSets.dataPoints) { dataPoint in - GradientColoursBar(chartData : chartData, - dataPoint : dataPoint, - colours : colours, - startPoint : startPoint, - endPoint : endPoint) + GradientColoursBar(chartData: chartData, + dataPoint: dataPoint, + colours: colours, + startPoint: startPoint, + endPoint: endPoint) } - } else if chartData.barStyle.colour.colourType == .gradientStops, - let stops = chartData.barStyle.colour.stops, + let stops = chartData.barStyle.colour.stops, let startPoint = chartData.barStyle.colour.startPoint, - let endPoint = chartData.barStyle.colour.endPoint + let endPoint = chartData.barStyle.colour.endPoint { - let safeStops = GradientStop.convertToGradientStopsArray(stops: stops) - ForEach(chartData.dataSets.dataPoints) { dataPoint in - GradientStopsBar(chartData : chartData, - dataPoint : dataPoint, - stops : safeStops, - startPoint : startPoint, - endPoint : endPoint) + GradientStopsBar(chartData: chartData, + dataPoint: dataPoint, + stops: safeStops, + startPoint: startPoint, + endPoint: endPoint) } - } } } @@ -78,47 +71,38 @@ internal struct BarChartDataPointSubView: View { } internal var body: some View { - ForEach(chartData.dataSets.dataPoints) { dataPoint in - if dataPoint.colour.colourType == .colour, let colour = dataPoint.colour.colour { - - ColourBar(chartData : chartData, - dataPoint : dataPoint, - colour : colour) - + ColourBar(chartData: chartData, + dataPoint: dataPoint, + colour: colour) } else if dataPoint.colour.colourType == .gradientColour, - let colours = dataPoint.colour.colours, + let colours = dataPoint.colour.colours, let startPoint = dataPoint.colour.startPoint, - let endPoint = dataPoint.colour.endPoint + let endPoint = dataPoint.colour.endPoint { - - GradientColoursBar(chartData : chartData, - dataPoint : dataPoint, - colours : colours, - startPoint : startPoint, - endPoint : endPoint) - + GradientColoursBar(chartData: chartData, + dataPoint: dataPoint, + colours: colours, + startPoint: startPoint, + endPoint: endPoint) } else if dataPoint.colour.colourType == .gradientStops, - let stops = dataPoint.colour.stops, + let stops = dataPoint.colour.stops, let startPoint = dataPoint.colour.startPoint, - let endPoint = dataPoint.colour.endPoint + let endPoint = dataPoint.colour.endPoint { - let safeStops = GradientStop.convertToGradientStopsArray(stops: stops) - - GradientStopsBar(chartData : chartData, - dataPoint : dataPoint, - stops : safeStops, - startPoint : startPoint, - endPoint : endPoint) - + GradientStopsBar(chartData: chartData, + dataPoint: dataPoint, + stops: safeStops, + startPoint: startPoint, + endPoint: endPoint) } else { - ColourBar(chartData : chartData, - dataPoint : dataPoint, - colour : .blue) + ColourBar(chartData: chartData, + dataPoint: dataPoint, + colour: .blue) } } } @@ -129,53 +113,53 @@ internal struct BarChartDataPointSubView: View { internal struct RangedBarChartBarStyleSubView: View { - private let chartData : CD + private let chartData: CD internal init(chartData: CD) { self.chartData = chartData } var body: some View { - if chartData.barStyle.colour.colourType == .colour, - let colour = chartData.barStyle.colour.colour { + let colour = chartData.barStyle.colour.colour + { ForEach(chartData.dataSets.dataPoints) { dataPoint in GeometryReader { geo in - RangedBarChartColourCell(chartData : chartData, - dataPoint : dataPoint, - colour : colour, - barSize : geo.frame(in: .local)) + RangedBarChartColourCell(chartData: chartData, + dataPoint: dataPoint, + colour: colour, + barSize: geo.frame(in: .local)) } } } else if chartData.barStyle.colour.colourType == .gradientColour, - let colours = chartData.barStyle.colour.colours, + let colours = chartData.barStyle.colour.colours, let startPoint = chartData.barStyle.colour.startPoint, - let endPoint = chartData.barStyle.colour.endPoint { + let endPoint = chartData.barStyle.colour.endPoint + { ForEach(chartData.dataSets.dataPoints) { dataPoint in GeometryReader { geo in - RangedBarChartColoursCell(chartData : chartData, - dataPoint : dataPoint, - colours : colours, + RangedBarChartColoursCell(chartData: chartData, + dataPoint: dataPoint, + colours: colours, startPoint: startPoint, - endPoint : endPoint, - barSize : geo.frame(in: .local)) + endPoint: endPoint, + barSize: geo.frame(in: .local)) } } } else if chartData.barStyle.colour.colourType == .gradientStops, - let stops = chartData.barStyle.colour.stops, + let stops = chartData.barStyle.colour.stops, let startPoint = chartData.barStyle.colour.startPoint, - let endPoint = chartData.barStyle.colour.endPoint { - + let endPoint = chartData.barStyle.colour.endPoint + { let safeStops = GradientStop.convertToGradientStopsArray(stops: stops) - ForEach(chartData.dataSets.dataPoints) { dataPoint in GeometryReader { geo in - RangedBarChartStopsCell(chartData : chartData, - dataPoint : dataPoint, - stops : safeStops, + RangedBarChartStopsCell(chartData: chartData, + dataPoint: dataPoint, + stops: safeStops, startPoint: startPoint, - endPoint : endPoint, - barSize : geo.frame(in: .local)) + endPoint: endPoint, + barSize: geo.frame(in: .local)) } } } @@ -184,48 +168,46 @@ internal struct RangedBarChartBarStyleSubView: View { // MARK: DataPoints internal struct RangedBarChartDataPointSubView: View { - - private let chartData : CD + + private let chartData: CD internal init(chartData: CD) { self.chartData = chartData } internal var body: some View { - ForEach(chartData.dataSets.dataPoints) { dataPoint in GeometryReader { geo in if dataPoint.colour.colourType == .colour, - let colour = dataPoint.colour.colour { - - RangedBarChartColourCell(chartData : chartData, - dataPoint : dataPoint, - colour : colour, - barSize : geo.frame(in: .local)) - + let colour = dataPoint.colour.colour + { + RangedBarChartColourCell(chartData: chartData, + dataPoint: dataPoint, + colour: colour, + barSize: geo.frame(in: .local)) } else if dataPoint.colour.colourType == .gradientColour, - let colours = dataPoint.colour.colours, + let colours = dataPoint.colour.colours, let startPoint = dataPoint.colour.startPoint, - let endPoint = dataPoint.colour.endPoint { - - RangedBarChartColoursCell(chartData : chartData, - dataPoint : dataPoint, - colours : colours, + let endPoint = dataPoint.colour.endPoint + { + RangedBarChartColoursCell(chartData: chartData, + dataPoint: dataPoint, + colours: colours, startPoint: startPoint, - endPoint : endPoint, - barSize : geo.frame(in: .local)) + endPoint: endPoint, + barSize: geo.frame(in: .local)) } else if dataPoint.colour.colourType == .gradientStops, - let stops = dataPoint.colour.stops, + let stops = dataPoint.colour.stops, let startPoint = dataPoint.colour.startPoint, - let endPoint = dataPoint.colour.endPoint { + let endPoint = dataPoint.colour.endPoint + { let safeStops = GradientStop.convertToGradientStopsArray(stops: stops) - - RangedBarChartStopsCell(chartData : chartData, - dataPoint : dataPoint, - stops : safeStops, + RangedBarChartStopsCell(chartData: chartData, + dataPoint: dataPoint, + stops: safeStops, startPoint: startPoint, - endPoint : endPoint, - barSize : geo.frame(in: .local)) + endPoint: endPoint, + barSize: geo.frame(in: .local)) } } } diff --git a/Sources/SwiftUICharts/BarChart/Views/SubViews/Bars.swift b/Sources/SwiftUICharts/BarChart/Views/SubViews/Bars.swift index 81ea1d9d..562184c6 100644 --- a/Sources/SwiftUICharts/BarChart/Views/SubViews/Bars.swift +++ b/Sources/SwiftUICharts/BarChart/Views/SubViews/Bars.swift @@ -16,20 +16,21 @@ import SwiftUI internal struct ColourBar: View { - private let chartData : CD - private let colour : Color - private let dataPoint : DP - - internal init(chartData : CD, - dataPoint : DP, - colour : Color + private let chartData: CD + private let colour: Color + private let dataPoint: DP + + internal init( + chartData: CD, + dataPoint: DP, + colour: Color ) { self.chartData = chartData self.dataPoint = dataPoint - self.colour = colour + self.colour = colour } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, @@ -60,26 +61,27 @@ internal struct ColourBar: View { - private let chartData : CD - private let dataPoint : DP - private let colours : [Color] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - - internal init(chartData : CD, - dataPoint : DP, - colours : [Color], - startPoint : UnitPoint, - endPoint : UnitPoint + private let chartData: CD + private let dataPoint: DP + private let colours: [Color] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + + internal init( + chartData: CD, + dataPoint: DP, + colours: [Color], + startPoint: UnitPoint, + endPoint: UnitPoint ) { - self.chartData = chartData - self.dataPoint = dataPoint - self.colours = colours + self.chartData = chartData + self.dataPoint = dataPoint + self.colours = colours self.startPoint = startPoint - self.endPoint = endPoint + self.endPoint = endPoint } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, @@ -110,26 +112,27 @@ internal struct GradientColoursBar: View { - private let chartData : CD - private let dataPoint : DP - private let stops : [Gradient.Stop] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - - internal init(chartData : CD, - dataPoint : DP, - stops : [Gradient.Stop], - startPoint: UnitPoint, - endPoint : UnitPoint + private let chartData: CD + private let dataPoint: DP + private let stops: [Gradient.Stop] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + + internal init( + chartData: CD, + dataPoint: DP, + stops: [Gradient.Stop], + startPoint: UnitPoint, + endPoint: UnitPoint ) { - self.chartData = chartData - self.dataPoint = dataPoint - self.stops = stops + self.chartData = chartData + self.dataPoint = dataPoint + self.stops = stops self.startPoint = startPoint - self.endPoint = endPoint + self.endPoint = endPoint } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, @@ -158,10 +161,13 @@ internal struct GradientStopsBar CGFloat { let value = dataPoint.value - let sum = dataSet.dataPoints.reduce(0) { $0 + $1.value } + let sum = dataSet.dataPoints + .map(\.value) + .reduce(0, +) return height * CGFloat(value / sum) } } - /** Sub view of an element of a bar using a single colour. @@ -235,16 +235,17 @@ internal struct StackElementSubView: View { */ internal struct ColourPartBar: View { - private let colour : Color - private let height : CGFloat + private let colour: Color + private let height: CGFloat - internal init(_ colour : Color, - _ height : CGFloat + internal init( + _ colour: Color, + _ height: CGFloat ) { - self.colour = colour - self.height = height + self.colour = colour + self.height = height } - + internal var body: some View { Rectangle() .fill(colour) @@ -259,27 +260,28 @@ internal struct ColourPartBar: View { */ internal struct GradientColoursPartBar: View { - private let colours : [Color] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - private let height : CGFloat + private let colours: [Color] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let height: CGFloat - internal init(_ colours : [Color], - _ startPoint : UnitPoint, - _ endPoint : UnitPoint, - _ height : CGFloat + internal init( + _ colours: [Color], + _ startPoint: UnitPoint, + _ endPoint: UnitPoint, + _ height: CGFloat ) { - self.colours = colours + self.colours = colours self.startPoint = startPoint - self.endPoint = endPoint - self.height = height + self.endPoint = endPoint + self.height = height } - + internal var body: some View { Rectangle() - .fill(LinearGradient(gradient : Gradient(colors: colours), - startPoint : startPoint, - endPoint : endPoint)) + .fill(LinearGradient(gradient: Gradient(colors: colours), + startPoint: startPoint, + endPoint: endPoint)) .frame(height: height) } } @@ -291,51 +293,53 @@ internal struct GradientColoursPartBar: View { */ internal struct GradientStopsPartBar: View { - private let stops : [Gradient.Stop] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - private let height : CGFloat + private let stops: [Gradient.Stop] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let height: CGFloat - internal init(_ stops : [Gradient.Stop], - _ startPoint : UnitPoint, - _ endPoint : UnitPoint, - _ height : CGFloat + internal init( + _ stops: [Gradient.Stop], + _ startPoint: UnitPoint, + _ endPoint: UnitPoint, + _ height: CGFloat ) { - self.stops = stops + self.stops = stops self.startPoint = startPoint - self.endPoint = endPoint - self.height = height + self.endPoint = endPoint + self.height = height } - + internal var body: some View { Rectangle() - .fill(LinearGradient(gradient : Gradient(stops: stops), - startPoint : startPoint, - endPoint : endPoint)) + .fill(LinearGradient(gradient: Gradient(stops: stops), + startPoint: startPoint, + endPoint: endPoint)) .frame(height: height) } } // MARK: - Ranged internal struct RangedBarChartColourCell: View { - + private let chartData: CD private let dataPoint: CD.Set.DataPoint - private let colour : Color - private let barSize : CGRect + private let colour: Color + private let barSize: CGRect - internal init(chartData : CD, - dataPoint : CD.Set.DataPoint, - colour : Color, - barSize : CGRect + internal init( + chartData: CD, + dataPoint: CD.Set.DataPoint, + colour: Color, + barSize: CGRect ) { self.chartData = chartData self.dataPoint = dataPoint - self.colour = colour - self.barSize = barSize + self.colour = colour + self.barSize = barSize } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, @@ -343,12 +347,10 @@ internal struct RangedBarChartColourCell: View { bl: chartData.barStyle.cornerRadius.bottom, br: chartData.barStyle.cornerRadius.bottom) .fill(colour) - .scaleEffect(y: startAnimation ? CGFloat((dataPoint.upperValue - dataPoint.lowerValue) / chartData.range) : 0, anchor: .center) .scaleEffect(x: chartData.barStyle.barWidth, anchor: .center) .position(x: barSize.midX, y: chartData.getBarPositionX(dataPoint: dataPoint, height: barSize.height)) - .background(Color(.gray).opacity(0.000000001)) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = true @@ -362,45 +364,44 @@ internal struct RangedBarChartColourCell: View { internal struct RangedBarChartColoursCell: View { - - private let chartData : CD - private let dataPoint : CD.Set.DataPoint - private let colours : [Color] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - private let barSize : CGRect - - internal init(chartData : CD, - dataPoint : CD.Set.DataPoint, - colours : [Color], - startPoint: UnitPoint, - endPoint : UnitPoint, - barSize : CGRect + + private let chartData: CD + private let dataPoint: CD.Set.DataPoint + private let colours: [Color] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let barSize: CGRect + + internal init( + chartData: CD, + dataPoint: CD.Set.DataPoint, + colours: [Color], + startPoint: UnitPoint, + endPoint: UnitPoint, + barSize: CGRect ) { - self.chartData = chartData - self.dataPoint = dataPoint - self.colours = colours + self.chartData = chartData + self.dataPoint = dataPoint + self.colours = colours self.startPoint = startPoint - self.endPoint = endPoint - self.barSize = barSize + self.endPoint = endPoint + self.barSize = barSize } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, tr: chartData.barStyle.cornerRadius.top, bl: chartData.barStyle.cornerRadius.bottom, br: chartData.barStyle.cornerRadius.bottom) - .fill(LinearGradient(gradient : Gradient(colors: colours), - startPoint : startPoint, - endPoint : endPoint)) - + .fill(LinearGradient(gradient: Gradient(colors: colours), + startPoint: startPoint, + endPoint: endPoint)) .scaleEffect(y: startAnimation ? CGFloat((dataPoint.upperValue - dataPoint.lowerValue) / chartData.range) : 0, anchor: .center) .scaleEffect(x: chartData.barStyle.barWidth, anchor: .center) .position(x: barSize.midX, y: chartData.getBarPositionX(dataPoint: dataPoint, height: barSize.height)) - .background(Color(.gray).opacity(0.000000001)) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = true @@ -413,45 +414,44 @@ internal struct RangedBarChartColoursCell: View { } internal struct RangedBarChartStopsCell: View { - - private let chartData : CD - private let dataPoint : CD.Set.DataPoint - private let stops : [Gradient.Stop] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - private let barSize : CGRect - - internal init(chartData : CD, - dataPoint : CD.Set.DataPoint, - stops : [Gradient.Stop], - startPoint: UnitPoint, - endPoint : UnitPoint, - barSize : CGRect + + private let chartData: CD + private let dataPoint: CD.Set.DataPoint + private let stops: [Gradient.Stop] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let barSize: CGRect + + internal init( + chartData: CD, + dataPoint: CD.Set.DataPoint, + stops: [Gradient.Stop], + startPoint: UnitPoint, + endPoint: UnitPoint, + barSize: CGRect ) { - self.chartData = chartData - self.dataPoint = dataPoint - self.stops = stops + self.chartData = chartData + self.dataPoint = dataPoint + self.stops = stops self.startPoint = startPoint - self.endPoint = endPoint - self.barSize = barSize + self.endPoint = endPoint + self.barSize = barSize } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { RoundedRectangleBarShape(tl: chartData.barStyle.cornerRadius.top, tr: chartData.barStyle.cornerRadius.top, bl: chartData.barStyle.cornerRadius.bottom, br: chartData.barStyle.cornerRadius.bottom) - .fill(LinearGradient(gradient : Gradient(stops: stops), - startPoint : startPoint, - endPoint : endPoint)) - + .fill(LinearGradient(gradient: Gradient(stops: stops), + startPoint: startPoint, + endPoint: endPoint)) .scaleEffect(y: startAnimation ? CGFloat((dataPoint.upperValue - dataPoint.lowerValue) / chartData.range) : 0, anchor: .center) .scaleEffect(x: chartData.barStyle.barWidth, anchor: .center) .position(x: barSize.midX, y: chartData.getBarPositionX(dataPoint: dataPoint, height: barSize.height)) - .background(Color(.gray).opacity(0.000000001)) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = true diff --git a/Sources/SwiftUICharts/LineChart/Extras/PathExtensions.swift b/Sources/SwiftUICharts/LineChart/Extras/PathExtensions.swift index b3a07fb4..4591d4c1 100644 --- a/Sources/SwiftUICharts/LineChart/Extras/PathExtensions.swift +++ b/Sources/SwiftUICharts/LineChart/Extras/PathExtensions.swift @@ -18,8 +18,8 @@ extension Path { range: Double, isFilled: Bool ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() if dataPoints.count >= 2 { @@ -31,11 +31,10 @@ extension Path { for index in 1 ..< dataPoints.count { let nextPoint = CGPoint(x: CGFloat(index) * x, y: (CGFloat(dataPoints[index].value - minValue) * -y) + rect.height) - path.addLine(to: nextPoint) } if isFilled { - path.addLine(to: CGPoint(x: CGFloat(dataPoints.count-1) * x, y: rect.height)) + path.addLine(to: CGPoint(x: CGFloat(dataPoints.count-1) * x, y: rect.height)) path.addLine(to: CGPoint(x: 0, y: rect.height)) path.closeSubpath() } @@ -51,12 +50,12 @@ extension Path { range: Double, isFilled: Bool ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() - let firstPoint = CGPoint(x: 0, - y: (CGFloat(dataPoints[0].value - minValue) * -y) + rect.height) + let firstPoint: CGPoint = CGPoint(x: 0, + y: (CGFloat(dataPoints[0].value - minValue) * -y) + rect.height) path.move(to: firstPoint) var previousPoint = firstPoint @@ -65,7 +64,6 @@ extension Path { for index in 1 ..< dataPoints.count { let nextPoint = CGPoint(x: CGFloat(index) * x, y: (CGFloat(dataPoints[index].value - minValue) * -y) + rect.height) - path.addCurve(to: nextPoint, control1: CGPoint(x: previousPoint.x + (nextPoint.x - previousPoint.x) / 2, y: previousPoint.y), @@ -73,7 +71,6 @@ extension Path { y: nextPoint.y)) lastIndex = index previousPoint = nextPoint - } if isFilled { path.addLine(to: CGPoint(x: CGFloat(lastIndex) * x, @@ -92,8 +89,8 @@ extension Path { minValue: Double, range: Double ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() @@ -129,8 +126,8 @@ extension Path { minValue: Double, range: Double ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() @@ -182,8 +179,8 @@ extension Path { range: Double, isFilled: Bool ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() if dataPoints.count >= 2 { @@ -204,10 +201,9 @@ extension Path { if dataPoints[index].value != 0 { path.addLine(to: nextPoint) } - } if isFilled { - path.addLine(to: CGPoint(x: CGFloat(dataPoints.count-1) * x, y: rect.height)) + path.addLine(to: CGPoint(x: CGFloat(dataPoints.count-1) * x, y: rect.height)) path.addLine(to: CGPoint(x: 0, y: rect.height)) path.closeSubpath() } @@ -223,8 +219,8 @@ extension Path { range: Double, isFilled: Bool ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() var firstPoint: CGPoint = .zero @@ -239,7 +235,7 @@ extension Path { } var previousPoint = firstPoint - var lastIndex : Int = 0 + var lastIndex: Int = 0 for index in 1 ..< dataPoints.count { @@ -274,8 +270,8 @@ extension Path { minValue: Double, range: Double ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() @@ -315,8 +311,8 @@ extension Path { minValue: Double, range: Double ) -> Path { - let x : CGFloat = rect.width / CGFloat(dataPoints.count - 1) - let y : CGFloat = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataPoints.count - 1) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() diff --git a/Sources/SwiftUICharts/LineChart/Models/ChartData/LineChartData.swift b/Sources/SwiftUICharts/LineChart/Models/ChartData/LineChartData.swift index 01a678cb..d11595fb 100644 --- a/Sources/SwiftUICharts/LineChart/Models/ChartData/LineChartData.swift +++ b/Sources/SwiftUICharts/LineChart/Models/ChartData/LineChartData.swift @@ -15,21 +15,21 @@ import SwiftUI public final class LineChartData: CTLineChartDataProtocol { // MARK: Properties - public final let id : UUID = UUID() + public final let id: UUID = UUID() - @Published public final var dataSets : LineDataSet - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var chartStyle : LineChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() - - public final var noDataText : Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) + @Published public final var dataSets: LineDataSet + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var chartStyle: LineChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + + public final var noDataText: Text + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) - internal final var isFilled : Bool = false + internal final var isFilled: Bool = false // MARK: Initializer /// Initialises a Single Line Chart. @@ -41,25 +41,25 @@ public final class LineChartData: CTLineChartDataProtocol { /// - yAxisLabels: Labels for the Y axis instead of the labels generated from data point values. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : LineDataSet, - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - chartStyle : LineChartStyle = LineChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: LineDataSet, + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + chartStyle: LineChartStyle = LineChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.chartStyle = chartStyle - self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (chartType: .line, dataSetType: .single) + self.dataSets = dataSets + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.chartStyle = chartStyle + self.noDataText = noDataText + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (chartType: .line, dataSetType: .single) self.setupLegends() } - // , calc : @escaping (LineDataSet) -> LineDataSet // MARK: Labels public final func getXAxisLabels() -> some View { @@ -99,16 +99,16 @@ public final class LineChartData: CTLineChartDataProtocol { } } } - + // MARK: Points public final func getPointMarker() -> some View { - PointsSubView(dataSets : dataSets, - minValue : self.minValue, - range : self.range, - animation : self.chartStyle.globalAnimation, - isFilled : self.isFilled) + PointsSubView(dataSets: dataSets, + minValue: self.minValue, + range: self.range, + animation: self.chartStyle.globalAnimation, + isFilled: self.isFilled) } - + public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView(dataSet: dataSets, dataPoints: dataSets.dataPoints, @@ -116,8 +116,8 @@ public final class LineChartData: CTLineChartDataProtocol { touchLocation: touchLocation, chartSize: chartSize) } - - public typealias Set = LineDataSet + + public typealias Set = LineDataSet public typealias DataPoint = LineChartDataPoint } @@ -126,13 +126,13 @@ extension LineChartData { public final func getPointLocation(dataSet: LineDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - let minValue : Double = self.minValue - let range : Double = self.range + let minValue: Double = self.minValue + let range: Double = self.range - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) - let ySection : CGFloat = chartSize.height / CGFloat(range) + let xSection: CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) + let ySection: CGFloat = chartSize.height / CGFloat(range) - let index : Int = Int((touchLocation.x + (xSection / 2)) / xSection) + let index: Int = Int((touchLocation.x + (xSection / 2)) / xSection) if index >= 0 && index < dataSet.dataPoints.count { if !dataSet.style.ignoreZero { @@ -147,30 +147,24 @@ extension LineChartData { } return nil } - + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - - var points : [LineChartDataPoint] = [] - let xSection : CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count - 1) - let index = Int((touchLocation.x + (xSection / 2)) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count - 1) + let index: Int = Int((touchLocation.x + (xSection / 2)) / xSection) if index >= 0 && index < dataSets.dataPoints.count { if !dataSets.style.ignoreZero { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } else { if dataSets.dataPoints[index].value != 0 { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } else { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - dataPoint.value = -Double.greatestFiniteMagnitude - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + dataSets.dataPoints[index].value = -Double.greatestFiniteMagnitude + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } } } - self.infoView.touchOverlayInfo = points } } diff --git a/Sources/SwiftUICharts/LineChart/Models/ChartData/MultiLineChartData.swift b/Sources/SwiftUICharts/LineChart/Models/ChartData/MultiLineChartData.swift index d08cb9ca..07a38087 100644 --- a/Sources/SwiftUICharts/LineChart/Models/ChartData/MultiLineChartData.swift +++ b/Sources/SwiftUICharts/LineChart/Models/ChartData/MultiLineChartData.swift @@ -13,22 +13,22 @@ import SwiftUI This model contains all the data and styling information for a single line, line chart. */ public final class MultiLineChartData: CTLineChartDataProtocol { - + // MARK: Properties - public let id : UUID = UUID() + public let id: UUID = UUID() - @Published public final var dataSets : MultiLineDataSet - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var chartStyle : LineChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() + @Published public final var dataSets: MultiLineDataSet + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var chartStyle: LineChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + + public final var noDataText: Text + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) - public final var noDataText : Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) - // MARK: Initializers /// Initialises a Multi Line Chart. /// @@ -39,25 +39,26 @@ public final class MultiLineChartData: CTLineChartDataProtocol { /// - yAxisLabels: Labels for the Y axis instead of the labels generated from data point values. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : MultiLineDataSet, - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - chartStyle : LineChartStyle = LineChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: MultiLineDataSet, + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + chartStyle: LineChartStyle = LineChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.chartStyle = chartStyle - self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (.line, .multi) + self.dataSets = dataSets + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.chartStyle = chartStyle + self.noDataText = noDataText + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (.line, .multi) self.setupLegends() } - + // MARK: Labels public final func getXAxisLabels() -> some View { Group { @@ -100,16 +101,16 @@ public final class MultiLineChartData: CTLineChartDataProtocol { // MARK: Points public final func getPointMarker() -> some View { ForEach(self.dataSets.dataSets, id: \.id) { dataSet in - PointsSubView(dataSets : dataSet, - minValue : self.minValue, - range : self.range, - animation : self.chartStyle.globalAnimation, - isFilled : false) + PointsSubView(dataSets: dataSet, + minValue: self.minValue, + range: self.range, + animation: self.chartStyle.globalAnimation, + isFilled: false) } } - + public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { - ZStack { + ZStack { ForEach(self.dataSets.dataSets, id: \.id) { dataSet in self.markerSubView(dataSet: dataSet, dataPoints: dataSet.dataPoints, @@ -122,10 +123,10 @@ public final class MultiLineChartData: CTLineChartDataProtocol { // MARK: Accessibility public func getAccessibility() -> some View { - ForEach(self.dataSets.dataSets, id: \.self) { dataSet in + ForEach(self.dataSets.dataSets, id: \.self) { dataSet in ForEach(dataSet.dataPoints.indices, id: \.self) { point in - AccessibilityRectangle(dataPointCount : dataSet.dataPoints.count, - dataPointNo : point) + AccessibilityRectangle(dataPointCount: dataSet.dataPoints.count, + dataPointNo: point) .foregroundColor(Color(.gray).opacity(0.000000001)) .accessibilityLabel(Text("\(self.metadata.title)")) .accessibilityValue(dataSet.dataPoints[point].getCellAccessibilityValue(specifier: self.infoView.touchSpecifier)) @@ -133,25 +134,22 @@ public final class MultiLineChartData: CTLineChartDataProtocol { } } - public typealias Set = MultiLineDataSet - public typealias DataPoint = LineChartDataPoint - public typealias CTStyle = LineChartStyle + public typealias Set = MultiLineDataSet + public typealias DataPoint = LineChartDataPoint + public typealias CTStyle = LineChartStyle } // MARK: - Touch extension MultiLineChartData { public func getPointLocation(dataSet: LineDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { + let minValue: Double = self.minValue + let range: Double = self.range + let xSection: CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) + let ySection: CGFloat = chartSize.height / CGFloat(range) + let index: Int = Int((touchLocation.x + (xSection / 2)) / xSection) - let minValue : Double = self.minValue - let range : Double = self.range - - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) - let ySection : CGFloat = chartSize.height / CGFloat(range) - - let index : Int = Int((touchLocation.x + (xSection / 2)) / xSection) if index >= 0 && index < dataSet.dataPoints.count { - if !dataSet.style.ignoreZero { return CGPoint(x: CGFloat(index) * xSection, y: (CGFloat(dataSet.dataPoints[index].value - minValue) * -ySection) + chartSize.height) @@ -166,29 +164,26 @@ extension MultiLineChartData { } public func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [LineChartDataPoint] = [] - for dataSet in dataSets.dataSets { - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) - let index = Int((touchLocation.x + (xSection / 2)) / xSection) - if index >= 0 && index < dataSet.dataPoints.count { - if !dataSet.style.ignoreZero { - var dataPoint = dataSet.dataPoints[index] - dataPoint.legendTag = dataSet.legendTitle - points.append(dataPoint) + + self.infoView.touchOverlayInfo = dataSets.dataSets.indices.compactMap { setIndex in + let xSection: CGFloat = chartSize.width / CGFloat(dataSets.dataSets[setIndex].dataPoints.count - 1) + let index = Int((touchLocation.x + (xSection / 2)) / xSection) + if index >= 0 && index < dataSets.dataSets[setIndex].dataPoints.count { + if !dataSets.dataSets[setIndex].style.ignoreZero { + dataSets.dataSets[setIndex].dataPoints[index].legendTag = dataSets.dataSets[setIndex].legendTitle + return dataSets.dataSets[setIndex].dataPoints[index] } else { - if dataSet.dataPoints[index].value != 0 { - var dataPoint = dataSet.dataPoints[index] - dataPoint.legendTag = dataSet.legendTitle - points.append(dataPoint) + if dataSets.dataSets[setIndex].dataPoints[index].value != 0 { + dataSets.dataSets[setIndex].dataPoints[index].legendTag = dataSets.dataSets[setIndex].legendTitle + return dataSets.dataSets[setIndex].dataPoints[index] } else { - var dataPoint = dataSet.dataPoints[index] - dataPoint.legendTag = dataSet.legendTitle - dataPoint.value = -Double.greatestFiniteMagnitude - points.append(dataPoint) + dataSets.dataSets[setIndex].dataPoints[index].legendTag = dataSets.dataSets[setIndex].legendTitle + dataSets.dataSets[setIndex].dataPoints[index].value = -Double.greatestFiniteMagnitude + return dataSets.dataSets[setIndex].dataPoints[index] } } } + return nil } - self.infoView.touchOverlayInfo = points } } diff --git a/Sources/SwiftUICharts/LineChart/Models/ChartData/RangedLineChartData.swift b/Sources/SwiftUICharts/LineChart/Models/ChartData/RangedLineChartData.swift index 6816eab4..5be93d9c 100644 --- a/Sources/SwiftUICharts/LineChart/Models/ChartData/RangedLineChartData.swift +++ b/Sources/SwiftUICharts/LineChart/Models/ChartData/RangedLineChartData.swift @@ -15,20 +15,20 @@ import SwiftUI public final class RangedLineChartData: CTLineChartDataProtocol { // MARK: Properties - public let id : UUID = UUID() + public let id: UUID = UUID() - @Published public final var dataSets : RangedLineDataSet - @Published public final var metadata : ChartMetadata - @Published public final var xAxisLabels : [String]? - @Published public final var yAxisLabels : [String]? - @Published public final var chartStyle : LineChartStyle - @Published public final var legends : [LegendData] - @Published public final var viewData : ChartViewData - @Published public final var infoView : InfoViewData = InfoViewData() + @Published public final var dataSets: RangedLineDataSet + @Published public final var metadata: ChartMetadata + @Published public final var xAxisLabels: [String]? + @Published public final var yAxisLabels: [String]? + @Published public final var chartStyle: LineChartStyle + @Published public final var legends: [LegendData] + @Published public final var viewData: ChartViewData + @Published public final var infoView: InfoViewData = InfoViewData() + + public final var noDataText: Text + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) - public final var noDataText : Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) - // MARK: Initializer /// Initialises a ranged line chart. /// @@ -39,30 +39,33 @@ public final class RangedLineChartData: CTLineChartDataProtocol { /// - yAxisLabels: Labels for the Y axis instead of the labels generated from data point values. /// - chartStyle: The style data for the aesthetic of the chart. /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : RangedLineDataSet, - metadata : ChartMetadata = ChartMetadata(), - xAxisLabels : [String]? = nil, - yAxisLabels : [String]? = nil, - chartStyle : LineChartStyle = LineChartStyle(), - noDataText : Text = Text("No Data") + public init( + dataSets: RangedLineDataSet, + metadata: ChartMetadata = ChartMetadata(), + xAxisLabels: [String]? = nil, + yAxisLabels: [String]? = nil, + chartStyle: LineChartStyle = LineChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.xAxisLabels = xAxisLabels - self.yAxisLabels = yAxisLabels - self.chartStyle = chartStyle - self.noDataText = noDataText - self.legends = [LegendData]() - self.viewData = ChartViewData() - self.chartType = (chartType: .line, dataSetType: .single) + self.dataSets = dataSets + self.metadata = metadata + self.xAxisLabels = xAxisLabels + self.yAxisLabels = yAxisLabels + self.chartStyle = chartStyle + self.noDataText = noDataText + self.legends = [LegendData]() + self.viewData = ChartViewData() + self.chartType = (chartType: .line, dataSetType: .single) self.setupLegends() self.setupRangeLegends() } - public final var average : Double { - let sum = dataSets.dataPoints.reduce(0) { $0 + $1.value } - return sum / Double(dataSets.dataPoints.count) + public final var average: Double { + dataSets.dataPoints + .map(\.value) + .reduce(0, +) + .divide(by: Double(dataSets.dataPoints.count)) } // MARK: Labels @@ -103,16 +106,16 @@ public final class RangedLineChartData: CTLineChartDataProtocol { } } } - + // MARK: Points public final func getPointMarker() -> some View { - PointsSubView(dataSets : dataSets, - minValue : self.minValue, - range : self.range, - animation : self.chartStyle.globalAnimation, - isFilled : false) + PointsSubView(dataSets: dataSets, + minValue: self.minValue, + range: self.range, + animation: self.chartStyle.globalAnimation, + isFilled: false) } - + public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { self.markerSubView(dataSet: dataSets, dataPoints: dataSets.dataPoints, @@ -122,14 +125,11 @@ public final class RangedLineChartData: CTLineChartDataProtocol { } public final func getPointLocation(dataSet: RangedLineDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { - - let minValue : Double = self.minValue - let range : Double = self.range - - let xSection : CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) - let ySection : CGFloat = chartSize.height / CGFloat(range) - - let index : Int = Int((touchLocation.x + (xSection / 2)) / xSection) + let minValue: Double = self.minValue + let range: Double = self.range + let xSection: CGFloat = chartSize.width / CGFloat(dataSet.dataPoints.count - 1) + let ySection: CGFloat = chartSize.height / CGFloat(range) + let index: Int = Int((touchLocation.x + (xSection / 2)) / xSection) if index >= 0 && index < dataSet.dataPoints.count { if !dataSet.style.ignoreZero { return CGPoint(x: CGFloat(index) * xSection, @@ -145,31 +145,25 @@ public final class RangedLineChartData: CTLineChartDataProtocol { } public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [RangedLineChartDataPoint] = [] - let xSection : CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count - 1) - let index = Int((touchLocation.x + (xSection / 2)) / xSection) + let xSection: CGFloat = chartSize.width / CGFloat(dataSets.dataPoints.count - 1) + let index = Int((touchLocation.x + (xSection / 2)) / xSection) if index >= 0 && index < dataSets.dataPoints.count { - if !dataSets.style.ignoreZero { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } else { if dataSets.dataPoints[index].value != 0 { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } else { - var dataPoint = dataSets.dataPoints[index] - dataPoint.legendTag = dataSets.legendTitle - dataPoint.value = -Double.greatestFiniteMagnitude - points.append(dataPoint) + dataSets.dataPoints[index].legendTag = dataSets.legendTitle + dataSets.dataPoints[index].value = -Double.greatestFiniteMagnitude + self.infoView.touchOverlayInfo = [dataSets.dataPoints[index]] } } } - self.infoView.touchOverlayInfo = points } - public typealias Set = RangedLineDataSet + public typealias Set = RangedLineDataSet public typealias DataPoint = RangedLineChartDataPoint } diff --git a/Sources/SwiftUICharts/LineChart/Models/DataPoints/LineChartDataPoint.swift b/Sources/SwiftUICharts/LineChart/Models/DataPoints/LineChartDataPoint.swift index 95e615d5..10377598 100644 --- a/Sources/SwiftUICharts/LineChart/Models/DataPoints/LineChartDataPoint.swift +++ b/Sources/SwiftUICharts/LineChart/Models/DataPoints/LineChartDataPoint.swift @@ -9,39 +9,32 @@ import SwiftUI /** Data for a single data point. - - # Example - ``` - LineChartDataPoint(value : 20, - xAxisLabel : "M", - description: "Monday", - date : Date()) - ``` */ public struct LineChartDataPoint: CTStandardLineDataPoint { - public let id : UUID = UUID() - public var value : Double - public var xAxisLabel : String? - public var description : String? - public var date : Date? + public let id: UUID = UUID() + public var value: Double + public var xAxisLabel: String? + public var description: String? + public var date: Date? + + public var legendTag: String = "" - public var legendTag : String = "" - /// Data model for a single data point with colour for use with a line chart. /// - Parameters: /// - value: Value of the data point /// - xAxisLabel: Label that can be shown on the X axis. /// - description: A longer label that can be shown on touch input. /// - date: Date of the data point if any data based calculations are required. - public init(value : Double, - xAxisLabel : String? = nil, - description : String? = nil, - date : Date? = nil + public init( + value: Double, + xAxisLabel: String? = nil, + description: String? = nil, + date: Date? = nil ) { - self.value = value - self.xAxisLabel = xAxisLabel + self.value = value + self.xAxisLabel = xAxisLabel self.description = description - self.date = date + self.date = date } } diff --git a/Sources/SwiftUICharts/LineChart/Models/DataPoints/RangedLineChartDataPoint.swift b/Sources/SwiftUICharts/LineChart/Models/DataPoints/RangedLineChartDataPoint.swift index 10578e18..f9f3ac61 100644 --- a/Sources/SwiftUICharts/LineChart/Models/DataPoints/RangedLineChartDataPoint.swift +++ b/Sources/SwiftUICharts/LineChart/Models/DataPoints/RangedLineChartDataPoint.swift @@ -9,28 +9,19 @@ import SwiftUI /** Data for a single ranged data point. - - # Example - ``` - RangedLineChartDataPoint(value: 10, - upperValue: 20, - lowerValue: 0, - xAxisLabel: "M", - description: "Monday") - ``` */ public struct RangedLineChartDataPoint: CTRangedLineDataPoint { - public let id : UUID = UUID() - public var value : Double - public var upperValue : Double - public var lowerValue : Double - public var xAxisLabel : String? - public var description : String? - public var date : Date? + public let id: UUID = UUID() + public var value: Double + public var upperValue: Double + public var lowerValue: Double + public var xAxisLabel: String? + public var description: String? + public var date: Date? + + public var legendTag: String = "" - public var legendTag : String = "" - /// Data model for a single data point with colour for use with a ranged line chart. /// - Parameters: /// - value: Value of the data point. @@ -39,18 +30,19 @@ public struct RangedLineChartDataPoint: CTRangedLineDataPoint { /// - xAxisLabel: Label that can be shown on the X axis. /// - description: A longer label that can be shown on touch input. /// - date: Date of the data point if any data based calculations are required. - public init(value : Double, - upperValue : Double, - lowerValue : Double, - xAxisLabel : String? = nil, - description : String? = nil, - date : Date? = nil + public init( + value: Double, + upperValue: Double, + lowerValue: Double, + xAxisLabel: String? = nil, + description: String? = nil, + date: Date? = nil ) { - self.value = value - self.upperValue = upperValue - self.lowerValue = lowerValue - self.xAxisLabel = xAxisLabel + self.value = value + self.upperValue = upperValue + self.lowerValue = lowerValue + self.xAxisLabel = xAxisLabel self.description = description - self.date = date + self.date = date } } diff --git a/Sources/SwiftUICharts/LineChart/Models/DataSet/LineDataSet.swift b/Sources/SwiftUICharts/LineChart/Models/DataSet/LineDataSet.swift index d9049dd5..6fcc88b0 100644 --- a/Sources/SwiftUICharts/LineChart/Models/DataSet/LineDataSet.swift +++ b/Sources/SwiftUICharts/LineChart/Models/DataSet/LineDataSet.swift @@ -13,13 +13,12 @@ import SwiftUI Contains information specific to each line within the chart . */ public struct LineDataSet: CTLineChartDataSet { - - public let id : UUID = UUID() - public var dataPoints : [LineChartDataPoint] - public var legendTitle : String - public var pointStyle : PointStyle - public var style : LineStyle + public let id: UUID = UUID() + public var dataPoints: [LineChartDataPoint] + public var legendTitle: String + public var pointStyle: PointStyle + public var style: LineStyle /// Initialises a data set for a line in a Line Chart. /// - Parameters: @@ -27,17 +26,18 @@ public struct LineDataSet: CTLineChartDataSet { /// - legendTitle: Label for the data in legend. /// - pointStyle: Styling information for the data point markers. /// - style: Styling for how the line will be draw in. - public init(dataPoints : [LineChartDataPoint], - legendTitle : String = "", - pointStyle : PointStyle = PointStyle(), - style : LineStyle = LineStyle() + public init( + dataPoints: [LineChartDataPoint], + legendTitle: String = "", + pointStyle: PointStyle = PointStyle(), + style: LineStyle = LineStyle() ) { - self.dataPoints = dataPoints - self.legendTitle = legendTitle - self.pointStyle = pointStyle - self.style = style + self.dataPoints = dataPoints + self.legendTitle = legendTitle + self.pointStyle = pointStyle + self.style = style } - public typealias ID = UUID + public typealias ID = UUID public typealias Styling = LineStyle } diff --git a/Sources/SwiftUICharts/LineChart/Models/DataSet/MultiLineDataSet.swift b/Sources/SwiftUICharts/LineChart/Models/DataSet/MultiLineDataSet.swift index 0a5c67e8..23973b42 100644 --- a/Sources/SwiftUICharts/LineChart/Models/DataSet/MultiLineDataSet.swift +++ b/Sources/SwiftUICharts/LineChart/Models/DataSet/MultiLineDataSet.swift @@ -14,12 +14,11 @@ import SwiftUI */ public struct MultiLineDataSet: CTMultiLineChartDataSet { - public let id : UUID = UUID() - public var dataSets : [LineDataSet] + public let id: UUID = UUID() + public var dataSets: [LineDataSet] /// Initialises a new data set for multi-line Line Charts. public init(dataSets: [LineDataSet]) { self.dataSets = dataSets } } - diff --git a/Sources/SwiftUICharts/LineChart/Models/DataSet/RangedLineDataSet.swift b/Sources/SwiftUICharts/LineChart/Models/DataSet/RangedLineDataSet.swift index 5fe00e8d..dcdc7357 100644 --- a/Sources/SwiftUICharts/LineChart/Models/DataSet/RangedLineDataSet.swift +++ b/Sources/SwiftUICharts/LineChart/Models/DataSet/RangedLineDataSet.swift @@ -13,14 +13,14 @@ import SwiftUI Contains information specific to the line and range fill. */ public struct RangedLineDataSet: CTRangedLineChartDataSet { - - public let id : UUID = UUID() - public var dataPoints : [RangedLineChartDataPoint] - public var legendTitle : String + + public let id: UUID = UUID() + public var dataPoints: [RangedLineChartDataPoint] + public var legendTitle: String public var legendFillTitle: String - public var pointStyle : PointStyle - public var style : RangedLineStyle - + public var pointStyle: PointStyle + public var style: RangedLineStyle + /// Initialises a data set for a line in a ranged line chart. /// - Parameters: /// - dataPoints: Array of elements. @@ -28,20 +28,20 @@ public struct RangedLineDataSet: CTRangedLineChartDataSet { /// - legendFillTitle: Label for the range data in legend. /// - pointStyle: Styling information for the data point markers. /// - style: Styling for how the line will be draw in. - public init(dataPoints : [RangedLineChartDataPoint], - legendTitle : String = "", - legendFillTitle : String = "", - pointStyle : PointStyle = PointStyle(), - style : RangedLineStyle = RangedLineStyle() + public init( + dataPoints: [RangedLineChartDataPoint], + legendTitle: String = "", + legendFillTitle: String = "", + pointStyle: PointStyle = PointStyle(), + style: RangedLineStyle = RangedLineStyle() ) { - self.dataPoints = dataPoints - self.legendTitle = legendTitle + self.dataPoints = dataPoints + self.legendTitle = legendTitle self.legendFillTitle = legendFillTitle - self.pointStyle = pointStyle - self.style = style + self.pointStyle = pointStyle + self.style = style } - - public typealias ID = UUID + + public typealias ID = UUID public typealias Styling = RangedLineStyle - } diff --git a/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocols.swift b/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocols.swift index 6b8283b2..c31bd243 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocols.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocols.swift @@ -12,11 +12,12 @@ import SwiftUI A protocol to extend functionality of `CTLineBarChartDataProtocol` specifically for Line Charts. */ public protocol CTLineChartDataProtocol: CTLineBarChartDataProtocol { - + /// A type representing opaque View - associatedtype Points : View + associatedtype Points: View + /// A type representing opaque View - associatedtype Access : View + associatedtype Access: View /** Displays Shapes over the data points. @@ -37,26 +38,33 @@ public protocol CTLineChartDataProtocol: CTLineBarChartDataProtocol { /** A protocol to extend functionality of `CTLineBarChartStyle` specifically for Line Charts. */ -public protocol CTLineChartStyle : CTLineBarChartStyle {} +public protocol CTLineChartStyle: CTLineBarChartStyle {} /** Protocol to set up the styling for individual lines. */ public protocol CTLineStyle { /// Drawing style of the line. - var lineType : LineType { get set } + var lineType: LineType { get set } /// Colour styling of the line. - var lineColour : ColourStyle { get set } + var lineColour: ColourStyle { get set } /** - Styling for stroke + Styling for stroke Replica of Apple’s StrokeStyle that conforms to Hashable */ - var strokeStyle : Stroke { get set } + var strokeStyle: Stroke { get set } - var ignoreZero : Bool { get set } + /** + Whether the chart should skip data points who's value is 0. + + This might be useful when showing trends over time but each day does not necessarily have data. + + The default is false. + */ + var ignoreZero: Bool { get set } } /** @@ -64,7 +72,7 @@ public protocol CTLineStyle { */ public protocol CTRangedLineStyle: CTLineStyle { /// Drawing style of the range fill. - var fillColour : ColourStyle { get set } + var fillColour: ColourStyle { get set } } @@ -76,17 +84,17 @@ public protocol CTRangedLineStyle: CTLineStyle { public protocol CTLineChartDataSet: CTSingleDataSetProtocol { /// A type representing colour styling - associatedtype Styling : CTLineStyle + associatedtype Styling: CTLineStyle /** Label to display in the legend. */ - var legendTitle : String { get set } + var legendTitle: String { get set } /** Sets the style for the Data Set (as opposed to Chart Data Style). */ - var style : Styling { get set } + var style: Styling { get set } /** Sets the look of the markers over the data points. @@ -94,14 +102,18 @@ public protocol CTLineChartDataSet: CTSingleDataSetProtocol { The markers are layed out when the ViewModifier `PointMarkers` is applied. */ - var pointStyle : PointStyle { get set } + var pointStyle: PointStyle { get set } } /** A protocol to extend functionality of `CTLineChartDataSet` specifically for Ranged Line Charts. */ public protocol CTRangedLineChartDataSet: CTLineChartDataSet { - var legendFillTitle : String { get set } + + /** + Label to display in the legend for the range area.. + */ + var legendFillTitle: String { get set } } /** diff --git a/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocolsExtensions.swift b/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocolsExtensions.swift index 5c3b933a..baf73eed 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocolsExtensions.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Protocols/LineChartProtocolsExtensions.swift @@ -21,14 +21,13 @@ extension CTLineChartDataProtocol { range: Double, ignoreZero: Bool ) -> CGPoint { - - let path = Self.getPath(lineType : lineType, - rect : rect, - dataPoints : dataPoints, - minValue : minValue, - range : range, - isFilled : false, - ignoreZero : ignoreZero) + let path = Self.getPath(lineType: lineType, + rect: rect, + dataPoints: dataPoints, + minValue: minValue, + range: range, + isFilled: false, + ignoreZero: ignoreZero) return Self.locationOnPath(Self.getPercentageOfPath(path: path, touchLocation: touchLocation), path) } } @@ -54,52 +53,51 @@ extension CTLineChartDataProtocol { minValue: Double, range: Double, isFilled: Bool, - ignoreZero : Bool + ignoreZero: Bool ) -> Path { switch lineType { case .line: switch ignoreZero { case false: - return Path.straightLine(rect : rect, - dataPoints : dataPoints, - minValue : minValue, - range : range, - isFilled : isFilled) + return Path.straightLine(rect: rect, + dataPoints: dataPoints, + minValue: minValue, + range: range, + isFilled: isFilled) case true: - return Path.straightLineIgnoreZero(rect : rect, - dataPoints : dataPoints, - minValue : minValue, - range : range, - isFilled : isFilled) + return Path.straightLineIgnoreZero(rect: rect, + dataPoints: dataPoints, + minValue: minValue, + range: range, + isFilled: isFilled) } case .curvedLine: switch ignoreZero { case false: - return Path.curvedLine(rect : rect, - dataPoints : dataPoints, - minValue : minValue, - range : range, - isFilled : isFilled) + return Path.curvedLine(rect: rect, + dataPoints: dataPoints, + minValue: minValue, + range: range, + isFilled: isFilled) case true: - return Path.curvedLineIgnoreZero(rect : rect, - dataPoints : dataPoints, - minValue : minValue, - range : range, - isFilled : isFilled) + return Path.curvedLineIgnoreZero(rect: rect, + dataPoints: dataPoints, + minValue: minValue, + range: range, + isFilled: isFilled) } } } - + /** How far along the path the touch or pointer is as a percent of the total. - . - Parameters: - path: Path being acted on. - touchLocation: Location of the touch or pointer input. - Returns: How far along the path the touch is. */ static func getPercentageOfPath(path: Path, touchLocation: CGPoint) -> CGFloat { - let totalLength = self.getTotalLength(of: path) + let totalLength = self.getTotalLength(of: path) let lengthToTouch = self.getLength(to: touchLocation, on: path) let pointLocation = lengthToTouch / totalLength return pointLocation @@ -115,9 +113,9 @@ extension CTLineChartDataProtocol { - Returns: Total length of the path. */ static func getTotalLength(of path: Path) -> CGFloat { - var total : CGFloat = 0 + var total: CGFloat = 0 var currentPoint: CGPoint = .zero - path.forEach { (element) in + path.forEach { element in switch element { case .move(to: let first): currentPoint = first @@ -147,10 +145,10 @@ extension CTLineChartDataProtocol { - Returns: Length of path to touch point. */ static func getLength(to touchLocation: CGPoint, on path: Path) -> CGFloat { - var total : CGFloat = 0 + var total: CGFloat = 0 var currentPoint: CGPoint = .zero - var isComplete : Bool = false - path.forEach { (element) in + var isComplete: Bool = false + path.forEach { element in if isComplete { return } switch element { case .move(to: let point): @@ -162,8 +160,8 @@ extension CTLineChartDataProtocol { } case .line(to: let nextPoint): if touchLocation.x < nextPoint.x { - total += distanceToTouch(from : currentPoint, - to : nextPoint, + total += distanceToTouch(from: currentPoint, + to: nextPoint, touchX: touchLocation.x) isComplete = true return @@ -173,8 +171,8 @@ extension CTLineChartDataProtocol { } case .curve(to: let nextPoint, control1: _, control2: _ ): if touchLocation.x < nextPoint.x { - total += distanceToTouch(from : currentPoint, - to : nextPoint, + total += distanceToTouch(from: currentPoint, + to: nextPoint, touchX: touchLocation.x) isComplete = true return @@ -184,8 +182,8 @@ extension CTLineChartDataProtocol { } case .quadCurve(to: let nextPoint, control: _): if touchLocation.x < nextPoint.x { - total += distanceToTouch(from : currentPoint, - to : nextPoint, + total += distanceToTouch(from: currentPoint, + to: nextPoint, touchX: touchLocation.x) isComplete = true return @@ -201,7 +199,7 @@ extension CTLineChartDataProtocol { } return total } - + /** Returns a point on the path based on the location of the touch or pointer input on the X axis. @@ -216,7 +214,7 @@ extension CTLineChartDataProtocol { CGPoint(x: touchX, y: from.y + (touchX - from.x) * ((to.y - from.y) / (to.x - from.x))) } - + /** Returns the length along the path from a point to the touch locatiions X axis. @@ -229,7 +227,7 @@ extension CTLineChartDataProtocol { static func distanceToTouch(from: CGPoint, to: CGPoint, touchX: CGFloat) -> CGFloat { distance(from: from, to: relativePoint(from: from, to: to, touchX: touchX)) } - + /** Returns the distance between two points. @@ -251,8 +249,8 @@ extension CTLineChartDataProtocol { [SwiftUI Lab](https://swiftui-lab.com/swiftui-animations-part2/) - Parameters: - - percent: The distance along the path as a percentage. - - path: Path to find location on. + - percent: The distance along the path as a percentage. + - path: Path to find location on. - Returns: Point on path. */ static func locationOnPath(_ percent: CGFloat, _ path: Path) -> CGPoint { @@ -264,7 +262,7 @@ extension CTLineChartDataProtocol { let pct = percent > 1 ? 0 : (percent < 0 ? 1 : percent) let from = pct > comp ? comp : pct - let to = pct > comp ? 1 : pct + diff + let to = pct > comp ? 1 : pct + diff let trimmedPoint = path.trimmedPath(from: from, to: to) return CGPoint(x: trimmedPoint.boundingRect.midX, @@ -274,14 +272,15 @@ extension CTLineChartDataProtocol { // MARK: - Markers extension CTLineChartDataProtocol where Self.CTStyle.Mark == LineMarkerType { - + internal func markerSubView - (dataSet : DS, - dataPoints : [DP], - lineType : LineType, - touchLocation : CGPoint, - chartSize : CGRect) -> some View { + DP: CTStandardDataPointProtocol>( + dataSet: DS, + dataPoints: [DP], + lineType: LineType, + touchLocation: CGPoint, + chartSize: CGRect + ) -> some View { Group { switch self.chartStyle.markerType { case .none: @@ -344,7 +343,7 @@ extension CTLineChartDataProtocol where Self.CTStyle.Mark == LineMarkerType { .stroke(colour, style: style) IndicatorSwitch(indicator: indicator, location: position) - + case .point: @@ -481,9 +480,9 @@ extension CTLineChartDataProtocol where Self.CTStyle.Mark == LineMarkerType { Sub view for laying out and styling the indicator dot. */ internal struct IndicatorSwitch: View { - + private let indicator: Dot - private let location : CGPoint + private let location: CGPoint internal init(indicator: Dot, location: CGPoint) { self.indicator = indicator @@ -494,10 +493,11 @@ internal struct IndicatorSwitch: View { switch indicator { case .none: EmptyView() case .style(let style): - PosistionIndicator(fillColour: style.fillColour, lineColour: style.lineColour, lineWidth: style.lineWidth) + PosistionIndicator(fillColour: style.fillColour, + lineColour: style.lineColour, + lineWidth: style.lineWidth) .frame(width: style.size, height: style.size) .position(location) - } } @@ -505,7 +505,7 @@ internal struct IndicatorSwitch: View { // MARK: - Legends extension CTLineChartDataProtocol where Self.Set.ID == UUID, - Self.Set : CTLineChartDataSet { + Self.Set: CTLineChartDataSet { internal func setupLegends() { lineLegendSetup(dataSet: dataSets) } @@ -513,49 +513,47 @@ extension CTLineChartDataProtocol where Self.Set.ID == UUID, extension CTLineChartDataProtocol where Self.Set == MultiLineDataSet { internal func setupLegends() { - for dataSet in dataSets.dataSets { - lineLegendSetup(dataSet: dataSet) - } + dataSets.dataSets.forEach { lineLegendSetup(dataSet: $0) } } } + extension CTLineChartDataProtocol { internal func lineLegendSetup(dataSet: DS) where DS.ID == UUID { if dataSet.style.lineColour.colourType == .colour, let colour = dataSet.style.lineColour.colour { - self.legends.append(LegendData(id : dataSet.id, - legend : dataSet.legendTitle, - colour : ColourStyle(colour: colour), + self.legends.append(LegendData(id: dataSet.id, + legend: dataSet.legendTitle, + colour: ColourStyle(colour: colour), strokeStyle: dataSet.style.strokeStyle, - prioity : 1, - chartType : .line)) - + prioity: 1, + chartType: .line)) } else if dataSet.style.lineColour.colourType == .gradientColour, let colours = dataSet.style.lineColour.colours { - self.legends.append(LegendData(id : dataSet.id, - legend : dataSet.legendTitle, - colour : ColourStyle(colours: colours, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: dataSet.id, + legend: dataSet.legendTitle, + colour: ColourStyle(colours: colours, + startPoint: .leading, + endPoint: .trailing), strokeStyle: dataSet.style.strokeStyle, - prioity : 1, - chartType : .line)) - + prioity: 1, + chartType: .line)) } else if dataSet.style.lineColour.colourType == .gradientStops, let stops = dataSet.style.lineColour.stops { - self.legends.append(LegendData(id : dataSet.id, - legend : dataSet.legendTitle, - colour : ColourStyle(stops: stops, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: dataSet.id, + legend: dataSet.legendTitle, + colour: ColourStyle(stops: stops, + startPoint: .leading, + endPoint: .trailing), strokeStyle: dataSet.style.strokeStyle, - prioity : 1, - chartType : .line)) + prioity: 1, + chartType: .line)) } } } + extension CTLineChartDataProtocol where Self.Set.ID == UUID, Self.Set: CTRangedLineChartDataSet, Self.Set.Styling: CTRangedLineStyle { @@ -563,36 +561,34 @@ extension CTLineChartDataProtocol where Self.Set.ID == UUID, if dataSets.style.fillColour.colourType == .colour, let colour = dataSets.style.fillColour.colour { - self.legends.append(LegendData(id : UUID(), - legend : dataSets.legendFillTitle, - colour : ColourStyle(colour: colour), + self.legends.append(LegendData(id: UUID(), + legend: dataSets.legendFillTitle, + colour: ColourStyle(colour: colour), strokeStyle: dataSets.style.strokeStyle, - prioity : 1, - chartType : .bar)) - + prioity: 1, + chartType: .bar)) } else if dataSets.style.fillColour.colourType == .gradientColour, let colours = dataSets.style.fillColour.colours { - self.legends.append(LegendData(id : UUID(), - legend : dataSets.legendFillTitle, - colour : ColourStyle(colours: colours, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: UUID(), + legend: dataSets.legendFillTitle, + colour: ColourStyle(colours: colours, + startPoint: .leading, + endPoint: .trailing), strokeStyle: dataSets.style.strokeStyle, - prioity : 1, - chartType : .bar)) - + prioity: 1, + chartType: .bar)) } else if dataSets.style.fillColour.colourType == .gradientStops, let stops = dataSets.style.fillColour.stops { - self.legends.append(LegendData(id : UUID(), - legend : dataSets.legendFillTitle, - colour : ColourStyle(stops: stops, - startPoint: .leading, - endPoint: .trailing), + self.legends.append(LegendData(id: UUID(), + legend: dataSets.legendFillTitle, + colour: ColourStyle(stops: stops, + startPoint: .leading, + endPoint: .trailing), strokeStyle: dataSets.style.strokeStyle, - prioity : 1, - chartType : .bar)) + prioity: 1, + chartType: .bar)) } } } @@ -602,8 +598,8 @@ extension CTLineChartDataProtocol where Set: CTLineChartDataSet { public func getAccessibility() -> some View { ForEach(dataSets.dataPoints.indices, id: \.self) { point in - AccessibilityRectangle(dataPointCount : self.dataSets.dataPoints.count, - dataPointNo : point) + AccessibilityRectangle(dataPointCount: self.dataSets.dataPoints.count, + dataPointNo: point) .foregroundColor(Color(.gray).opacity(0.000000001)) .accessibilityLabel(Text("\(self.metadata.title)")) diff --git a/Sources/SwiftUICharts/LineChart/Models/Style/LineChartStyle.swift b/Sources/SwiftUICharts/LineChart/Models/Style/LineChartStyle.swift index e8db0aa8..db7c0e45 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Style/LineChartStyle.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Style/LineChartStyle.swift @@ -15,45 +15,45 @@ import SwiftUI */ public struct LineChartStyle: CTLineChartStyle { - public var infoBoxPlacement : InfoBoxPlacement - public var infoBoxContentAlignment : InfoBoxAlignment + public var infoBoxPlacement: InfoBoxPlacement + public var infoBoxContentAlignment: InfoBoxAlignment - public var infoBoxValueFont : Font - public var infoBoxValueColour : Color + public var infoBoxValueFont: Font + public var infoBoxValueColour: Color - public var infoBoxDescriptionFont : Font - public var infoBoxDescriptionColour : Color - public var infoBoxBackgroundColour : Color - public var infoBoxBorderColour : Color - public var infoBoxBorderStyle : StrokeStyle + public var infoBoxDescriptionFont: Font + public var infoBoxDescriptionColour: Color + public var infoBoxBackgroundColour: Color + public var infoBoxBorderColour: Color + public var infoBoxBorderStyle: StrokeStyle - public var markerType : LineMarkerType - - public var xAxisGridStyle : GridStyle + public var markerType: LineMarkerType + + public var xAxisGridStyle: GridStyle - public var xAxisLabelPosition : XAxisLabelPosistion - public var xAxisLabelFont : Font - public var xAxisLabelColour : Color - public var xAxisLabelsFrom : LabelsFrom + public var xAxisLabelPosition: XAxisLabelPosistion + public var xAxisLabelFont: Font + public var xAxisLabelColour: Color + public var xAxisLabelsFrom: LabelsFrom - public var xAxisTitle : String? - public var xAxisTitleFont : Font + public var xAxisTitle: String? + public var xAxisTitleFont: Font - public var yAxisGridStyle : GridStyle + public var yAxisGridStyle: GridStyle - public var yAxisLabelPosition : YAxisLabelPosistion - public var yAxisLabelFont : Font - public var yAxisLabelColour : Color - public var yAxisNumberOfLabels : Int - public var yAxisLabelType : YAxisLabelType + public var yAxisLabelPosition: YAxisLabelPosistion + public var yAxisLabelFont: Font + public var yAxisLabelColour: Color + public var yAxisNumberOfLabels: Int + public var yAxisLabelType: YAxisLabelType - public var yAxisTitle : String? - public var yAxisTitleFont : Font + public var yAxisTitle: String? + public var yAxisTitleFont: Font - public var baseline : Baseline - public var topLine : Topline + public var baseline: Baseline + public var topLine: Topline - public var globalAnimation : Animation + public var globalAnimation: Animation /// Model for controlling the overall aesthetic of the chart. /// - Parameters: @@ -87,7 +87,8 @@ public struct LineChartStyle: CTLineChartStyle { /// - yAxisLabelPosition: Location of the X axis labels - Leading or Trailing. /// - yAxisLabelFont: Font of the labels on the Y axis. /// - yAxisLabelColour: Text Colour for the labels on the Y axis. - /// - yAxisNumberOfLabel: Number Of Labels on Y Axis. + /// - yAxisNumberOfLabels: Number Of Labels on Y Axis. + /// - yAxisLabelType: Option to add custom Strings to Y axis rather than auto generated numbers. /// /// - yAxisTitle: Label to display next to the chart giving info about the axis. /// - yAxisTitleFont: Font of the y axis title. @@ -96,86 +97,87 @@ public struct LineChartStyle: CTLineChartStyle { /// - topLine: Where to finish drawing the chart from. Data set maximum or custom. /// /// - globalAnimation: Global control of animations. - public init(infoBoxPlacement : InfoBoxPlacement = .floating, - infoBoxContentAlignment : InfoBoxAlignment = .vertical, - - infoBoxValueFont : Font = .title3, - infoBoxValueColour : Color = Color.primary, - - infoBoxDescriptionFont : Font = .caption, - infoBoxDescriptionColour: Color = Color.primary, - - infoBoxBackgroundColour : Color = Color.systemsBackground, - infoBoxBorderColour : Color = Color.clear, - infoBoxBorderStyle : StrokeStyle = StrokeStyle(lineWidth: 0), - - markerType : LineMarkerType = .indicator(style: DotStyle()), - - xAxisGridStyle : GridStyle = GridStyle(), - - xAxisLabelPosition : XAxisLabelPosistion = .bottom, - xAxisLabelFont : Font = .caption, - xAxisLabelColour : Color = Color.primary, - xAxisLabelsFrom : LabelsFrom = .dataPoint(rotation: .degrees(0)), - - xAxisTitle : String? = nil, - xAxisTitleFont : Font = .caption, - - yAxisGridStyle : GridStyle = GridStyle(), - - yAxisLabelPosition : YAxisLabelPosistion = .leading, - yAxisLabelFont : Font = .caption, - yAxisLabelColour : Color = Color.primary, - yAxisNumberOfLabels : Int = 10, - yAxisLabelType : YAxisLabelType = .numeric, - - yAxisTitle : String? = nil, - yAxisTitleFont : Font = .caption, - - baseline : Baseline = .minimumValue, - topLine : Topline = .maximumValue, - - globalAnimation : Animation = Animation.linear(duration: 1) + public init( + infoBoxPlacement: InfoBoxPlacement = .floating, + infoBoxContentAlignment: InfoBoxAlignment = .vertical, + + infoBoxValueFont: Font = .title3, + infoBoxValueColour: Color = Color.primary, + + infoBoxDescriptionFont: Font = .caption, + infoBoxDescriptionColour: Color = Color.primary, + + infoBoxBackgroundColour: Color = Color.systemsBackground, + infoBoxBorderColour: Color = Color.clear, + infoBoxBorderStyle: StrokeStyle = StrokeStyle(lineWidth: 0), + + markerType: LineMarkerType = .indicator(style: DotStyle()), + + xAxisGridStyle: GridStyle = GridStyle(), + + xAxisLabelPosition: XAxisLabelPosistion = .bottom, + xAxisLabelFont: Font = .caption, + xAxisLabelColour: Color = Color.primary, + xAxisLabelsFrom: LabelsFrom = .dataPoint(rotation: .degrees(0)), + + xAxisTitle: String? = nil, + xAxisTitleFont: Font = .caption, + + yAxisGridStyle: GridStyle = GridStyle(), + + yAxisLabelPosition: YAxisLabelPosistion = .leading, + yAxisLabelFont: Font = .caption, + yAxisLabelColour: Color = Color.primary, + yAxisNumberOfLabels: Int = 10, + yAxisLabelType: YAxisLabelType = .numeric, + + yAxisTitle: String? = nil, + yAxisTitleFont: Font = .caption, + + baseline: Baseline = .minimumValue, + topLine: Topline = .maximumValue, + + globalAnimation: Animation = Animation.linear(duration: 1) ) { - self.infoBoxPlacement = infoBoxPlacement - self.infoBoxContentAlignment = infoBoxContentAlignment + self.infoBoxPlacement = infoBoxPlacement + self.infoBoxContentAlignment = infoBoxContentAlignment - self.infoBoxValueFont = infoBoxValueFont - self.infoBoxValueColour = infoBoxValueColour + self.infoBoxValueFont = infoBoxValueFont + self.infoBoxValueColour = infoBoxValueColour - self.infoBoxDescriptionFont = infoBoxDescriptionFont + self.infoBoxDescriptionFont = infoBoxDescriptionFont self.infoBoxDescriptionColour = infoBoxDescriptionColour - self.infoBoxBackgroundColour = infoBoxBackgroundColour - self.infoBoxBorderColour = infoBoxBorderColour - self.infoBoxBorderStyle = infoBoxBorderStyle + self.infoBoxBackgroundColour = infoBoxBackgroundColour + self.infoBoxBorderColour = infoBoxBorderColour + self.infoBoxBorderStyle = infoBoxBorderStyle - self.markerType = markerType + self.markerType = markerType - self.xAxisGridStyle = xAxisGridStyle + self.xAxisGridStyle = xAxisGridStyle - self.xAxisLabelPosition = xAxisLabelPosition - self.xAxisLabelFont = xAxisLabelFont - self.xAxisLabelsFrom = xAxisLabelsFrom - self.xAxisLabelColour = xAxisLabelColour + self.xAxisLabelPosition = xAxisLabelPosition + self.xAxisLabelFont = xAxisLabelFont + self.xAxisLabelsFrom = xAxisLabelsFrom + self.xAxisLabelColour = xAxisLabelColour - self.xAxisTitle = xAxisTitle - self.xAxisTitleFont = xAxisTitleFont + self.xAxisTitle = xAxisTitle + self.xAxisTitleFont = xAxisTitleFont - self.yAxisGridStyle = yAxisGridStyle + self.yAxisGridStyle = yAxisGridStyle - self.yAxisLabelPosition = yAxisLabelPosition + self.yAxisLabelPosition = yAxisLabelPosition self.yAxisNumberOfLabels = yAxisNumberOfLabels - self.yAxisLabelFont = yAxisLabelFont - self.yAxisLabelColour = yAxisLabelColour - self.yAxisLabelType = yAxisLabelType + self.yAxisLabelFont = yAxisLabelFont + self.yAxisLabelColour = yAxisLabelColour + self.yAxisLabelType = yAxisLabelType - self.yAxisTitle = yAxisTitle - self.yAxisTitleFont = yAxisTitleFont + self.yAxisTitle = yAxisTitle + self.yAxisTitleFont = yAxisTitleFont - self.baseline = baseline - self.topLine = topLine + self.baseline = baseline + self.topLine = topLine - self.globalAnimation = globalAnimation + self.globalAnimation = globalAnimation } } diff --git a/Sources/SwiftUICharts/LineChart/Models/Style/LineStyle.swift b/Sources/SwiftUICharts/LineChart/Models/Style/LineStyle.swift index de9096c4..d4443358 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Style/LineStyle.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Style/LineStyle.swift @@ -11,39 +11,28 @@ import SwiftUI Model for controlling the styling for individual lines. */ public struct LineStyle: CTLineStyle, Hashable { - - public var lineColour : ColourStyle - public var lineType : LineType - public var strokeStyle : Stroke - - /** - Whether the chart should skip data points who's value is 0. - - This might be useful when showing trends over time but each day does not necessarily have data. - - The default is false. - */ - public var ignoreZero : Bool - + + public var lineColour: ColourStyle + public var lineType: LineType + public var strokeStyle: Stroke + public var ignoreZero: Bool + /// Style of the line. + /// /// - Parameters: /// - lineColour: Colour styling of the line. /// - lineType: Drawing style of the line /// - strokeStyle: Stroke Style /// - ignoreZero: Whether the chart should skip data points who's value is 0. - public init(lineColour : ColourStyle = ColourStyle(colour: .red), - lineType : LineType = .curvedLine, - strokeStyle : Stroke = Stroke(lineWidth : 3, - lineCap : .round, - lineJoin : .round, - miterLimit: 10, - dash : [CGFloat](), - dashPhase : 0), - ignoreZero : Bool = false + public init( + lineColour: ColourStyle = ColourStyle(colour: .red), + lineType: LineType = .curvedLine, + strokeStyle: Stroke = Stroke(), + ignoreZero: Bool = false ) { - self.lineColour = lineColour - self.lineType = lineType + self.lineColour = lineColour + self.lineType = lineType self.strokeStyle = strokeStyle - self.ignoreZero = ignoreZero + self.ignoreZero = ignoreZero } } diff --git a/Sources/SwiftUICharts/LineChart/Models/Style/PointStyle.swift b/Sources/SwiftUICharts/LineChart/Models/Style/PointStyle.swift index 41671e74..618109dd 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Style/PointStyle.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Style/PointStyle.swift @@ -11,36 +11,26 @@ import SwiftUI Model for controlling the aesthetic of the point markers. Point markers are placed on top of the line, marking where the data points are. - - # Example - ``` - PointStyle(pointSize: 9, - borderColour: .primary, - fillColour: .red, - lineWidth: 2, - pointType: .filledOutLine, - pointShape: .circle) - ``` */ public struct PointStyle: Hashable { - + /// Overall size of the mark - public var pointSize : CGFloat + public var pointSize: CGFloat /// Outter ring colour public var borderColour: Color /// Center fill colour - public var fillColour : Color + public var fillColour: Color /// Outter ring line width - public var lineWidth : CGFloat + public var lineWidth: CGFloat /// Style of the point marks - public var pointType : PointType + public var pointType: PointType /// Shape of the points - public var pointShape : PointShape + public var pointShape: PointShape /// Styling for the point markers. /// - Parameters: @@ -50,18 +40,19 @@ public struct PointStyle: Hashable { /// - lineWidth: Outter ring line width /// - pointType: Style of the point marks /// - pointShape: Shape of the points - public init(pointSize : CGFloat = 9, - borderColour: Color = .primary, - fillColour : Color = Color(.gray), - lineWidth : CGFloat = 3, - pointType : PointType = .outline, - pointShape : PointShape = .circle + public init( + pointSize: CGFloat = 9, + borderColour: Color = .primary, + fillColour: Color = Color(.gray), + lineWidth: CGFloat = 3, + pointType: PointType = .outline, + pointShape: PointShape = .circle ) { - self.pointSize = pointSize + self.pointSize = pointSize self.borderColour = borderColour - self.fillColour = fillColour - self.lineWidth = lineWidth - self.pointType = pointType - self.pointShape = pointShape + self.fillColour = fillColour + self.lineWidth = lineWidth + self.pointType = pointType + self.pointShape = pointShape } } diff --git a/Sources/SwiftUICharts/LineChart/Models/Style/RangedLineStyle.swift b/Sources/SwiftUICharts/LineChart/Models/Style/RangedLineStyle.swift index 8b24a97d..3a3c9a3b 100644 --- a/Sources/SwiftUICharts/LineChart/Models/Style/RangedLineStyle.swift +++ b/Sources/SwiftUICharts/LineChart/Models/Style/RangedLineStyle.swift @@ -11,21 +11,12 @@ import SwiftUI */ public struct RangedLineStyle: CTRangedLineStyle, Hashable { - public var lineColour : ColourStyle - public var fillColour : ColourStyle + public var lineColour: ColourStyle + public var fillColour: ColourStyle + public var lineType: LineType + public var strokeStyle: Stroke + public var ignoreZero: Bool - public var lineType : LineType - public var strokeStyle : Stroke - - /** - Whether the chart should skip data points who's value is 0. - - This might be useful when showing trends over time but each day does not necessarily have data. - - The default is false. - */ - public var ignoreZero : Bool - // MARK: Initializer /// Initialize the styling for ranged line chart. /// @@ -34,16 +25,11 @@ public struct RangedLineStyle: CTRangedLineStyle, Hashable { /// - lineType: Drawing style of the line /// - strokeStyle: Stroke Style /// - ignoreZero: Whether the chart should skip data points who's value is 0. - public init(lineColour : ColourStyle = ColourStyle(), - fillColour : ColourStyle = ColourStyle(), - lineType : LineType = .curvedLine, - strokeStyle : Stroke = Stroke(lineWidth : 3, - lineCap : .round, - lineJoin : .round, - miterLimit: 10, - dash : [CGFloat](), - dashPhase : 0), - ignoreZero : Bool = false + public init(lineColour: ColourStyle = ColourStyle(), + fillColour: ColourStyle = ColourStyle(), + lineType: LineType = .curvedLine, + strokeStyle: Stroke = Stroke(), + ignoreZero: Bool = false ) { self.lineColour = lineColour self.fillColour = fillColour diff --git a/Sources/SwiftUICharts/LineChart/Shapes/LegendLine.swift b/Sources/SwiftUICharts/LineChart/Shapes/LegendLine.swift index 07e80dc1..8590a91c 100644 --- a/Sources/SwiftUICharts/LineChart/Shapes/LegendLine.swift +++ b/Sources/SwiftUICharts/LineChart/Shapes/LegendLine.swift @@ -8,20 +8,18 @@ import SwiftUI /// Draw line in legend view -internal struct LegendLine : Shape { +internal struct LegendLine: Shape { - private let width : CGFloat + private let width: CGFloat - internal init(width : CGFloat) { + internal init(width: CGFloat) { self.width = width } internal func path(in rect: CGRect) -> Path { var path = Path() - path.move(to: CGPoint(x: 0, y: 0)) path.addLine(to: CGPoint(x: width, y: 0)) - return path } } diff --git a/Sources/SwiftUICharts/LineChart/Shapes/LineShape.swift b/Sources/SwiftUICharts/LineChart/Shapes/LineShape.swift index 038a730a..6743edb5 100644 --- a/Sources/SwiftUICharts/LineChart/Shapes/LineShape.swift +++ b/Sources/SwiftUICharts/LineChart/Shapes/LineShape.swift @@ -11,29 +11,27 @@ import SwiftUI Main line shape */ internal struct LineShape: Shape where DP: CTStandardDataPointProtocol { - - private let dataPoints : [DP] - private let lineType : LineType - private let isFilled : Bool - - private let minValue : Double - private let range : Double + private let dataPoints: [DP] + private let lineType: LineType + private let isFilled: Bool + private let minValue: Double + private let range: Double private let ignoreZero: Bool - internal init(dataPoints: [DP], - lineType : LineType, - isFilled : Bool, - minValue : Double, - range : Double, - ignoreZero: Bool + internal init( + dataPoints: [DP], + lineType: LineType, + isFilled: Bool, + minValue: Double, + range: Double, + ignoreZero: Bool ) { self.dataPoints = dataPoints - self.lineType = lineType - self.isFilled = isFilled - - self.minValue = minValue - self.range = range + self.lineType = lineType + self.isFilled = isFilled + self.minValue = minValue + self.range = range self.ignoreZero = ignoreZero } @@ -46,7 +44,6 @@ internal struct LineShape: Shape where DP: CTStandardDataPointProtocol { case true: return Path.curvedLineIgnoreZero(rect: rect, dataPoints: dataPoints, minValue: minValue, range: range, isFilled: isFilled) } - case .line: switch ignoreZero { case false: @@ -54,7 +51,6 @@ internal struct LineShape: Shape where DP: CTStandardDataPointProtocol { case true: return Path.straightLineIgnoreZero(rect: rect, dataPoints: dataPoints, minValue: minValue, range: range, isFilled: isFilled) } - } } } @@ -64,30 +60,28 @@ internal struct LineShape: Shape where DP: CTStandardDataPointProtocol { for a Ranged Line Chart. */ internal struct RangedLineFillShape: Shape where DP: CTRangedLineDataPoint { - - private let dataPoints : [DP] - private let lineType : LineType - - private let minValue : Double - private let range : Double + private let dataPoints: [DP] + private let lineType: LineType + private let minValue: Double + private let range: Double private let ignoreZero: Bool - internal init(dataPoints: [DP], - lineType : LineType, - minValue : Double, - range : Double, - ignoreZero: Bool + internal init( + dataPoints: [DP], + lineType: LineType, + minValue: Double, + range: Double, + ignoreZero: Bool ) { self.dataPoints = dataPoints - self.lineType = lineType - self.minValue = minValue - self.range = range + self.lineType = lineType + self.minValue = minValue + self.range = range self.ignoreZero = ignoreZero } - + internal func path(in rect: CGRect) -> Path { - switch lineType { case .curvedLine: switch ignoreZero { @@ -96,7 +90,6 @@ internal struct RangedLineFillShape: Shape where DP: CTRangedLineDataPoint { case true: return Path.curvedLineBoxIgnoreZero(rect: rect, dataPoints: dataPoints, minValue: minValue, range: range) } - case .line: switch ignoreZero { case false: @@ -106,7 +99,6 @@ internal struct RangedLineFillShape: Shape where DP: CTRangedLineDataPoint { } } - } } diff --git a/Sources/SwiftUICharts/LineChart/Shapes/PointShape.swift b/Sources/SwiftUICharts/LineChart/Shapes/PointShape.swift index de7d56a6..a76c7a83 100644 --- a/Sources/SwiftUICharts/LineChart/Shapes/PointShape.swift +++ b/Sources/SwiftUICharts/LineChart/Shapes/PointShape.swift @@ -13,35 +13,32 @@ import SwiftUI internal struct Point: Shape where T: CTLineChartDataSet, T.DataPoint: CTStandardDataPointProtocol { - private let dataSet : T + private let dataSet: T + private let minValue: Double + private let range: Double - private let minValue : Double - private let range : Double - - - internal init(dataSet : T, - minValue : Double, - range : Double + internal init( + dataSet: T, + minValue: Double, + range: Double ) { - self.dataSet = dataSet - self.minValue = minValue - self.range = range + self.dataSet = dataSet + self.minValue = minValue + self.range = range } - + internal func path(in rect: CGRect) -> Path { var path = Path() if dataSet.dataPoints.count >= 2 { - let x = rect.width / CGFloat(dataSet.dataPoints.count-1) - let y = rect.height / CGFloat(range) + let x: CGFloat = rect.width / CGFloat(dataSet.dataPoints.count-1) + let y: CGFloat = rect.height / CGFloat(range) + let offset: CGFloat = dataSet.pointStyle.pointSize / CGFloat(2) - let firstPointX : CGFloat = (CGFloat(0) * x) - dataSet.pointStyle.pointSize / CGFloat(2) - let firstPointY : CGFloat = ((CGFloat(dataSet.dataPoints[0].value - minValue) * -y) + rect.height) - dataSet.pointStyle.pointSize / CGFloat(2) - let firstPoint : CGRect = CGRect(x : firstPointX, - y : firstPointY, - width : dataSet.pointStyle.pointSize, - height: dataSet.pointStyle.pointSize) + let firstPointX: CGFloat = (CGFloat(0) * x) - offset + let firstPointY: CGFloat = ((CGFloat(dataSet.dataPoints[0].value - minValue) * -y) + rect.height) - offset + let firstPoint: CGRect = CGRect(x: firstPointX, y: firstPointY, width: dataSet.pointStyle.pointSize, height: dataSet.pointStyle.pointSize) if !dataSet.style.ignoreZero { pointSwitch(&path, firstPoint) } else { @@ -51,12 +48,9 @@ internal struct Point: Shape where T: CTLineChartDataSet, } for index in 1 ..< dataSet.dataPoints.count - 1 { - let pointX : CGFloat = (CGFloat(index) * x) - dataSet.pointStyle.pointSize / CGFloat(2) - let pointY : CGFloat = ((CGFloat(dataSet.dataPoints[index].value - minValue) * -y) + rect.height) - dataSet.pointStyle.pointSize / CGFloat(2) - let point : CGRect = CGRect(x : pointX, - y : pointY, - width : dataSet.pointStyle.pointSize, - height: dataSet.pointStyle.pointSize) + let pointX: CGFloat = (CGFloat(index) * x) - offset + let pointY: CGFloat = ((CGFloat(dataSet.dataPoints[index].value - minValue) * -y) + rect.height) - offset + let point: CGRect = CGRect(x: pointX, y: pointY, width: dataSet.pointStyle.pointSize, height: dataSet.pointStyle.pointSize) if !dataSet.style.ignoreZero { pointSwitch(&path, point) } else { @@ -66,12 +60,9 @@ internal struct Point: Shape where T: CTLineChartDataSet, } } - let lastPointX : CGFloat = (CGFloat(dataSet.dataPoints.count-1) * x) - dataSet.pointStyle.pointSize / CGFloat(2) - let lastPointY : CGFloat = ((CGFloat(dataSet.dataPoints[dataSet.dataPoints.count-1].value - minValue) * -y) + rect.height) - dataSet.pointStyle.pointSize / CGFloat(2) - let lastPoint : CGRect = CGRect(x : lastPointX, - y : lastPointY, - width : dataSet.pointStyle.pointSize, - height: dataSet.pointStyle.pointSize) + let lastPointX: CGFloat = (CGFloat(dataSet.dataPoints.count-1) * x) - offset + let lastPointY: CGFloat = ((CGFloat(dataSet.dataPoints[dataSet.dataPoints.count-1].value - minValue) * -y) + rect.height) - offset + let lastPoint: CGRect = CGRect(x: lastPointX, y: lastPointY, width: dataSet.pointStyle.pointSize, height: dataSet.pointStyle.pointSize) if !dataSet.style.ignoreZero { pointSwitch(&path, lastPoint) } else { @@ -79,9 +70,7 @@ internal struct Point: Shape where T: CTLineChartDataSet, pointSwitch(&path, lastPoint) } } - } - return path } diff --git a/Sources/SwiftUICharts/LineChart/ViewModifiers/FilledTopLine.swift b/Sources/SwiftUICharts/LineChart/ViewModifiers/FilledTopLine.swift index ceb27735..3d413bfc 100644 --- a/Sources/SwiftUICharts/LineChart/ViewModifiers/FilledTopLine.swift +++ b/Sources/SwiftUICharts/LineChart/ViewModifiers/FilledTopLine.swift @@ -12,25 +12,25 @@ import SwiftUI */ internal struct FilledTopLine: ViewModifier where T: LineChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T private let lineColour: ColourStyle private let strokeStyle: StrokeStyle + private let minValue: Double + private let range: Double - private let minValue : Double - private let range : Double - - internal init(chartData : T, - lineColour: ColourStyle, - strokeStyle: StrokeStyle + internal init( + chartData: T, + lineColour: ColourStyle, + strokeStyle: StrokeStyle ) { - self.chartData = chartData + self.chartData = chartData self.lineColour = lineColour self.strokeStyle = strokeStyle - self.minValue = chartData.minValue - self.range = chartData.range + self.minValue = chartData.minValue + self.range = chartData.range } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal func body(content: Content) -> some View { ZStack { @@ -38,14 +38,12 @@ internal struct FilledTopLine: ViewModifier where T: LineChartData { if lineColour.colourType == .colour, let colour = lineColour.colour { - LineShape(dataPoints: chartData.dataSets.dataPoints, - lineType : chartData.dataSets.style.lineType, - isFilled : false, - minValue : self.minValue, - range : self.range, + lineType: chartData.dataSets.style.lineType, + isFilled: false, + minValue: self.minValue, + range: self.range, ignoreZero: chartData.dataSets.style.ignoreZero) - .scale(y: startAnimation ? 1 : 0, anchor: .bottom) .stroke(colour, style: strokeStyle) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { @@ -54,20 +52,17 @@ internal struct FilledTopLine: ViewModifier where T: LineChartData { .animateOnDisappear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = false } - } else if lineColour.colourType == .gradientColour, - let colours = lineColour.colours, - let startPoint = lineColour.startPoint, - let endPoint = lineColour.endPoint + let colours = lineColour.colours, + let startPoint = lineColour.startPoint, + let endPoint = lineColour.endPoint { - LineShape(dataPoints: chartData.dataSets.dataPoints, - lineType : chartData.dataSets.style.lineType, - isFilled : false, - minValue : self.minValue, - range : self.range, + lineType: chartData.dataSets.style.lineType, + isFilled: false, + minValue: self.minValue, + range: self.range, ignoreZero: chartData.dataSets.style.ignoreZero) - .scale(y: startAnimation ? 1 : 0, anchor: .bottom) .stroke(LinearGradient(gradient: Gradient(colors: colours), startPoint: startPoint, @@ -79,22 +74,18 @@ internal struct FilledTopLine: ViewModifier where T: LineChartData { .animateOnDisappear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = false } - - } else if lineColour.colourType == .gradientStops, - let stops = lineColour.stops, + let stops = lineColour.stops, let startPoint = lineColour.startPoint, - let endPoint = lineColour.endPoint + let endPoint = lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - LineShape(dataPoints: chartData.dataSets.dataPoints, - lineType : chartData.dataSets.style.lineType, - isFilled : false, - minValue : self.minValue, - range : self.range, + lineType: chartData.dataSets.style.lineType, + isFilled: false, + minValue: self.minValue, + range: self.range, ignoreZero: chartData.dataSets.style.ignoreZero) - .scale(y: startAnimation ? 1 : 0, anchor: .bottom) .stroke(LinearGradient(gradient: Gradient(stops: stops), startPoint: startPoint, @@ -106,7 +97,6 @@ internal struct FilledTopLine: ViewModifier where T: LineChartData { .animateOnDisappear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = false } - } content } else { content } @@ -153,4 +143,3 @@ extension View { self.modifier(FilledTopLine(chartData: chartData, lineColour: lineColour, strokeStyle: strokeStyle)) } } - diff --git a/Sources/SwiftUICharts/LineChart/ViewModifiers/PointMarkers.swift b/Sources/SwiftUICharts/LineChart/ViewModifiers/PointMarkers.swift index 82d719f7..581372ac 100644 --- a/Sources/SwiftUICharts/LineChart/ViewModifiers/PointMarkers.swift +++ b/Sources/SwiftUICharts/LineChart/ViewModifiers/PointMarkers.swift @@ -12,15 +12,15 @@ import SwiftUI */ internal struct PointMarkers: ViewModifier where T: CTLineChartDataProtocol { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T - private let minValue : Double - private let range : Double + private let minValue: Double + private let range: Double - internal init(chartData : T) { - self.chartData = chartData - self.minValue = chartData.minValue - self.range = chartData.range + internal init(chartData: T) { + self.chartData = chartData + self.minValue = chartData.minValue + self.range = chartData.range } internal func body(content: Content) -> some View { ZStack { diff --git a/Sources/SwiftUICharts/LineChart/Views/FilledLineChart.swift b/Sources/SwiftUICharts/LineChart/Views/FilledLineChart.swift index 32675140..9da65276 100644 --- a/Sources/SwiftUICharts/LineChart/Views/FilledLineChart.swift +++ b/Sources/SwiftUICharts/LineChart/Views/FilledLineChart.swift @@ -41,75 +41,64 @@ import SwiftUI .legends(chartData: data) ``` */ - public struct FilledLineChart: View where ChartData: LineChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData + + private let minValue: Double + private let range: Double - private let minValue : Double - private let range : Double - /// Initialises a filled line chart /// - Parameter chartData: Must be LineChartData model. public init(chartData: ChartData) { - self.chartData = chartData - self.minValue = chartData.minValue - self.range = chartData.range + self.chartData = chartData + self.minValue = chartData.minValue + self.range = chartData.range self.chartData.isFilled = true } - @State private var startAnimation : Bool = false - + @State private var startAnimation: Bool = false + public var body: some View { - if chartData.isGreaterThanTwo() { - ZStack { - chartData.getAccessibility() - if chartData.dataSets.style.lineColour.colourType == .colour, let colour = chartData.dataSets.style.lineColour.colour { - LineChartColourSubView(chartData: chartData, - dataSet : chartData.dataSets, - minValue : minValue, - range : range, - colour : colour, - isFilled : true) - + dataSet: chartData.dataSets, + minValue: minValue, + range: range, + colour: colour, + isFilled: true) } else if chartData.dataSets.style.lineColour.colourType == .gradientColour, - let colours = chartData.dataSets.style.lineColour.colours, - let startPoint = chartData.dataSets.style.lineColour.startPoint, - let endPoint = chartData.dataSets.style.lineColour.endPoint + let colours = chartData.dataSets.style.lineColour.colours, + let startPoint = chartData.dataSets.style.lineColour.startPoint, + let endPoint = chartData.dataSets.style.lineColour.endPoint { - - LineChartColoursSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : minValue, - range : range, - colours : colours, + LineChartColoursSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: minValue, + range: range, + colours: colours, startPoint: startPoint, - endPoint : endPoint, - isFilled : true) - + endPoint: endPoint, + isFilled: true) } else if chartData.dataSets.style.lineColour.colourType == .gradientStops, - let stops = chartData.dataSets.style.lineColour.stops, + let stops = chartData.dataSets.style.lineColour.stops, let startPoint = chartData.dataSets.style.lineColour.startPoint, - let endPoint = chartData.dataSets.style.lineColour.endPoint + let endPoint = chartData.dataSets.style.lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - - LineChartStopsSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : minValue, - range : range, - stops : stops, - startPoint : startPoint, - endPoint : endPoint, - isFilled : true) - + LineChartStopsSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: minValue, + range: range, + stops: stops, + startPoint: startPoint, + endPoint: endPoint, + isFilled: true) } } } else { CustomNoDataView(chartData: chartData) } diff --git a/Sources/SwiftUICharts/LineChart/Views/LineChartView.swift b/Sources/SwiftUICharts/LineChart/Views/LineChartView.swift index fe1ccbff..2ea7c50c 100644 --- a/Sources/SwiftUICharts/LineChart/Views/LineChartView.swift +++ b/Sources/SwiftUICharts/LineChart/Views/LineChartView.swift @@ -46,63 +46,54 @@ import SwiftUI */ public struct LineChart: View where ChartData: LineChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData /// Initialises a line chart view. /// - Parameter chartData: Must be LineChartData model. public init(chartData: ChartData) { - self.chartData = chartData + self.chartData = chartData } - + public var body: some View { - if chartData.isGreaterThanTwo() { - ZStack { - chartData.getAccessibility() - if chartData.dataSets.style.lineColour.colourType == .colour, let colour = chartData.dataSets.style.lineColour.colour { LineChartColourSubView(chartData: chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - colour : colour, - isFilled : false) - - + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + colour: colour, + isFilled: false) } else if chartData.dataSets.style.lineColour.colourType == .gradientColour, - let colours = chartData.dataSets.style.lineColour.colours, - let startPoint = chartData.dataSets.style.lineColour.startPoint, - let endPoint = chartData.dataSets.style.lineColour.endPoint + let colours = chartData.dataSets.style.lineColour.colours, + let startPoint = chartData.dataSets.style.lineColour.startPoint, + let endPoint = chartData.dataSets.style.lineColour.endPoint { - - LineChartColoursSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - colours : colours, - startPoint : startPoint, - endPoint : endPoint, - isFilled : false) - + LineChartColoursSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + colours: colours, + startPoint: startPoint, + endPoint: endPoint, + isFilled: false) } else if chartData.dataSets.style.lineColour.colourType == .gradientStops, let stops = chartData.dataSets.style.lineColour.stops, let startPoint = chartData.dataSets.style.lineColour.startPoint, let endPoint = chartData.dataSets.style.lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - - LineChartStopsSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - stops : stops, + LineChartStopsSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + stops: stops, startPoint: startPoint, - endPoint : endPoint, - isFilled : false) + endPoint: endPoint, + isFilled: false) } } } else { CustomNoDataView(chartData: chartData) } diff --git a/Sources/SwiftUICharts/LineChart/Views/MultiLineChart.swift b/Sources/SwiftUICharts/LineChart/Views/MultiLineChart.swift index a75bcfaf..b70c1ea4 100644 --- a/Sources/SwiftUICharts/LineChart/Views/MultiLineChart.swift +++ b/Sources/SwiftUICharts/LineChart/Views/MultiLineChart.swift @@ -43,73 +43,62 @@ import SwiftUI */ public struct MultiLineChart: View where ChartData: MultiLineChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData + + private let minValue: Double + private let range: Double - private let minValue : Double - private let range : Double - /// Initialises a multi-line, line chart. /// - Parameter chartData: Must be MultiLineChartData model. public init(chartData: ChartData) { - self.chartData = chartData - self.minValue = chartData.minValue - self.range = chartData.range + self.chartData = chartData + self.minValue = chartData.minValue + self.range = chartData.range } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false public var body: some View { - if chartData.isGreaterThanTwo() { - ZStack { - chartData.getAccessibility() - ForEach(chartData.dataSets.dataSets, id: \.id) { dataSet in - if dataSet.style.lineColour.colourType == .colour, let colour = dataSet.style.lineColour.colour { - LineChartColourSubView(chartData: chartData, - dataSet : dataSet, - minValue : minValue, - range : range, - colour : colour, - isFilled : false) - + dataSet: dataSet, + minValue: minValue, + range: range, + colour: colour, + isFilled: false) } else if dataSet.style.lineColour.colourType == .gradientColour, - let colours = dataSet.style.lineColour.colours, - let startPoint = dataSet.style.lineColour.startPoint, - let endPoint = dataSet.style.lineColour.endPoint + let colours = dataSet.style.lineColour.colours, + let startPoint = dataSet.style.lineColour.startPoint, + let endPoint = dataSet.style.lineColour.endPoint { - - LineChartColoursSubView(chartData : chartData, - dataSet : dataSet, - minValue : minValue, - range : range, - colours : colours, + LineChartColoursSubView(chartData: chartData, + dataSet: dataSet, + minValue: minValue, + range: range, + colours: colours, startPoint: startPoint, - endPoint : endPoint, - isFilled : false) - + endPoint: endPoint, + isFilled: false) } else if dataSet.style.lineColour.colourType == .gradientStops, - let stops = dataSet.style.lineColour.stops, + let stops = dataSet.style.lineColour.stops, let startPoint = dataSet.style.lineColour.startPoint, - let endPoint = dataSet.style.lineColour.endPoint + let endPoint = dataSet.style.lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - - LineChartStopsSubView(chartData : chartData, - dataSet : dataSet, - minValue : minValue, - range : range, - stops : stops, + LineChartStopsSubView(chartData: chartData, + dataSet: dataSet, + minValue: minValue, + range: range, + stops: stops, startPoint: startPoint, - endPoint : endPoint, - isFilled : false) - + endPoint: endPoint, + isFilled: false) } } } diff --git a/Sources/SwiftUICharts/LineChart/Views/RangedLineChart.swift b/Sources/SwiftUICharts/LineChart/Views/RangedLineChart.swift index fd295e8e..f396dc7f 100644 --- a/Sources/SwiftUICharts/LineChart/Views/RangedLineChart.swift +++ b/Sources/SwiftUICharts/LineChart/Views/RangedLineChart.swift @@ -46,111 +46,94 @@ import SwiftUI */ public struct RangedLineChart: View where ChartData: RangedLineChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData /// Initialises a line chart view. /// - Parameter chartData: Must be RangedLineChartData model. public init(chartData: ChartData) { - self.chartData = chartData + self.chartData = chartData } public var body: some View { - if chartData.isGreaterThanTwo() { - ZStack { - chartData.getAccessibility() - // MARK: Ranged Box if chartData.dataSets.style.fillColour.colourType == .colour, let colour = chartData.dataSets.style.fillColour.colour { - RangedLineFillShape(dataPoints: chartData.dataSets.dataPoints, lineType: chartData.dataSets.style.lineType, minValue: chartData.minValue, range: chartData.range, ignoreZero: chartData.dataSets.style.ignoreZero) .fill(colour) - - } else if chartData.dataSets.style.fillColour.colourType == .gradientColour, - let colours = chartData.dataSets.style.fillColour.colours, - let startPoint = chartData.dataSets.style.fillColour.startPoint, - let endPoint = chartData.dataSets.style.fillColour.endPoint + let colours = chartData.dataSets.style.fillColour.colours, + let startPoint = chartData.dataSets.style.fillColour.startPoint, + let endPoint = chartData.dataSets.style.fillColour.endPoint { - RangedLineFillShape(dataPoints: chartData.dataSets.dataPoints, - lineType: chartData.dataSets.style.lineType, - minValue: chartData.minValue, - range: chartData.range, - ignoreZero: chartData.dataSets.style.ignoreZero) + lineType: chartData.dataSets.style.lineType, + minValue: chartData.minValue, + range: chartData.range, + ignoreZero: chartData.dataSets.style.ignoreZero) .fill(LinearGradient(gradient: Gradient(colors: colours), startPoint: startPoint, endPoint: endPoint)) - } else if chartData.dataSets.style.fillColour.colourType == .gradientStops, - let stops = chartData.dataSets.style.fillColour.stops, + let stops = chartData.dataSets.style.fillColour.stops, let startPoint = chartData.dataSets.style.fillColour.startPoint, - let endPoint = chartData.dataSets.style.fillColour.endPoint + let endPoint = chartData.dataSets.style.fillColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - RangedLineFillShape(dataPoints: chartData.dataSets.dataPoints, - lineType: chartData.dataSets.style.lineType, - minValue: chartData.minValue, - range: chartData.range, - ignoreZero: chartData.dataSets.style.ignoreZero) + lineType: chartData.dataSets.style.lineType, + minValue: chartData.minValue, + range: chartData.range, + ignoreZero: chartData.dataSets.style.ignoreZero) .fill(LinearGradient(gradient: Gradient(stops: stops), startPoint: startPoint, endPoint: endPoint)) - } - + // MARK: Main Line if chartData.dataSets.style.lineColour.colourType == .colour, let colour = chartData.dataSets.style.lineColour.colour { - LineChartColourSubView(chartData: chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - colour : colour, - isFilled : false) - + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + colour: colour, + isFilled: false) } else if chartData.dataSets.style.lineColour.colourType == .gradientColour, - let colours = chartData.dataSets.style.lineColour.colours, - let startPoint = chartData.dataSets.style.lineColour.startPoint, - let endPoint = chartData.dataSets.style.lineColour.endPoint + let colours = chartData.dataSets.style.lineColour.colours, + let startPoint = chartData.dataSets.style.lineColour.startPoint, + let endPoint = chartData.dataSets.style.lineColour.endPoint { - - LineChartColoursSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - colours : colours, - startPoint : startPoint, - endPoint : endPoint, - isFilled : false) - + LineChartColoursSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + colours: colours, + startPoint: startPoint, + endPoint: endPoint, + isFilled: false) } else if chartData.dataSets.style.lineColour.colourType == .gradientStops, - let stops = chartData.dataSets.style.lineColour.stops, + let stops = chartData.dataSets.style.lineColour.stops, let startPoint = chartData.dataSets.style.lineColour.startPoint, - let endPoint = chartData.dataSets.style.lineColour.endPoint + let endPoint = chartData.dataSets.style.lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - - LineChartStopsSubView(chartData : chartData, - dataSet : chartData.dataSets, - minValue : chartData.minValue, - range : chartData.range, - stops : stops, + LineChartStopsSubView(chartData: chartData, + dataSet: chartData.dataSets, + minValue: chartData.minValue, + range: chartData.range, + stops: stops, startPoint: startPoint, - endPoint : endPoint, - isFilled : false) - + endPoint: endPoint, + isFilled: false) } } } else { CustomNoDataView(chartData: chartData) } diff --git a/Sources/SwiftUICharts/LineChart/Views/SubViews/LineChartSubViews.swift b/Sources/SwiftUICharts/LineChart/Views/SubViews/LineChartSubViews.swift index 1120261f..9ac9b241 100644 --- a/Sources/SwiftUICharts/LineChart/Views/SubViews/LineChartSubViews.swift +++ b/Sources/SwiftUICharts/LineChart/Views/SubViews/LineChartSubViews.swift @@ -16,38 +16,37 @@ import SwiftUI internal struct LineChartColourSubView: View where CD: CTLineChartDataProtocol, DS: CTLineChartDataSet, DS.DataPoint: CTStandardDataPointProtocol { + private let chartData: CD + private let dataSet: DS + private let minValue: Double + private let range: Double + private let colour: Color + private let isFilled: Bool - private let chartData : CD - private let dataSet : DS - private let minValue : Double - private let range : Double - private let colour : Color - private let isFilled : Bool - - internal init(chartData : CD, - dataSet : DS, - minValue : Double, - range : Double, - colour : Color, - isFilled : Bool + internal init( + chartData: CD, + dataSet: DS, + minValue: Double, + range: Double, + colour: Color, + isFilled: Bool ) { - self.chartData = chartData - self.dataSet = dataSet - self.minValue = minValue - self.range = range - self.colour = colour - self.isFilled = isFilled + self.chartData = chartData + self.dataSet = dataSet + self.minValue = minValue + self.range = range + self.colour = colour + self.isFilled = isFilled } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { - LineShape(dataPoints: dataSet.dataPoints, - lineType : dataSet.style.lineType, - isFilled : isFilled, - minValue : minValue, - range : range, + lineType: dataSet.style.lineType, + isFilled: isFilled, + minValue: minValue, + range: range, ignoreZero: dataSet.style.ignoreZero) .ifElse(isFilled, if: { $0.scale(y: startAnimation ? 1 : 0, anchor: .bottom) @@ -78,46 +77,43 @@ internal struct LineChartColourSubView: View where CD: CTLineChartDataPr internal struct LineChartColoursSubView: View where CD: CTLineChartDataProtocol, DS: CTLineChartDataSet, DS.DataPoint: CTStandardDataPointProtocol { + private let chartData: CD + private let dataSet: DS + private let minValue: Double + private let range: Double + private let colours: [Color] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let isFilled: Bool - private let chartData : CD - private let dataSet : DS - - private let minValue : Double - private let range : Double - private let colours : [Color] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - - private let isFilled : Bool - - internal init(chartData : CD, - dataSet : DS, - minValue : Double, - range : Double, - colours : [Color], - startPoint: UnitPoint, - endPoint : UnitPoint, - isFilled : Bool + internal init( + chartData: CD, + dataSet: DS, + minValue: Double, + range: Double, + colours: [Color], + startPoint: UnitPoint, + endPoint: UnitPoint, + isFilled: Bool ) { - self.chartData = chartData - self.dataSet = dataSet - self.minValue = minValue - self.range = range - self.colours = colours + self.chartData = chartData + self.dataSet = dataSet + self.minValue = minValue + self.range = range + self.colours = colours self.startPoint = startPoint - self.endPoint = endPoint - self.isFilled = isFilled + self.endPoint = endPoint + self.isFilled = isFilled } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { - LineShape(dataPoints: dataSet.dataPoints, - lineType : dataSet.style.lineType, - isFilled : isFilled, - minValue : minValue, - range : range, + lineType: dataSet.style.lineType, + isFilled: isFilled, + minValue: minValue, + range: range, ignoreZero: dataSet.style.ignoreZero) .ifElse(isFilled, if: { $0 @@ -133,8 +129,6 @@ internal struct LineChartColoursSubView: View where CD: CTLineChartDataP endPoint: endPoint), style: dataSet.style.strokeStyle.strokeToStrokeStyle()) }) - - .background(Color(.gray).opacity(0.000000001)) .if(chartData.viewData.hasXAxisLabels) { $0.xAxisBorder(chartData: chartData) } .if(chartData.viewData.hasYAxisLabels) { $0.yAxisBorder(chartData: chartData) } @@ -156,48 +150,44 @@ internal struct LineChartColoursSubView: View where CD: CTLineChartDataP internal struct LineChartStopsSubView: View where CD: CTLineChartDataProtocol, DS: CTLineChartDataSet, DS.DataPoint: CTStandardDataPointProtocol { - - private let chartData : CD - private let dataSet : DS - - private let minValue : Double - private let range : Double - private let stops : [Gradient.Stop] - private let startPoint : UnitPoint - private let endPoint : UnitPoint - - private let isFilled : Bool + private let chartData: CD + private let dataSet: DS + private let minValue: Double + private let range: Double + private let stops: [Gradient.Stop] + private let startPoint: UnitPoint + private let endPoint: UnitPoint + private let isFilled: Bool - internal init(chartData : CD, - dataSet : DS, - minValue : Double, - range : Double, - stops : [Gradient.Stop], - startPoint: UnitPoint, - endPoint : UnitPoint, - isFilled : Bool + internal init( + chartData: CD, + dataSet: DS, + minValue: Double, + range: Double, + stops: [Gradient.Stop], + startPoint: UnitPoint, + endPoint: UnitPoint, + isFilled: Bool ) { - self.chartData = chartData - self.dataSet = dataSet - self.minValue = minValue - self.range = range - self.stops = stops + self.chartData = chartData + self.dataSet = dataSet + self.minValue = minValue + self.range = range + self.stops = stops self.startPoint = startPoint - self.endPoint = endPoint - self.isFilled = isFilled + self.endPoint = endPoint + self.isFilled = isFilled } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { - LineShape(dataPoints: dataSet.dataPoints, - lineType : dataSet.style.lineType, - isFilled : isFilled, - minValue : minValue, - range : range, + lineType: dataSet.style.lineType, + isFilled: isFilled, + minValue: minValue, + range: range, ignoreZero: dataSet.style.ignoreZero) - .ifElse(isFilled, if: { $0 .scale(y: startAnimation ? 1 : 0, anchor: .bottom) @@ -212,7 +202,6 @@ internal struct LineChartStopsSubView: View where CD: CTLineChartDataPro endPoint: endPoint), style: dataSet.style.strokeStyle.strokeToStrokeStyle()) }) - .background(Color(.gray).opacity(0.000000001)) .if(chartData.viewData.hasXAxisLabels) { $0.xAxisBorder(chartData: chartData) } .if(chartData.viewData.hasYAxisLabels) { $0.yAxisBorder(chartData: chartData) } diff --git a/Sources/SwiftUICharts/LineChart/Views/SubViews/PointsSubView.swift b/Sources/SwiftUICharts/LineChart/Views/SubViews/PointsSubView.swift index af4738b2..77c8cd26 100644 --- a/Sources/SwiftUICharts/LineChart/Views/SubViews/PointsSubView.swift +++ b/Sources/SwiftUICharts/LineChart/Views/SubViews/PointsSubView.swift @@ -13,17 +13,18 @@ import SwiftUI internal struct PointsSubView: View where DS: CTLineChartDataSet, DS.DataPoint: CTStandardDataPointProtocol { - private let dataSets : DS - private let minValue : Double - private let range : Double + private let dataSets: DS + private let minValue: Double + private let range: Double private let animation: Animation - private let isFilled : Bool - - internal init(dataSets : DS, - minValue : Double, - range : Double, - animation : Animation, - isFilled : Bool + private let isFilled: Bool + + internal init( + dataSets: DS, + minValue: Double, + range: Double, + animation: Animation, + isFilled: Bool ) { self.dataSets = dataSets self.minValue = minValue @@ -32,15 +33,14 @@ internal struct PointsSubView: View where DS: CTLineChartDataSet, self.isFilled = isFilled } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal var body: some View { switch dataSets.pointStyle.pointType { case .filled: - - Point(dataSet : dataSets, - minValue : minValue, - range : range) + Point(dataSet: dataSets, + minValue: minValue, + range: range) .ifElse(!isFilled, if: { $0.trim(to: startAnimation ? 1 : 0) .fill(dataSets.pointStyle.fillColour) @@ -54,12 +54,10 @@ internal struct PointsSubView: View where DS: CTLineChartDataSet, .animateOnDisappear(using: animation) { self.startAnimation = false } - case .outline: - - Point(dataSet : dataSets, - minValue : minValue, - range : range) + Point(dataSet: dataSets, + minValue: minValue, + range: range) .ifElse(!isFilled, if: { $0.trim(to: startAnimation ? 1 : 0) .stroke(dataSets.pointStyle.borderColour, lineWidth: dataSets.pointStyle.lineWidth) @@ -73,12 +71,10 @@ internal struct PointsSubView: View where DS: CTLineChartDataSet, .animateOnDisappear(using: animation) { self.startAnimation = false } - case .filledOutLine: - - Point(dataSet : dataSets, - minValue : minValue, - range : range) + Point(dataSet: dataSets, + minValue: minValue, + range: range) .ifElse(!isFilled, if: { $0.trim(to: startAnimation ? 1 : 0) .stroke(dataSets.pointStyle.borderColour, lineWidth: dataSets.pointStyle.lineWidth) @@ -86,10 +82,9 @@ internal struct PointsSubView: View where DS: CTLineChartDataSet, $0.scale(y: startAnimation ? 1 : 0, anchor: .bottom) .stroke(dataSets.pointStyle.borderColour, lineWidth: dataSets.pointStyle.lineWidth) }) - - .background(Point(dataSet : dataSets, - minValue : minValue, - range : range) + .background(Point(dataSet: dataSets, + minValue: minValue, + range: range) .foregroundColor(dataSets.pointStyle.fillColour) ) .animateOnAppear(using: animation) { @@ -101,4 +96,3 @@ internal struct PointsSubView: View where DS: CTLineChartDataSet, } } } - diff --git a/Sources/SwiftUICharts/PieChart/Extras/PieChartEnums.swift b/Sources/SwiftUICharts/PieChart/Extras/PieChartEnums.swift new file mode 100644 index 00000000..f8e8b642 --- /dev/null +++ b/Sources/SwiftUICharts/PieChart/Extras/PieChartEnums.swift @@ -0,0 +1,48 @@ +// +// PieChartEnums.swift +// +// +// Created by Will Dale on 21/04/2021. +// + +import SwiftUI + +/** + Option to add overlays on top of the segment. + + ``` + case none // No overlay + case barStyle // Text overlay + case dataPoints // System icon overlay + ``` + */ +public enum OverlayType: Hashable { + /// No overlay + case none + + /** + Text overlay + + # Parameters: + - text: Text the use as label. + - colour: Foreground colour. + - font: System font. + - rFactor: Distance the from center of chart. + 0 is center, 1 is perimeter. It can go beyond 1 to + place it outside. + */ + case label(text: String, colour: Color = .primary, font: Font = .caption, rFactor: CGFloat = 0.75) + + /** + System icon overlay + + # Parameters: + - systemName: SF Symbols name. + - colour: Foreground colour. + - size: Image frame size. + - rFactor: Distance the from center of chart. + 0 is center, 1 is perimeter. It can go beyond 1 to + place it outside. + */ + case icon(systemName: String, colour: Color = .primary, size: CGFloat = 30, rFactor: CGFloat = 0.75) +} diff --git a/Sources/SwiftUICharts/PieChart/Models/ChartData/DoughnutChartData.swift b/Sources/SwiftUICharts/PieChart/Models/ChartData/DoughnutChartData.swift index 4124cec2..bb3210a5 100644 --- a/Sources/SwiftUICharts/PieChart/Models/ChartData/DoughnutChartData.swift +++ b/Sources/SwiftUICharts/PieChart/Models/ChartData/DoughnutChartData.swift @@ -13,17 +13,17 @@ import SwiftUI This model contains the data and styling information for a doughnut chart. */ public final class DoughnutChartData: CTDoughnutChartDataProtocol { - + // MARK: Properties - public var id : UUID = UUID() - @Published public final var dataSets : PieDataSet - @Published public final var metadata : ChartMetadata - @Published public final var chartStyle : DoughnutChartStyle - @Published public final var legends : [LegendData] - @Published public final var infoView : InfoViewData - + public var id: UUID = UUID() + @Published public final var dataSets: PieDataSet + @Published public final var metadata: ChartMetadata + @Published public final var chartStyle: DoughnutChartStyle + @Published public final var legends: [LegendData] + @Published public final var infoView: InfoViewData + public final var noDataText: Text - public final var chartType : (chartType: ChartType, dataSetType: DataSetType) + public final var chartType: (chartType: ChartType, dataSetType: DataSetType) // MARK: Initializer /// Initialises Doughnut Chart data. @@ -31,45 +31,45 @@ public final class DoughnutChartData: CTDoughnutChartDataProtocol { /// - Parameters: /// - dataSets: Data to draw and style the chart. /// - metadata: Data model containing the charts Title, Subtitle and the Title for Legend. - /// - chartStyle : The style data for the aesthetic of the chart. - /// - noDataText : Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : PieDataSet, - metadata : ChartMetadata, - chartStyle : DoughnutChartStyle = DoughnutChartStyle(), - noDataText : Text + /// - chartStyle: The style data for the aesthetic of the chart. + /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. + public init( + dataSets: PieDataSet, + metadata: ChartMetadata, + chartStyle: DoughnutChartStyle = DoughnutChartStyle(), + noDataText: Text ) { - self.dataSets = dataSets - self.metadata = metadata - self.chartStyle = chartStyle - self.legends = [LegendData]() - self.infoView = InfoViewData() - self.noDataText = noDataText - self.chartType = (chartType: .pie, dataSetType: .single) + self.dataSets = dataSets + self.metadata = metadata + self.chartStyle = chartStyle + self.legends = [LegendData]() + self.infoView = InfoViewData() + self.noDataText = noDataText + self.chartType = (chartType: .pie, dataSetType: .single) self.setupLegends() self.makeDataPoints() } public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { EmptyView() } - - public typealias Set = PieDataSet - public typealias DataPoint = PieChartDataPoint - public typealias CTStyle = DoughnutChartStyle + + public typealias Set = PieDataSet + public typealias DataPoint = PieChartDataPoint + public typealias CTStyle = DoughnutChartStyle } // MARK: - Touch extension DoughnutChartData { public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [PieChartDataPoint] = [] let touchDegree = degree(from: touchLocation, in: chartSize) - - let dataPoint = self.dataSets.dataPoints.first(where: { $0.startAngle * Double(180 / Double.pi) <= Double(touchDegree) && ($0.startAngle * Double(180 / Double.pi)) + ($0.amount * Double(180 / Double.pi)) >= Double(touchDegree) } ) - if let data = dataPoint { - var finalDataPoint = data - finalDataPoint.legendTag = dataSets.legendTitle - points.append(finalDataPoint) + let index = self.dataSets.dataPoints.firstIndex(where:) { + let start = $0.startAngle * Double(180 / Double.pi) <= Double(touchDegree) + let end = ($0.startAngle * Double(180 / Double.pi)) + ($0.amount * Double(180 / Double.pi)) >= Double(touchDegree) + return start && end } - self.infoView.touchOverlayInfo = points + guard let wrappedIndex = index else { return } + self.dataSets.dataPoints[wrappedIndex].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [self.dataSets.dataPoints[wrappedIndex]] } public func getPointLocation(dataSet: PieDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { return nil diff --git a/Sources/SwiftUICharts/PieChart/Models/ChartData/PieChartData.swift b/Sources/SwiftUICharts/PieChart/Models/ChartData/PieChartData.swift index c6546364..9f9ab2f2 100644 --- a/Sources/SwiftUICharts/PieChart/Models/ChartData/PieChartData.swift +++ b/Sources/SwiftUICharts/PieChart/Models/ChartData/PieChartData.swift @@ -15,13 +15,13 @@ import SwiftUI public final class PieChartData: CTPieChartDataProtocol { // MARK: Properties - public var id : UUID = UUID() - @Published public final var dataSets : PieDataSet - @Published public final var metadata : ChartMetadata - @Published public final var chartStyle : PieChartStyle - @Published public final var legends : [LegendData] - @Published public final var infoView : InfoViewData - + public var id: UUID = UUID() + @Published public final var dataSets: PieDataSet + @Published public final var metadata: ChartMetadata + @Published public final var chartStyle: PieChartStyle + @Published public final var legends: [LegendData] + @Published public final var infoView: InfoViewData + public final var noDataText: Text public final var chartType: (chartType: ChartType, dataSetType: DataSetType) @@ -31,48 +31,49 @@ public final class PieChartData: CTPieChartDataProtocol { /// - Parameters: /// - dataSets: Data to draw and style the chart. /// - metadata: Data model containing the charts Title, Subtitle and the Title for Legend. - /// - chartStyle : The style data for the aesthetic of the chart. - /// - noDataText : Customisable Text to display when where is not enough data to draw the chart. - public init(dataSets : PieDataSet, - metadata : ChartMetadata, - chartStyle : PieChartStyle = PieChartStyle(), - noDataText : Text = Text("No Data") + /// - chartStyle: The style data for the aesthetic of the chart. + /// - noDataText: Customisable Text to display when where is not enough data to draw the chart. + public init( + dataSets: PieDataSet, + metadata: ChartMetadata, + chartStyle: PieChartStyle = PieChartStyle(), + noDataText: Text = Text("No Data") ) { - self.dataSets = dataSets - self.metadata = metadata - self.chartStyle = chartStyle - self.legends = [LegendData]() - self.infoView = InfoViewData() - self.noDataText = noDataText - self.chartType = (chartType: .pie, dataSetType: .single) + self.dataSets = dataSets + self.metadata = metadata + self.chartStyle = chartStyle + self.legends = [LegendData]() + self.infoView = InfoViewData() + self.noDataText = noDataText + self.chartType = (chartType: .pie, dataSetType: .single) self.setupLegends() - self.makeDataPoints() } public final func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> some View { EmptyView() } - - public typealias Set = PieDataSet + + public typealias Set = PieDataSet public typealias DataPoint = PieChartDataPoint - public typealias CTStyle = PieChartStyle + public typealias CTStyle = PieChartStyle } // MARK: - Touch extension PieChartData { + public final func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) { - var points : [PieChartDataPoint] = [] let touchDegree = degree(from: touchLocation, in: chartSize) - - let dataPoint = self.dataSets.dataPoints.first(where: { $0.startAngle * Double(180 / Double.pi) <= Double(touchDegree) && ($0.startAngle * Double(180 / Double.pi)) + ($0.amount * Double(180 / Double.pi)) >= Double(touchDegree) } ) - if let data = dataPoint { - var finalDataPoint = data - finalDataPoint.legendTag = dataSets.legendTitle - points.append(finalDataPoint) + let index = self.dataSets.dataPoints.firstIndex(where:) { + let start = $0.startAngle * Double(180 / Double.pi) <= Double(touchDegree) + let end = ($0.startAngle * Double(180 / Double.pi)) + ($0.amount * Double(180 / Double.pi)) >= Double(touchDegree) + return start && end } - self.infoView.touchOverlayInfo = points + guard let wrappedIndex = index else { return } + self.dataSets.dataPoints[wrappedIndex].legendTag = dataSets.legendTitle + self.infoView.touchOverlayInfo = [self.dataSets.dataPoints[wrappedIndex]] } - public final func getPointLocation(dataSet: PieDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { + + public func getPointLocation(dataSet: PieDataSet, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? { return nil } } diff --git a/Sources/SwiftUICharts/PieChart/Models/DataPoints/PieChartDataPoint.swift b/Sources/SwiftUICharts/PieChart/Models/DataPoints/PieChartDataPoint.swift index 11d867e2..419c7df3 100644 --- a/Sources/SwiftUICharts/PieChart/Models/DataPoints/PieChartDataPoint.swift +++ b/Sources/SwiftUICharts/PieChart/Models/DataPoints/PieChartDataPoint.swift @@ -12,15 +12,15 @@ import SwiftUI */ public struct PieChartDataPoint: CTPieDataPoint { - public var id : UUID = UUID() - public var value : Double - public var description : String? - public var date : Date? - public var colour : Color - public var startAngle : Double = 0 - public var amount : Double = 0 - - public var legendTag : String = "" + public var id: UUID = UUID() + public var value: Double + public var description: String? + public var date: Date? + public var colour: Color + public var label: OverlayType + public var startAngle: Double = 0 + public var amount: Double = 0 + public var legendTag: String = "" /// Data model for a single data point for a pie chart. /// - Parameters: @@ -28,19 +28,22 @@ public struct PieChartDataPoint: CTPieDataPoint { /// - description: A longer label that can be shown on touch input. /// - date: Date of the data point if any data based calculations are required. /// - colour: Colour of the segment. - public init(value : Double, - description : String? = nil, - date : Date? = nil, - colour : Color = Color.red + /// - label: Option to add overlays on top of the segment. + public init( + value: Double, + description: String? = nil, + date: Date? = nil, + colour: Color = Color.red, + label: OverlayType = .none ) { - self.value = value + self.value = value self.description = description - self.date = date - self.colour = colour + self.date = date + self.colour = colour + self.label = label } } - extension PieChartDataPoint { // Remove legend tag from compare public static func == (left: PieChartDataPoint, right: PieChartDataPoint) -> Bool { @@ -49,6 +52,7 @@ extension PieChartDataPoint { (left.startAngle == right.startAngle) && (left.value == right.value) && (left.date == right.date) && - (left.description == right.description) - } + (left.description == right.description) && + (left.label == right.label) + } } diff --git a/Sources/SwiftUICharts/PieChart/Models/DataSets/PieDataSet.swift b/Sources/SwiftUICharts/PieChart/Models/DataSets/PieDataSet.swift index 2f19f8c3..f27f64a7 100644 --- a/Sources/SwiftUICharts/PieChart/Models/DataSets/PieDataSet.swift +++ b/Sources/SwiftUICharts/PieChart/Models/DataSets/PieDataSet.swift @@ -11,24 +11,23 @@ import SwiftUI Data set for a pie chart. */ public struct PieDataSet: CTSingleDataSetProtocol { - - public var id : UUID = UUID() - public var dataPoints : [PieChartDataPoint] - public var legendTitle : String + + public var id: UUID = UUID() + public var dataPoints: [PieChartDataPoint] + public var legendTitle: String /// Initialises a new data set for a standard pie chart. /// - Parameters: /// - dataPoints: Array of elements. /// - legendTitle: Label for the data in legend. - public init(dataPoints : [PieChartDataPoint], - legendTitle : String//, + public init( + dataPoints: [PieChartDataPoint], + legendTitle: String ) { - self.dataPoints = dataPoints - self.legendTitle = legendTitle + self.dataPoints = dataPoints + self.legendTitle = legendTitle } public typealias ID = UUID public typealias DataPoint = PieChartDataPoint } - - diff --git a/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocols.swift b/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocols.swift index 8059b456..484e255b 100644 --- a/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocols.swift +++ b/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocols.swift @@ -16,12 +16,12 @@ public protocol CTPieDoughnutChartDataProtocol: CTChartData {} /** A protocol to extend functionality of `CTPieDoughnutChartDataProtocol` specifically for Pie Charts. */ -public protocol CTPieChartDataProtocol : CTPieDoughnutChartDataProtocol {} +public protocol CTPieChartDataProtocol: CTPieDoughnutChartDataProtocol {} /** A protocol to extend functionality of `CTPieDoughnutChartDataProtocol` specifically for Doughnut Charts. */ -public protocol CTDoughnutChartDataProtocol : CTPieDoughnutChartDataProtocol {} +public protocol CTDoughnutChartDataProtocol: CTPieDoughnutChartDataProtocol {} // MARK: - DataPoints @@ -30,20 +30,28 @@ public protocol CTDoughnutChartDataProtocol : CTPieDoughnutChartDataProtocol {} */ public protocol CTPieDataPoint: CTStandardDataPointProtocol, CTnotRanged { + /** + Colour of the segment + */ + var colour: Color { get set } + /** Where the data point should start drawing from based on where the prvious one finished. In radians. */ - var startAngle : Double { get set } + var startAngle: Double { get set } /** The data points value in radians. */ - var amount : Double { get set } + var amount: Double { get set } - var colour : Color { get set } + /** + Option to add overlays on top of the segment. + */ + var label: OverlayType { get set } } @@ -71,6 +79,6 @@ public protocol CTDoughnutChartStyle: CTPieAndDoughnutChartStyle { /** Width / Delta of the Doughnut Chart - */ + */ var strokeWidth: CGFloat { get set } } diff --git a/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocolsExtentions.swift b/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocolsExtentions.swift index 6e952577..cc3cfda9 100644 --- a/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocolsExtentions.swift +++ b/Sources/SwiftUICharts/PieChart/Models/Protocols/PieChartProtocolsExtentions.swift @@ -10,19 +10,21 @@ import SwiftUI // MARK: - Extentions extension CTPieDoughnutChartDataProtocol where Set == PieDataSet, DataPoint == PieChartDataPoint { - + /** Sets up the data points in a way that can be sent to renderer for drawing. It configures each data point with startAngle and amount variables in radians. */ internal func makeDataPoints() { - let total = self.dataSets.dataPoints.reduce(0) { $0 + $1.value } - var startAngle = -Double.pi / 2 - self.dataSets.dataPoints.indices.forEach { (point) in - let amount = .pi * 2 * (self.dataSets.dataPoints[point].value / total) - self.dataSets.dataPoints[point].amount = amount - self.dataSets.dataPoints[point].startAngle = startAngle + let total = self.dataSets.dataPoints + .map(\.value) + .reduce(0, +) + var startAngle = -Double.pi / 2 + self.dataSets.dataPoints.indices.forEach { index in + let amount = .pi * 2 * (self.dataSets.dataPoints[index].value / total) + self.dataSets.dataPoints[index].amount = amount + self.dataSets.dataPoints[index].startAngle = startAngle startAngle += amount } } @@ -32,12 +34,12 @@ extension CTPieDoughnutChartDataProtocol where Set == PieDataSet, DataPoint == P # Reference [Atan2](http://www.cplusplus.com/reference/cmath/atan2/) - + [Rotate to north](https://stackoverflow.com/a/25398191) - Parameters: - - touchLocation: Current location of the touch. - - rect: The size of the chart view as the parent view. + - touchLocation: Current location of the touch. + - rect: The size of the chart view as the parent view. - Returns: Degrees around the chart. */ func degree(from touchLocation: CGPoint, in rect: CGRect) -> CGFloat { @@ -58,15 +60,72 @@ extension CTPieDoughnutChartDataProtocol where Self.Set.DataPoint.ID == UUID, Self.Set: CTSingleDataSetProtocol, Self.Set.DataPoint: CTPieDataPoint { internal func setupLegends() { - for data in dataSets.dataPoints { - if let legend = data.description { - self.legends.append(LegendData(id : data.id, - legend : legend, - colour : ColourStyle(colour: data.colour), - strokeStyle: nil, - prioity : 1, - chartType : .pie)) - } + dataSets.dataPoints.forEach { dataPoint in + guard let legend = dataPoint.description else { return } + self.legends.append(LegendData(id: dataPoint.id, + legend: legend, + colour: ColourStyle(colour: dataPoint.colour), + strokeStyle: nil, + prioity: 1, + chartType: .pie)) } } } + +extension View { + internal func overlay( + dataPoint: PieChartDataPoint, + chartData: CD, + rect: CGRect + ) -> some View { + self + .overlay( + Group { + switch dataPoint.label { + case .none: + EmptyView() + case .label(let text, let colour, let font, let rFactor): + Text(text) + .font(font) + .foregroundColor(colour) + .position(chartData.getOverlayPosition(rect: rect, + startRad: dataPoint.startAngle, + amountRad: dataPoint.amount, + rFactor: rFactor)) + case .icon(let name, let colour, let size, let rFactor): + Image(systemName: name) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: size, height: size) + .foregroundColor(colour) + .position(chartData.getOverlayPosition(rect: rect, + startRad: dataPoint.startAngle, + amountRad: dataPoint.amount, + rFactor: rFactor)) + } + } + ) + } +} + +extension CTPieDoughnutChartDataProtocol { + internal func getOverlayPosition( + rect: CGRect, + startRad: Double, + amountRad: Double, + rFactor: CGFloat + ) -> CGPoint { + let radius = min(rect.width, rect.height) / 2 + let center = CGPoint(x: rect.width / 2, y: rect.height / 2) + + let startDegree = (startRad * Double(180 / Double.pi)) + 90 + let amountDegree = amountRad * Double(180 / Double.pi) + let segmentDegree = (startDegree + (startDegree + amountDegree)) / 2 + let segmentRad = CGFloat.pi * CGFloat(segmentDegree - 90) / 180 + + let x = center.x + (radius * rFactor) * cos(segmentRad) + let y = center.y + (radius * rFactor) * sin(segmentRad) + + return CGPoint(x: x, y: y) + } +} diff --git a/Sources/SwiftUICharts/PieChart/Models/Style/DoughnutChartStyle.swift b/Sources/SwiftUICharts/PieChart/Models/Style/DoughnutChartStyle.swift index 134fafdb..a0ebeb66 100644 --- a/Sources/SwiftUICharts/PieChart/Models/Style/DoughnutChartStyle.swift +++ b/Sources/SwiftUICharts/PieChart/Models/Style/DoughnutChartStyle.swift @@ -11,21 +11,22 @@ import SwiftUI Model for controlling the overall aesthetic of the chart. */ public struct DoughnutChartStyle: CTDoughnutChartStyle { - - public var infoBoxPlacement : InfoBoxPlacement - public var infoBoxContentAlignment : InfoBoxAlignment - public var infoBoxValueFont : Font - public var infoBoxValueColour : Color - public var infoBoxDescriptionFont : Font - public var infoBoxDescriptionColour : Color - public var infoBoxBackgroundColour : Color - public var infoBoxBorderColour : Color - public var infoBoxBorderStyle : StrokeStyle + public var infoBoxPlacement: InfoBoxPlacement + public var infoBoxContentAlignment: InfoBoxAlignment + + public var infoBoxValueFont: Font + public var infoBoxValueColour: Color + + public var infoBoxDescriptionFont: Font + public var infoBoxDescriptionColour: Color + public var infoBoxBackgroundColour: Color + public var infoBoxBorderColour: Color + public var infoBoxBorderStyle: StrokeStyle - public var globalAnimation : Animation + public var globalAnimation: Animation - public var strokeWidth : CGFloat + public var strokeWidth: CGFloat /// Model for controlling the overall aesthetic of the chart. /// - Parameters: @@ -40,35 +41,36 @@ public struct DoughnutChartStyle: CTDoughnutChartStyle { /// - infoBoxBorderStyle: Border style of the touch info. /// - globalAnimation: Global control of animations. /// - strokeWidth: Width / Delta of the Doughnut Chart - public init(infoBoxPlacement : InfoBoxPlacement = .floating, - infoBoxContentAlignment : InfoBoxAlignment = .vertical, - infoBoxValueFont : Font = .title3, - infoBoxValueColour : Color = Color.primary, - - infoBoxDescriptionFont : Font = .caption, - infoBoxDescriptionColour: Color = Color.primary, - - infoBoxBackgroundColour : Color = Color.systemsBackground, - infoBoxBorderColour : Color = Color.clear, - infoBoxBorderStyle : StrokeStyle = StrokeStyle(lineWidth: 0), - - globalAnimation : Animation = Animation.linear(duration: 1), - strokeWidth : CGFloat = 30 + public init( + infoBoxPlacement: InfoBoxPlacement = .floating, + infoBoxContentAlignment: InfoBoxAlignment = .vertical, + infoBoxValueFont: Font = .title3, + infoBoxValueColour: Color = Color.primary, + + infoBoxDescriptionFont: Font = .caption, + infoBoxDescriptionColour: Color = Color.primary, + + infoBoxBackgroundColour: Color = Color.systemsBackground, + infoBoxBorderColour: Color = Color.clear, + infoBoxBorderStyle: StrokeStyle = StrokeStyle(lineWidth: 0), + + globalAnimation: Animation = Animation.linear(duration: 1), + strokeWidth: CGFloat = 30 ) { - self.infoBoxPlacement = infoBoxPlacement - self.infoBoxContentAlignment = infoBoxContentAlignment + self.infoBoxPlacement = infoBoxPlacement + self.infoBoxContentAlignment = infoBoxContentAlignment - self.infoBoxValueFont = infoBoxValueFont - self.infoBoxValueColour = infoBoxValueColour + self.infoBoxValueFont = infoBoxValueFont + self.infoBoxValueColour = infoBoxValueColour - self.infoBoxDescriptionFont = infoBoxDescriptionFont + self.infoBoxDescriptionFont = infoBoxDescriptionFont self.infoBoxDescriptionColour = infoBoxDescriptionColour - self.infoBoxBackgroundColour = infoBoxBackgroundColour - self.infoBoxBorderColour = infoBoxBorderColour - self.infoBoxBorderStyle = infoBoxBorderStyle + self.infoBoxBackgroundColour = infoBoxBackgroundColour + self.infoBoxBorderColour = infoBoxBorderColour + self.infoBoxBorderStyle = infoBoxBorderStyle - self.globalAnimation = globalAnimation - self.strokeWidth = strokeWidth + self.globalAnimation = globalAnimation + self.strokeWidth = strokeWidth } } diff --git a/Sources/SwiftUICharts/PieChart/Models/Style/PieChartStyle.swift b/Sources/SwiftUICharts/PieChart/Models/Style/PieChartStyle.swift index d90d681b..409ae8c2 100644 --- a/Sources/SwiftUICharts/PieChart/Models/Style/PieChartStyle.swift +++ b/Sources/SwiftUICharts/PieChart/Models/Style/PieChartStyle.swift @@ -11,21 +11,21 @@ import SwiftUI Model for controlling the overall aesthetic of the chart. */ public struct PieChartStyle: CTPieChartStyle { - - public var infoBoxPlacement : InfoBoxPlacement - public var infoBoxContentAlignment : InfoBoxAlignment - public var infoBoxValueFont : Font - public var infoBoxValueColour : Color + public var infoBoxPlacement: InfoBoxPlacement + public var infoBoxContentAlignment: InfoBoxAlignment + + public var infoBoxValueFont: Font + public var infoBoxValueColour: Color - public var infoBoxDescriptionFont : Font - public var infoBoxDescriptionColour : Color + public var infoBoxDescriptionFont: Font + public var infoBoxDescriptionColour: Color - public var infoBoxBackgroundColour : Color - public var infoBoxBorderColour : Color - public var infoBoxBorderStyle : StrokeStyle + public var infoBoxBackgroundColour: Color + public var infoBoxBorderColour: Color + public var infoBoxBorderStyle: StrokeStyle - public var globalAnimation : Animation + public var globalAnimation: Animation /// Model for controlling the overall aesthetic of the chart. /// - Parameters: @@ -42,34 +42,33 @@ public struct PieChartStyle: CTPieChartStyle { /// - infoBoxBorderColour: Border colour of the touch info. /// - infoBoxBorderStyle: Border style of the touch info. /// - globalAnimation: Global control of animations. - public init(infoBoxPlacement : InfoBoxPlacement = .floating, - infoBoxContentAlignment : InfoBoxAlignment = .vertical, - - infoBoxValueFont : Font = .title3, - infoBoxValueColour : Color = Color.primary, - - infoBoxDescriptionFont : Font = .caption, - infoBoxDescriptionColour: Color = Color.primary, - - infoBoxBackgroundColour : Color = Color.systemsBackground, - infoBoxBorderColour : Color = Color.clear, - infoBoxBorderStyle : StrokeStyle = StrokeStyle(lineWidth: 0), - globalAnimation : Animation = Animation.linear(duration: 1) + public init( + infoBoxPlacement: InfoBoxPlacement = .floating, + infoBoxContentAlignment: InfoBoxAlignment = .vertical, + + infoBoxValueFont: Font = .title3, + infoBoxValueColour: Color = Color.primary, + + infoBoxDescriptionFont: Font = .caption, + infoBoxDescriptionColour: Color = Color.primary, + + infoBoxBackgroundColour: Color = Color.systemsBackground, + infoBoxBorderColour: Color = Color.clear, + infoBoxBorderStyle: StrokeStyle = StrokeStyle(lineWidth: 0), + globalAnimation: Animation = Animation.linear(duration: 1) ) { - self.infoBoxPlacement = infoBoxPlacement - self.infoBoxContentAlignment = infoBoxContentAlignment + self.infoBoxPlacement = infoBoxPlacement + self.infoBoxContentAlignment = infoBoxContentAlignment - self.infoBoxValueFont = infoBoxValueFont - self.infoBoxValueColour = infoBoxValueColour + self.infoBoxValueFont = infoBoxValueFont + self.infoBoxValueColour = infoBoxValueColour - self.infoBoxDescriptionFont = infoBoxDescriptionFont + self.infoBoxDescriptionFont = infoBoxDescriptionFont self.infoBoxDescriptionColour = infoBoxDescriptionColour - self.infoBoxBackgroundColour = infoBoxBackgroundColour - self.infoBoxBorderColour = infoBoxBorderColour - self.infoBoxBorderStyle = infoBoxBorderStyle - self.globalAnimation = globalAnimation + self.infoBoxBackgroundColour = infoBoxBackgroundColour + self.infoBoxBorderColour = infoBoxBorderColour + self.infoBoxBorderStyle = infoBoxBorderStyle + self.globalAnimation = globalAnimation } } - - diff --git a/Sources/SwiftUICharts/PieChart/Shapes/DoughnutSegmentShape.swift b/Sources/SwiftUICharts/PieChart/Shapes/DoughnutSegmentShape.swift index 3d3256f1..b0905ee6 100644 --- a/Sources/SwiftUICharts/PieChart/Shapes/DoughnutSegmentShape.swift +++ b/Sources/SwiftUICharts/PieChart/Shapes/DoughnutSegmentShape.swift @@ -12,10 +12,10 @@ import SwiftUI */ internal struct DoughnutSegmentShape: InsettableShape, Identifiable { - var id : UUID - var startAngle : Double - var amount : Double - var insetAmount : CGFloat = 0 + var id: UUID + var startAngle: Double + var amount: Double + var insetAmount: CGFloat = 0 func inset(by amount: CGFloat) -> some InsettableShape { var arc = self @@ -24,17 +24,13 @@ internal struct DoughnutSegmentShape: InsettableShape, Identifiable { } internal func path(in rect: CGRect) -> Path { - let radius = min(rect.width, rect.height) / 2 let center = CGPoint(x: rect.width / 2, y: rect.height / 2) - var path = Path() - - path.addRelativeArc(center : center, - radius : radius - insetAmount, - startAngle : Angle(radians: startAngle), - delta : Angle(radians: amount)) - + path.addRelativeArc(center: center, + radius: radius - insetAmount, + startAngle: Angle(radians: startAngle), + delta: Angle(radians: amount)) return path } } diff --git a/Sources/SwiftUICharts/PieChart/Shapes/PieSegmentShape.swift b/Sources/SwiftUICharts/PieChart/Shapes/PieSegmentShape.swift index eb4b7f79..3d2c17b5 100644 --- a/Sources/SwiftUICharts/PieChart/Shapes/PieSegmentShape.swift +++ b/Sources/SwiftUICharts/PieChart/Shapes/PieSegmentShape.swift @@ -12,22 +12,19 @@ import SwiftUI */ internal struct PieSegmentShape: Shape, Identifiable { - var id : UUID - var startAngle : Double - var amount : Double + var id: UUID + var startAngle: Double + var amount: Double internal func path(in rect: CGRect) -> Path { - let radius = min(rect.width, rect.height) / 2 let center = CGPoint(x: rect.width / 2, y: rect.height / 2) - var path = Path() path.move(to: center) - path.addRelativeArc(center : center, - radius : radius, - startAngle : Angle(radians: startAngle), - delta : Angle(radians: amount)) - + path.addRelativeArc(center: center, + radius: radius, + startAngle: Angle(radians: startAngle), + delta: Angle(radians: amount)) return path } } diff --git a/Sources/SwiftUICharts/PieChart/Views/DoughnutChart.swift b/Sources/SwiftUICharts/PieChart/Views/DoughnutChart.swift index 2658876c..834dc84e 100644 --- a/Sources/SwiftUICharts/PieChart/Views/DoughnutChart.swift +++ b/Sources/SwiftUICharts/PieChart/Views/DoughnutChart.swift @@ -31,34 +31,40 @@ import SwiftUI */ public struct DoughnutChart: View where ChartData: DoughnutChartData { - @ObservedObject var chartData: ChartData + @ObservedObject private var chartData: ChartData /// Initialises a bar chart view. /// - Parameter chartData: Must be DoughnutChartData. - public init(chartData : ChartData) { + public init(chartData: ChartData) { self.chartData = chartData } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false public var body: some View { - ZStack { - ForEach(chartData.dataSets.dataPoints.indices, id: \.self) { data in - DoughnutSegmentShape(id: chartData.dataSets.dataPoints[data].id, - startAngle: chartData.dataSets.dataPoints[data].startAngle, - amount: chartData.dataSets.dataPoints[data].amount) - .stroke(chartData.dataSets.dataPoints[data].colour, lineWidth: chartData.chartStyle.strokeWidth) - .scaleEffect(startAnimation ? 1 : 0) - .opacity(startAnimation ? 1 : 0) - .animation(Animation.spring().delay(Double(data) * 0.06)) - .if(chartData.infoView.touchOverlayInfo == [chartData.dataSets.dataPoints[data]]) { - $0 - .scaleEffect(1.1) - .zIndex(1) - .shadow(color: Color.primary, radius: 10) - } - .accessibilityLabel(Text("\(chartData.metadata.title)")) - .accessibilityValue(chartData.dataSets.dataPoints[data].getCellAccessibilityValue(specifier: chartData.infoView.touchSpecifier)) + GeometryReader { geo in + ZStack { + ForEach(chartData.dataSets.dataPoints.indices, id: \.self) { data in + DoughnutSegmentShape(id: chartData.dataSets.dataPoints[data].id, + startAngle: chartData.dataSets.dataPoints[data].startAngle, + amount: chartData.dataSets.dataPoints[data].amount) + .stroke(chartData.dataSets.dataPoints[data].colour, + lineWidth: chartData.chartStyle.strokeWidth) + .overlay(dataPoint: chartData.dataSets.dataPoints[data], + chartData: chartData, + rect: geo.frame(in: .local)) + .scaleEffect(startAnimation ? 1 : 0) + .opacity(startAnimation ? 1 : 0) + .animation(Animation.spring().delay(Double(data) * 0.06)) + .if(chartData.infoView.touchOverlayInfo == [chartData.dataSets.dataPoints[data]]) { + $0 + .scaleEffect(1.1) + .zIndex(1) + .shadow(color: Color.primary, radius: 10) + } + .accessibilityLabel(Text("\(chartData.metadata.title)")) + .accessibilityValue(chartData.dataSets.dataPoints[data].getCellAccessibilityValue(specifier: chartData.infoView.touchSpecifier)) + } } } .animateOnAppear(using: chartData.chartStyle.globalAnimation) { @@ -68,5 +74,4 @@ public struct DoughnutChart: View where ChartData: DoughnutChartData self.startAnimation = false } } - } diff --git a/Sources/SwiftUICharts/PieChart/Views/PieChart.swift b/Sources/SwiftUICharts/PieChart/Views/PieChart.swift index 90bd25b3..6f16ddd9 100644 --- a/Sources/SwiftUICharts/PieChart/Views/PieChart.swift +++ b/Sources/SwiftUICharts/PieChart/Views/PieChart.swift @@ -31,38 +31,39 @@ import SwiftUI */ public struct PieChart: View where ChartData: PieChartData { - @ObservedObject var chartData: ChartData - + @ObservedObject private var chartData: ChartData + /// Initialises a bar chart view. /// - Parameter chartData: Must be PieChartData. public init(chartData: ChartData) { self.chartData = chartData } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false public var body: some View { - - ZStack { - ForEach(chartData.dataSets.dataPoints.indices, id: \.self) { data in - PieSegmentShape(id: chartData.dataSets.dataPoints[data].id, - startAngle: chartData.dataSets.dataPoints[data].startAngle, - amount: chartData.dataSets.dataPoints[data].amount) - .fill(chartData.dataSets.dataPoints[data].colour) - .scaleEffect(startAnimation ? 1 : 0) - .opacity(startAnimation ? 1 : 0) - .animation(Animation.spring().delay(Double(data) * 0.06)) - .if(chartData.infoView.touchOverlayInfo == [chartData.dataSets.dataPoints[data]]) { - $0 - .scaleEffect(1.1) - .zIndex(1) - .shadow(color: Color.primary, radius: 10) - } - .accessibilityLabel(Text("\(chartData.metadata.title)")) - .accessibilityValue(chartData.dataSets.dataPoints[data].getCellAccessibilityValue(specifier: chartData.infoView.touchSpecifier)) + GeometryReader { geo in + ZStack { + ForEach(chartData.dataSets.dataPoints.indices, id: \.self) { data in + PieSegmentShape(id: chartData.dataSets.dataPoints[data].id, + startAngle: chartData.dataSets.dataPoints[data].startAngle, + amount: chartData.dataSets.dataPoints[data].amount) + .fill(chartData.dataSets.dataPoints[data].colour) + .overlay(dataPoint: chartData.dataSets.dataPoints[data], chartData: chartData, rect: geo.frame(in: .local)) + .scaleEffect(startAnimation ? 1 : 0) + .opacity(startAnimation ? 1 : 0) + .animation(Animation.spring().delay(Double(data) * 0.06)) + .if(chartData.infoView.touchOverlayInfo == [chartData.dataSets.dataPoints[data]]) { + $0 + .scaleEffect(1.1) + .zIndex(1) + .shadow(color: Color.primary, radius: 10) + } + .accessibilityLabel(Text("\(chartData.metadata.title)")) + .accessibilityValue(chartData.dataSets.dataPoints[data].getCellAccessibilityValue(specifier: chartData.infoView.touchSpecifier)) + } } } - .animateOnAppear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = true } diff --git a/Sources/SwiftUICharts/Shared/API.swift b/Sources/SwiftUICharts/Shared/API.swift index c7899a1b..10ed88f2 100644 --- a/Sources/SwiftUICharts/Shared/API.swift +++ b/Sources/SwiftUICharts/Shared/API.swift @@ -10,9 +10,9 @@ import SwiftUI /** Displays the data points value with the unit. */ -public struct InfoValue : View where T: CTChartData { +public struct InfoValue: View where T: CTChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T public init(chartData: T) { self.chartData = chartData @@ -28,9 +28,9 @@ public struct InfoValue : View where T: CTChartData { /** Displays the data points description. */ -public struct InfoDescription : View where T: CTChartData { +public struct InfoDescription: View where T: CTChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T public init(chartData: T) { self.chartData = chartData @@ -48,15 +48,14 @@ public struct InfoDescription : View where T: CTChartData { */ public struct InfoExtra: View where T: CTChartData { - @ObservedObject var chartData: T - + @ObservedObject private var chartData: T private let text: String public init(chartData: T, text: String) { self.chartData = chartData self.text = text } - + public var body: some View { if chartData.infoView.isTouchCurrent { Text(text) @@ -92,7 +91,6 @@ extension LegendData { .font(font) .foregroundColor(textColor) } - } else if let colours = self.colour.colours { HStack { LegendLine(width: width) @@ -123,8 +121,7 @@ extension LegendData { case.bar: Group { - if let colour = self.colour.colour - { + if let colour = self.colour.colour { HStack { Rectangle() .fill(colour) @@ -132,9 +129,9 @@ extension LegendData { Text(self.legend) .font(font) } - } else if let colours = self.colour.colours, + } else if let colours = self.colour.colours, let startPoint = self.colour.startPoint, - let endPoint = self.colour.endPoint + let endPoint = self.colour.endPoint { HStack { Rectangle() @@ -145,9 +142,9 @@ extension LegendData { Text(self.legend) .font(font) } - } else if let stops = self.colour.stops, + } else if let stops = self.colour.stops, let startPoint = self.colour.startPoint, - let endPoint = self.colour.endPoint + let endPoint = self.colour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) HStack { @@ -170,10 +167,9 @@ extension LegendData { Text(self.legend) .font(font) } - - } else if let colours = self.colour.colours, + } else if let colours = self.colour.colours, let startPoint = self.colour.startPoint, - let endPoint = self.colour.endPoint + let endPoint = self.colour.endPoint { HStack { Circle() @@ -185,9 +181,9 @@ extension LegendData { .font(font) } - } else if let stops = self.colour.stops, + } else if let stops = self.colour.stops, let startPoint = self.colour.startPoint, - let endPoint = self.colour.endPoint + let endPoint = self.colour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) HStack { @@ -224,7 +220,6 @@ extension LegendData { .font(font) .foregroundColor(textColor) } - } else if let colours = self.colour.colours { HStack { Circle() @@ -251,7 +246,7 @@ extension LegendData { } else { EmptyView() } } } - + internal func accessibilityLegendLabel() -> String { switch self.chartType { case .line: diff --git a/Sources/SwiftUICharts/Shared/Extras/Extensions.swift b/Sources/SwiftUICharts/Shared/Extras/Extensions.swift index 9daef759..939649c5 100644 --- a/Sources/SwiftUICharts/Shared/Extras/Extensions.swift +++ b/Sources/SwiftUICharts/Shared/Extras/Extensions.swift @@ -13,7 +13,10 @@ extension View { [SO](https://stackoverflow.com/a/62962375) */ - @ViewBuilder func `if`(_ condition: Bool, transform: (Self) -> Transform) -> some View { + @ViewBuilder func `if`( + _ condition: Bool, + transform: (Self) -> Transform + ) -> some View { if condition { transform(self) } else { self } } @@ -24,10 +27,11 @@ extension View { View modifier to conditionally add a view modifier else add a different one. [Five Stars](https://fivestars.blog/swiftui/conditional-modifiers.html) - */ - @ViewBuilder func `ifElse`(_ condition: Bool, - if ifTransform: (Self) -> TrueContent, - else elseTransform: (Self) -> FalseContent + */ + @ViewBuilder func `ifElse`( + _ condition: Bool, + if ifTransform: (Self) -> TrueContent, + else elseTransform: (Self) -> FalseContent ) -> some View { if condition { ifTransform(self) @@ -37,15 +41,16 @@ extension View { } } - -// extension View { /** Start animation when the view appears. [HWS](https://www.hackingwithswift.com/quick-start/swiftui/how-to-start-an-animation-immediately-after-a-view-appears) */ - func animateOnAppear(using animation: Animation = Animation.easeInOut(duration: 1), _ action: @escaping () -> Void) -> some View { + func animateOnAppear( + using animation: Animation = Animation.easeInOut(duration: 1), + _ action: @escaping () -> Void + ) -> some View { return onAppear { withAnimation(animation) { action() @@ -58,7 +63,10 @@ extension View { [HWS](https://www.hackingwithswift.com/quick-start/swiftui/how-to-start-an-animation-immediately-after-a-view-appears) */ - func animateOnDisappear(using animation: Animation = Animation.easeInOut(duration: 1), _ action: @escaping () -> Void) -> some View { + func animateOnDisappear( + using animation: Animation = Animation.easeInOut(duration: 1), + _ action: @escaping () -> Void + ) -> some View { return onDisappear { withAnimation(animation) { action() diff --git a/Sources/SwiftUICharts/Shared/Models/ChartMetadata.swift b/Sources/SwiftUICharts/Shared/Models/ChartMetadata.swift index fe3c1e5a..d3422bb5 100644 --- a/Sources/SwiftUICharts/Shared/Models/ChartMetadata.swift +++ b/Sources/SwiftUICharts/Shared/Models/ChartMetadata.swift @@ -14,16 +14,16 @@ import SwiftUI */ public struct ChartMetadata { /// The charts title - public var title : String + public var title: String /// Font of the title - public var titleFont : Font + public var titleFont: Font /// Color of the title - public var titleColour : Color + public var titleColour: Color /// The charts subtitle - public var subtitle : String + public var subtitle: String /// Font of the subtitle - public var subtitleFont : Font + public var subtitleFont: Font /// Color of the subtitle public var subtitleColour: Color @@ -35,18 +35,19 @@ public struct ChartMetadata { /// - titleColour: Color of the title. /// - subtitleFont: Font of the subtitle. /// - subtitleColour: Color of the subtitle. - public init(title : String = "", - subtitle : String = "", - titleFont : Font = .title3, - titleColour : Color = Color.primary, - subtitleFont : Font = .subheadline, - subtitleColour : Color = Color.primary + public init( + title: String = "", + subtitle: String = "", + titleFont: Font = .title3, + titleColour: Color = Color.primary, + subtitleFont: Font = .subheadline, + subtitleColour: Color = Color.primary ) { - self.title = title - self.subtitle = subtitle - self.titleFont = titleFont - self.titleColour = titleColour - self.subtitleFont = subtitleFont + self.title = title + self.subtitle = subtitle + self.titleFont = titleFont + self.titleColour = titleColour + self.subtitleFont = subtitleFont self.subtitleColour = subtitleColour } } diff --git a/Sources/SwiftUICharts/Shared/Models/ColourStyle.swift b/Sources/SwiftUICharts/Shared/Models/ColourStyle.swift index 97100366..cd53b401 100644 --- a/Sources/SwiftUICharts/Shared/Models/ColourStyle.swift +++ b/Sources/SwiftUICharts/Shared/Models/ColourStyle.swift @@ -12,12 +12,12 @@ import SwiftUI */ public struct ColourStyle: CTColourStyle, Hashable { - public var colourType : ColourType - public var colour : Color? - public var colours : [Color]? - public var stops : [GradientStop]? - public var startPoint : UnitPoint? - public var endPoint : UnitPoint? + public var colourType: ColourType + public var colour: Color? + public var colours: [Color]? + public var stops: [GradientStop]? + public var startPoint: UnitPoint? + public var endPoint: UnitPoint? // MARK: Single colour /// Single Colour @@ -26,11 +26,11 @@ public struct ColourStyle: CTColourStyle, Hashable { public init(colour: Color = Color(.red) ) { self.colourType = .colour - self.colour = colour - self.colours = nil - self.stops = nil + self.colour = colour + self.colours = nil + self.stops = nil self.startPoint = nil - self.endPoint = nil + self.endPoint = nil } // MARK: Gradient colour @@ -39,17 +39,17 @@ public struct ColourStyle: CTColourStyle, Hashable { /// - colours: Colours for Gradient. /// - startPoint: Start point for Gradient. /// - endPoint: End point for Gradient. - public init(colours : [Color] = [Color(.red), Color(.blue)], - startPoint : UnitPoint = .leading, - endPoint : UnitPoint = .trailing - + public init( + colours: [Color] = [Color(.red), Color(.blue)], + startPoint: UnitPoint = .leading, + endPoint: UnitPoint = .trailing ) { - self.colourType = .gradientColour - self.colour = nil - self.colours = colours - self.stops = nil + self.colourType = .gradientColour + self.colour = nil + self.colours = colours + self.stops = nil self.startPoint = startPoint - self.endPoint = endPoint + self.endPoint = endPoint } // MARK: Gradient with stops @@ -58,15 +58,16 @@ public struct ColourStyle: CTColourStyle, Hashable { /// - stops: Colours and Stops for Gradient with stop control. /// - startPoint: Start point for Gradient. /// - endPoint: End point for Gradient. - public init(stops : [GradientStop] = [GradientStop(color: Color(.red), location: 0.0)], - startPoint : UnitPoint = .leading, - endPoint : UnitPoint = .trailing + public init( + stops: [GradientStop] = [GradientStop(color: Color(.red), location: 0.0)], + startPoint: UnitPoint = .leading, + endPoint: UnitPoint = .trailing ) { - self.colourType = .gradientStops - self.colour = nil - self.colours = nil - self.stops = stops - self.startPoint = startPoint - self.endPoint = endPoint + self.colourType = .gradientStops + self.colour = nil + self.colours = nil + self.stops = stops + self.startPoint = startPoint + self.endPoint = endPoint } } diff --git a/Sources/SwiftUICharts/Shared/Models/InfoViewData.swift b/Sources/SwiftUICharts/Shared/Models/InfoViewData.swift index f9942375..8052eb71 100644 --- a/Sources/SwiftUICharts/Shared/Models/InfoViewData.swift +++ b/Sources/SwiftUICharts/Shared/Models/InfoViewData.swift @@ -13,11 +13,11 @@ import SwiftUI public struct InfoViewData { /** - Is there currently input (touch or click) on the chart. - - Set from TouchOverlay via the relevant protocol. - - Used by `InfoBox`, `FloatingInfoBox` and `HeaderBox`. + Is there currently input (touch or click) on the chart. + + Set from TouchOverlay via the relevant protocol. + + Used by `InfoBox`, `FloatingInfoBox` and `HeaderBox`. */ var isTouchCurrent: Bool = false @@ -38,7 +38,7 @@ public struct InfoViewData { Used by `InfoBox`, `FloatingInfoBox` and `HeaderBox`. */ var touchSpecifier: String = "%.0f" - + /** X axis posistion of the overlay box. @@ -48,7 +48,7 @@ public struct InfoViewData { Used by `InfoBox`, `FloatingInfoBox` and `HeaderBox`. */ - var touchLocation: CGPoint = .zero + var touchLocation: CGPoint = .zero /** Size of the chart. diff --git a/Sources/SwiftUICharts/Shared/Models/LegendData.swift b/Sources/SwiftUICharts/Shared/Models/LegendData.swift index e41a5b97..629f4754 100644 --- a/Sources/SwiftUICharts/Shared/Models/LegendData.swift +++ b/Sources/SwiftUICharts/Shared/Models/LegendData.swift @@ -10,20 +10,23 @@ import SwiftUI /** Data model to hold data for Legends */ - public struct LegendData: Hashable, Identifiable { - - public var id : UUID +public struct LegendData: Hashable, Identifiable { + + public var id: UUID + /// The type of chart being used. - public var chartType : ChartType + public var chartType: ChartType + /// Text to be displayed - public var legend : String + public var legend: String + /// Style of the stroke - public var strokeStyle : Stroke? + public var strokeStyle: Stroke? /// Used to make sure the charts data legend is first - public let prioity : Int + public let prioity: Int - public var colour : ColourStyle + public var colour: ColourStyle /// Legend. /// - Parameters: @@ -32,19 +35,19 @@ import SwiftUI /// - strokeStyle: Stroke Style. /// - prioity: Used to make sure the charts data legend is first. /// - chartType: Type of chart being used. - public init(id : UUID, - legend : String, - colour : ColourStyle, + public init(id: UUID, + legend: String, + colour: ColourStyle, strokeStyle: Stroke?, - prioity : Int, - chartType : ChartType + prioity: Int, + chartType: ChartType ) { - self.id = id - self.legend = legend - self.colour = colour + self.id = id + self.legend = legend + self.colour = colour self.strokeStyle = strokeStyle - self.prioity = prioity - self.chartType = chartType - + self.prioity = prioity + self.chartType = chartType + } } diff --git a/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocols.swift b/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocols.swift index 13870563..de4f0912 100644 --- a/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocols.swift +++ b/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocols.swift @@ -4,7 +4,7 @@ // // Created by Will Dale on 23/01/2021. // - + import SwiftUI @@ -35,25 +35,25 @@ public protocol CTChartData: ObservableObject, Identifiable { /** Data model containing datapoints and styling information. - */ + */ var dataSets: Set { get set } /** Data model containing the charts Title, Subtitle and the Title for Legend. - */ + */ var metadata: ChartMetadata { get set } /** Array of `LegendData` to populate the charts legend. - + This is populated automatically from within each view. - */ + */ var legends: [LegendData] { get set } /** Data model pass data from `TouchOverlay` ViewModifier to `HeaderBox` or `InfoBox` for display. - */ + */ var infoView: InfoViewData { get set } /** @@ -63,22 +63,22 @@ public protocol CTChartData: ObservableObject, Identifiable { /** Customisable `Text` to display when where is not enough data to draw the chart. - */ + */ var noDataText: Text { get set } /** Holds data about the charts type. Allows for internal logic based on the type of chart. - */ + */ var chartType: (chartType: ChartType, dataSetType: DataSetType) { get } - + /** Returns whether there are two or more data points. */ func isGreaterThanTwo() -> Bool - + // MARK: Touch /** Takes in the required data to set up all the touch interactions. @@ -86,8 +86,8 @@ public protocol CTChartData: ObservableObject, Identifiable { Output via `getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> Touch` - Parameters: - - touchLocation: Current location of the touch - - chartSize: The size of the chart view as the parent view. + - touchLocation: Current location of the touch + - chartSize: The size of the chart view as the parent view. */ func setTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) @@ -97,31 +97,30 @@ public protocol CTChartData: ObservableObject, Identifiable { Inputs from `setTouchInteraction(touchLocation: CGPoint, chartSize: CGRect)` - Parameters: - - touchLocation: Current location of the touch - - chartSize: The size of the chart view as the parent view. + - touchLocation: Current location of the touch + - chartSize: The size of the chart view as the parent view. - Returns: The relevent view for the chart type and options. */ func getTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) -> Touch /** - Gets the nearest data points to the touch location. - - Parameters: - - touchLocation: Current location of the touch. - - chartSize: The size of the chart view as the parent view. - - Returns: Array of data points. - */ + Gets the nearest data points to the touch location. + - Parameters: + - touchLocation: Current location of the touch. + - chartSize: The size of the chart view as the parent view. + - Returns: Array of data points. + */ func getDataPoint(touchLocation: CGPoint, chartSize: CGRect) /** - Gets the location of the data point in the view. - - Parameters: - - dataSet: Data set to work with. - - touchLocation: Current location of the touch. - - chartSize: The size of the chart view as the parent view. - - Returns: Array of points with the location on screen of data points. - */ + Gets the location of the data point in the view. + - Parameters: + - dataSet: Data set to work with. + - touchLocation: Current location of the touch. + - chartSize: The size of the chart view as the parent view. + - Returns: Array of points with the location on screen of data points. + */ func getPointLocation(dataSet: SetPoint, touchLocation: CGPoint, chartSize: CGRect) -> CGPoint? - } // MARK: - Data Sets @@ -129,11 +128,12 @@ public protocol CTChartData: ObservableObject, Identifiable { Main protocol to set conformace for types of Data Sets. */ public protocol CTDataSetProtocol: Hashable, Identifiable { + var id: ID { get } /** Returns the highest value in the data set. - - Returns: Highest value in data set. + - Returns: Highest value in data set. */ func maxValue() -> Double @@ -148,13 +148,14 @@ public protocol CTDataSetProtocol: Hashable, Identifiable { - Returns: Average of values in data set. */ func average() -> Double - + } /** Protocol for data sets that only require a single set of data . */ public protocol CTSingleDataSetProtocol: CTDataSetProtocol { + /// A type representing a data point. -- `CTChartDataPoint` associatedtype DataPoint: CTDataPointBaseProtocol @@ -162,7 +163,7 @@ public protocol CTSingleDataSetProtocol: CTDataSetProtocol { Array of data points. */ var dataPoints: [DataPoint] { get set } - + } /** @@ -172,7 +173,7 @@ public protocol CTMultiDataSetProtocol: CTDataSetProtocol { /// A type representing a single data set -- `SingleDataSet` associatedtype DataSet: CTSingleDataSetProtocol - + /** Array of single data sets. */ @@ -186,14 +187,15 @@ public protocol CTMultiDataSetProtocol: CTDataSetProtocol { Protocol to set base configuration for data points. */ public protocol CTDataPointBaseProtocol: Hashable, Identifiable { + var id: ID { get } /** A label that can be displayed on touch input - + It can be displayed in a floating box that tracks the users input location or placed in the header. - */ + */ var description: String? { get set } /** @@ -201,11 +203,20 @@ public protocol CTDataPointBaseProtocol: Hashable, Identifiable { */ var date: Date? { get set } - var legendTag : String { get set } + /** + Internal property that has to be exposed publicly through the protocol. + + This is used for displaying legends outside of the `.legends()` + view modifier. + + + Do __Not__ Use. + */ + var legendTag: String { get set } /** Gets the relevant value(s) from the data point. - + - Parameter specifier: Specifier - Returns: Value as a string. */ @@ -217,6 +228,7 @@ public protocol CTDataPointBaseProtocol: Hashable, Identifiable { type that needs a value. */ public protocol CTStandardDataPointProtocol: CTDataPointBaseProtocol { + /** Value of the data point */ @@ -284,6 +296,7 @@ public protocol CTChartStyle { Border colour of the touch info. */ var infoBoxBorderColour: Color { get set } + /** Border style of the touch info. */ @@ -296,7 +309,7 @@ public protocol CTChartStyle { Animation.linear(duration: 1) ``` */ - var globalAnimation : Animation { get set } + var globalAnimation: Animation { get set } } @@ -312,10 +325,13 @@ public protocol CTColourStyle { */ var colourType: ColourType { get set } - /// Single Colour + /** + Single Colour + */ var colour: Color? { get set } - - /// Array of colours for gradient + /** + Array of colours for gradient + */ var colours: [Color]? { get set } /** @@ -325,10 +341,14 @@ public protocol CTColourStyle { */ var stops: [GradientStop]? { get set } - /// Start point for the gradient + /** + Start point for the gradient + */ var startPoint: UnitPoint? { get set } - /// End point for the gradient + /** + End point for the gradient + */ var endPoint: UnitPoint? { get set } } diff --git a/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocolsExtensions.swift b/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocolsExtensions.swift index 08651e0f..98764abf 100644 --- a/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocolsExtensions.swift +++ b/Sources/SwiftUICharts/Shared/Models/Protocols/SharedProtocolsExtensions.swift @@ -7,18 +7,21 @@ import SwiftUI +// MARK: - Is Greater Than extension CTChartData where Self: CTLineChartDataProtocol, Set: CTSingleDataSetProtocol { public func isGreaterThanTwo() -> Bool { return dataSets.dataPoints.count >= 2 } } + extension CTChartData where Self: CTBarChartDataProtocol, Set: CTSingleDataSetProtocol { public func isGreaterThanTwo() -> Bool { return dataSets.dataPoints.count >= 1 } } + extension CTChartData where Self: CTPieDoughnutChartDataProtocol, Set: CTSingleDataSetProtocol { public func isGreaterThanTwo() -> Bool { @@ -48,12 +51,12 @@ extension CTChartData where Self: CTBarChartDataProtocol, } } -// MARK: Touch +// MARK: - Touch extension CTChartData { public func setTouchInteraction(touchLocation: CGPoint, chartSize: CGRect) { - self.infoView.isTouchCurrent = true - self.infoView.touchLocation = touchLocation - self.infoView.chartSize = chartSize + self.infoView.isTouchCurrent = true + self.infoView.touchLocation = touchLocation + self.infoView.chartSize = chartSize self.getDataPoint(touchLocation: touchLocation, chartSize: chartSize) } } @@ -115,15 +118,15 @@ extension CTChartData { } /** - Displays the relevent Legend for the data point. - - - Parameter info: A data point - - Returns: A View of a Legend. - */ + Displays the relevent Legend for the data point. + + - Parameter info: A data point + - Returns: A View of a Legend. + */ @ViewBuilder public func infoLegend(info: DataPoint) -> some View { if let legend = self.legends.first(where: { $0.prioity == 1 && - $0.legend == info.legendTag + $0.legend == info.legendTag }) { legend.getLegendAsCircle(textColor: .primary) } else { @@ -141,7 +144,7 @@ extension CTChartData { /// - boxFrame: The size of the point info box. /// - chartSize: The size of the chart view as the parent view. internal func setBoxLocationation(touchLocation: CGFloat, boxFrame: CGRect, chartSize: CGRect) -> CGFloat { - var returnPoint : CGFloat = .zero + var returnPoint: CGFloat = .zero if touchLocation < chartSize.minX + (boxFrame.width / 2) { returnPoint = chartSize.minX + (boxFrame.width / 2) } else if touchLocation > chartSize.maxX - (boxFrame.width / 2) { @@ -192,8 +195,12 @@ extension CTSingleDataSetProtocol where Self.DataPoint: CTRangeDataPointProtocol extension CTMultiDataSetProtocol where Self.DataSet.DataPoint: CTStandardDataPointProtocol { public func maxValue() -> Double { - self.dataSets.compactMap { $0.dataPoints.map(\.value).max() } - .max() ?? 0 + self.dataSets.compactMap { + $0.dataPoints + .map(\.value) + .max() + } + .max() ?? 0 } public func minValue() -> Double { self.dataSets.compactMap { @@ -204,7 +211,7 @@ extension CTMultiDataSetProtocol where Self.DataSet.DataPoint: CTStandardDataPoi .min() ?? 0 } public func average() -> Double { - + self.dataSets .compactMap { $0.dataPoints @@ -220,34 +227,39 @@ extension CTMultiDataSetProtocol where Self.DataSet.DataPoint: CTStandardDataPoi extension CTMultiDataSetProtocol where Self == StackedBarDataSets { /** Returns the highest sum value in the data sets - + - Note: This differs from other charts, as Stacked Bar Charts need to consider the sum value for each data set, instead of the max value of a data point. - + - Returns: Highest sum value in data sets. */ public func maxValue() -> Double { - let maxSums = self.dataSets.map { set in - set.dataPoints.map(\.value).reduce(0.0, +) - } - return maxSums.max() ?? 0 + self.dataSets + .map { + $0.dataPoints + .map(\.value) + .reduce(0.0, +) + } + .max() ?? 0 } } extension CTMultiBarChartDataSet where Self == StackedBarDataSet { /** Returns the highest sum value in the data set. - + - Note: This differs from other charts, as Stacked Bar Charts need to consider the sum value for each data set, instead of the max value of a data point. - + - Returns: Highest sum value in data set. */ public func maxValue() -> Double { - self.dataPoints.map(\.value).reduce(0, +) + self.dataPoints + .map(\.value) + .reduce(0, +) } } @@ -280,26 +292,29 @@ extension CTDataPointBaseProtocol { extension CTDataPointBaseProtocol { /// Unwraps description - public var wrappedDescription : String { + public var wrappedDescription: String { self.description ?? "" } } + extension CTStandardDataPointProtocol { /// Data point's value as a string public func valueAsString(specifier: String) -> String { if self.value != -Double.greatestFiniteMagnitude { - return String(format: specifier, self.value) + return String(format: specifier, self.value) } else { return String("") } } } + extension CTRangeDataPointProtocol { /// Data point's value as a string public func valueAsString(specifier: String) -> String { String(format: specifier, self.lowerValue) + "-" + String(format: specifier, self.upperValue) } } + extension CTRangedLineDataPoint { /// Data point's value as a string public func valueAsString(specifier: String) -> String { diff --git a/Sources/SwiftUICharts/Shared/Shapes/AccessibilityRectangle.swift b/Sources/SwiftUICharts/Shared/Shapes/AccessibilityRectangle.swift index 01c480e3..1e91004b 100644 --- a/Sources/SwiftUICharts/Shared/Shapes/AccessibilityRectangle.swift +++ b/Sources/SwiftUICharts/Shared/Shapes/AccessibilityRectangle.swift @@ -12,11 +12,12 @@ import SwiftUI */ internal struct AccessibilityRectangle: Shape { - private let dataPointCount : Int - private let dataPointNo : Int + private let dataPointCount: Int + private let dataPointNo: Int - internal init(dataPointCount: Int, - dataPointNo: Int + internal init( + dataPointCount: Int, + dataPointNo: Int ) { self.dataPointCount = dataPointCount self.dataPointNo = dataPointNo @@ -24,17 +25,13 @@ internal struct AccessibilityRectangle: Shape { internal func path(in rect: CGRect) -> Path { var path = Path() - let x = rect.width / CGFloat(dataPointCount-1) - let pointX : CGFloat = (CGFloat(dataPointNo) * x) - x / CGFloat(2) - - let point : CGRect = CGRect(x : pointX, - y : 0, - width : x, - height: rect.height) - + let pointX: CGFloat = (CGFloat(dataPointNo) * x) - x / CGFloat(2) + let point: CGRect = CGRect(x: pointX, + y: 0, + width: x, + height: rect.height) path.addRoundedRect(in: point, cornerSize: CGSize(width: 10, height: 10)) - return path } } diff --git a/Sources/SwiftUICharts/Shared/Shapes/TouchOverlayMarker.swift b/Sources/SwiftUICharts/Shared/Shapes/TouchOverlayMarker.swift index 67a7c0b6..623ff6d7 100644 --- a/Sources/SwiftUICharts/Shared/Shapes/TouchOverlayMarker.swift +++ b/Sources/SwiftUICharts/Shared/Shapes/TouchOverlayMarker.swift @@ -10,19 +10,17 @@ import SwiftUI /// Vertical line from top to bottom. internal struct Vertical: Shape { - private var position : CGPoint + private var position: CGPoint - internal init(position : CGPoint) { - self.position = position + internal init(position: CGPoint) { + self.position = position } internal func path(in rect: CGRect) -> Path { - var verticalPath = Path() - + var verticalPath = Path() verticalPath.move(to: CGPoint(x: position.x, y: 0)) verticalPath.addLine(to: CGPoint(x: position.x, y: rect.height)) - return verticalPath } } @@ -30,16 +28,16 @@ internal struct Vertical: Shape { /// Full width and height of view intersecting at a specified point. internal struct MarkerFull: Shape { - private var position : CGPoint + private var position: CGPoint - internal init(position : CGPoint) { + internal init(position: CGPoint) { self.position = position } internal func path(in rect: CGRect) -> Path { - var combinedPaths = Path() - var horizontalPath = Path() - var verticalPath = Path() + var combinedPaths = Path() + var horizontalPath = Path() + var verticalPath = Path() horizontalPath.move(to: CGPoint(x: 0, y: position.y)) horizontalPath.addLine(to: CGPoint(x: rect.width, y: position.y)) @@ -55,16 +53,16 @@ internal struct MarkerFull: Shape { /// From bottom and leading edges meeting at a specified point. internal struct MarkerBottomLeading: Shape { - private var position : CGPoint - - internal init(position : CGPoint) { + private var position: CGPoint + + internal init(position: CGPoint) { self.position = position } internal func path(in rect: CGRect) -> Path { - var combinedPaths = Path() - var horizontalPath = Path() - var verticalPath = Path() + var combinedPaths = Path() + var horizontalPath = Path() + var verticalPath = Path() horizontalPath.move(to: CGPoint(x: 0, y: position.y)) horizontalPath.addLine(to: CGPoint(x: position.x, y: position.y)) @@ -80,16 +78,16 @@ internal struct MarkerBottomLeading: Shape { /// From bottom and trailing edges meeting at a specified point. internal struct MarkerBottomTrailing: Shape { - private var position : CGPoint - - internal init(position : CGPoint) { + private var position: CGPoint + + internal init(position: CGPoint) { self.position = position } internal func path(in rect: CGRect) -> Path { - var combinedPaths = Path() - var horizontalPath = Path() - var verticalPath = Path() + var combinedPaths = Path() + var horizontalPath = Path() + var verticalPath = Path() horizontalPath.move(to: CGPoint(x: rect.width, y: position.y)) horizontalPath.addLine(to: CGPoint(x: position.x, y: position.y)) @@ -105,16 +103,16 @@ internal struct MarkerBottomTrailing: Shape { // From top and leading edges meeting at a specified point. internal struct MarkerTopLeading: Shape { - private var position : CGPoint - - internal init(position : CGPoint) { + private var position: CGPoint + + internal init(position: CGPoint) { self.position = position } internal func path(in rect: CGRect) -> Path { - var combinedPaths = Path() - var horizontalPath = Path() - var verticalPath = Path() + var combinedPaths = Path() + var horizontalPath = Path() + var verticalPath = Path() horizontalPath.move(to: CGPoint(x: rect.width, y: position.y)) horizontalPath.addLine(to: CGPoint(x: position.x, y: position.y)) @@ -130,16 +128,16 @@ internal struct MarkerTopLeading: Shape { // From top and trailing edges meeting at a specified point. internal struct MarkerTopTrailing: Shape { - private var position : CGPoint + private var position: CGPoint - internal init(position : CGPoint) { + internal init(position: CGPoint) { self.position = position } internal func path(in rect: CGRect) -> Path { - var combinedPaths = Path() - var horizontalPath = Path() - var verticalPath = Path() + var combinedPaths = Path() + var horizontalPath = Path() + var verticalPath = Path() horizontalPath.move(to: CGPoint(x: rect.width, y: position.y)) horizontalPath.addLine(to: CGPoint(x: position.x, y: position.y)) diff --git a/Sources/SwiftUICharts/Shared/Types/GradientStop.swift b/Sources/SwiftUICharts/Shared/Types/GradientStop.swift index e265179a..e78198ab 100644 --- a/Sources/SwiftUICharts/Shared/Types/GradientStop.swift +++ b/Sources/SwiftUICharts/Shared/Types/GradientStop.swift @@ -13,13 +13,15 @@ import SwiftUI Gradient.Stop doesn't conform to Hashable. */ public struct GradientStop: Hashable { - public var color : Color + + public var color: Color public var location: CGFloat - public init(color : Color, - location: CGFloat + public init( + color: Color, + location: CGFloat ) { - self.color = color + self.color = color self.location = location } } @@ -29,10 +31,6 @@ extension GradientStop { /// - Parameter stops: Array of GradientStop /// - Returns: Array of Gradient.Stop static func convertToGradientStopsArray(stops: [GradientStop]) -> [Gradient.Stop] { - var stopsArray : [Gradient.Stop] = [] - for stop in stops { - stopsArray.append(Gradient.Stop(color: stop.color, location: stop.location)) - } - return stopsArray + stops.map { Gradient.Stop(color: $0.color, location: $0.location) } } } diff --git a/Sources/SwiftUICharts/Shared/Types/Stroke.swift b/Sources/SwiftUICharts/Shared/Types/Stroke.swift index eb562003..d6b2300e 100644 --- a/Sources/SwiftUICharts/Shared/Types/Stroke.swift +++ b/Sources/SwiftUICharts/Shared/Types/Stroke.swift @@ -14,51 +14,52 @@ import SwiftUI */ public struct Stroke: Hashable, Identifiable { - public let id : UUID = UUID() + public let id: UUID = UUID() - private let lineWidth : CGFloat - private let lineCap : CGLineCap - private let lineJoin : CGLineJoin - private let miterLimit : CGFloat - private let dash : [CGFloat] - private let dashPhase : CGFloat + private let lineWidth: CGFloat + private let lineCap: CGLineCap + private let lineJoin: CGLineJoin + private let miterLimit: CGFloat + private let dash: [CGFloat] + private let dashPhase: CGFloat - public init(lineWidth : CGFloat = 3, - lineCap : CGLineCap = .round, - lineJoin : CGLineJoin = .round, - miterLimit: CGFloat = 10, - dash : [CGFloat] = [CGFloat](), - dashPhase : CGFloat = 0 + public init( + lineWidth: CGFloat = 3, + lineCap: CGLineCap = .round, + lineJoin: CGLineJoin = .round, + miterLimit: CGFloat = 10, + dash: [CGFloat] = [CGFloat](), + dashPhase: CGFloat = 0 ) { - self.lineWidth = lineWidth - self.lineCap = lineCap - self.lineJoin = lineJoin + self.lineWidth = lineWidth + self.lineCap = lineCap + self.lineJoin = lineJoin self.miterLimit = miterLimit - self.dash = dash - self.dashPhase = dashPhase + self.dash = dash + self.dashPhase = dashPhase } } extension Stroke { /// Convert `Stroke` to `StrokeStyle` - internal func strokeToStrokeStyle() -> StrokeStyle { - StrokeStyle(lineWidth : self.lineWidth, - lineCap : self.lineCap, - lineJoin : self.lineJoin, + internal func strokeToStrokeStyle() -> StrokeStyle { + StrokeStyle(lineWidth: self.lineWidth, + lineCap: self.lineCap, + lineJoin: self.lineJoin, miterLimit: self.miterLimit, - dash : self.dash, - dashPhase : self.dashPhase) + dash: self.dash, + dashPhase: self.dashPhase) } } extension StrokeStyle { /// Convert `StrokeStyle` to `Stroke` internal func toStroke() -> Stroke { - Stroke(lineWidth : self.lineWidth, - lineCap : self.lineCap, - lineJoin : self.lineJoin, + Stroke(lineWidth: self.lineWidth, + lineCap: self.lineCap, + lineJoin: self.lineJoin, miterLimit: self.miterLimit, - dash : self.dash, - dashPhase : self.dashPhase) + dash: self.dash, + dashPhase: self.dashPhase) } } diff --git a/Sources/SwiftUICharts/Shared/ViewModifiers/FloatingInfoBox.swift b/Sources/SwiftUICharts/Shared/ViewModifiers/FloatingInfoBox.swift index 0ba9cbcc..ac8b5428 100644 --- a/Sources/SwiftUICharts/Shared/ViewModifiers/FloatingInfoBox.swift +++ b/Sources/SwiftUICharts/Shared/ViewModifiers/FloatingInfoBox.swift @@ -12,7 +12,7 @@ import SwiftUI */ internal struct FloatingInfoBox: ViewModifier where T: CTChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T internal init(chartData: T) { self.chartData = chartData @@ -38,10 +38,10 @@ internal struct FloatingInfoBox: ViewModifier where T: CTChartData { private var floating: some View { TouchOverlayBox(chartData: chartData, - boxFrame : $boxFrame) + boxFrame: $boxFrame) .position(x: chartData.setBoxLocationation(touchLocation: chartData.infoView.touchLocation.x, - boxFrame : boxFrame, - chartSize : chartData.infoView.chartSize) - 6, // -6 to compensate for `.padding(.horizontal, 6)` + boxFrame: boxFrame, + chartSize: chartData.infoView.chartSize) - 6, // -6 to compensate for `.padding(.horizontal, 6)` y: boxFrame.midY - 10) .padding(.horizontal, 6) .zIndex(1) diff --git a/Sources/SwiftUICharts/Shared/ViewModifiers/HeaderBox.swift b/Sources/SwiftUICharts/Shared/ViewModifiers/HeaderBox.swift index 3dced45d..5423e5bf 100644 --- a/Sources/SwiftUICharts/Shared/ViewModifiers/HeaderBox.swift +++ b/Sources/SwiftUICharts/Shared/ViewModifiers/HeaderBox.swift @@ -6,13 +6,13 @@ // import SwiftUI - + /** Displays the metadata about the chart as well as optionally touch overlay information. */ internal struct HeaderBox: ViewModifier where T: CTChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T init(chartData: T) { self.chartData = chartData @@ -23,26 +23,21 @@ internal struct HeaderBox: ViewModifier where T: CTChartData { Text(chartData.metadata.title) .font(chartData.metadata.titleFont) .foregroundColor(chartData.metadata.titleColour) - Text(chartData.metadata.subtitle) .font(chartData.metadata.subtitleFont) .foregroundColor(chartData.metadata.subtitleColour) } } var touchOverlay: some View { - VStack(alignment: .trailing) { if chartData.infoView.isTouchCurrent { ForEach(chartData.infoView.touchOverlayInfo, id: \.id) { point in - chartData.infoValueUnit(info: point) .font(chartData.chartStyle.infoBoxValueFont) .foregroundColor(chartData.chartStyle.infoBoxValueColour) - chartData.infoDescription(info: point) .font(chartData.chartStyle.infoBoxDescriptionFont) .foregroundColor(chartData.chartStyle.infoBoxDescriptionColour) - } } else { Text("") @@ -53,7 +48,6 @@ internal struct HeaderBox: ViewModifier where T: CTChartData { } } - internal func body(content: Content) -> some View { Group { #if !os(tvOS) diff --git a/Sources/SwiftUICharts/Shared/ViewModifiers/InfoBox.swift b/Sources/SwiftUICharts/Shared/ViewModifiers/InfoBox.swift index eb4f0180..bc552b21 100644 --- a/Sources/SwiftUICharts/Shared/ViewModifiers/InfoBox.swift +++ b/Sources/SwiftUICharts/Shared/ViewModifiers/InfoBox.swift @@ -12,7 +12,7 @@ import SwiftUI */ internal struct InfoBox: ViewModifier where T: CTChartData { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T internal init(chartData: T) { self.chartData = chartData @@ -46,27 +46,23 @@ internal struct InfoBox: ViewModifier where T: CTChartData { private var floating: some View { TouchOverlayBox(chartData: chartData, - boxFrame : $boxFrame) + boxFrame: $boxFrame) .position(x: chartData.setBoxLocationation(touchLocation: chartData.infoView.touchLocation.x, - boxFrame : boxFrame, - chartSize : chartData.infoView.chartSize) - 6, // -6 to compensate for `.padding(.horizontal, 6)` + boxFrame: boxFrame, + chartSize: chartData.infoView.chartSize) - 6, // -6 to compensate for `.padding(.horizontal, 6)` y: 35) .frame(height: 70) .padding(.horizontal, 6) .zIndex(1) } - private var fixed: some View { TouchOverlayBox(chartData: chartData, - boxFrame : $boxFrame) + boxFrame: $boxFrame) .frame(height: 70) .padding(.horizontal, 6) .zIndex(1) } - - - } extension View { diff --git a/Sources/SwiftUICharts/Shared/ViewModifiers/Legends.swift b/Sources/SwiftUICharts/Shared/ViewModifiers/Legends.swift index a26d6e3c..8074e70e 100644 --- a/Sources/SwiftUICharts/Shared/ViewModifiers/Legends.swift +++ b/Sources/SwiftUICharts/Shared/ViewModifiers/Legends.swift @@ -12,23 +12,27 @@ import SwiftUI */ internal struct Legends: ViewModifier where T: CTChartData { - @ObservedObject var chartData: T - private let columns : [GridItem] - private let width : CGFloat - private let font : Font - private let textColor : Color + @ObservedObject private var chartData: T + private let columns: [GridItem] + private let width: CGFloat + private let font: Font + private let textColor: Color + private let topPadding: CGFloat - init(chartData: T, - columns : [GridItem], - width : CGFloat, - font : Font, - textColor: Color + internal init( + chartData: T, + columns: [GridItem], + width: CGFloat, + font: Font, + textColor: Color, + topPadding: CGFloat ) { self.chartData = chartData - self.columns = columns - self.width = width - self.font = font + self.columns = columns + self.width = width + self.font = font self.textColor = textColor + self.topPadding = topPadding } internal func body(content: Content) -> some View { @@ -41,13 +45,13 @@ internal struct Legends: ViewModifier where T: CTChartData { width: width, font: font, textColor: textColor) - + .padding(.top, topPadding) } } else { content } } } } - + extension View { /** Displays legends under the chart. @@ -63,13 +67,14 @@ extension View { columns: [GridItem] = [GridItem(.flexible())], iconWidth: CGFloat = 40, font: Font = .caption, - textColor: Color = Color.primary + textColor: Color = Color.primary, + topPadding: CGFloat = 18 ) -> some View { self.modifier(Legends(chartData: chartData, columns: columns, width: iconWidth, font: font, - textColor: textColor)) + textColor: textColor, + topPadding: topPadding)) } } - diff --git a/Sources/SwiftUICharts/Shared/ViewModifiers/TouchOverlay.swift b/Sources/SwiftUICharts/Shared/ViewModifiers/TouchOverlay.swift index c4f5f5e4..8b4670df 100644 --- a/Sources/SwiftUICharts/Shared/ViewModifiers/TouchOverlay.swift +++ b/Sources/SwiftUICharts/Shared/ViewModifiers/TouchOverlay.swift @@ -12,18 +12,19 @@ import SwiftUI Finds the nearest data point and displays the relevent information. */ internal struct TouchOverlay: ViewModifier where T: CTChartData { - - @ObservedObject var chartData: T - - internal init(chartData : T, - specifier : String, - unit : TouchUnit + + @ObservedObject private var chartData: T + + internal init( + chartData: T, + specifier: String, + unit: TouchUnit ) { self.chartData = chartData self.chartData.infoView.touchSpecifier = specifier self.chartData.infoView.touchUnit = unit } - + internal func body(content: Content) -> some View { Group { if chartData.isGreaterThanTwo() { @@ -37,7 +38,7 @@ internal struct TouchOverlay: ViewModifier where T: CTChartData { chartSize: geo.frame(in: .local)) } .onEnded { _ in - chartData.infoView.isTouchCurrent = false + chartData.infoView.isTouchCurrent = false chartData.infoView.touchOverlayInfo = [] } ) @@ -79,13 +80,14 @@ extension View { - unit: Unit to put before or after the value. - Returns: A new view containing the chart with a touch overlay. */ - public func touchOverlay(chartData: T, - specifier: String = "%.0f", - unit : TouchUnit = .none + public func touchOverlay( + chartData: T, + specifier: String = "%.0f", + unit: TouchUnit = .none ) -> some View { self.modifier(TouchOverlay(chartData: chartData, specifier: specifier, - unit : unit)) + unit: unit)) } #elseif os(tvOS) /** @@ -94,9 +96,10 @@ extension View { - Attention: Unavailable in tvOS */ - public func touchOverlay(chartData: T, - specifier: String = "%.0f", - unit : TouchUnit = .none + public func touchOverlay( + chartData: T, + specifier: String = "%.0f", + unit: TouchUnit = .none ) -> some View { self.modifier(EmptyModifier()) } diff --git a/Sources/SwiftUICharts/Shared/Views/CustomNoDataView.swift b/Sources/SwiftUICharts/Shared/Views/CustomNoDataView.swift index bfae4c4f..9b49d63f 100644 --- a/Sources/SwiftUICharts/Shared/Views/CustomNoDataView.swift +++ b/Sources/SwiftUICharts/Shared/Views/CustomNoDataView.swift @@ -12,7 +12,7 @@ import SwiftUI */ public struct CustomNoDataView: View where T: CTChartData { - let chartData : T + private let chartData: T init(chartData: T) { self.chartData = chartData diff --git a/Sources/SwiftUICharts/Shared/Views/LegendView.swift b/Sources/SwiftUICharts/Shared/Views/LegendView.swift index 5cb6cee8..4e70149b 100644 --- a/Sources/SwiftUICharts/Shared/Views/LegendView.swift +++ b/Sources/SwiftUICharts/Shared/Views/LegendView.swift @@ -12,33 +12,31 @@ import SwiftUI */ internal struct LegendView: View where T: CTChartData { - @ObservedObject var chartData : T - private let columns : [GridItem] - private let width : CGFloat - private let font : Font - private let textColor : Color - + @ObservedObject private var chartData: T + private let columns: [GridItem] + private let width: CGFloat + private let font: Font + private let textColor: Color + internal init(chartData: T, - columns : [GridItem], - width : CGFloat, - font : Font, + columns: [GridItem], + width: CGFloat, + font: Font, textColor: Color ) { self.chartData = chartData - self.columns = columns - self.width = width - self.font = font + self.columns = columns + self.width = width + self.font = font self.textColor = textColor } internal var body: some View { - LazyVGrid(columns: columns, alignment: .leading) { ForEach(chartData.legends, id: \.id) { legend in - legend.getLegend(width: width, font: font, textColor: textColor) .if(scaleLegendBar(legend: legend)) { $0.scaleEffect(1.2, anchor: .leading) } - .if(scaleLegendPie(legend: legend)) {$0.scaleEffect(1.2, anchor: .leading) } + .if(scaleLegendPie(legend: legend)) { $0.scaleEffect(1.2, anchor: .leading) } .accessibilityLabel(Text(legend.accessibilityLegendLabel())) .accessibilityValue(Text(legend.legend)) } @@ -47,24 +45,21 @@ internal struct LegendView: View where T: CTChartData { /// Detects whether to run the scale effect on the legend. private func scaleLegendBar(legend: LegendData) -> Bool { - if let chartData = chartData as? BarChartData, let datapoint = chartData.infoView.touchOverlayInfo.first { return chartData.infoView.isTouchCurrent && legend.id == datapoint.id } - if let chartData = chartData as? GroupedBarChartData, let datapoint = chartData.infoView.touchOverlayInfo.first { return chartData.infoView.isTouchCurrent && legend.colour == datapoint.group.colour } - if let chartData = chartData as? StackedBarChartData, let datapoint = chartData.infoView.touchOverlayInfo.first { return chartData.infoView.isTouchCurrent && legend.colour == datapoint.group.colour } - return false } + /// Detects whether to run the scale effect on the legend. private func scaleLegendPie(legend: LegendData) -> Bool { @@ -75,7 +70,7 @@ internal struct LegendView: View where T: CTChartData { return false } } else { - return false - } + return false + } } } diff --git a/Sources/SwiftUICharts/Shared/Views/PosistionIndicator.swift b/Sources/SwiftUICharts/Shared/Views/PosistionIndicator.swift index d4a5aa7e..9a35188c 100644 --- a/Sources/SwiftUICharts/Shared/Views/PosistionIndicator.swift +++ b/Sources/SwiftUICharts/Shared/Views/PosistionIndicator.swift @@ -12,17 +12,18 @@ import SwiftUI */ internal struct PosistionIndicator: View { - private let fillColour : Color - private let lineColour : Color - private let lineWidth : CGFloat + private let fillColour: Color + private let lineColour: Color + private let lineWidth: CGFloat - internal init(fillColour : Color = Color.primary, - lineColour : Color = Color.blue, - lineWidth : CGFloat = 3 + internal init( + fillColour: Color = Color.primary, + lineColour: Color = Color.blue, + lineWidth: CGFloat = 3 ) { self.fillColour = fillColour self.lineColour = lineColour - self.lineWidth = lineWidth + self.lineWidth = lineWidth } internal var body: some View { @@ -33,7 +34,6 @@ internal struct PosistionIndicator: View { .foregroundColor(fillColour) .padding(EdgeInsets(top: lineWidth, leading: lineWidth, bottom: lineWidth, trailing: lineWidth)) } - } } @@ -42,10 +42,10 @@ internal struct PosistionIndicator: View { */ public struct DotStyle { - let size : CGFloat - let fillColour : Color - let lineColour : Color - let lineWidth : CGFloat + let size: CGFloat + let fillColour: Color + let lineColour: Color + let lineWidth: CGFloat /// Sets the style of the Posistion Indicator /// - Parameters: @@ -53,14 +53,15 @@ public struct DotStyle { /// - fillColour: Fill colour. /// - lineColour: Border colour. /// - lineWidth: Border width. - public init(size : CGFloat = 15, - fillColour : Color = Color.primary, - lineColour : Color = Color.blue, - lineWidth : CGFloat = 3 + public init( + size: CGFloat = 15, + fillColour: Color = Color.primary, + lineColour: Color = Color.blue, + lineWidth: CGFloat = 3 ) { - self.size = size + self.size = size self.fillColour = fillColour self.lineColour = lineColour - self.lineWidth = lineWidth + self.lineWidth = lineWidth } } diff --git a/Sources/SwiftUICharts/Shared/Views/TouchOverlayBox.swift b/Sources/SwiftUICharts/Shared/Views/TouchOverlayBox.swift index 6cbf0cbc..ef1ddb3c 100644 --- a/Sources/SwiftUICharts/Shared/Views/TouchOverlayBox.swift +++ b/Sources/SwiftUICharts/Shared/Views/TouchOverlayBox.swift @@ -12,32 +12,29 @@ import SwiftUI */ internal struct TouchOverlayBox: View { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T @Binding private var boxFrame: CGRect - internal init(chartData: T, - boxFrame : Binding + internal init( + chartData: T, + boxFrame: Binding ) { self.chartData = chartData self._boxFrame = boxFrame } internal var body: some View { - Group { if chartData.chartStyle.infoBoxContentAlignment == .vertical { VStack(alignment: .leading, spacing: 0) { ForEach(chartData.infoView.touchOverlayInfo, id: \.id) { point in - chartData.infoDescription(info: point) .font(chartData.chartStyle.infoBoxDescriptionFont) .foregroundColor(chartData.chartStyle.infoBoxDescriptionColour) - chartData.infoValueUnit(info: point) .font(chartData.chartStyle.infoBoxValueFont) .foregroundColor(chartData.chartStyle.infoBoxValueColour) - chartData.infoLegend(info: point) .foregroundColor(chartData.chartStyle.infoBoxDescriptionColour) } @@ -45,15 +42,12 @@ internal struct TouchOverlayBox: View { } else { HStack { ForEach(chartData.infoView.touchOverlayInfo, id: \.id) { point in - chartData.infoLegend(info: point) .foregroundColor(chartData.chartStyle.infoBoxDescriptionColour) .layoutPriority(1) - chartData.infoDescription(info: point) .font(chartData.chartStyle.infoBoxDescriptionFont) .foregroundColor(chartData.chartStyle.infoBoxDescriptionColour) - chartData.infoValueUnit(info: point) .font(chartData.chartStyle.infoBoxValueFont) .foregroundColor(chartData.chartStyle.infoBoxValueColour) diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Extras/LineAndBarEnums.swift b/Sources/SwiftUICharts/SharedLineAndBar/Extras/LineAndBarEnums.swift index 7e54d6b9..a1ae5473 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Extras/LineAndBarEnums.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Extras/LineAndBarEnums.swift @@ -9,7 +9,7 @@ import SwiftUI // MARK: - XAxisLabels /** -Location of the X axis labels + Location of the X axis labels ``` case top case bottom @@ -40,7 +40,7 @@ public enum LabelsFrom { // MARK: - YAxisLabels /** -Location of the Y axis labels + Location of the Y axis labels ``` case leading case trailing diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Models/ChartViewData.swift b/Sources/SwiftUICharts/SharedLineAndBar/Models/ChartViewData.swift index c35bf6f2..7b7ec750 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Models/ChartViewData.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Models/ChartViewData.swift @@ -6,27 +6,32 @@ import SwiftUI -/// Data model to pass view information internally so the layout can configure its self. +/** + Data model to pass view information internally so the layout can configure its self. + */ public struct ChartViewData { - /// If the chart has labels on the X axis, the Y axis needs a different layout - var hasXAxisLabels : Bool = false + /** + If the chart has labels on the X axis, the Y axis needs a different layout + */ + var hasXAxisLabels: Bool = false /** The hieght of X Axis Title if it is there. Needed to set the position of the Y Axis labels. */ - var xAxisTitleHeight : CGFloat = 0 + var xAxisTitleHeight: CGFloat = 0 /** The hieght of X Axis labels if they are there. Needed to set the position of the Y Axis labels. */ - var xAxisLabelHeights : [CGFloat] = [] - - /// If the chart has labels on the Y axis, the X axis needs a different layout - var hasYAxisLabels : Bool = false + var xAxisLabelHeights: [CGFloat] = [] + /** + If the chart has labels on the Y axis, the X axis needs a different layout + */ + var hasYAxisLabels: Bool = false } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Models/GridStyle.swift b/Sources/SwiftUICharts/SharedLineAndBar/Models/GridStyle.swift index 49cab48f..69b3117e 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Models/GridStyle.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Models/GridStyle.swift @@ -8,17 +8,34 @@ import SwiftUI /** - Controlling for the look of the Grid + Control for the look of the Grid */ public struct GridStyle { - /// Number of lines to break up the axis + /** + Number of lines to break up the axis + */ public var numberOfLines: Int - public var lineColour : Color - public var lineWidth : CGFloat - public var dash : [CGFloat] - public var dashPhase : CGFloat + /** + Line Colour + */ + public var lineColour: Color + + /** + Line Width + */ + public var lineWidth: CGFloat + + /** + Dash + */ + public var dash: [CGFloat] + + /** + Dash Phase + */ + public var dashPhase: CGFloat /// Model for controlling the look of the Grid /// - Parameters: @@ -27,16 +44,17 @@ public struct GridStyle { /// - lineWidth: Line Width /// - dash: Dash /// - dashPhase: Dash Phase - public init(numberOfLines: Int = 10, - lineColour : Color = Color(.gray).opacity(0.25), - lineWidth : CGFloat = 1, - dash : [CGFloat] = [10], - dashPhase : CGFloat = 0 + public init( + numberOfLines: Int = 10, + lineColour: Color = Color(.gray).opacity(0.25), + lineWidth: CGFloat = 1, + dash: [CGFloat] = [10], + dashPhase: CGFloat = 0 ) { - self.numberOfLines = numberOfLines - self.lineColour = lineColour - self.lineWidth = lineWidth - self.dash = dash - self.dashPhase = dashPhase + self.numberOfLines = numberOfLines + self.lineColour = lineColour + self.lineWidth = lineWidth + self.dash = dash + self.dashPhase = dashPhase } } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocols.swift b/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocols.swift index abd7e0ce..fb297c0c 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocols.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocols.swift @@ -12,9 +12,9 @@ import SwiftUI A protocol to extend functionality of `CTChartData` specifically for Line and Bar Charts. */ public protocol CTLineBarChartDataProtocol: CTChartData where CTStyle: CTLineBarChartStyle { - + /// A type representing a View for displaying labels on the X axis. - associatedtype XLabels : View + associatedtype XLabels: View /** Returns the difference between the highest and lowest numbers in the data set or data sets. @@ -29,27 +29,27 @@ public protocol CTLineBarChartDataProtocol: CTChartData where CTStyle: CTLineBar /** Returns the highest value in the data set or data sets */ - var maxValue: Double { get } + var maxValue: Double { get } /** Returns the average value from the data set or data sets. */ - var average : Double { get } + var average: Double { get } /** Array of strings for the labels on the X Axis instead of the labels in the data points. - */ + */ var xAxisLabels: [String]? { get set } /** Array of strings for the labels on the Y Axis instead of the labels generated from data point values. - */ + */ var yAxisLabels: [String]? { get set } /** Data model to hold data about the Views layout. - + This informs some `ViewModifiers` whether the chart has X and/or Y axis labels so they can configure thier layouts appropriately. */ @@ -193,10 +193,11 @@ public protocol CTLineBarDataPointProtocol: CTDataPointBaseProtocol { } extension CTLineBarDataPointProtocol { + /** Unwarpped xAxisLabel */ - var wrappedXAxisLabel : String { + var wrappedXAxisLabel: String { self.xAxisLabel ?? "" } } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocolsExtentions.swift b/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocolsExtentions.swift index 6e5b6392..821aa8fe 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocolsExtentions.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Models/Protocols/LineAndBarProtocolsExtentions.swift @@ -9,10 +9,10 @@ import SwiftUI // MARK: - Data Set extension CTLineBarChartDataProtocol { - public var range : Double { + public var range: Double { get { - var _lowestValue : Double - var _highestValue : Double + var _lowestValue: Double + var _highestValue: Double switch self.chartStyle.baseline { case .minimumValue: @@ -34,7 +34,7 @@ extension CTLineBarChartDataProtocol { } } - public var minValue : Double { + public var minValue: Double { get { switch self.chartStyle.baseline { case .minimumValue: @@ -47,7 +47,7 @@ extension CTLineBarChartDataProtocol { } } - public var maxValue : Double { + public var maxValue: Double { get { switch self.chartStyle.topLine { case .maximumValue: @@ -58,33 +58,24 @@ extension CTLineBarChartDataProtocol { } } - public var average : Double { + public var average: Double { return self.dataSets.average() } } - // MARK: - Y Labels extension CTLineBarChartDataProtocol { public func getYLabels(_ specifier: String) -> [String] { - switch self.chartStyle.yAxisLabelType { case .numeric: - - var labels : [String] = [] - let dataRange : Double = self.range - let minValue : Double = self.minValue - let range : Double = dataRange / Double(self.chartStyle.yAxisNumberOfLabels-1) - labels.append(String(format: specifier, minValue)) - for index in 1...self.chartStyle.yAxisNumberOfLabels-1 { - let labelValue = minValue + range * Double(index) - let labelString = String(format: specifier, labelValue) - labels.append(labelString) - } + let dataRange: Double = self.range + let minValue: Double = self.minValue + let range: Double = dataRange / Double(self.chartStyle.yAxisNumberOfLabels-1) + let firstLabel = [String(format: specifier, minValue)] + let otherLabels = (1...self.chartStyle.yAxisNumberOfLabels-1).map { String(format: specifier, minValue + range * Double($0)) } + let labels = firstLabel + otherLabels return labels - case .custom: - return self.yAxisLabels ?? [] } } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/HorizontalGridShape.swift b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/HorizontalGridShape.swift index f88233f2..271f7f9a 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/HorizontalGridShape.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/HorizontalGridShape.swift @@ -8,7 +8,9 @@ import SwiftUI /** - Horizontal line. + Basic horizontal line shape. + + Used in Grid */ internal struct HorizontalGridShape: Shape { internal func path(in rect: CGRect) -> Path { diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/LinearTrendLineShape.swift b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/LinearTrendLineShape.swift index 618a4f38..6766e60a 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/LinearTrendLineShape.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/LinearTrendLineShape.swift @@ -14,14 +14,14 @@ internal struct LinearTrendLineShape: Shape { private let firstValue: Double private let lastValue: Double - - private let range : Double + private let range: Double private let minValue: Double - internal init(firstValue: Double, - lastValue: Double, - range: Double, - minValue: Double + internal init( + firstValue: Double, + lastValue: Double, + range: Double, + minValue: Double ) { self.firstValue = firstValue self.lastValue = lastValue @@ -30,7 +30,7 @@ internal struct LinearTrendLineShape: Shape { } internal func path(in rect: CGRect) -> Path { - let y : CGFloat = rect.height / CGFloat(range) + let y: CGFloat = rect.height / CGFloat(range) var path = Path() path.move(to: CGPoint(x: 0, y: (CGFloat(firstValue - minValue) * -y) + rect.height)) diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/Marker.swift b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/Marker.swift index 78b86b85..28ceaa70 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/Marker.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/Marker.swift @@ -7,55 +7,53 @@ import SwiftUI -/// Generic line drawn horizontally across the chart +/// Generic line, drawn horizontally across the chart internal struct Marker: Shape { - private let value : Double - private let chartType : ChartType + private let value: Double + private let chartType: ChartType + private let range: Double + private let minValue: Double - let range : Double - let minValue: Double - let maxValue: Double - - internal init(value : Double, - range : Double, - minValue : Double, - maxValue : Double, - chartType : ChartType + internal init( + value: Double, + range: Double, + minValue: Double, + chartType: ChartType ) { - self.value = value - self.range = range - self.minValue = minValue - self.maxValue = maxValue - self.chartType = chartType + self.value = value + self.range = range + self.minValue = minValue + self.chartType = chartType } internal func path(in rect: CGRect) -> Path { - - var path = Path() - - let pointY : CGFloat + let pointY: CGFloat = getYPointLocation(rect: rect, chartType: chartType, value: value, minValue: minValue, range: range) + let firstPoint = CGPoint(x: 0, y: pointY) + let nextPoint = CGPoint(x: rect.width, y: pointY) + + var path = Path() + path.move(to: firstPoint) + path.addLine(to: nextPoint) + return path + } + + private func getYPointLocation( + rect: CGRect, + chartType: ChartType, + value: Double, + minValue: Double, + range: Double + ) -> CGFloat { switch chartType { case .line: let y = rect.height / CGFloat(range) - pointY = (CGFloat(value - minValue) * -y) + rect.height + return (CGFloat(value - minValue) * -y) + rect.height case .bar: let y = CGFloat(value - minValue) - pointY = (rect.height - (y / CGFloat(range)) * rect.height) + return (rect.height - (y / CGFloat(range)) * rect.height) case .pie: - pointY = 0 + return 0 } - - let firstPoint = CGPoint(x: 0, - y: pointY) - - let nextPoint = CGPoint(x: rect.width, - y: pointY) - - path.move(to: firstPoint) - path.addLine(to: nextPoint) - - return path } - } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/VerticalGridShape.swift b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/VerticalGridShape.swift index 8a4be76b..9d26c6c5 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Shapes/VerticalGridShape.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Shapes/VerticalGridShape.swift @@ -8,11 +8,13 @@ import SwiftUI /** - Vertical line. + Basic Vertical line shape. + + Used in Grid */ internal struct VerticalGridShape: Shape { internal func path(in rect: CGRect) -> Path { - var path = Path() + var path = Path() path.move(to: CGPoint(x: 0, y: rect.height)) path.addLine(to: CGPoint(x: 0, y: 0)) return path diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/AxisBorders.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/AxisBorders.swift index b385041e..045f8108 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/AxisBorders.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/AxisBorders.swift @@ -12,14 +12,13 @@ import SwiftUI */ internal struct XAxisBorder: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData: T - private let labelsAndTop : Bool - private let labelsAndBottom : Bool + @ObservedObject private var chartData: T + private let labelsAndTop: Bool + private let labelsAndBottom: Bool init(chartData: T) { self.chartData = chartData - - self.labelsAndTop = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .top + self.labelsAndTop = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .top self.labelsAndBottom = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .bottom } @@ -53,13 +52,13 @@ internal struct XAxisBorder: ViewModifier where T: CTLineBarChartDataProtocol */ internal struct YAxisBorder: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData: T - private let labelsAndLeading : Bool + @ObservedObject private var chartData: T + private let labelsAndLeading: Bool private let labelsAndTrailing: Bool internal init(chartData: T) { self.chartData = chartData - self.labelsAndLeading = chartData.viewData.hasYAxisLabels && chartData.chartStyle.yAxisLabelPosition == .leading + self.labelsAndLeading = chartData.viewData.hasYAxisLabels && chartData.chartStyle.yAxisLabelPosition == .leading self.labelsAndTrailing = chartData.viewData.hasYAxisLabels && chartData.chartStyle.yAxisLabelPosition == .trailing } @@ -90,7 +89,7 @@ extension View { internal func xAxisBorder(chartData: T) -> some View { self.modifier(XAxisBorder(chartData: chartData)) } - + internal func yAxisBorder(chartData: T) -> some View { self.modifier(YAxisBorder(chartData: chartData)) } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/TrendLine.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/TrendLine.swift index 0fd871f0..dc94cded 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/TrendLine.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/TrendLine.swift @@ -12,17 +12,18 @@ import SwiftUI */ internal struct LinearTrendLine: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject private var chartData : T + @ObservedObject private var chartData: T private let firstValue: Double private let lastValue: Double private let lineColour: ColourStyle private let strokeStyle: StrokeStyle - init(chartData: T, - firstValue: Double, - lastValue: Double, - lineColour: ColourStyle, - strokeStyle: StrokeStyle + init( + chartData: T, + firstValue: Double, + lastValue: Double, + lineColour: ColourStyle, + strokeStyle: StrokeStyle ) { self.chartData = chartData self.firstValue = firstValue @@ -34,8 +35,6 @@ internal struct LinearTrendLine: ViewModifier where T: CTLineBarChartDataProt internal func body(content: Content) -> some View { ZStack { content - - if lineColour.colourType == .colour, let colour = lineColour.colour { @@ -44,13 +43,11 @@ internal struct LinearTrendLine: ViewModifier where T: CTLineBarChartDataProt range: chartData.range, minValue: chartData.minValue) .stroke(colour, style: strokeStyle) - } else if lineColour.colourType == .gradientColour, - let colours = lineColour.colours, - let startPoint = lineColour.startPoint, - let endPoint = lineColour.endPoint + let colours = lineColour.colours, + let startPoint = lineColour.startPoint, + let endPoint = lineColour.endPoint { - LinearTrendLineShape(firstValue: firstValue, lastValue: lastValue, range: chartData.range, @@ -59,14 +56,12 @@ internal struct LinearTrendLine: ViewModifier where T: CTLineBarChartDataProt startPoint: startPoint, endPoint: endPoint), style: strokeStyle) - } else if lineColour.colourType == .gradientStops, - let stops = lineColour.stops, + let stops = lineColour.stops, let startPoint = lineColour.startPoint, - let endPoint = lineColour.endPoint + let endPoint = lineColour.endPoint { let stops = GradientStop.convertToGradientStopsArray(stops: stops) - LinearTrendLineShape(firstValue: firstValue, lastValue: lastValue, range: chartData.range, @@ -85,11 +80,11 @@ extension View { Draws a line across the chart to show the the trend in the data. - Parameters: - - chartData: Chart data model. - - firstValue: The value of the leading data point. - - lastValue: The value of the trailnig data point. - - lineColour: Line Colour. - - strokeStyle: Stroke Style. + - chartData: Chart data model. + - firstValue: The value of the leading data point. + - lastValue: The value of the trailnig data point. + - lineColour: Line Colour. + - strokeStyle: Stroke Style. - Returns: A new view containing the chart with a trend line. */ public func linearTrendLine( diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisGrid.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisGrid.swift index cf3157f6..a11061fc 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisGrid.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisGrid.swift @@ -12,8 +12,12 @@ import SwiftUI */ internal struct XAxisGrid: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData : T - + @ObservedObject private var chartData: T + + internal init(chartData: T) { + self.chartData = chartData + } + internal func body(content: Content) -> some View { ZStack { if chartData.isGreaterThanTwo() { @@ -29,7 +33,6 @@ internal struct XAxisGrid: ViewModifier where T: CTLineBarChartDataProtocol { } content } else { content } - } } } @@ -59,7 +62,7 @@ extension View { - Parameter chartData: Chart data model. - Returns: A new view containing the chart with vertical lines under it. - */ + */ public func xAxisGrid(chartData: T) -> some View { self.modifier(XAxisGrid(chartData: chartData)) } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisLabels.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisLabels.swift index 82324c7a..8d20141d 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisLabels.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/XAxisLabels.swift @@ -12,7 +12,7 @@ import SwiftUI */ internal struct XAxisLabels: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T private var titleHeight: CGFloat = 0 internal init(chartData: T) { @@ -70,7 +70,7 @@ extension View { - Requires: Chart Data to conform to CTLineBarChartDataProtocol. - + - Requires: Chart Data to conform to CTLineBarChartDataProtocol. diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisGrid.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisGrid.swift index bb28dfa6..ede55103 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisGrid.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisGrid.swift @@ -12,7 +12,11 @@ import SwiftUI */ internal struct YAxisGrid: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData : T + @ObservedObject private var chartData: T + + internal init(chartData: T) { + self.chartData = chartData + } internal func body(content: Content) -> some View { ZStack { @@ -58,7 +62,7 @@ extension View { - Parameter chartData: Chart data model. - Returns: A new view containing the chart with horizontal lines under it. - */ + */ public func yAxisGrid(chartData: T) -> some View { self.modifier(YAxisGrid(chartData: chartData)) } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisLabels.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisLabels.swift index 9968ddbe..1199d407 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisLabels.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisLabels.swift @@ -12,22 +12,21 @@ import SwiftUI */ internal struct YAxisLabels: ViewModifier where T: CTLineBarChartDataProtocol { - @ObservedObject var chartData: T + @ObservedObject private var chartData: T + private let specifier: String + private var labelsArray: [String] { chartData.getYLabels(specifier) } + private let labelsAndTop: Bool + private let labelsAndBottom: Bool - private let specifier : String - private var labelsArray : [String] { chartData.getYLabels(specifier) } - - private let labelsAndTop : Bool - private let labelsAndBottom : Bool - - internal init(chartData: T, - specifier: String + internal init( + chartData: T, + specifier: String ) { self.chartData = chartData self.specifier = specifier chartData.viewData.hasYAxisLabels = true - labelsAndTop = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .top + labelsAndTop = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .top labelsAndBottom = chartData.viewData.hasXAxisLabels && chartData.chartStyle.xAxisLabelPosition == .bottom } @@ -139,7 +138,7 @@ extension View { - Doughnut Chart - Parameters: - - specifier: Decimal precision specifier + - specifier: Decimal precision specifier - Returns: HStack of labels */ public func yAxisLabels(chartData: T, specifier: String = "%.0f") -> some View { diff --git a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisPOI.swift b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisPOI.swift index 1b213943..d7453d13 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisPOI.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/ViewModifiers/YAxisPOI.swift @@ -11,55 +11,56 @@ import SwiftUI Configurable Point of interest */ internal struct YAxisPOI: ViewModifier where T: CTLineBarChartDataProtocol { - - @ObservedObject var chartData: T - private let uuid : UUID = UUID() + @ObservedObject private var chartData: T - private let markerName : String - private var markerValue : Double - private let lineColour : Color - private let strokeStyle : StrokeStyle + private let uuid: UUID = UUID() - private let labelPosition : DisplayValue - private let labelFont : Font - private let labelColour : Color - private let labelBackground : Color + private let markerName: String + private var markerValue: Double + private let lineColour: Color + private let strokeStyle: StrokeStyle - private let range : Double - private let minValue : Double - private let maxValue : Double - - internal init(chartData : T, - markerName : String, - markerValue : Double = 0, - labelPosition : DisplayValue, - labelFont : Font, - labelColour : Color, - labelBackground: Color, - lineColour : Color, - strokeStyle : StrokeStyle, - isAverage : Bool + private let labelPosition: DisplayValue + private let labelFont: Font + private let labelColour: Color + private let labelBackground: Color + + private let range: Double + private let minValue: Double + private let maxValue: Double + + internal init( + chartData: T, + markerName: String, + markerValue: Double = 0, + labelPosition: DisplayValue, + labelFont: Font, + labelColour: Color, + labelBackground: Color, + lineColour: Color, + strokeStyle: StrokeStyle, + isAverage: Bool ) { - self.chartData = chartData - self.markerName = markerName - self.lineColour = lineColour + self.chartData = chartData + self.markerName = markerName + self.lineColour = lineColour self.strokeStyle = strokeStyle - self.labelPosition = labelPosition - self.labelFont = labelFont - self.labelColour = labelColour + self.labelPosition = labelPosition + self.labelFont = labelFont + self.labelColour = labelColour self.labelBackground = labelBackground self.markerValue = isAverage ? chartData.average : markerValue - self.maxValue = chartData.maxValue - self.range = chartData.range - self.minValue = chartData.minValue + self.maxValue = chartData.maxValue + self.range = chartData.range + self.minValue = chartData.minValue self.setupPOILegends() } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false internal func body(content: Content) -> some View { ZStack { @@ -78,77 +79,75 @@ internal struct YAxisPOI: ViewModifier where T: CTLineBarChartDataProtocol { } var marker: some View { - Marker(value : markerValue, - range : range, - minValue : minValue, - maxValue : maxValue, - chartType : chartData.chartType.chartType) + Marker(value: markerValue, + range: range, + minValue: minValue, + chartType: chartData.chartType.chartType) .trim(to: startAnimation ? 1 : 0) .stroke(lineColour, style: strokeStyle) } var valueLabel: some View { GeometryReader { geo in - switch labelPosition { case .none: - EmptyView() - case .yAxis(specifier: let specifier): - - ValueLabelYAxisSubView(chartData : chartData, - markerValue : markerValue, - specifier : specifier, - labelFont : labelFont, - labelColour : labelColour, - labelBackground : labelBackground, - lineColour : lineColour) + ValueLabelYAxisSubView(chartData: chartData, + markerValue: markerValue, + specifier: specifier, + labelFont: labelFont, + labelColour: labelColour, + labelBackground: labelBackground, + lineColour: lineColour) .position(x: -(chartData.infoView.yAxisLabelWidth / 2) - 6, - y: getYPoint(chartType: chartData.chartType.chartType, - height: geo.size.height)) + y: getYPoint(chartType: chartData.chartType.chartType, height: geo.size.height, markerValue: markerValue, range: range, minValue: minValue)) .accessibilityLabel(Text("P O I Marker")) .accessibilityValue(Text("\(markerName), \(markerValue, specifier: specifier)")) - case .center(specifier: let specifier): - - ValueLabelCenterSubView(chartData : chartData, - markerValue : markerValue, - specifier : specifier, - labelFont : labelFont, - labelColour : labelColour, - labelBackground : labelBackground, - lineColour : lineColour, - strokeStyle : strokeStyle) + ValueLabelCenterSubView(chartData: chartData, + markerValue: markerValue, + specifier: specifier, + labelFont: labelFont, + labelColour: labelColour, + labelBackground: labelBackground, + lineColour: lineColour, + strokeStyle: strokeStyle) .position(x: geo.frame(in: .local).width / 2, - y: getYPoint(chartType: chartData.chartType.chartType, height: geo.size.height)) - + y: getYPoint(chartType: chartData.chartType.chartType, height: geo.size.height, markerValue: markerValue, range: range, minValue: minValue)) .accessibilityLabel(Text("P O I Marker")) .accessibilityValue(Text("\(markerName), \(markerValue, specifier: specifier)")) } } } - private func getYPoint(chartType: ChartType, height: CGFloat) -> CGFloat { - switch chartData.chartType.chartType { - case .line: - let y = height / CGFloat(chartData.range) - return (CGFloat(markerValue - chartData.minValue) * -y) + height - case .bar: - let value = CGFloat(markerValue) - CGFloat(chartData.minValue) - return (height - (value / CGFloat(chartData.range)) * height) - - case .pie: - return 0 - } - } + + private func getYPoint( + chartType: ChartType, + height: CGFloat, + markerValue: Double, + range: Double, + minValue: Double + ) -> CGFloat { + switch chartType { + case .line: + let y = height / CGFloat(range) + return (CGFloat(markerValue - minValue) * -y) + height + case .bar: + let value = CGFloat(markerValue) - CGFloat(minValue) + return (height - (value / CGFloat(range)) * height) + case .pie: + return 0 + } + } + private func setupPOILegends() { if !chartData.legends.contains(where: { $0.legend == markerName }) { // init twice - chartData.legends.append(LegendData(id : uuid, - legend : markerName, - colour : ColourStyle(colour: lineColour), - strokeStyle : strokeStyle.toStroke(), - prioity : 2, - chartType : .line)) + chartData.legends.append(LegendData(id: uuid, + legend: markerName, + colour: ColourStyle(colour: lineColour), + strokeStyle: strokeStyle.toStroke(), + prioity: 2, + chartType: .line)) } } } @@ -206,26 +205,26 @@ extension View { - Returns: A new view containing the chart with a marker line at a specified value. */ public func yAxisPOI( - chartData : T, - markerName : String, - markerValue : Double, - labelPosition : DisplayValue = .center(specifier: "%.0f"), - labelFont : Font = .caption, - labelColour : Color = Color.primary, - labelBackground: Color = Color.systemsBackground, - lineColour : Color = Color(.blue), - strokeStyle : StrokeStyle = StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [CGFloat](), dashPhase: 0) + chartData: T, + markerName: String, + markerValue: Double, + labelPosition: DisplayValue = .center(specifier: "%.0f"), + labelFont: Font = .caption, + labelColour: Color = Color.primary, + labelBackground: Color = Color.systemsBackground, + lineColour: Color = Color(.blue), + strokeStyle: StrokeStyle = StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [CGFloat](), dashPhase: 0) ) -> some View { - self.modifier(YAxisPOI(chartData : chartData, - markerName : markerName, - markerValue : markerValue, - labelPosition : labelPosition, - labelFont : labelFont, - labelColour : labelColour, + self.modifier(YAxisPOI(chartData: chartData, + markerName: markerName, + markerValue: markerValue, + labelPosition: labelPosition, + labelFont: labelFont, + labelColour: labelColour, labelBackground: labelBackground, - lineColour : lineColour, - strokeStyle : strokeStyle, - isAverage : false)) + lineColour: lineColour, + strokeStyle: strokeStyle, + isAverage: false)) } @@ -278,27 +277,25 @@ extension View { - lineColour: Line Colour. - strokeStyle: Style of Stroke. - Returns: A new view containing the chart with a marker line at the average. - - - Tag: AverageLine - */ + */ public func averageLine( - chartData : T, - markerName : String = "Average", - labelPosition : DisplayValue = .yAxis(specifier: "%.0f"), - labelFont : Font = .caption, - labelColour : Color = Color.primary, - labelBackground: Color = Color.systemsBackground, - lineColour : Color = Color.primary, - strokeStyle : StrokeStyle = StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [CGFloat](), dashPhase: 0) + chartData: T, + markerName: String = "Average", + labelPosition: DisplayValue = .yAxis(specifier: "%.0f"), + labelFont: Font = .caption, + labelColour: Color = Color.primary, + labelBackground: Color = Color.systemsBackground, + lineColour: Color = Color.primary, + strokeStyle: StrokeStyle = StrokeStyle(lineWidth: 2, lineCap: .round, lineJoin: .round, miterLimit: 10, dash: [CGFloat](), dashPhase: 0) ) -> some View { - self.modifier(YAxisPOI(chartData : chartData, - markerName : markerName, - labelPosition : labelPosition, - labelFont : labelFont, - labelColour : labelColour, + self.modifier(YAxisPOI(chartData: chartData, + markerName: markerName, + labelPosition: labelPosition, + labelFont: labelFont, + labelColour: labelColour, labelBackground: labelBackground, - lineColour : lineColour, - strokeStyle : strokeStyle, - isAverage : true)) + lineColour: lineColour, + strokeStyle: strokeStyle, + isAverage: true)) } } diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Views/ YAxisLabelCell.swift b/Sources/SwiftUICharts/SharedLineAndBar/Views/ YAxisLabelCell.swift index 6cecd2a8..f4beff83 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Views/ YAxisLabelCell.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Views/ YAxisLabelCell.swift @@ -9,21 +9,23 @@ import SwiftUI internal struct XAxisDataPointCell: View where ChartData: CTLineBarChartDataProtocol { - @ObservedObject var chartData: ChartData - + @ObservedObject private var chartData: ChartData private let label: String private let rotationAngle: Angle - internal init(chartData: ChartData, label: String, rotationAngle : Angle) { - self.chartData = chartData - self.label = label + internal init( + chartData: ChartData, + label: String, + rotationAngle: Angle + ) { + self.chartData = chartData + self.label = label self.rotationAngle = rotationAngle } @State private var width: CGFloat = 0 - + internal var body: some View { - Text(label) .font(chartData.chartStyle.xAxisLabelFont) .lineLimit(1) @@ -46,14 +48,17 @@ internal struct XAxisDataPointCell: View where ChartData: CTLineBarCh internal struct XAxisChartDataCell: View where ChartData: CTLineBarChartDataProtocol { - @ObservedObject var chartData: ChartData - + @ObservedObject private var chartData: ChartData private let label: String private let rotationAngle: Angle - internal init(chartData: ChartData, label: String, rotationAngle: Angle) { - self.chartData = chartData - self.label = label + internal init( + chartData: ChartData, + label: String, + rotationAngle: Angle + ) { + self.chartData = chartData + self.label = label self.rotationAngle = rotationAngle } @@ -61,12 +66,11 @@ internal struct XAxisChartDataCell: View where ChartData: CTLineBarCh @State private var height: CGFloat = 0 internal var body: some View { - Text(label) .font(chartData.chartStyle.xAxisLabelFont) .lineLimit(1) .fixedSize(horizontal: true, vertical: false) - .rotationEffect(rotationAngle, anchor: .top) + .rotationEffect(rotationAngle, anchor: .center) .overlay( GeometryReader { geo in Color.clear @@ -76,10 +80,17 @@ internal struct XAxisChartDataCell: View where ChartData: CTLineBarCh } } ) - .frame(width: width, height: rotationAngle == .init(degrees: 0) || rotationAngle == .init(radians: 0) ? height : width) + .frame(width: rotationDegrees || rotationRadians ? 10 : width, + height: rotationAngle == .init(degrees: 0) || rotationAngle == .init(radians: 0) ? height : width) .onAppear { chartData.viewData.xAxisLabelHeights.append(width) } } + + private var rotationDegrees: Bool { + rotationAngle == .init(degrees: 90) || rotationAngle == .init(degrees: -90) + } + private var rotationRadians: Bool { + rotationAngle == .init(radians: 1.5708) || rotationAngle == .init(radians: -1.5708) + } } - diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Views/HorizontalGridView.swift b/Sources/SwiftUICharts/SharedLineAndBar/Views/HorizontalGridView.swift index 9cca9cf2..2e9da2e4 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Views/HorizontalGridView.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Views/HorizontalGridView.swift @@ -12,20 +12,20 @@ import SwiftUI */ internal struct HorizontalGridView: View where T: CTLineBarChartDataProtocol { - @ObservedObject private var chartData : T - - internal init(chartData: T) { - self.chartData = chartData - } + @ObservedObject private var chartData: T - @State private var startAnimation : Bool = false + internal init(chartData: T) { + self.chartData = chartData + } + + @State private var startAnimation: Bool = false var body: some View { HorizontalGridShape() .trim(to: startAnimation ? 1 : 0) .stroke(chartData.chartStyle.yAxisGridStyle.lineColour, style: StrokeStyle(lineWidth: chartData.chartStyle.yAxisGridStyle.lineWidth, - dash : chartData.chartStyle.yAxisGridStyle.dash, + dash: chartData.chartStyle.yAxisGridStyle.dash, dashPhase: chartData.chartStyle.yAxisGridStyle.dashPhase)) .frame(height: chartData.chartStyle.yAxisGridStyle.lineWidth) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelCenterSubView.swift b/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelCenterSubView.swift index 007763cd..f7022dd7 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelCenterSubView.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelCenterSubView.swift @@ -8,36 +8,37 @@ import SwiftUI internal struct ValueLabelCenterSubView: View where T: CTLineBarChartDataProtocol { - - private let chartData : T - private let markerValue : Double - private let specifier : String - private let labelFont : Font - private let labelColour : Color - private let labelBackground : Color - private let lineColour : Color - private let strokeStyle : StrokeStyle - internal init(chartData : T, - markerValue : Double, - specifier : String, - labelFont : Font, - labelColour : Color, - labelBackground : Color, - lineColour : Color, - strokeStyle : StrokeStyle + private let chartData: T + private let markerValue: Double + private let specifier: String + private let labelFont: Font + private let labelColour: Color + private let labelBackground: Color + private let lineColour: Color + private let strokeStyle: StrokeStyle + + internal init( + chartData: T, + markerValue: Double, + specifier: String, + labelFont: Font, + labelColour: Color, + labelBackground: Color, + lineColour: Color, + strokeStyle: StrokeStyle ) { - self.chartData = chartData - self.markerValue = markerValue - self.specifier = specifier - self.labelFont = labelFont - self.labelColour = labelColour + self.chartData = chartData + self.markerValue = markerValue + self.specifier = specifier + self.labelFont = labelFont + self.labelColour = labelColour self.labelBackground = labelBackground - self.lineColour = lineColour - self.strokeStyle = strokeStyle + self.lineColour = lineColour + self.strokeStyle = strokeStyle } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false var body: some View { Text("\(markerValue, specifier: specifier)") @@ -56,7 +57,5 @@ internal struct ValueLabelCenterSubView: View where T: CTLineBarChartDataProt .animateOnDisappear(using: chartData.chartStyle.globalAnimation) { self.startAnimation = false } - } } - diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelYAxisSubView.swift b/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelYAxisSubView.swift index 107c5e4f..02c1aab8 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelYAxisSubView.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Views/ValueLabelYAxisSubView.swift @@ -8,30 +8,31 @@ import SwiftUI internal struct ValueLabelYAxisSubView: View where T: CTLineBarChartDataProtocol { - - @ObservedObject var chartData: T - private let markerValue : Double - private let specifier : String - private let labelFont : Font - private let labelColour : Color - private let labelBackground : Color - private let lineColour : Color - internal init(chartData : T, - markerValue : Double, - specifier : String, - labelFont : Font, - labelColour : Color, - labelBackground : Color, - lineColour : Color + @ObservedObject private var chartData: T + private let markerValue: Double + private let specifier: String + private let labelFont: Font + private let labelColour: Color + private let labelBackground: Color + private let lineColour: Color + + internal init( + chartData: T, + markerValue: Double, + specifier: String, + labelFont: Font, + labelColour: Color, + labelBackground: Color, + lineColour: Color ) { - self.chartData = chartData - self.markerValue = markerValue - self.specifier = specifier - self.labelFont = labelFont - self.labelColour = labelColour + self.chartData = chartData + self.markerValue = markerValue + self.specifier = specifier + self.labelFont = labelFont + self.labelColour = labelColour self.labelBackground = labelBackground - self.lineColour = lineColour + self.lineColour = lineColour } var body: some View { diff --git a/Sources/SwiftUICharts/SharedLineAndBar/Views/VerticalGridView.swift b/Sources/SwiftUICharts/SharedLineAndBar/Views/VerticalGridView.swift index 33b50037..083d1699 100644 --- a/Sources/SwiftUICharts/SharedLineAndBar/Views/VerticalGridView.swift +++ b/Sources/SwiftUICharts/SharedLineAndBar/Views/VerticalGridView.swift @@ -12,20 +12,20 @@ import SwiftUI */ internal struct VerticalGridView: View where T: CTLineBarChartDataProtocol { - @ObservedObject private var chartData : T + @ObservedObject private var chartData: T internal init(chartData: T) { self.chartData = chartData } - @State private var startAnimation : Bool = false + @State private var startAnimation: Bool = false var body: some View { VerticalGridShape() .trim(to: startAnimation ? 1 : 0) .stroke(chartData.chartStyle.xAxisGridStyle.lineColour, style: StrokeStyle(lineWidth: chartData.chartStyle.xAxisGridStyle.lineWidth, - dash : chartData.chartStyle.xAxisGridStyle.dash, + dash: chartData.chartStyle.xAxisGridStyle.dash, dashPhase: chartData.chartStyle.xAxisGridStyle.dashPhase)) .frame(width: chartData.chartStyle.xAxisGridStyle.lineWidth) .animateOnAppear(using: chartData.chartStyle.globalAnimation) { diff --git a/Tests/SwiftUIChartsTests/BarCharts/BarChartTests.swift b/Tests/SwiftUIChartsTests/BarCharts/BarChartTests.swift index c04acb92..230aeec8 100644 --- a/Tests/SwiftUIChartsTests/BarCharts/BarChartTests.swift +++ b/Tests/SwiftUIChartsTests/BarCharts/BarChartTests.swift @@ -32,10 +32,10 @@ final class BarChartTests: XCTestCase { let chartData = BarChartData(dataSets: BarDataSet(dataPoints: dataPoints)) XCTAssertTrue(chartData.isGreaterThanTwo()) } - + // MARK: - Labels func testBarGetYLabels() { - + let chartData = BarChartData(dataSets: BarDataSet(dataPoints: dataPoints), chartStyle: BarChartStyle(yAxisNumberOfLabels: 3)) @@ -61,7 +61,7 @@ final class BarChartTests: XCTestCase { XCTAssertEqual(chartData.getYLabels("%.2f")[1], "50.00" ) XCTAssertEqual(chartData.getYLabels("%.2f")[2], "100.00") } - + // MARK: - Touch func testBarGetDataPoint() { let rect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100) @@ -95,7 +95,6 @@ final class BarChartTests: XCTestCase { let testAgainstFour = chartData.dataSets.dataPoints XCTAssertEqual(testOutputFour[0], testAgainstFour[3]) } - func testBarGetPointLocation() { let rect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100) diff --git a/Tests/SwiftUIChartsTests/BarCharts/GroupedBarChartTests.swift b/Tests/SwiftUIChartsTests/BarCharts/GroupedBarChartTests.swift index 569e3f54..398943d0 100644 --- a/Tests/SwiftUIChartsTests/BarCharts/GroupedBarChartTests.swift +++ b/Tests/SwiftUIChartsTests/BarCharts/GroupedBarChartTests.swift @@ -11,7 +11,7 @@ final class GroupedBarChartTests: XCTestCase { case three case four - var data : GroupingData { + var data: GroupingData { switch self { case .one: return GroupingData(title: "One" , colour: ColourStyle(colour: .blue)) @@ -25,7 +25,7 @@ final class GroupedBarChartTests: XCTestCase { } } - let groups : [GroupingData] = [Group.one.data, Group.two.data, Group.three.data, Group.four.data] + let groups: [GroupingData] = [Group.one.data, Group.two.data, Group.three.data, Group.four.data] let data = GroupedBarDataSets(dataSets: [ GroupedBarDataSet(dataPoints: [ @@ -56,7 +56,7 @@ final class GroupedBarChartTests: XCTestCase { GroupedBarDataPoint(value: 50, description: "Four Four" , group: Group.four.data) ]) ]) - + // MARK: - Data func testGroupedBarMaxValue() { let chartData = GroupedBarChartData(dataSets: data, groups: groups) @@ -82,13 +82,13 @@ final class GroupedBarChartTests: XCTestCase { XCTAssertTrue(chartData.isGreaterThanTwo()) } - + // MARK: - Labels func testGroupedBarGetYLabels() { let chartData = GroupedBarChartData(dataSets: data, groups: groups, chartStyle: BarChartStyle(yAxisNumberOfLabels: 3)) - + chartData.chartStyle.topLine = .maximumValue chartData.chartStyle.baseline = .zero XCTAssertEqual(chartData.getYLabels("%.2f")[0], "0.00") @@ -164,7 +164,7 @@ final class GroupedBarChartTests: XCTestCase { // Group 1 let touchLocationOne: CGPoint = CGPoint(x: 0, y: 25) - + let testOne: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, touchLocation: touchLocationOne, chartSize: rect)! @@ -174,30 +174,30 @@ final class GroupedBarChartTests: XCTestCase { // Group 2 let touchLocationTwo: CGPoint = CGPoint(x: 30, y: 25) - + let testTwo: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, touchLocation: touchLocationTwo, chartSize: rect)! let testAgainstTwo = CGPoint(x: 29.68, y: 77.77) XCTAssertEqual(testTwo.x, testAgainstTwo.x, accuracy: 0.01) XCTAssertEqual(testTwo.y, testAgainstTwo.y, accuracy: 0.01) - + // Group 3 let touchLocationThree: CGPoint = CGPoint(x: 55, y: 25) let testThree: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, - touchLocation: touchLocationThree, - chartSize: rect)! + touchLocation: touchLocationThree, + chartSize: rect)! let testAgainstThree = CGPoint(x: 57.18, y: 66.66) XCTAssertEqual(testThree.x, testAgainstThree.x, accuracy: 0.01) XCTAssertEqual(testThree.y, testAgainstThree.y, accuracy: 0.01) - + // Group 4 let touchLocationFour: CGPoint = CGPoint(x: 83, y: 25) let testFour: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, - touchLocation: touchLocationFour, - chartSize: rect)! + touchLocation: touchLocationFour, + chartSize: rect)! let testAgainstFour = CGPoint(x: 84.68, y: 55.55) XCTAssertEqual(testFour.x, testAgainstFour.x, accuracy: 0.01) XCTAssertEqual(testFour.y, testAgainstFour.y, accuracy: 0.01) diff --git a/Tests/SwiftUIChartsTests/BarCharts/StackedBarChartTests.swift b/Tests/SwiftUIChartsTests/BarCharts/StackedBarChartTests.swift index e8805a7e..1f2ea80b 100644 --- a/Tests/SwiftUIChartsTests/BarCharts/StackedBarChartTests.swift +++ b/Tests/SwiftUIChartsTests/BarCharts/StackedBarChartTests.swift @@ -11,7 +11,7 @@ final class StackedBarChartTests: XCTestCase { case three case four - var data : GroupingData { + var data: GroupingData { switch self { case .one: return GroupingData(title: "One" , colour: ColourStyle(colour: .blue)) @@ -25,7 +25,7 @@ final class StackedBarChartTests: XCTestCase { } } - let groups : [GroupingData] = [Group.one.data, Group.two.data, Group.three.data, Group.four.data] + let groups: [GroupingData] = [Group.one.data, Group.two.data, Group.three.data, Group.four.data] let data = StackedBarDataSets(dataSets: [ StackedBarDataSet(dataPoints: [ @@ -56,7 +56,7 @@ final class StackedBarChartTests: XCTestCase { StackedBarDataPoint(value: 50, description: "Four Four" , group: Group.four.data) ]) ]) - + // MARK: - Data func testStackedBarMaxValue() { let chartData = StackedBarChartData(dataSets: data, groups: groups) @@ -81,13 +81,13 @@ final class StackedBarChartTests: XCTestCase { XCTAssertTrue(chartData.isGreaterThanTwo()) } - + // MARK: Labels func testStackedBarGetYLabels() { let chartData = StackedBarChartData(dataSets: data, groups: groups, chartStyle: BarChartStyle(yAxisNumberOfLabels: 3)) - + chartData.chartStyle.topLine = .maximumValue chartData.chartStyle.baseline = .zero XCTAssertEqual(chartData.getYLabels("%.2f")[0], "0.00") @@ -233,8 +233,8 @@ final class StackedBarChartTests: XCTestCase { // Stack 3 - Point 4 let touchLocationThreeFour: CGPoint = CGPoint(x: 55, y: 10) let testThreeFour: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, - touchLocation: touchLocationThreeFour, - chartSize: rect)! + touchLocation: touchLocationThreeFour, + chartSize: rect)! let testAgainstThreeFour = CGPoint(x: 62.50, y: 0.00) XCTAssertEqual(testThreeFour.x, testAgainstThreeFour.x, accuracy: 0.01) XCTAssertEqual(testThreeFour.y, testAgainstThreeFour.y, accuracy: 0.01) @@ -242,8 +242,8 @@ final class StackedBarChartTests: XCTestCase { // Stack 4 - Point 2 let touchLocationFourTwo: CGPoint = CGPoint(x: 83, y: 50) let testFourTwo: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, - touchLocation: touchLocationFourTwo, - chartSize: rect)! + touchLocation: touchLocationFourTwo, + chartSize: rect)! let testAgainstFourTwo = CGPoint(x: 87.50, y: 45.45) XCTAssertEqual(testFourTwo.x, testAgainstFourTwo.x, accuracy: 0.01) XCTAssertEqual(testFourTwo.y, testAgainstFourTwo.y, accuracy: 0.01) @@ -251,8 +251,8 @@ final class StackedBarChartTests: XCTestCase { // Stack 4 - Point 3 let touchLocationFourThree: CGPoint = CGPoint(x: 83, y: 40) let testFourThree: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets, - touchLocation: touchLocationFourThree, - chartSize: rect)! + touchLocation: touchLocationFourThree, + chartSize: rect)! let testAgainstFourThree = CGPoint(x: 87.50, y: 36.36) XCTAssertEqual(testFourThree.x, testAgainstFourThree.x, accuracy: 0.01) XCTAssertEqual(testFourThree.y, testAgainstFourThree.y, accuracy: 0.01) diff --git a/Tests/SwiftUIChartsTests/LineCharts/LineChartPathTests.swift b/Tests/SwiftUIChartsTests/LineCharts/LineChartPathTests.swift index 24aa8a66..3e06cef8 100644 --- a/Tests/SwiftUIChartsTests/LineCharts/LineChartPathTests.swift +++ b/Tests/SwiftUIChartsTests/LineCharts/LineChartPathTests.swift @@ -3,7 +3,7 @@ import SwiftUI @testable import SwiftUICharts final class LineChartPathTests: XCTestCase { - + let chartData = LineChartData(dataSets: LineDataSet(dataPoints: [ LineChartDataPoint(value: 0), LineChartDataPoint(value: 25), @@ -11,106 +11,105 @@ final class LineChartPathTests: XCTestCase { LineChartDataPoint(value: 75), LineChartDataPoint(value: 100) ])) - - let rect : CGRect = CGRect(x: 0, y: 0, width: 100, height: 100) + + let rect: CGRect = CGRect(x: 0, y: 0, width: 100, height: 100) let touchLocation: CGPoint = CGPoint(x: 25, y: 25) - + func testGetIndicatorLocation() { - - let test = LineChartData.getIndicatorLocation(rect : rect, - dataPoints : chartData.dataSets.dataPoints, - touchLocation : touchLocation, - lineType : .line, - minValue : chartData.minValue, - range : chartData.range, - ignoreZero : false) - + + let test = LineChartData.getIndicatorLocation(rect: rect, + dataPoints: chartData.dataSets.dataPoints, + touchLocation: touchLocation, + lineType: .line, + minValue: chartData.minValue, + range: chartData.range, + ignoreZero: false) + XCTAssertEqual(test.x, 25, accuracy: 0.1) XCTAssertEqual(test.y, 75, accuracy: 0.1) } - - + + func testGetPercentageOfPath() { - - let path = Path.straightLine(rect : rect, - dataPoints : chartData.dataSets.dataPoints, - minValue : chartData.minValue, - range : chartData.range, - isFilled : false) - + + let path = Path.straightLine(rect: rect, + dataPoints: chartData.dataSets.dataPoints, + minValue: chartData.minValue, + range: chartData.range, + isFilled: false) + let test = LineChartData.getPercentageOfPath(path: path, touchLocation: touchLocation) - + XCTAssertEqual(test, 0.25, accuracy: 0.1) } - + func testGetTotalLength() { - - let path = Path.straightLine(rect : rect, - dataPoints : chartData.dataSets.dataPoints, - minValue : chartData.minValue, - range : chartData.range, - isFilled : false) - + + let path = Path.straightLine(rect: rect, + dataPoints: chartData.dataSets.dataPoints, + minValue: chartData.minValue, + range: chartData.range, + isFilled: false) + let test = LineChartData.getTotalLength(of: path) - + XCTAssertEqual(test, 141.42, accuracy: 0.01) } - + func testGetLengthToTouch() { - - let path = Path.straightLine(rect : rect, - dataPoints : chartData.dataSets.dataPoints, - minValue : chartData.minValue, - range : chartData.range, - isFilled : false) - + + let path = Path.straightLine(rect: rect, + dataPoints: chartData.dataSets.dataPoints, + minValue: chartData.minValue, + range: chartData.range, + isFilled: false) + let test = LineChartData.getLength(to: touchLocation, on: path) - + XCTAssertEqual(test, 35.35, accuracy: 0.01) } - + func testRelativePoint() { - + let pointOne = CGPoint(x: 0.0, y: 0.0) let pointTwo = CGPoint(x: 100, y: 100) - + let test = LineChartData.relativePoint(from: pointOne, to: pointTwo, touchX: touchLocation.x) - + XCTAssertEqual(test.x, 25, accuracy: 0.01) XCTAssertEqual(test.y, 25, accuracy: 0.01) } - + func testDistanceToTouch() { - + let pointOne = CGPoint(x: 0.0, y: 0.0) let pointTwo = CGPoint(x: 100, y: 100) - + let test = LineChartData.distanceToTouch(from: pointOne, to: pointTwo, touchX: touchLocation.x) - + XCTAssertEqual(test, 35.355, accuracy: 0.01) } - + func testDistance() { - + let pointOne = CGPoint(x: 0.0, y: 0.0) let pointTwo = CGPoint(x: 100, y: 100) - + let test = LineChartData.distance(from: pointOne, to: pointTwo) - + XCTAssertEqual(test, 141.421356237309, accuracy: 0.01) } - + func testGetLocationOnPath() { - - let path = Path.straightLine(rect : rect, - dataPoints : chartData.dataSets.dataPoints, - minValue : chartData.minValue, - range : chartData.range, - isFilled : false) - - + + let path = Path.straightLine(rect: rect, + dataPoints: chartData.dataSets.dataPoints, + minValue: chartData.minValue, + range: chartData.range, + isFilled: false) + let test = LineChartData.locationOnPath(0.5, path) - + XCTAssertEqual(test.x, 50, accuracy: 0.1) XCTAssertEqual(test.y, 50, accuracy: 0.1) } diff --git a/Tests/SwiftUIChartsTests/LineCharts/LineChartTests.swift b/Tests/SwiftUIChartsTests/LineCharts/LineChartTests.swift index b0870fec..97bfe0b2 100644 --- a/Tests/SwiftUIChartsTests/LineCharts/LineChartTests.swift +++ b/Tests/SwiftUIChartsTests/LineCharts/LineChartTests.swift @@ -68,7 +68,7 @@ final class LineChartTests: XCTestCase { XCTAssertEqual(chartData.getYLabels("%.2f")[1], "50.00") XCTAssertEqual(chartData.getYLabels("%.2f")[2], "100.00") } - + // MARK: - Touch func testLineGetDataPoint() { diff --git a/Tests/SwiftUIChartsTests/LineCharts/MultiLineChartTest.swift b/Tests/SwiftUIChartsTests/LineCharts/MultiLineChartTest.swift index 5c8b3498..751a67b9 100644 --- a/Tests/SwiftUIChartsTests/LineCharts/MultiLineChartTest.swift +++ b/Tests/SwiftUIChartsTests/LineCharts/MultiLineChartTest.swift @@ -134,8 +134,8 @@ final class MultiLineChartTest: XCTestCase { // Data set 1 - point 1 let touchLocationOneOne: CGPoint = CGPoint(x: 5, y: 25) let testOneOne: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets.dataSets[0], - touchLocation: touchLocationOneOne, - chartSize: rect)! + touchLocation: touchLocationOneOne, + chartSize: rect)! let testAgainstOneOne = CGPoint(x: 0, y: 100) XCTAssertEqual(testOneOne.x, testAgainstOneOne.x) XCTAssertEqual(testOneOne.y, testAgainstOneOne.y) @@ -143,20 +143,20 @@ final class MultiLineChartTest: XCTestCase { // Data set 1 - point 3 let touchLocationOneThree: CGPoint = CGPoint(x: 66, y: 25) let testOneThree: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets.dataSets[0], - touchLocation: touchLocationOneThree, - chartSize: rect)! + touchLocation: touchLocationOneThree, + chartSize: rect)! let testAgainstOneThree = CGPoint(x: 66.66, y: 77.77) XCTAssertEqual(testOneThree.x, testAgainstOneThree.x, accuracy: 0.01) XCTAssertEqual(testOneThree.y, testAgainstOneThree.y, accuracy: 0.01) - + // Data set 2 - point 2 let touchLocationTwoTwo: CGPoint = CGPoint(x: 66, y: 25) let testTwoTwo: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets.dataSets[0], - touchLocation: touchLocationTwoTwo, - chartSize: rect)! + touchLocation: touchLocationTwoTwo, + chartSize: rect)! let testAgainstTwoTwo = CGPoint(x: 66.66, y: 77.77) XCTAssertEqual(testTwoTwo.x, testAgainstTwoTwo.x, accuracy: 0.01) XCTAssertEqual(testTwoTwo.y, testAgainstTwoTwo.y, accuracy: 0.01) @@ -164,8 +164,8 @@ final class MultiLineChartTest: XCTestCase { // Data set 2 - point 4 let touchLocationTwoFour: CGPoint = CGPoint(x: 5, y: 25) let testTwoFour: CGPoint = chartData.getPointLocation(dataSet: chartData.dataSets.dataSets[0], - touchLocation: touchLocationTwoFour, - chartSize: rect)! + touchLocation: touchLocationTwoFour, + chartSize: rect)! let testAgainstTwoFour = CGPoint(x: 0, y: 100) XCTAssertEqual(testTwoFour.x, testAgainstTwoFour.x) XCTAssertEqual(testTwoFour.y, testAgainstTwoFour.y)