From aa7e20ab931b9726026614763fa0030750e3b711 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Mon, 13 Nov 2017 17:45:45 -0800 Subject: [PATCH 01/20] Cleaned up `ChartDataSet` logic Added TODOs for areas where simple changes can help improve Swift consistency. --- .../Implementations/Standard/ChartDataSet.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index 7f90119ae9..44f06aa94f 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -41,9 +41,9 @@ open class ChartDataSet: ChartBaseDataSet @objc public init(values: [ChartDataEntry], label: String) { self.values = values - + super.init(label: label) - + self.calcMinMax() } @@ -99,6 +99,8 @@ open class ChartDataSet: ChartBaseDataSet open override func calcMinMaxY(fromX: Double, toX: Double) { + guard !_values.isEmpty else { return } + _yMax = -Double.greatestFiniteMagnitude _yMin = Double.greatestFiniteMagnitude @@ -152,7 +154,7 @@ open class ChartDataSet: ChartBaseDataSet /// if `i` is out of bounds, it may throw an out-of-bounds exception open override func entryForIndex(_ i: Int) -> ChartDataEntry? { - guard i >= values.startIndex, i < values.endIndex else { + guard _values.indices.contains(i) else { return nil } return values[i] @@ -425,8 +427,12 @@ open class ChartDataSet: ChartBaseDataSet // TODO: This should return the removed entry to follow Swift convention. open override func removeFirst() -> Bool { - let entry: ChartDataEntry? = values.isEmpty ? nil : values.removeFirst() - return entry != nil + guard !_values.isEmpty else { return false } + + _values.removeFirst() + calcMinMax() + + return true } /// Removes the last Entry (at index size-1) of this DataSet from the entries array. From 438dae757969b27239d281f906ac1500be5fa9dd Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sat, 18 Nov 2017 14:25:17 -0800 Subject: [PATCH 02/20] Added Collection conformances MutableCollection RandomAccessCollection RangeReplaceableCollection --- .../Implementations/Standard/ChartData.swift | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index ef4d1430db..7c4f26eba6 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -546,3 +546,118 @@ extension ChartData return self[index] } } + +// MARK: MutableCollection +extension ChartData: MutableCollection { + public typealias Index = Int + public typealias Element = IChartDataSet + + public var startIndex: Index { + return _dataSets.startIndex + } + + public var endIndex: Index { + return _dataSets.endIndex + } + + public func index(after: Index) -> Index { + return _dataSets.index(after: after) + } + + public subscript(position: Index) -> Element { + get{ return _dataSets[position] } + set{ self._dataSets[position] = newValue } + } +} + +// MARK: RandomAccessCollection +extension ChartData: RandomAccessCollection { + public func index(before: Index) -> Index { + return _dataSets.index(before: before) + } +} + +// MARK: RangeReplaceableCollection +extension ChartData: RangeReplaceableCollection { + public func append(_ newElement: Element) { + self._dataSets.append(newElement) + calcMinMax(dataSet: newElement) + } + + public func remove(at position: Index) -> Element { + let element = self._dataSets.remove(at: position) + calcMinMax() + return element + } + + public func removeFirst() -> Element { + let element = self._dataSets.removeFirst() + notifyDataChanged() + return element + } + + public func removeFirst(_ n: Int) { + self._dataSets.removeFirst(n) + notifyDataChanged() + } + + public func removeLast() -> Element { + let element = self._dataSets.removeLast() + notifyDataChanged() + return element + } + + public func removeLast(_ n: Int) { + self._dataSets.removeLast(n) + notifyDataChanged() + } + +// public func removeSubrange(_ bounds: Range) { +// self.dataSets.removeSubrange(bounds) +// notifyDataChanged() +// } +} + +// MARK: Swift Accessors +extension ChartData { + //TODO: Reevaluate if warning is still true + /// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not. + /// **IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations.** + /// + /// - Parameters: + /// - label: The label to search for + /// - ignoreCase: if true, the search is not case-sensitive + /// - Returns: The index of the DataSet Object with the given label. `nil` if not found + public func index(forLabel label: String, ignoreCase: Bool) -> Index? { + return ignoreCase + ? index { $0.label?.caseInsensitiveCompare(label) == .orderedSame } + : index { $0.label == label } + } + + public subscript(label: String, ignoreCase: Bool) -> Element? { + get { + guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } + return self[index] + } + } + + public subscript(entry: ChartDataEntry) -> Element? { + get { + guard let index = index(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { + return nil + } + return self[index] + } + } + + public func appendEntry(_ e: ChartDataEntry, toDataSet dataSetIndex: Index) { + guard indices.contains(dataSetIndex) else { + print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") + } + + let set = self[dataSetIndex] + if !set.addEntry(e) { return } + calcMinMax(entry: e, axis: set.axisDependency) + } + +} From 8aba0227a2e17ec82e8fd9d124f04ca6d987bb57 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sat, 18 Nov 2017 16:04:02 -0800 Subject: [PATCH 03/20] [#3018] Refactored use of `ChartData` to use new `Collection` conformances --- .../Implementations/Standard/ChartData.swift | 161 +++++------------- .../Standard/ChartDataSet.swift | 8 +- .../Charts/Highlight/ChartHighlighter.swift | 2 +- 3 files changed, 50 insertions(+), 121 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index 7c4f26eba6..f39c966866 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -91,6 +91,50 @@ open class ChartData: NSObject, ExpressibleByArrayLiteral xMin = .greatestFiniteMagnitude forEach { calcMinMax(dataSet: $0) } + + // left axis + let firstLeft = getFirstLeft(dataSets: dataSets) + + if firstLeft !== nil + { + leftAxisMax = firstLeft!.yMax + leftAxisMin = firstLeft!.yMin + + for dataSet in _dataSets where dataSet.axisDependency == .left + { + if dataSet.yMin < leftAxisMin + { + leftAxisMin = dataSet.yMin + } + + if dataSet.yMax > leftAxisMax + { + leftAxisMax = dataSet.yMax + } + } + } + + // right axis + let firstRight = getFirstRight(dataSets: dataSets) + + if firstRight !== nil + { + rightAxisMax = firstRight!.yMax + rightAxisMin = firstRight!.yMin + + for dataSet in _dataSets where dataSet.axisDependency == .right + { + if dataSet.yMin < rightAxisMin + { + rightAxisMin = dataSet.yMin + } + + if dataSet.yMax > rightAxisMax + { + rightAxisMax = dataSet.yMax + } + } + } } /// Adjusts the current minimum and maximum values based on the provided Entry object. @@ -345,7 +389,7 @@ open class ChartData: NSObject, ExpressibleByArrayLiteral /// If set to true, this means that values can be highlighted programmatically or by touch gesture. @objc open var isHighlightEnabled: Bool { - get { return first { $0.highlightEnabled == false } == nil } + get { return allSatisfy { $0.isHighlightEnabled } } set { forEach { $0.highlightEnabled = newValue } } } @@ -546,118 +590,3 @@ extension ChartData return self[index] } } - -// MARK: MutableCollection -extension ChartData: MutableCollection { - public typealias Index = Int - public typealias Element = IChartDataSet - - public var startIndex: Index { - return _dataSets.startIndex - } - - public var endIndex: Index { - return _dataSets.endIndex - } - - public func index(after: Index) -> Index { - return _dataSets.index(after: after) - } - - public subscript(position: Index) -> Element { - get{ return _dataSets[position] } - set{ self._dataSets[position] = newValue } - } -} - -// MARK: RandomAccessCollection -extension ChartData: RandomAccessCollection { - public func index(before: Index) -> Index { - return _dataSets.index(before: before) - } -} - -// MARK: RangeReplaceableCollection -extension ChartData: RangeReplaceableCollection { - public func append(_ newElement: Element) { - self._dataSets.append(newElement) - calcMinMax(dataSet: newElement) - } - - public func remove(at position: Index) -> Element { - let element = self._dataSets.remove(at: position) - calcMinMax() - return element - } - - public func removeFirst() -> Element { - let element = self._dataSets.removeFirst() - notifyDataChanged() - return element - } - - public func removeFirst(_ n: Int) { - self._dataSets.removeFirst(n) - notifyDataChanged() - } - - public func removeLast() -> Element { - let element = self._dataSets.removeLast() - notifyDataChanged() - return element - } - - public func removeLast(_ n: Int) { - self._dataSets.removeLast(n) - notifyDataChanged() - } - -// public func removeSubrange(_ bounds: Range) { -// self.dataSets.removeSubrange(bounds) -// notifyDataChanged() -// } -} - -// MARK: Swift Accessors -extension ChartData { - //TODO: Reevaluate if warning is still true - /// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not. - /// **IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations.** - /// - /// - Parameters: - /// - label: The label to search for - /// - ignoreCase: if true, the search is not case-sensitive - /// - Returns: The index of the DataSet Object with the given label. `nil` if not found - public func index(forLabel label: String, ignoreCase: Bool) -> Index? { - return ignoreCase - ? index { $0.label?.caseInsensitiveCompare(label) == .orderedSame } - : index { $0.label == label } - } - - public subscript(label: String, ignoreCase: Bool) -> Element? { - get { - guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } - return self[index] - } - } - - public subscript(entry: ChartDataEntry) -> Element? { - get { - guard let index = index(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { - return nil - } - return self[index] - } - } - - public func appendEntry(_ e: ChartDataEntry, toDataSet dataSetIndex: Index) { - guard indices.contains(dataSetIndex) else { - print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") - } - - let set = self[dataSetIndex] - if !set.addEntry(e) { return } - calcMinMax(entry: e, axis: set.axisDependency) - } - -} diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index 44f06aa94f..a945a8aabe 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -99,7 +99,7 @@ open class ChartDataSet: ChartBaseDataSet open override func calcMinMaxY(fromX: Double, toX: Double) { - guard !_values.isEmpty else { return } + guard !values.isEmpty else { return } _yMax = -Double.greatestFiniteMagnitude _yMin = Double.greatestFiniteMagnitude @@ -154,7 +154,7 @@ open class ChartDataSet: ChartBaseDataSet /// if `i` is out of bounds, it may throw an out-of-bounds exception open override func entryForIndex(_ i: Int) -> ChartDataEntry? { - guard _values.indices.contains(i) else { + guard values.indices.contains(i) else { return nil } return values[i] @@ -427,9 +427,9 @@ open class ChartDataSet: ChartBaseDataSet // TODO: This should return the removed entry to follow Swift convention. open override func removeFirst() -> Bool { - guard !_values.isEmpty else { return false } + guard !values.isEmpty else { return false } - _values.removeFirst() + values.removeFirst() calcMinMax() return true diff --git a/Source/Charts/Highlight/ChartHighlighter.swift b/Source/Charts/Highlight/ChartHighlighter.swift index d22302cf45..a75cc940a2 100644 --- a/Source/Charts/Highlight/ChartHighlighter.swift +++ b/Source/Charts/Highlight/ChartHighlighter.swift @@ -74,7 +74,7 @@ open class ChartHighlighter : NSObject, Highlighter guard let data = self.data else { return vals } for (i, set) in zip(data.indices, data) where set.isHighlightEnabled - { + { // extract all y-values from all DataSets at the given x-value. // some datasets (i.e bubble charts) make sense to have multiple values for an x-value. We'll have to find a way to handle that later on. It's more complicated now when x-indices are floating point. vals.append(contentsOf: buildHighlights(dataSet: set, dataSetIndex: i, xValue: xValue, rounding: .closest)) From 2ced54fd113b943ca3d262f4aac3edcbc1476e97 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sun, 19 Nov 2017 21:10:47 -0500 Subject: [PATCH 04/20] Fixed required initializers --- .../Charts/Data/Implementations/Standard/ChartData.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index f39c966866..23fcec2097 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -578,12 +578,20 @@ extension ChartData guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } return self[index] } +<<<<<<< refs/remotes/origin/4.0.0 public subscript(entry entry: ChartDataEntry) -> Element? { guard !(self is CombinedChartData) else { fatalError("subscript(entry:) not supported for CombinedData") +======= + + public func appendEntry(_ e: ChartDataEntry, toDataSet dataSetIndex: Index) { + guard indices.contains(dataSetIndex) else { + print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") + return +>>>>>>> HEAD~31 } guard let index = index(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { return nil } From 7dfb59e6acc5ff07ee087514b1e812d0e08c4a79 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Fri, 1 Dec 2017 12:07:29 -0500 Subject: [PATCH 05/20] ChartData adopts ExressibleByArrayLiteral --- .../Data/Implementations/Standard/ChartData.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index 23fcec2097..f1b580d2e4 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -578,20 +578,12 @@ extension ChartData guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } return self[index] } -<<<<<<< refs/remotes/origin/4.0.0 - + public subscript(entry entry: ChartDataEntry) -> Element? { guard !(self is CombinedChartData) else { fatalError("subscript(entry:) not supported for CombinedData") -======= - - public func appendEntry(_ e: ChartDataEntry, toDataSet dataSetIndex: Index) { - guard indices.contains(dataSetIndex) else { - print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") - return ->>>>>>> HEAD~31 } guard let index = index(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { return nil } From b554fb0a499ec5963d3e1164857158fcd9381d12 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Fri, 1 Dec 2017 12:22:09 -0500 Subject: [PATCH 06/20] Modified demos to take advantage of collection conformance. --- .../Swift/Demos/CubicLineChartViewController.swift | 2 +- .../Swift/Demos/LineChart2ViewController.swift | 10 +++++----- .../Swift/Demos/LineChartTimeViewController.swift | 10 +++++----- .../Swift/Demos/MultipleLinesChartViewController.swift | 8 ++++---- .../Swift/Demos/RadarChartViewController.swift | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift index 320b02e2c0..ee55bcee50 100644 --- a/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift @@ -131,7 +131,7 @@ class CubicLineChartViewController: DemoBaseViewController { chartView.setNeedsDisplay() case .toggleStepped: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift b/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift index f7d8e5a1fb..a0d0582051 100644 --- a/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift @@ -147,31 +147,31 @@ class LineChart2ViewController: DemoBaseViewController { switch option { case .toggleFilled: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() case .toggleHorizontalCubic: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift b/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift index caacc15e07..5a39c8d5e8 100644 --- a/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift @@ -122,31 +122,31 @@ class LineChartTimeViewController: DemoBaseViewController { switch option { case .toggleFilled: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() case .toggleHorizontalCubic: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift index c358e8b3a2..f12418cb8c 100644 --- a/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift @@ -103,25 +103,25 @@ class MultipleLinesChartViewController: DemoBaseViewController { switch option { case .toggleFilled: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for case let set as LineChartDataSet in data { + for set in chartView.data as! LineChartData { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift index ce5443ff04..91c89a89d6 100644 --- a/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift @@ -168,14 +168,14 @@ class RadarChartViewController: DemoBaseViewController { chartView.rotationEnabled = !chartView.rotationEnabled case .toggleFilled: - for case let set as RadarChartDataSet in data { + for set in chartView.data as! RadarChartData { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleHighlightCircle: - for case let set as RadarChartDataSet in data { + for set in chartView.data as! RadarChartData { set.drawHighlightCircleEnabled = !set.drawHighlightCircleEnabled } chartView.setNeedsDisplay() From 51501a15011406e21248581efb27c52e3afa53fa Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Wed, 13 Dec 2017 22:02:23 -0400 Subject: [PATCH 07/20] Pulled latest master --- Source/Charts/Data/Implementations/Standard/ChartDataSet.swift | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index a945a8aabe..7769bf653a 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -41,9 +41,8 @@ open class ChartDataSet: ChartBaseDataSet @objc public init(values: [ChartDataEntry], label: String) { self.values = values - super.init(label: label) - + self.calcMinMax() } From 78fb68274091aafa172b2a48c712d66ccc3870af Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Mon, 8 Jan 2018 20:31:15 -0400 Subject: [PATCH 08/20] Unified Style Replaced custom algorithms with built-in ones Made axis renderer implementations feel "Swift-ier" --- Source/Charts/Renderers/XAxisRenderer.swift | 364 +++++++----------- .../XAxisRendererHorizontalBarChart.swift | 157 +++----- .../Renderers/XAxisRendererRadarChart.swift | 21 +- Source/Charts/Renderers/YAxisRenderer.swift | 240 +++++------- .../YAxisRendererHorizontalBarChart.swift | 136 +++---- .../Renderers/YAxisRendererRadarChart.swift | 109 ++---- 6 files changed, 381 insertions(+), 646 deletions(-) diff --git a/Source/Charts/Renderers/XAxisRenderer.swift b/Source/Charts/Renderers/XAxisRenderer.swift index b802486b75..d4f14d9928 100644 --- a/Source/Charts/Renderers/XAxisRenderer.swift +++ b/Source/Charts/Renderers/XAxisRenderer.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif @objc(ChartXAxisRenderer) open class XAxisRenderer: NSObject, AxisRenderer @@ -36,26 +33,16 @@ open class XAxisRenderer: NSObject, AxisRenderer { var min = min, max = max - if let transformer = self.transformer + if let transformer = self.transformer, + viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutX { // calculate the starting and entry point of the y-labels (depending on // zoom / contentrect bounds) - if viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutX - { - let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) - let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) - - if inverted - { - min = Double(p2.x) - max = Double(p1.x) - } - else - { - min = Double(p1.x) - max = Double(p2.x) - } - } + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) + + min = inverted ? Double(p2.x) : Double(p1.x) + max = inverted ? Double(p1.x) : Double(p2.x) } computeAxisValues(min: min, max: max) @@ -69,10 +56,14 @@ open class XAxisRenderer: NSObject, AxisRenderer let labelCount = axis.labelCount let range = abs(yMax - yMin) - if labelCount == 0 || range <= 0 || range.isInfinite + guard + labelCount != 0, + range > 0, + range.isFinite + else { - axis.entries = [Double]() - axis.centeredEntries = [Double]() + axis.entries = [] + axis.centeredEntries = [] return } @@ -84,7 +75,7 @@ open class XAxisRenderer: NSObject, AxisRenderer // This is used to avoid repeated values when rounding values for display. if axis.granularityEnabled { - interval = interval < axis.granularity ? axis.granularity : interval + interval = Swift.max(interval, axis.granularity) } // Normalize interval @@ -101,19 +92,14 @@ open class XAxisRenderer: NSObject, AxisRenderer // force label count if axis.isForceLabelsEnabled { - interval = Double(range) / Double(labelCount - 1) + interval = range / Double(labelCount - 1) // Ensure stops contains at least n elements. axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - var v = yMin - - for _ in 0 ..< labelCount - { - axis.entries.append(v) - v += interval - } + let values = stride(from: yMin, to: Double(labelCount) * interval + yMin, by: interval) + axis.entries.append(contentsOf: values) n = labelCount } @@ -130,33 +116,17 @@ open class XAxisRenderer: NSObject, AxisRenderer let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp - if interval != 0.0 && last != first + if interval != 0.0, last != first { - for _ in stride(from: first, through: last, by: interval) - { - n += 1 - } + stride(from: first, through: last, by: interval).forEach { _ in n += 1 } } // Ensure stops contains at least n elements. axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - var f = first - var i = 0 - while i < n - { - if f == 0.0 - { - // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) - f = 0.0 - } - - axis.entries.append(Double(f)) - - f += interval - i += 1 - } + let values = stride(from: first, to: Double(n) * interval + first, by: interval) + axis.entries.append(contentsOf: values) } // set decimals @@ -171,15 +141,11 @@ open class XAxisRenderer: NSObject, AxisRenderer if axis.centerAxisLabelsEnabled { + axis.centeredEntries.removeAll(keepingCapacity: true) axis.centeredEntries.reserveCapacity(n) - axis.centeredEntries.removeAll() let offset: Double = interval / 2.0 - - for i in 0 ..< n - { - axis.centeredEntries.append(axis.entries[i] + offset) - } + axis.centeredEntries.append(contentsOf: axis.entries.map { $0 + offset }) } computeSize() @@ -204,46 +170,43 @@ open class XAxisRenderer: NSObject, AxisRenderer open func renderAxisLabels(context: CGContext) { - if !axis.isEnabled || !axis.isDrawLabelsEnabled - { - return - } - + guard + axis.isEnabled, + axis.isDrawLabelsEnabled + else { return } + let yOffset = axis.yOffset - if axis.labelPosition == .top - { + switch axis.labelPosition { + case .top: drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) - } - else if axis.labelPosition == .topInside - { + + case .topInside: drawLabels(context: context, pos: viewPortHandler.contentTop + yOffset + axis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 1.0)) - } - else if axis.labelPosition == .bottom - { + + case .bottom: drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) - } - else if axis.labelPosition == .bottomInside - { + + case .bottomInside: drawLabels(context: context, pos: viewPortHandler.contentBottom - yOffset - axis.labelRotatedHeight, anchor: CGPoint(x: 0.5, y: 0.0)) - } - else - { // BOTH SIDED + + case .bothSided: drawLabels(context: context, pos: viewPortHandler.contentTop - yOffset, anchor: CGPoint(x: 0.5, y: 1.0)) drawLabels(context: context, pos: viewPortHandler.contentBottom + yOffset, anchor: CGPoint(x: 0.5, y: 0.0)) } } - private var _axisLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) + private var axisLineSegmentsBuffer = [CGPoint](repeating: .zero, count: 2) open func renderAxisLine(context: CGContext) - { - if !axis.isEnabled || !axis.isDrawAxisLineEnabled - { - return - } - + { + guard + axis.isEnabled, + axis.isDrawAxisLineEnabled + else { return } + context.saveGState() + defer { context.restoreGState() } context.setStrokeColor(axis.axisLineColor.cgColor) context.setLineWidth(axis.axisLineWidth) @@ -260,25 +223,23 @@ open class XAxisRenderer: NSObject, AxisRenderer || axis.labelPosition == .topInside || axis.labelPosition == .bothSided { - _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft - _axisLineSegmentsBuffer[0].y = viewPortHandler.contentTop - _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight - _axisLineSegmentsBuffer[1].y = viewPortHandler.contentTop - context.strokeLineSegments(between: _axisLineSegmentsBuffer) + axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + axisLineSegmentsBuffer[0].y = viewPortHandler.contentTop + axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + axisLineSegmentsBuffer[1].y = viewPortHandler.contentTop + context.strokeLineSegments(between: axisLineSegmentsBuffer) } if axis.labelPosition == .bottom || axis.labelPosition == .bottomInside || axis.labelPosition == .bothSided { - _axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft - _axisLineSegmentsBuffer[0].y = viewPortHandler.contentBottom - _axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight - _axisLineSegmentsBuffer[1].y = viewPortHandler.contentBottom - context.strokeLineSegments(between: _axisLineSegmentsBuffer) + axisLineSegmentsBuffer[0].x = viewPortHandler.contentLeft + axisLineSegmentsBuffer[0].y = viewPortHandler.contentBottom + axisLineSegmentsBuffer[1].x = viewPortHandler.contentRight + axisLineSegmentsBuffer[1].y = viewPortHandler.contentBottom + context.strokeLineSegments(between: axisLineSegmentsBuffer) } - - context.restoreGState() } /// draws the x-labels on the specified y-position @@ -286,25 +247,19 @@ open class XAxisRenderer: NSObject, AxisRenderer { guard let transformer = self.transformer else { return } - #if os(OSX) - let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle - #else - let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle - #endif + let paraStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle paraStyle.alignment = .center let labelAttrs: [NSAttributedString.Key : Any] = [.font: axis.labelFont, .foregroundColor: axis.labelTextColor, .paragraphStyle: paraStyle] - let labelRotationAngleRadians = axis.labelRotationAngle.DEG2RAD - - let centeringEnabled = axis.isCenterAxisLabelsEnabled + let labelRotationAngleRadians = axis.labelRotationAngle.DEG2RAD + let isCenteringEnabled = axis.isCenterAxisLabelsEnabled let valueToPixelMatrix = transformer.valueToPixelMatrix - - var position = CGPoint(x: 0.0, y: 0.0) - - var labelMaxSize = CGSize() + + var position = CGPoint.zero + var labelMaxSize = CGSize.zero if axis.isWordWrapEnabled { @@ -313,55 +268,45 @@ open class XAxisRenderer: NSObject, AxisRenderer let entries = axis.entries - for i in stride(from: 0, to: entries.count, by: 1) + for i in entries.indices { - if centeringEnabled - { - position.x = CGFloat(axis.centeredEntries[i]) - } - else - { - position.x = CGFloat(entries[i]) - } + let px = isCenteringEnabled ? CGFloat(axis.centeredEntries[i]) : CGFloat(entries[i]) + position = CGPoint(x: px, y: 0) + .applying(valueToPixelMatrix) + + guard viewPortHandler.isInBoundsX(position.x) else { continue } - position.y = 0.0 - position = position.applying(valueToPixelMatrix) + let label = axis.valueFormatter?.stringForValue(axis.entries[i], axis: axis) ?? "" + let labelns = label as NSString - if viewPortHandler.isInBoundsX(position.x) + if axis.isAvoidFirstLastClippingEnabled { - let label = axis.valueFormatter?.stringForValue(axis.entries[i], axis: axis) ?? "" - - let labelns = label as NSString - - if axis.isAvoidFirstLastClippingEnabled + // avoid clipping of the last + if i == axis.entryCount - 1 && axis.entryCount > 1 { - // avoid clipping of the last - if i == axis.entryCount - 1 && axis.entryCount > 1 + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + + if width > viewPortHandler.offsetRight * 2.0, + position.x + width > viewPortHandler.chartWidth { - let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width - - if width > viewPortHandler.offsetRight * 2.0 - && position.x + width > viewPortHandler.chartWidth - { - position.x -= width / 2.0 - } - } - else if i == 0 - { // avoid clipping of the first - let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width - position.x += width / 2.0 + position.x -= width / 2.0 } } - - drawLabel(context: context, - formattedLabel: label, - x: position.x, - y: pos, - attributes: labelAttrs, - constrainedTo: labelMaxSize, - anchor: anchor, - angleRadians: labelRotationAngleRadians) + else if i == 0 + { // avoid clipping of the first + let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width + position.x += width / 2.0 + } } + + drawLabel(context: context, + formattedLabel: label, + x: position.x, + y: pos, + attributes: labelAttrs, + constrainedTo: labelMaxSize, + anchor: anchor, + angleRadians: labelRotationAngleRadians) } } @@ -385,15 +330,15 @@ open class XAxisRenderer: NSObject, AxisRenderer open func renderGridLines(context: CGContext) { - guard let transformer = self.transformer else { return } - - if !axis.isDrawGridLinesEnabled || !axis.isEnabled - { - return - } + guard + let transformer = self.transformer, + axis.isEnabled, + axis.isDrawGridLinesEnabled + else { return } context.saveGState() defer { context.restoreGState() } + context.clip(to: self.gridClippingRect) context.setShouldAntialias(axis.gridAntialiasEnabled) @@ -412,14 +357,14 @@ open class XAxisRenderer: NSObject, AxisRenderer let valueToPixelMatrix = transformer.valueToPixelMatrix - var position = CGPoint(x: 0.0, y: 0.0) + var position = CGPoint.zero let entries = axis.entries - for i in stride(from: 0, to: entries.count, by: 1) + for entry in entries { - position.x = CGFloat(entries[i]) - position.y = position.x + position.x = CGFloat(entry) + position.y = CGFloat(entry) position = position.applying(valueToPixelMatrix) drawGridLine(context: context, x: position.x, y: position.y) @@ -437,14 +382,12 @@ open class XAxisRenderer: NSObject, AxisRenderer @objc open func drawGridLine(context: CGContext, x: CGFloat, y: CGFloat) { - if x >= viewPortHandler.offsetLeft - && x <= viewPortHandler.chartWidth - { - context.beginPath() - context.move(to: CGPoint(x: x, y: viewPortHandler.contentTop)) - context.addLine(to: CGPoint(x: x, y: viewPortHandler.contentBottom)) - context.strokePath() - } + guard (viewPortHandler.offsetLeft...viewPortHandler.chartWidth).contains(x) else { return } + + context.beginPath() + context.move(to: CGPoint(x: x, y: viewPortHandler.contentTop)) + context.addLine(to: CGPoint(x: x, y: viewPortHandler.contentBottom)) + context.strokePath() } open func renderLimitLines(context: CGContext) @@ -453,24 +396,14 @@ open class XAxisRenderer: NSObject, AxisRenderer var limitLines = axis.limitLines - if limitLines.count == 0 - { - return - } - + guard !limitLines.isEmpty else { return } + let trans = transformer.valueToPixelMatrix - var position = CGPoint(x: 0.0, y: 0.0) + var position = CGPoint.zero - for i in 0 ..< limitLines.count + for l in limitLines where l.isEnabled { - let l = limitLines[i] - - if !l.isEnabled - { - continue - } - context.saveGState() defer { context.restoreGState() } @@ -490,7 +423,6 @@ open class XAxisRenderer: NSObject, AxisRenderer @objc open func renderLimitLineLine(context: CGContext, limitLine: ChartLimitLine, position: CGPoint) { - context.beginPath() context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) @@ -511,47 +443,45 @@ open class XAxisRenderer: NSObject, AxisRenderer @objc open func renderLimitLineLabel(context: CGContext, limitLine: ChartLimitLine, position: CGPoint, yOffset: CGFloat) { - let label = limitLine.label // if drawing the limit-value label is enabled - if limitLine.drawLabelEnabled && label.count > 0 - { - let labelLineHeight = limitLine.valueFont.lineHeight - - let xOffset: CGFloat = limitLine.lineWidth + limitLine.xOffset + guard limitLine.drawLabelEnabled, !label.isEmpty else { return } - let align: NSTextAlignment - let point: CGPoint + let labelLineHeight = limitLine.valueFont.lineHeight - switch limitLine.labelPosition - { - case .rightTop: - align = .left - point = CGPoint(x: position.x + xOffset, - y: viewPortHandler.contentTop + yOffset) - - case .rightBottom: - align = .left - point = CGPoint(x: position.x + xOffset, - y: viewPortHandler.contentBottom - labelLineHeight - yOffset) - - case .leftTop: - align = .right - point = CGPoint(x: position.x - xOffset, - y: viewPortHandler.contentTop + yOffset) - - case .leftBottom: - align = .right - point = CGPoint(x: position.x - xOffset, - y: viewPortHandler.contentBottom - labelLineHeight - yOffset) - } + let xOffset: CGFloat = limitLine.lineWidth + limitLine.xOffset - context.drawText(label, - at: point, - align: align, - attributes: [.font: limitLine.valueFont, - .foregroundColor: limitLine.valueTextColor]) - } + let align: NSTextAlignment + let point: CGPoint + + switch limitLine.labelPosition + { + case .rightTop: + align = .left + point = CGPoint(x: position.x + xOffset, + y: viewPortHandler.contentTop + yOffset) + + case .rightBottom: + align = .left + point = CGPoint(x: position.x + xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset) + + case .leftTop: + align = .right + point = CGPoint(x: position.x - xOffset, + y: viewPortHandler.contentTop + yOffset) + + case .leftBottom: + align = .right + point = CGPoint(x: position.x - xOffset, + y: viewPortHandler.contentBottom - labelLineHeight - yOffset) + } + + context.drawText(label, + at: point, + align: align, + attributes: [.font: limitLine.valueFont, + .foregroundColor: limitLine.valueTextColor]) } } diff --git a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift index 94d7bf2b1b..8242c224e3 100644 --- a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif open class XAxisRendererHorizontalBarChart: XAxisRenderer { @@ -31,26 +28,17 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer { var min = min, max = max - if let transformer = self.transformer + if let transformer = self.transformer, + viewPortHandler.contentWidth > 10, + !viewPortHandler.isFullyZoomedOutY { // calculate the starting and entry point of the y-labels (depending on // zoom / contentrect bounds) - if viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutY - { - let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) - let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) - - if inverted - { - min = Double(p2.y) - max = Double(p1.y) - } - else - { - min = Double(p1.y) - max = Double(p2.y) - } - } + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + + min = inverted ? Double(p2.y) : Double(p1.y) + max = inverted ? Double(p1.y) : Double(p2.y) } computeAxisValues(min: min, max: max) @@ -74,31 +62,28 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer open override func renderAxisLabels(context: CGContext) { - if !axis.isEnabled || !axis.isDrawLabelsEnabled || chart?.data === nil - { - return - } + guard + axis.isEnabled, + axis.isDrawLabelsEnabled, + chart?.data === nil + else { return } let xoffset = axis.xOffset - - if axis.labelPosition == .top - { + + switch axis.labelPosition { + case .top: drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) - } - else if axis.labelPosition == .topInside - { + + case .topInside: drawLabels(context: context, pos: viewPortHandler.contentRight - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) - } - else if axis.labelPosition == .bottom - { + + case .bottom: drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) - } - else if axis.labelPosition == .bottomInside - { + + case .bottomInside: drawLabels(context: context, pos: viewPortHandler.contentLeft + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) - } - else - { // BOTH SIDED + + case .bothSided: drawLabels(context: context, pos: viewPortHandler.contentRight + xoffset, anchor: CGPoint(x: 0.0, y: 0.5)) drawLabels(context: context, pos: viewPortHandler.contentLeft - xoffset, anchor: CGPoint(x: 1.0, y: 0.5)) } @@ -116,38 +101,26 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer let centeringEnabled = axis.isCenterAxisLabelsEnabled // pre allocate to save performance (dont allocate in loop) - var position = CGPoint(x: 0.0, y: 0.0) + var position = CGPoint.zero - for i in stride(from: 0, to: axis.entryCount, by: 1) + for i in 0.. 0 + if l.drawLabelEnabled, !label.isEmpty { let labelLineHeight = l.valueFont.lineHeight - let xOffset: CGFloat = 4.0 + l.xOffset - let yOffset: CGFloat = l.lineWidth + labelLineHeight + l.yOffset + let xOffset = 4.0 + l.xOffset + let yOffset = l.lineWidth + labelLineHeight + l.yOffset let align: NSTextAlignment let point: CGPoint diff --git a/Source/Charts/Renderers/XAxisRendererRadarChart.swift b/Source/Charts/Renderers/XAxisRendererRadarChart.swift index e517d4911b..4ee9fef72a 100644 --- a/Source/Charts/Renderers/XAxisRendererRadarChart.swift +++ b/Source/Charts/Renderers/XAxisRendererRadarChart.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif open class XAxisRendererRadarChart: XAxisRenderer { @@ -29,13 +26,12 @@ open class XAxisRendererRadarChart: XAxisRenderer open override func renderAxisLabels(context: CGContext) { - guard let chart = chart else { return } - - if !axis.isEnabled || !axis.isDrawLabelsEnabled - { - return - } - + guard + let chart = chart, + axis.isEnabled, + axis.isDrawLabelsEnabled + else { return } + let labelFont = axis.labelFont let labelTextColor = axis.labelTextColor let labelRotationAngleRadians = axis.labelRotationAngle.RAD2DEG @@ -48,13 +44,10 @@ open class XAxisRendererRadarChart: XAxisRenderer let center = chart.centerOffsets - for i in stride(from: 0, to: chart.data?.maxEntryCountSet?.entryCount ?? 0, by: 1) + for i in 0..<(chart.data?.maxEntryCountSet?.entryCount ?? 0) { - let label = axis.valueFormatter?.stringForValue(Double(i), axis: axis) ?? "" - let angle = (sliceangle * CGFloat(i) + chart.rotationAngle).truncatingRemainder(dividingBy: 360.0) - let p = center.moving(distance: CGFloat(chart.yRange) * factor + axis.labelRotatedWidth / 2.0, atAngle: angle) drawLabel(context: context, diff --git a/Source/Charts/Renderers/YAxisRenderer.swift b/Source/Charts/Renderers/YAxisRenderer.swift index d77cb0f31b..b4de50942b 100644 --- a/Source/Charts/Renderers/YAxisRenderer.swift +++ b/Source/Charts/Renderers/YAxisRenderer.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif @objc(ChartYAxisRenderer) open class YAxisRenderer: NSObject, AxisRenderer @@ -35,20 +32,19 @@ open class YAxisRenderer: NSObject, AxisRenderer /// draws the y-axis labels to the screen open func renderAxisLabels(context: CGContext) { - if !axis.isEnabled || !axis.isDrawLabelsEnabled - { - return - } - + guard + axis.isEnabled, + axis.isDrawLabelsEnabled + else { return } + let xoffset = axis.xOffset let yoffset = axis.labelFont.lineHeight / 2.5 + axis.yOffset let dependency = axis.axisDependency let labelPosition = axis.labelPosition - var xPos = CGFloat(0.0) - - var textAlign: NSTextAlignment + let xPos: CGFloat + let textAlign: NSTextAlignment if dependency == .left { @@ -62,7 +58,6 @@ open class YAxisRenderer: NSObject, AxisRenderer textAlign = .left xPos = viewPortHandler.offsetLeft + xoffset } - } else { @@ -78,23 +73,23 @@ open class YAxisRenderer: NSObject, AxisRenderer } } - drawYLabels( - context: context, - fixedPosition: xPos, - positions: transformedPositions(), - offset: yoffset - axis.labelFont.lineHeight, - textAlign: textAlign) + drawYLabels(context: context, + fixedPosition: xPos, + positions: transformedPositions(), + offset: yoffset - axis.labelFont.lineHeight, + textAlign: textAlign) } open func renderAxisLine(context: CGContext) { - if !axis.isEnabled || !axis.drawAxisLineEnabled - { - return - } - + guard + axis.isEnabled, + axis.drawAxisLineEnabled + else { return } + context.saveGState() - + defer { context.restoreGState() } + context.setStrokeColor(axis.axisLineColor.cgColor) context.setLineWidth(axis.axisLineWidth) if axis.axisLineDashLengths != nil @@ -120,8 +115,6 @@ open class YAxisRenderer: NSObject, AxisRenderer context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) context.strokePath() } - - context.restoreGState() } /// draws the y-labels on the specified x-position @@ -138,10 +131,9 @@ open class YAxisRenderer: NSObject, AxisRenderer let from = axis.isDrawBottomYLabelEntryEnabled ? 0 : 1 let to = axis.isDrawTopYLabelEntryEnabled ? axis.entryCount : (axis.entryCount - 1) - for i in stride(from: from, to: to, by: 1) + for i in from.. 0 + guard l.drawLabelEnabled, !label.isEmpty else { return } + + let labelLineHeight = l.valueFont.lineHeight + + let xOffset = 4.0 + l.xOffset + let yOffset = l.lineWidth + labelLineHeight + l.yOffset + + let align: NSTextAlignment + let point: CGPoint + + switch l.labelPosition { - let labelLineHeight = l.valueFont.lineHeight - - let xOffset = 4.0 + l.xOffset - let yOffset = l.lineWidth + labelLineHeight + l.yOffset - - let align: NSTextAlignment - let point: CGPoint - - switch l.labelPosition - { - case .rightTop: - align = .right - point = CGPoint(x: viewPortHandler.contentRight - xOffset, - y: position.y - yOffset) - - case .rightBottom: - align = .right - point = CGPoint(x: viewPortHandler.contentRight - xOffset, - y: position.y + yOffset - labelLineHeight) - - case .leftTop: - align = .left - point = CGPoint(x: viewPortHandler.contentLeft + xOffset, - y: position.y - yOffset) - - case .leftBottom: - align = .left - point = CGPoint(x: viewPortHandler.contentLeft + xOffset, - y: position.y + yOffset - labelLineHeight) - } - - context.drawText(label, - at: point, - align: align, - attributes: [.font: l.valueFont, .foregroundColor: l.valueTextColor]) + case .rightTop: + align = .right + point = CGPoint(x: viewPortHandler.contentRight - xOffset, + y: position.y - yOffset) + + case .rightBottom: + align = .right + point = CGPoint(x: viewPortHandler.contentRight - xOffset, + y: position.y + yOffset - labelLineHeight) + + case .leftTop: + align = .left + point = CGPoint(x: viewPortHandler.contentLeft + xOffset, + y: position.y - yOffset) + + case .leftBottom: + align = .left + point = CGPoint(x: viewPortHandler.contentLeft + xOffset, + y: position.y + yOffset - labelLineHeight) } + + context.drawText(label, + at: point, + align: align, + attributes: [.font: l.valueFont, .foregroundColor: l.valueTextColor]) } - - context.restoreGState() } @objc open func computeAxis(min: Double, max: Double, inverted: Bool) { var min = min, max = max - if let transformer = self.transformer + if let transformer = self.transformer, + viewPortHandler.contentWidth > 10.0, + !viewPortHandler.isFullyZoomedOutY { // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) - if viewPortHandler.contentWidth > 10.0 && !viewPortHandler.isFullyZoomedOutY - { - let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) - let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) - - if !inverted - { - min = Double(p2.y) - max = Double(p1.y) - } - else - { - min = Double(p1.y) - max = Double(p2.y) - } - } + let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) + let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) + + min = inverted ? Double(p1.y) : Double(p2.y) + max = inverted ? Double(p2.y) : Double(p1.y) } computeAxisValues(min: min, max: max) @@ -403,10 +358,14 @@ open class YAxisRenderer: NSObject, AxisRenderer let labelCount = axis.labelCount let range = abs(yMax - yMin) - if labelCount == 0 || range <= 0 || range.isInfinite + guard + labelCount != 0, + range > 0, + range.isFinite + else { - axis.entries = [Double]() - axis.centeredEntries = [Double]() + axis.entries = [] + axis.centeredEntries = [] return } @@ -418,7 +377,7 @@ open class YAxisRenderer: NSObject, AxisRenderer // This is used to avoid repeated values when rounding values for display. if axis.granularityEnabled { - interval = interval < axis.granularity ? axis.granularity : interval + interval = Swift.max(interval, axis.granularity) } // Normalize interval @@ -441,13 +400,8 @@ open class YAxisRenderer: NSObject, AxisRenderer axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - var v = yMin - - for _ in 0 ..< labelCount - { - axis.entries.append(v) - v += interval - } + let values = stride(from: yMin, to: Double(labelCount) * interval + yMin, by: interval) + axis.entries.append(contentsOf: values) n = labelCount } @@ -464,33 +418,17 @@ open class YAxisRenderer: NSObject, AxisRenderer let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp - if interval != 0.0 && last != first + if interval != 0.0, last != first { - for _ in stride(from: first, through: last, by: interval) - { - n += 1 - } + stride(from: first, through: last, by: interval).forEach { _ in n += 1 } } // Ensure stops contains at least n elements. axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - var f = first - var i = 0 - while i < n - { - if f == 0.0 - { - // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) - f = 0.0 - } - - axis.entries.append(Double(f)) - - f += interval - i += 1 - } + let values = stride(from: first, to: Double(n) * interval + first, by: interval) + axis.entries.append(contentsOf: values) } // set decimals @@ -509,11 +447,7 @@ open class YAxisRenderer: NSObject, AxisRenderer axis.centeredEntries.removeAll() let offset: Double = interval / 2.0 - - for i in 0 ..< n - { - axis.centeredEntries.append(axis.entries[i] + offset) - } + axis.centeredEntries.append(contentsOf: axis.entries.map { $0 + offset }) } } } diff --git a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift index 78e36e8f36..b3177cbfed 100644 --- a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif open class YAxisRendererHorizontalBarChart: YAxisRenderer { @@ -26,26 +23,18 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer /// Computes the axis values. open override func computeAxis(min: Double, max: Double, inverted: Bool) { - guard let transformer = self.transformer else { return } - var min = min, max = max // calculate the starting and entry point of the y-labels (depending on zoom / contentrect bounds) - if viewPortHandler.contentHeight > 10.0 && !viewPortHandler.isFullyZoomedOutX + if let transformer = transformer, + viewPortHandler.contentHeight > 10.0, + !viewPortHandler.isFullyZoomedOutX { let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) - - if !inverted - { - min = Double(p1.x) - max = Double(p2.x) - } - else - { - min = Double(p2.x) - max = Double(p1.x) - } + + min = inverted ? Double(p2.x) : Double(p1.x) + max = inverted ? Double(p1.x) : Double(p2.x) } computeAxisValues(min: min, max: max) @@ -54,18 +43,21 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer /// draws the y-axis labels to the screen open override func renderAxisLabels(context: CGContext) { - if !axis.isEnabled || !axis.isDrawLabelsEnabled - { - return - } - + guard + axis.isEnabled, + axis.isDrawLabelsEnabled + else { return } + let lineHeight = axis.labelFont.lineHeight let baseYOffset: CGFloat = 2.5 let dependency = axis.axisDependency let labelPosition = axis.labelPosition - - var yPos: CGFloat = 0.0 + + + // For compatibility with Android code, we keep the calculation below the same, + // And here we pull the line back up + var yPos = -lineHeight if dependency == .left { @@ -89,26 +81,22 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset } } - - // For compatibility with Android code, we keep above calculation the same, - // And here we pull the line back up - yPos -= lineHeight - - drawYLabels( - context: context, - fixedPosition: yPos, - positions: transformedPositions(), - offset: axis.yOffset) + + drawYLabels(context: context, + fixedPosition: yPos, + positions: transformedPositions(), + offset: axis.yOffset) } open override func renderAxisLine(context: CGContext) { - if !axis.isEnabled || !axis.drawAxisLineEnabled - { - return - } - + guard + axis.isEnabled, + axis.drawAxisLineEnabled + else { return } + context.saveGState() + defer { context.restoreGState() } context.setStrokeColor(axis.axisLineColor.cgColor) context.setLineWidth(axis.axisLineWidth) @@ -121,21 +109,18 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer context.setLineDash(phase: 0.0, lengths: []) } + context.beginPath() if axis.axisDependency == .left { - context.beginPath() context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) - context.strokePath() } else { - context.beginPath() context.move(to: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentBottom)) context.addLine(to: CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentBottom)) - context.strokePath() } - - context.restoreGState() + } + context.strokePath() } /// draws the y-labels on the specified x-position @@ -151,10 +136,9 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer let from = axis.isDrawBottomYLabelEntryEnabled ? 0 : 1 let to = axis.isDrawTopYLabelEntryEnabled ? axis.entryCount : (axis.entryCount - 1) - for i in stride(from: from, to: to, by: 1) + for i in from.. 0 + if l.drawLabelEnabled, !label.isEmpty { let labelLineHeight = l.valueFont.lineHeight - let xOffset: CGFloat = l.lineWidth + l.xOffset - let yOffset: CGFloat = 2.0 + l.yOffset + let xOffset = l.lineWidth + l.xOffset + let yOffset = 2.0 + l.yOffset let align: NSTextAlignment let point: CGPoint @@ -334,7 +296,5 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer attributes: [.font: l.valueFont, .foregroundColor: l.valueTextColor]) } } - - context.restoreGState() } } diff --git a/Source/Charts/Renderers/YAxisRendererRadarChart.swift b/Source/Charts/Renderers/YAxisRendererRadarChart.swift index 4b495ad160..bb7508d43b 100644 --- a/Source/Charts/Renderers/YAxisRendererRadarChart.swift +++ b/Source/Charts/Renderers/YAxisRendererRadarChart.swift @@ -12,9 +12,6 @@ import Foundation import CoreGraphics -#if !os(OSX) - import UIKit -#endif open class YAxisRendererRadarChart: YAxisRenderer { @@ -22,9 +19,9 @@ open class YAxisRendererRadarChart: YAxisRenderer @objc public init(viewPortHandler: ViewPortHandler, axis: YAxis, chart: RadarChartView) { - super.init(viewPortHandler: viewPortHandler, axis: axis, transformer: nil) - self.chart = chart + + super.init(viewPortHandler: viewPortHandler, axis: axis, transformer: nil) } open override func computeAxisValues(min yMin: Double, max yMax: Double) @@ -32,10 +29,13 @@ open class YAxisRendererRadarChart: YAxisRenderer let labelCount = axis.labelCount let range = abs(yMax - yMin) - if labelCount == 0 || range <= 0 || range.isInfinite + guard labelCount != 0, + range > 0, + range.isFinite + else { - axis.entries = [Double]() - axis.centeredEntries = [Double]() + axis.entries = [] + axis.centeredEntries = [] return } @@ -47,7 +47,7 @@ open class YAxisRendererRadarChart: YAxisRenderer // This is used to avoid repeated values when rounding values for display. if axis.isGranularityEnabled { - interval = interval < axis.granularity ? axis.granularity : interval + interval = Swift.max(interval, axis.granularity) } // Normalize interval @@ -67,19 +67,14 @@ open class YAxisRendererRadarChart: YAxisRenderer // force label count if axis.isForceLabelsEnabled { - let step = Double(range) / Double(labelCount - 1) + let step = range / Double(labelCount - 1) // Ensure stops contains at least n elements. axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - - var v = yMin - - for _ in 0 ..< labelCount - { - axis.entries.append(v) - v += step - } + + let values = stride(from: yMin, to: Double(labelCount) * step + yMin, by: step) + axis.entries.append(contentsOf: values) n = labelCount } @@ -98,10 +93,7 @@ open class YAxisRendererRadarChart: YAxisRenderer if interval != 0.0 { - for _ in stride(from: first, through: last, by: interval) - { - n += 1 - } + stride(from: first, through: last, by: interval).forEach { _ in n += 1 } } n += 1 @@ -109,22 +101,9 @@ open class YAxisRendererRadarChart: YAxisRenderer // Ensure stops contains at least n elements. axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - - var f = first - var i = 0 - while i < n - { - if f == 0.0 - { - // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) - f = 0.0 - } - axis.entries.append(Double(f)) - - f += interval - i += 1 - } + let values = stride(from: first, to: Double(n) * interval + first, by: interval) + axis.entries.append(contentsOf: values) } // set decimals @@ -139,15 +118,8 @@ open class YAxisRendererRadarChart: YAxisRenderer if centeringEnabled { - axis.centeredEntries.reserveCapacity(n) - axis.centeredEntries.removeAll() - let offset = (axis.entries[1] - axis.entries[0]) / 2.0 - - for i in 0 ..< n - { - axis.centeredEntries.append(axis.entries[i] + offset) - } + axis.centeredEntries = axis.entries.map { $0 + offset } } axis._axisMinimum = axis.entries[0] @@ -157,13 +129,12 @@ open class YAxisRendererRadarChart: YAxisRenderer open override func renderAxisLabels(context: CGContext) { - guard let chart = chart else { return } - - if !axis.isEnabled || !axis.isDrawLabelsEnabled - { - return - } - + guard + let chart = chart, + axis.isEnabled, + axis.isDrawLabelsEnabled + else { return } + let labelFont = axis.labelFont let labelTextColor = axis.labelTextColor @@ -178,13 +149,13 @@ open class YAxisRendererRadarChart: YAxisRenderer let alignment = axis.labelAlignment let xOffset = axis.labelXOffset - for j in stride(from: from, to: to, by: 1) + for i in from.. Date: Wed, 10 Jan 2018 21:31:57 -0400 Subject: [PATCH 09/20] Updates for PR Also added remove subrange. --- .../Implementations/Standard/ChartData.swift | 35 ++++--------------- 1 file changed, 7 insertions(+), 28 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index f1b580d2e4..1447d2295e 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -478,10 +478,7 @@ extension ChartData//: RangeReplaceableCollection public func removeFirst() -> Element { - guard !(self is CombinedChartData) else - { - fatalError("removeFirst() not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") let element = _dataSets.removeFirst() notifyDataChanged() @@ -490,10 +487,7 @@ extension ChartData//: RangeReplaceableCollection public func removeFirst(_ n: Int) { - guard !(self is CombinedChartData) else - { - fatalError("removeFirst(_:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") _dataSets.removeFirst(n) notifyDataChanged() @@ -501,10 +495,7 @@ extension ChartData//: RangeReplaceableCollection public func removeLast() -> Element { - guard !(self is CombinedChartData) else - { - fatalError("removeLast() not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") let element = _dataSets.removeLast() notifyDataChanged() @@ -513,10 +504,7 @@ extension ChartData//: RangeReplaceableCollection public func removeLast(_ n: Int) { - guard !(self is CombinedChartData) else - { - fatalError("removeLast(_:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") _dataSets.removeLast(n) notifyDataChanged() @@ -524,10 +512,7 @@ extension ChartData//: RangeReplaceableCollection public func removeSubrange(_ bounds: R) where R : RangeExpression, Index == R.Bound { - guard !(self is CombinedChartData) else - { - fatalError("removeSubrange(_:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") _dataSets.removeSubrange(bounds) notifyDataChanged() @@ -535,10 +520,7 @@ extension ChartData//: RangeReplaceableCollection public func removeAll(keepingCapacity keepCapacity: Bool) { - guard !(self is CombinedChartData) else - { - fatalError("removeAll(keepingCapacity:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") _dataSets.removeAll(keepingCapacity: keepCapacity) notifyDataChanged() @@ -546,10 +528,7 @@ extension ChartData//: RangeReplaceableCollection public func replaceSubrange(_ subrange: Swift.Range, with newElements: C) where C : Collection, Element == C.Element { - guard !(self is CombinedChartData) else - { - fatalError("replaceSubrange(_:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") _dataSets.replaceSubrange(subrange, with: newElements) newElements.forEach { self.calcMinMax(dataSet: $0) } From da2ff2d43e7429992117d0ffc3036e31831f193a Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Wed, 10 Jan 2018 23:50:42 -0400 Subject: [PATCH 10/20] Refactored ChartData Removed redundancy from min/max logic. Lots of naming changes. Cleaner implementations. --- Source/Charts/Charts/BarLineChartViewBase.swift | 9 ++++----- .../Charts/Data/Implementations/Standard/ChartData.swift | 2 +- Source/Charts/Renderers/LineChartRenderer.swift | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Source/Charts/Charts/BarLineChartViewBase.swift b/Source/Charts/Charts/BarLineChartViewBase.swift index 82b3520da1..3f3f5214ef 100644 --- a/Source/Charts/Charts/BarLineChartViewBase.swift +++ b/Source/Charts/Charts/BarLineChartViewBase.swift @@ -1692,12 +1692,11 @@ open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartD /// - returns: The DataSet object displayed at the touched position of the chart @objc open func getDataSetByTouchPoint(point pt: CGPoint) -> BarLineScatterCandleBubbleChartDataSetProtocol? { - let h = getHighlightByTouchPoint(pt) - if let h = h - { - return data?[h.dataSetIndex] as? BarLineScatterCandleBubbleChartDataSetProtocol + guard let h = getHighlightByTouchPoint(pt) else { + return nil } - return nil + + return data?[h.dataSetIndex] as? BarLineScatterCandleBubbleChartDataSetProtocol } /// - returns: The current x-scale factor diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index 1447d2295e..089c6d3a0f 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -22,7 +22,7 @@ open class ChartData: NSObject, ExpressibleByArrayLiteral var leftAxisMin = Double.greatestFiniteMagnitude var rightAxisMax = -Double.greatestFiniteMagnitude var rightAxisMin = Double.greatestFiniteMagnitude - + // MARK: - Accessibility /// When the data entry labels are generated identifiers, set this property to prepend a string before each identifier diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index 1fdd82e0a3..31f9eaf609 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -642,7 +642,7 @@ open class LineChartRenderer: LineRadarRenderer for i in 0 ..< dataSets.count { guard let dataSet = lineData[i] as? LineChartDataSetProtocol else { continue } - + if !dataSet.isVisible || dataSet.entryCount == 0 { continue @@ -772,8 +772,8 @@ open class LineChartRenderer: LineRadarRenderer for high in indices { - guard let set = lineData[high.dataSetIndex] as? LineChartDataSetProtocol - , set.isHighlightEnabled + guard let set = lineData[high.dataSetIndex] as? LineChartDataSetProtocol, + set.isHighlightEnabled else { continue } guard let e = set.entryForXValue(high.x, closestToY: high.y) else { continue } From 9aaee1335982438f63b3acb26f7ee408903a3b70 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Thu, 11 Jan 2018 22:02:19 -0400 Subject: [PATCH 11/20] Fixed horizontal barchart bug, --- Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift index 8242c224e3..9e9a39ffac 100644 --- a/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift @@ -65,7 +65,7 @@ open class XAxisRendererHorizontalBarChart: XAxisRenderer guard axis.isEnabled, axis.isDrawLabelsEnabled, - chart?.data === nil + chart?.data != nil else { return } let xoffset = axis.xOffset From f399bca158f3484d15bbb57c4a5898caa65f88f7 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Fri, 12 Jan 2018 07:31:59 -0400 Subject: [PATCH 12/20] Removed unnecessary `get` from subscripts. --- Source/Charts/Data/Implementations/Standard/ChartData.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index 089c6d3a0f..e9ad8d2116 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -560,10 +560,7 @@ extension ChartData public subscript(entry entry: ChartDataEntry) -> Element? { - guard !(self is CombinedChartData) else - { - fatalError("subscript(entry:) not supported for CombinedData") - } + assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") guard let index = index(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { return nil } return self[index] From bd1bc8faea2bf851f2e87b5b803fc58a753e72f8 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sun, 14 Jan 2018 22:30:35 -0400 Subject: [PATCH 13/20] Disabled `remove(at:)` for CombinedChartView --- Source/Charts/Data/Implementations/Standard/ChartData.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index e9ad8d2116..ccad62e2db 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -471,6 +471,10 @@ extension ChartData//: RangeReplaceableCollection @objc(removeDataSetByIndex:) public func remove(at position: Index) -> Element { + guard !(self is CombinedChartData) else + { + fatalError("remove(at:) not supported for CombinedData") + } let element = _dataSets.remove(at: position) calcMinMax() return element From 74f28b11bad0dbce63be564e749f4234fbb07767 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sun, 14 Jan 2018 23:01:26 -0400 Subject: [PATCH 14/20] Relocated `appendEntry(_:todataSet:)` --- Source/Charts/Data/Implementations/Standard/ChartData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index ccad62e2db..be975d3aec 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -296,7 +296,7 @@ open class ChartData: NSObject, ExpressibleByArrayLiteral guard indices.contains(dataSetIndex) else { return print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") } - + let set = self[dataSetIndex] if !set.addEntry(e) { return } calcMinMax(entry: e, axis: set.axisDependency) From aec150ebfcd04160c67df2bcc2b7ff4688214cb4 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sun, 14 Jan 2018 23:34:08 -0400 Subject: [PATCH 15/20] Disabled Collection support for CombinedChartData --- Source/Charts/Data/Implementations/Standard/ChartData.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index be975d3aec..89adda595b 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -464,6 +464,11 @@ extension ChartData//: RangeReplaceableCollection @objc(addDataSet:) public func append(_ newElement: Element) { + guard !(self is CombinedChartData) else + { + fatalError("append(_:) not supported for CombinedData") + } + _dataSets.append(newElement) calcMinMax(dataSet: newElement) } @@ -475,6 +480,7 @@ extension ChartData//: RangeReplaceableCollection { fatalError("remove(at:) not supported for CombinedData") } + let element = _dataSets.remove(at: position) calcMinMax() return element From f6061750fa5f9a550d084d9a6100b3560d33a484 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Mon, 15 Jan 2018 20:33:37 -0400 Subject: [PATCH 16/20] Removed used of dataSet(forIndex:) --- Source/Charts/Renderers/LineChartRenderer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index 31f9eaf609..e46182b9ee 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -643,7 +643,7 @@ open class LineChartRenderer: LineRadarRenderer { guard let dataSet = lineData[i] as? LineChartDataSetProtocol else { continue } - if !dataSet.isVisible || dataSet.entryCount == 0 + if !dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0 { continue } From 6db01d27963432d24102b23d5924e8fe6fc49147 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Sun, 21 Jan 2018 17:56:51 -0400 Subject: [PATCH 17/20] Fixed merge conflicts --- .../Data/Implementations/Standard/ChartData.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartData.swift b/Source/Charts/Data/Implementations/Standard/ChartData.swift index 89adda595b..a548a8ccc5 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartData.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartData.swift @@ -464,11 +464,6 @@ extension ChartData//: RangeReplaceableCollection @objc(addDataSet:) public func append(_ newElement: Element) { - guard !(self is CombinedChartData) else - { - fatalError("append(_:) not supported for CombinedData") - } - _dataSets.append(newElement) calcMinMax(dataSet: newElement) } @@ -476,11 +471,6 @@ extension ChartData//: RangeReplaceableCollection @objc(removeDataSetByIndex:) public func remove(at position: Index) -> Element { - guard !(self is CombinedChartData) else - { - fatalError("remove(at:) not supported for CombinedData") - } - let element = _dataSets.remove(at: position) calcMinMax() return element From b8aa08ed7b766737c1698fb463aae7762ffb2205 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Fri, 26 Jan 2018 07:03:33 -0400 Subject: [PATCH 18/20] updated demos --- .../Swift/Demos/CubicLineChartViewController.swift | 2 +- .../Swift/Demos/LineChart2ViewController.swift | 10 +++++----- .../Swift/Demos/LineChartTimeViewController.swift | 10 +++++----- .../Swift/Demos/MultipleLinesChartViewController.swift | 8 ++++---- .../Swift/Demos/RadarChartViewController.swift | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift index ee55bcee50..320b02e2c0 100644 --- a/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift @@ -131,7 +131,7 @@ class CubicLineChartViewController: DemoBaseViewController { chartView.setNeedsDisplay() case .toggleStepped: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift b/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift index a0d0582051..f7d8e5a1fb 100644 --- a/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift @@ -147,31 +147,31 @@ class LineChart2ViewController: DemoBaseViewController { switch option { case .toggleFilled: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() case .toggleHorizontalCubic: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift b/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift index 5a39c8d5e8..caacc15e07 100644 --- a/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift @@ -122,31 +122,31 @@ class LineChartTimeViewController: DemoBaseViewController { switch option { case .toggleFilled: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() case .toggleHorizontalCubic: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .cubicBezier) ? .horizontalBezier : .cubicBezier } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift index f12418cb8c..c358e8b3a2 100644 --- a/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift @@ -103,25 +103,25 @@ class MultipleLinesChartViewController: DemoBaseViewController { switch option { case .toggleFilled: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleCircles: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.drawCirclesEnabled = !set.drawCirclesEnabled } chartView.setNeedsDisplay() case .toggleCubic: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .cubicBezier) ? .linear : .cubicBezier } chartView.setNeedsDisplay() case .toggleStepped: - for set in chartView.data as! LineChartData { + for case let set as LineChartDataSet in data { set.mode = (set.mode == .stepped) ? .linear : .stepped } chartView.setNeedsDisplay() diff --git a/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift b/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift index 91c89a89d6..ce5443ff04 100644 --- a/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift +++ b/ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift @@ -168,14 +168,14 @@ class RadarChartViewController: DemoBaseViewController { chartView.rotationEnabled = !chartView.rotationEnabled case .toggleFilled: - for set in chartView.data as! RadarChartData { + for case let set as RadarChartDataSet in data { set.drawFilledEnabled = !set.drawFilledEnabled } chartView.setNeedsDisplay() case .toggleHighlightCircle: - for set in chartView.data as! RadarChartData { + for case let set as RadarChartDataSet in data { set.drawHighlightCircleEnabled = !set.drawHighlightCircleEnabled } chartView.setNeedsDisplay() From 6d8471dc58c055923d4bd477f7c7a434ebbccd38 Mon Sep 17 00:00:00 2001 From: Jacob Christie Date: Tue, 25 Dec 2018 02:09:32 -0400 Subject: [PATCH 19/20] PR Fixes --- .../Standard/ChartDataSet.swift | 20 +++++---- Source/Charts/Renderers/XAxisRenderer.swift | 12 ++--- .../YAxisRendererHorizontalBarChart.swift | 45 ++++++++----------- .../Renderers/YAxisRendererRadarChart.swift | 45 +++++++++---------- 4 files changed, 57 insertions(+), 65 deletions(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index 7769bf653a..6452405ed0 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -98,8 +98,6 @@ open class ChartDataSet: ChartBaseDataSet open override func calcMinMaxY(fromX: Double, toX: Double) { - guard !values.isEmpty else { return } - _yMax = -Double.greatestFiniteMagnitude _yMin = Double.greatestFiniteMagnitude @@ -411,9 +409,9 @@ open class ChartDataSet: ChartBaseDataSet // TODO: This should return the removed entry to follow Swift convention. open override func removeEntry(_ entry: ChartDataEntry) -> Bool { - isIndirectValuesCall = true - guard let i = values.index(where: { $0 === entry }) else { return false } + + isIndirectValuesCall = true values.remove(at: i) notifyDataSetChanged() @@ -428,9 +426,10 @@ open class ChartDataSet: ChartBaseDataSet { guard !values.isEmpty else { return false } + isIndirectValuesCall = true values.removeFirst() - calcMinMax() - + + notifyDataSetChanged() return true } @@ -440,8 +439,13 @@ open class ChartDataSet: ChartBaseDataSet // TODO: This should return the removed entry to follow Swift convention. open override func removeLast() -> Bool { - let entry: ChartDataEntry? = values.isEmpty ? nil : values.removeLast() - return entry != nil + guard !values.isEmpty else { return false } + + isIndirectValuesCall = true + values.removeLast() + + notifyDataSetChanged() + return true } /// Checks if this DataSet contains the specified Entry. diff --git a/Source/Charts/Renderers/XAxisRenderer.swift b/Source/Charts/Renderers/XAxisRenderer.swift index d4f14d9928..358446c6d2 100644 --- a/Source/Charts/Renderers/XAxisRenderer.swift +++ b/Source/Charts/Renderers/XAxisRenderer.swift @@ -34,7 +34,8 @@ open class XAxisRenderer: NSObject, AxisRenderer var min = min, max = max if let transformer = self.transformer, - viewPortHandler.contentWidth > 10 && !viewPortHandler.isFullyZoomedOutX + viewPortHandler.contentWidth > 10, + !viewPortHandler.isFullyZoomedOutX { // calculate the starting and entry point of the y-labels (depending on // zoom / contentrect bounds) @@ -125,7 +126,8 @@ open class XAxisRenderer: NSObject, AxisRenderer axis.entries.removeAll(keepingCapacity: true) axis.entries.reserveCapacity(labelCount) - let values = stride(from: first, to: Double(n) * interval + first, by: interval) + let start = first, end = first + Double(n) * interval + let values = stride(from: start, to: end, by: interval) axis.entries.append(contentsOf: values) } @@ -141,11 +143,9 @@ open class XAxisRenderer: NSObject, AxisRenderer if axis.centerAxisLabelsEnabled { - axis.centeredEntries.removeAll(keepingCapacity: true) - axis.centeredEntries.reserveCapacity(n) - let offset: Double = interval / 2.0 - axis.centeredEntries.append(contentsOf: axis.entries.map { $0 + offset }) + axis.centeredEntries = axis.entries[.. Date: Thu, 3 Jan 2019 06:46:13 -0400 Subject: [PATCH 20/20] Fixed axisLabels calculation --- .../YAxisRendererHorizontalBarChart.swift | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift index b46e10cad8..eb452b2c4a 100644 --- a/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift +++ b/Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift @@ -54,24 +54,23 @@ open class YAxisRendererHorizontalBarChart: YAxisRenderer let dependency = axis.axisDependency let labelPosition = axis.labelPosition - - // For compatibility with Android code, we keep the calculation below the same, - // And here we pull the line back up - let yPos: CGFloat - switch (dependency, labelPosition) + let yPos: CGFloat = { - case (.left, .outsideChart): - yPos = viewPortHandler.contentTop - baseYOffset + switch (dependency, labelPosition) + { + case (.left, .outsideChart): + return viewPortHandler.contentTop - baseYOffset - lineHeight - case (.left, .insideChart): - yPos = viewPortHandler.contentTop - baseYOffset + case (.left, .insideChart): + return viewPortHandler.contentTop - baseYOffset - lineHeight - case (.right, .outsideChart): - yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + case (.right, .outsideChart): + return viewPortHandler.contentBottom + baseYOffset - case (.right, .insideChart): - yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset - } + case (.right, .insideChart): + return viewPortHandler.contentBottom + baseYOffset + } + }() drawYLabels( context: context,