From 606edc5610ab16c7da3447d67bd6d42b5823eda5 Mon Sep 17 00:00:00 2001 From: Jacob Christie <19879272+jjatie@users.noreply.github.com> Date: Thu, 5 Nov 2020 23:03:27 -0400 Subject: [PATCH 1/5] Use Algorithms implementation of binary search --- Charts.xcodeproj/project.pbxproj | 77 ++++++-- .../xcshareddata/swiftpm/Package.resolved | 25 +++ .../Standard/ChartDataSet.swift | 176 +++++------------- 3 files changed, 135 insertions(+), 143 deletions(-) create mode 100644 Charts.xcworkspace/xcshareddata/swiftpm/Package.resolved diff --git a/Charts.xcodeproj/project.pbxproj b/Charts.xcodeproj/project.pbxproj index 8f9b5913b1..e062be39b4 100644 --- a/Charts.xcodeproj/project.pbxproj +++ b/Charts.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ @@ -29,6 +29,7 @@ 219192CA6B4895319AB49DCA /* BarLineScatterCandleBubbleRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B1C588E9DF6FFD56D7ADF8E /* BarLineScatterCandleBubbleRenderer.swift */; }; 221CA2922588FCBC00C2DD1E /* Sequence+KeyPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = 221CA2912588FCBC00C2DD1E /* Sequence+KeyPath.swift */; }; 2243BBFD1FF156EC00B49D0B /* EquatableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2243BBFB1FF156D000B49D0B /* EquatableTests.swift */; }; + 228D56232554F27A00BEE75E /* Algorithms in Frameworks */ = {isa = PBXBuildFile; productRef = 228D56222554F27A00BEE75E /* Algorithms */; }; 23649EFC635A76022F07FFA6 /* PieChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD02157CF8CEE1189BF681DA /* PieChartDataEntry.swift */; }; 23FA50B2730D8C7ACA091C4F /* BarChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 75F279974FE650E57A061B09 /* BarChartRenderer.swift */; }; 24151B0729D77251A8494D70 /* LineRadarRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 105FFC9D3773A9C7A60A897F /* LineRadarRenderer.swift */; }; @@ -344,6 +345,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 228D56232554F27A00BEE75E /* Algorithms in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -748,6 +750,9 @@ dependencies = ( ); name = Charts; + packageProductDependencies = ( + 228D56222554F27A00BEE75E /* Algorithms */, + ); productName = Charts; productReference = 65AD9E95D9ED4DC0BD73A743 /* Charts.framework */; productType = "com.apple.product-type.framework"; @@ -798,6 +803,9 @@ Base, ); mainGroup = 865A1CF149F52850CAB7F177; + packageReferences = ( + 228D56212554F27A00BEE75E /* XCRemoteSwiftPackageReference "swift-algorithms" */, + ); productRefGroup = AB2D554102718F209377399E /* Products */; projectDirPath = ""; projectRoot = ""; @@ -1050,7 +1058,11 @@ INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.11; MARKETING_VERSION = 4.0.0; MTL_ENABLE_DEBUG_INFO = YES; @@ -1085,8 +1097,16 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = YES; PRODUCT_BUNDLE_IDENTIFIER = com.dcg.ChartsTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1145,7 +1165,8 @@ GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; VALIDATE_PRODUCT = YES; @@ -1206,7 +1227,8 @@ GCC_WARN_UNUSED_VARIABLE = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2,3,4"; }; @@ -1229,7 +1251,11 @@ INFOPLIST_FILE = "Source/Supporting Files/Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 10.11; MARKETING_VERSION = 4.0.0; MTL_ENABLE_DEBUG_INFO = NO; @@ -1238,7 +1264,8 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "macosx iphoneos iphonesimulator appletvos appletvsimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TVOS_DEPLOYMENT_TARGET = 9.0; VERSIONING_SYSTEM = "apple-generic"; @@ -1263,14 +1290,23 @@ GCC_NO_COMMON_BLOCKS = YES; INFOPLIST_FILE = "Tests/Supporting Files/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 9.0; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MTL_ENABLE_DEBUG_INFO = NO; PRODUCT_BUNDLE_IDENTIFIER = com.dcg.ChartsTests; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator appletvos appletvsimulator"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; TVOS_DEPLOYMENT_TARGET = 9.0; }; @@ -1307,6 +1343,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + 228D56212554F27A00BEE75E /* XCRemoteSwiftPackageReference "swift-algorithms" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-algorithms"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.0.2; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 228D56222554F27A00BEE75E /* Algorithms */ = { + isa = XCSwiftPackageProductDependency; + package = 228D56212554F27A00BEE75E /* XCRemoteSwiftPackageReference "swift-algorithms" */; + productName = Algorithms; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = 193FC8DF32D250560C5F5D77 /* Project object */; } diff --git a/Charts.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Charts.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..27daaabac3 --- /dev/null +++ b/Charts.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "swift-algorithms", + "repositoryURL": "https://github.com/apple/swift-algorithms", + "state": { + "branch": null, + "revision": "bb3bafeca0e164ece3403a9de646b7d38c07dd49", + "version": "0.0.2" + } + }, + { + "package": "swift-numerics", + "repositoryURL": "https://github.com/apple/swift-numerics", + "state": { + "branch": null, + "revision": "6b24333510e9044cf4716a07bed65eeed6bc6393", + "version": "0.0.8" + } + } + ] + }, + "version": 1 +} diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index 1d80da1632..e898682de0 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -9,6 +9,7 @@ // https://github.com/danielgindi/Charts // +import Algorithms import Foundation /// Determines how to round DataSet index values for `ChartDataSet.entryIndex(x, rounding)` when an exact x-value is not found. @@ -195,58 +196,10 @@ open class ChartDataSet: ChartBaseDataSet /// An empty array if no Entry object at that index. open override func entriesForXValue(_ xValue: Double) -> [ChartDataEntry] { - var entries = [ChartDataEntry]() - - var low = startIndex - var high = endIndex - 1 - - while low <= high - { - var m = (high + low) / 2 - var entry = self[m] - - // if we have a match - if xValue == entry.x - { - while m > 0 && self[m - 1].x == xValue - { - m -= 1 - } - - high = endIndex - - // loop over all "equal" entries - while m < high - { - entry = self[m] - if entry.x == xValue - { - entries.append(entry) - } - else - { - break - } - - m += 1 - } - - break - } - else - { - if xValue > entry.x - { - low = m + 1 - } - else - { - high = m - 1 - } - } - } - - return entries + let match: (ChartDataEntry) -> Bool = { $0.x == xValue } + let i = partitioningIndex(where: match) + guard i < endIndex else { return [] } + return self[i...].prefix(while: match) } /// - Parameters: @@ -260,98 +213,57 @@ open class ChartDataSet: ChartBaseDataSet closestToY yValue: Double, rounding: ChartDataSetRounding) -> Int { - var low = startIndex - var high = endIndex - 1 - var closest = high - - while low < high - { - let m = (low + high) / 2 - - let d1 = self[m].x - xValue - let d2 = self[m + 1].x - xValue - let ad1 = abs(d1), ad2 = abs(d2) - - if ad2 < ad1 - { - // [m + 1] is closer to xValue - // Search in an higher place - low = m + 1 - } - else if ad1 < ad2 + var closest = partitioningIndex { $0.x >= xValue } + guard closest < endIndex else { return closest } + + let closestXValue = self[closest].x + + switch rounding { + case .up: + // If rounding up, and found x-value is lower than specified x, and we can go upper... + if closestXValue < xValue && closest < index(before: endIndex) { - // [m] is closer to xValue - // Search in a lower place - high = m + formIndex(after: &closest) } - else + + case .down: + // If rounding down, and found x-value is upper than specified x, and we can go lower... + if closestXValue > xValue && closest > startIndex { - // We have multiple sequential x-value with same distance - - if d1 >= 0.0 - { - // Search in a lower place - high = m - } - else if d1 < 0.0 - { - // Search in an higher place - low = m + 1 - } + formIndex(before: &closest) } - - closest = high + + case .closest: + break } - - if closest != -1 - { - let closestXValue = self[closest].x + + guard closest < endIndex else { return endIndex } - if rounding == .up - { - // If rounding up, and found x-value is lower than specified x, and we can go upper... - if closestXValue < xValue && closest < endIndex - 1 - { - closest += 1 - } - } - else if rounding == .down + // Search by closest to y-value + if !yValue.isNaN + { + while closest > startIndex && self[index(before: closest)].x == closestXValue { - // If rounding down, and found x-value is upper than specified x, and we can go lower... - if closestXValue > xValue && closest > 0 - { - closest -= 1 - } + formIndex(before: &closest) } - - // Search by closest to y-value - if !yValue.isNaN + + var closestYValue = self[closest].y + var closestYIndex = closest + + while closest < endIndex { - while closest > 0 && self[closest - 1].x == closestXValue - { - closest -= 1 - } - - var closestYValue = self[closest].y - var closestYIndex = closest - - while true + formIndex(after: &closest) + let value = self[closest] + + if value.x != closestXValue { break } + if abs(value.y - yValue) <= abs(closestYValue - yValue) { - closest += 1 - if closest >= endIndex { break } - - let value = self[closest] - - if value.x != closestXValue { break } - if abs(value.y - yValue) <= abs(closestYValue - yValue) - { - closestYValue = yValue - closestYIndex = closest - } + closestYValue = yValue + closestYIndex = closest } - - closest = closestYIndex } + + closest = closestYIndex } return closest From d9ec5bbc619624ab5bd59394aa50756ebeb35b28 Mon Sep 17 00:00:00 2001 From: Jacob Christie <19879272+jjatie@users.noreply.github.com> Date: Thu, 5 Nov 2020 23:06:40 -0400 Subject: [PATCH 2/5] Use `indexed()` where appropriate --- Source/Charts/Highlight/ChartHighlighter.swift | 3 ++- Source/Charts/Highlight/CombinedHighlighter.swift | 7 +++---- Source/Charts/Highlight/RadarHighlighter.swift | 2 +- Source/Charts/Renderers/YAxisRendererRadarChart.swift | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Source/Charts/Highlight/ChartHighlighter.swift b/Source/Charts/Highlight/ChartHighlighter.swift index 08b75ac89b..f1f1873386 100644 --- a/Source/Charts/Highlight/ChartHighlighter.swift +++ b/Source/Charts/Highlight/ChartHighlighter.swift @@ -9,6 +9,7 @@ // https://github.com/danielgindi/Charts // +import Algorithms import Foundation import CoreGraphics @@ -73,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 + for (i, set) in data.indexed() 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. diff --git a/Source/Charts/Highlight/CombinedHighlighter.swift b/Source/Charts/Highlight/CombinedHighlighter.swift index 40c254cfbf..0a03db2301 100644 --- a/Source/Charts/Highlight/CombinedHighlighter.swift +++ b/Source/Charts/Highlight/CombinedHighlighter.swift @@ -9,6 +9,7 @@ // https://github.com/danielgindi/Charts // +import Algorithms import Foundation import CoreGraphics @@ -35,10 +36,8 @@ open class CombinedHighlighter: ChartHighlighter let dataObjects = chart.combinedData?.allData else { return vals } - for i in dataObjects.indices + for (i, dataObject) in dataObjects.indexed() { - let dataObject = dataObjects[i] - // in case of BarData, let the BarHighlighter take over if barHighlighter != nil && dataObject is BarChartData, let high = barHighlighter?.getHighlight(x: x, y: y) @@ -48,7 +47,7 @@ open class CombinedHighlighter: ChartHighlighter } else { - for (j, set) in zip(dataObject.indices, dataObject) where set.isHighlightEnabled + for (j, set) in dataObject.indexed() where set.isHighlightEnabled { let highs = buildHighlights(dataSet: set, dataSetIndex: j, xValue: xValue, rounding: .closest) diff --git a/Source/Charts/Highlight/RadarHighlighter.swift b/Source/Charts/Highlight/RadarHighlighter.swift index e5f087e00f..d01cb4fd6f 100644 --- a/Source/Charts/Highlight/RadarHighlighter.swift +++ b/Source/Charts/Highlight/RadarHighlighter.swift @@ -49,7 +49,7 @@ open class RadarHighlighter: PieRadarHighlighter let sliceangle = chart.sliceAngle let factor = chart.factor - for (i, dataSet) in zip(chartData.indices, chartData) + for (i, dataSet) in chartData.indexed() { guard let entry = dataSet.entryForIndex(index) else { continue } diff --git a/Source/Charts/Renderers/YAxisRendererRadarChart.swift b/Source/Charts/Renderers/YAxisRendererRadarChart.swift index a47bf6ac18..3b3cde0bf8 100644 --- a/Source/Charts/Renderers/YAxisRendererRadarChart.swift +++ b/Source/Charts/Renderers/YAxisRendererRadarChart.swift @@ -151,7 +151,7 @@ open class YAxisRendererRadarChart: YAxisRenderer let xOffset = axis.labelXOffset let entries = axis.entries[from.. Date: Tue, 12 Jan 2021 07:47:21 -0400 Subject: [PATCH 3/5] Update Package.swift to include SwiftAlgorithms --- Package.resolved | 25 +++++++++++++++++++++++++ Package.swift | 8 +++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000000..27daaabac3 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,25 @@ +{ + "object": { + "pins": [ + { + "package": "swift-algorithms", + "repositoryURL": "https://github.com/apple/swift-algorithms", + "state": { + "branch": null, + "revision": "bb3bafeca0e164ece3403a9de646b7d38c07dd49", + "version": "0.0.2" + } + }, + { + "package": "swift-numerics", + "repositoryURL": "https://github.com/apple/swift-numerics", + "state": { + "branch": null, + "revision": "6b24333510e9044cf4716a07bed65eeed6bc6393", + "version": "0.0.8" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index f4be90e76b..8b266f9287 100644 --- a/Package.swift +++ b/Package.swift @@ -18,8 +18,14 @@ let package = Package( type: .dynamic, targets: ["Charts"]) ], + dependencies: [ + .package(url: "https://github.com/apple/swift-algorithms", from: "0.0.1") + ], targets: [ - .target(name: "Charts") + .target( + name: "Charts", + dependencies: [.product(name: "Algorithms", package: "swift-algorithms")] + ) ], swiftLanguageVersions: [.v5] ) From 69a303f2178c51d95fa9f1e677d28a95c70b148d Mon Sep 17 00:00:00 2001 From: Jacob Christie <19879272+jjatie@users.noreply.github.com> Date: Wed, 27 Jan 2021 21:53:45 -0400 Subject: [PATCH 4/5] Use lazy implementation of `prefix(while:)` For better performance --- Source/Charts/Data/Implementations/Standard/ChartDataSet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index e898682de0..bafc7b0880 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -199,7 +199,7 @@ open class ChartDataSet: ChartBaseDataSet let match: (ChartDataEntry) -> Bool = { $0.x == xValue } let i = partitioningIndex(where: match) guard i < endIndex else { return [] } - return self[i...].prefix(while: match) + return self[i...].lazy.prefix(while: match) } /// - Parameters: From 47725b3ecf9f35ffb0d9ce0af72bf3cc44ba2c58 Mon Sep 17 00:00:00 2001 From: Jacob Christie <19879272+jjatie@users.noreply.github.com> Date: Thu, 28 Jan 2021 06:52:10 -0400 Subject: [PATCH 5/5] Revert "Use lazy implementation of `prefix(while:)`" This reverts commit 69a303f2178c51d95fa9f1e677d28a95c70b148d. --- Source/Charts/Data/Implementations/Standard/ChartDataSet.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift index bafc7b0880..e898682de0 100644 --- a/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift +++ b/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift @@ -199,7 +199,7 @@ open class ChartDataSet: ChartBaseDataSet let match: (ChartDataEntry) -> Bool = { $0.x == xValue } let i = partitioningIndex(where: match) guard i < endIndex else { return [] } - return self[i...].lazy.prefix(while: match) + return self[i...].prefix(while: match) } /// - Parameters: