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 ef4d1430db..a548a8ccc5 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 @@ -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. @@ -252,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) @@ -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 } } } @@ -434,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() @@ -446,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() @@ -457,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() @@ -469,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() @@ -480,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() @@ -491,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() @@ -502,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) } @@ -534,13 +557,10 @@ extension ChartData guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } return self[index] } - + 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] diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index 7f90119ae9..6452405ed0 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -41,7 +41,6 @@ open class ChartDataSet: ChartBaseDataSet @objc public init(values: [ChartDataEntry], label: String) { self.values = values - super.init(label: label) self.calcMinMax() @@ -152,7 +151,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] @@ -410,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() @@ -425,8 +424,13 @@ 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 } + + isIndirectValuesCall = true + values.removeFirst() + + notifyDataSetChanged() + return true } /// Removes the last Entry (at index size-1) of this DataSet from the entries array. @@ -435,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/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)) diff --git a/Source/Charts/Renderers/LineChartRenderer.swift b/Source/Charts/Renderers/LineChartRenderer.swift index 1fdd82e0a3..e46182b9ee 100644 --- a/Source/Charts/Renderers/LineChartRenderer.swift +++ b/Source/Charts/Renderers/LineChartRenderer.swift @@ -642,8 +642,8 @@ 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 + + if !dataSet.isVisible || !dataSet.isDrawCirclesEnabled || 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 } diff --git a/Source/Charts/Renderers/XAxisRenderer.swift b/Source/Charts/Renderers/XAxisRenderer.swift index b802486b75..358446c6d2 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,17 @@ 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 +57,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 +76,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 +93,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 +117,18 @@ 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 start = first, end = first + Double(n) * interval + let values = stride(from: start, to: end, by: interval) + axis.entries.append(contentsOf: values) } // set decimals @@ -171,15 +143,9 @@ open class XAxisRenderer: NSObject, AxisRenderer if axis.centerAxisLabelsEnabled { - 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 = axis.entries[.. 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..9e9a39ffac 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..eb452b2c4a 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,61 +43,52 @@ 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 - - if dependency == .left - { - if labelPosition == .outsideChart - { - yPos = viewPortHandler.contentTop - baseYOffset - } - else - { - yPos = viewPortHandler.contentTop - baseYOffset - } - } - else + + let yPos: CGFloat = { - if labelPosition == .outsideChart + switch (dependency, labelPosition) { - yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset - } - else - { - yPos = viewPortHandler.contentBottom + lineHeight + baseYOffset + case (.left, .outsideChart): + return viewPortHandler.contentTop - baseYOffset - lineHeight + + case (.left, .insideChart): + return viewPortHandler.contentTop - baseYOffset - lineHeight + + case (.right, .outsideChart): + return viewPortHandler.contentBottom + baseYOffset + + case (.right, .insideChart): + return viewPortHandler.contentBottom + 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) + 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 +101,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 +128,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 +288,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..397bb2d3a3 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,31 +118,23 @@ 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] - axis._axisMaximum = axis.entries[n-1] + axis._axisMinimum = axis.entries.first! + axis._axisMaximum = axis.entries.last! axis.axisRange = abs(axis._axisMaximum - axis._axisMinimum) } 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 @@ -177,20 +148,19 @@ open class YAxisRendererRadarChart: YAxisRenderer let alignment = axis.labelAlignment let xOffset = axis.labelXOffset - - for j in stride(from: from, to: to, by: 1) - { - let r = CGFloat(axis.entries[j] - axis._axisMinimum) * factor - + + let entries = axis.entries[from..