From 948141f0c4e2c92b003ddc10b79b714af2fe0502 Mon Sep 17 00:00:00 2001 From: liuxuan30 Date: Thu, 16 Jul 2015 11:00:54 +0800 Subject: [PATCH] merge from upstream master v2.1.2 and bar chart wrong position fix try to fix wrong position bug. use entry.xIndex instead of j of dataSet.entryCount to calculate the offset and position apply the same logic for getMarkerPosition add fix for horizontal bar chart merge from upstream master Wrong demo navigation title Maximum negative value was confused... Removed unused code startAtZeroEnabled killed that range when all values are negative Bound check for horizontal bars was wrong (Fixed issue #211) Fixed a crash when removing an entry (Fixed issue #203) Removed redundant calls Inverted axis mode taken care of in stacked bars Added back the 3rd dataset in demo project Bumped to 2.1.1 Improvements to stacked bars Major improvements to Highlights Improvements concerning value-drawing in barcharts. Improvements and bugfixes concerning highlighting. Fix ChartMarker position for stacked-bars Removed redundant function Feature to allow forcing YAxis label-count Bumped to 2.1.2 CONTRIBUTING.md You will now see this when creating new issues. Ha ha! --- CONTRIBUTING.md | 49 ++++ Charts.podspec | 2 +- Charts/Charts.xcodeproj/project.pbxproj | 32 ++- Charts/Classes/Charts/BarChartView.swift | 141 +--------- .../Classes/Charts/BarLineChartViewBase.swift | 140 +++------- Charts/Classes/Charts/ChartViewBase.swift | 8 +- Charts/Classes/Charts/CombinedChartView.swift | 10 - .../Charts/HorizontalBarChartView.swift | 13 +- Charts/Classes/Charts/PieChartView.swift | 2 +- Charts/Classes/Charts/RadarChartView.swift | 2 +- Charts/Classes/Components/ChartMarker.swift | 4 +- Charts/Classes/Components/ChartYAxis.swift | 35 ++- Charts/Classes/Data/BarChartDataEntry.swift | 120 ++++----- Charts/Classes/Data/BarChartDataSet.swift | 78 +++++- Charts/Classes/Data/ChartData.swift | 2 +- Charts/Classes/Data/ChartDataSet.swift | 10 +- .../Highlight/BarChartHighlighter.swift | 250 ++++++++++++++++++ .../{Utils => Highlight}/ChartHighlight.swift | 19 ++ .../Classes/Highlight/ChartHighlighter.swift | 118 +++++++++ Charts/Classes/Highlight/ChartRange.swift | 53 ++++ .../HorizontalBarChartHighlighter.swift | 120 +++++++++ .../Classes/Renderers/BarChartRenderer.swift | 74 +++--- .../Renderers/ChartYAxisRenderer.swift | 70 +++-- .../ChartYAxisRendererRadarChart.swift | 94 ++++--- .../Renderers/CombinedChartRenderer.swift | 11 - .../HorizontalBarChartRenderer.swift | 95 +++---- .../Renderers/ScatterChartRenderer.swift | 3 +- Charts/Classes/Utils/ChartTransformer.swift | 4 +- Charts/Classes/Utils/ChartUtils.swift | 4 +- Charts/Supporting Files/Info.plist | 2 +- .../Classes/Components/BalloonMarker.swift | 2 +- .../Demos/MultipleBarChartViewController.m | 2 +- .../NegativeStackedBarChartViewController.m | 38 +-- .../Demos/StackedBarChartViewController.m | 8 +- README.md | 2 +- 35 files changed, 1074 insertions(+), 543 deletions(-) create mode 100644 CONTRIBUTING.md create mode 100644 Charts/Classes/Highlight/BarChartHighlighter.swift rename Charts/Classes/{Utils => Highlight}/ChartHighlight.swift (73%) create mode 100644 Charts/Classes/Highlight/ChartHighlighter.swift create mode 100644 Charts/Classes/Highlight/ChartRange.swift create mode 100644 Charts/Classes/Highlight/HorizontalBarChartHighlighter.swift mode change 100755 => 100644 Charts/Classes/Utils/ChartUtils.swift diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..705f60a05a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,49 @@ +# How to contribute + +Bug-fixes and features often come from users of the Charts framework, and improving it greatly. We want to keep it as easy as possible to contribute changes that improve the experience for users all around the world. There are a few guidelines that we +need contributors to follow so that we can have a chance of keeping on +top of things. + +## Simple issues and bug reports + +If you are reporting a bug which can be observed visually, please add to your issue either: + +* Screenshots, if the bug is easily explainable +* A working sample project that we can compile, run, and immediately observe the issue + +## Getting Started with Contributions + +* Make sure you have a [GitHub account](https://github.com/signup/free) +* Submit a ticket for your issue, assuming one does not already exist. + * Clearly describe the issue including steps to reproduce when it is a bug. + * Make sure you fill in the earliest version (or commit number) that you know has the issue. +* Fork the repository on GitHub + +## Making Changes + +* Create a topic branch from where you want to base your work. This is usually the master branch. +* Make commits of logical units. +* Make sure your code conforms to the code style around it. It's easy, just look around! +* If you have made changes back and forth, or have made merges, your commit history might look messy and hard to understand. A single issue or change should still be in one commit. So please squash those commits together and rebase them however you need to - to make our lives easier when reading it later. +* Check for unnecessary whitespace with `git diff --check` before committing. +* Make sure your commit messages are in the proper format. + +```` + First line must be up to 50 chars (Fixes #1234) + + The first line should be a short statement as to what have changed, and should also include an issue number, prefixed with a dash. + The body of the message comes after an empty new line, and describes the changes + more thoroughly, especially if there was a special case handled there, + or maybe some trickery that only code wizards can understand. +```` + +* Make sure you have tested your changes well. +* If your changes could theoretically affect some other component or case, which you do not necessarily use, you still have to test it. +* Create a Pull Request from your topic branch to the relevant branch in the main repo. If you go to the main repo of the framework, you'll see a big green button which pretty much prepares the PR for you. You just have to hit it. + +## Making Trivial Changes + +For changes of a trivial nature to comments and documentation, it is not +always necessary to create a new ticket. In this case, it is +appropriate to start the first line of a commit with '(doc)' instead of +a ticket number. Even the default commit message the GitHub generates is fine with us. diff --git a/Charts.podspec b/Charts.podspec index 5091a0370e..3535ad71e2 100644 --- a/Charts.podspec +++ b/Charts.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "Charts" - s.version = "2.1.0" + s.version = "2.1.2" s.summary = "ios-charts is a powerful & easy to use chart library for iOS" s.homepage = "https://github.com/danielgindi/ios-charts" s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } diff --git a/Charts/Charts.xcodeproj/project.pbxproj b/Charts/Charts.xcodeproj/project.pbxproj index 263ddafe95..e9ecd2aa46 100644 --- a/Charts/Charts.xcodeproj/project.pbxproj +++ b/Charts/Charts.xcodeproj/project.pbxproj @@ -12,6 +12,11 @@ 55E356581ADC63CD00A57971 /* BubbleChartDataEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E356551ADC63CD00A57971 /* BubbleChartDataEntry.swift */; }; 55E356591ADC63CD00A57971 /* BubbleChartDataSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E356561ADC63CD00A57971 /* BubbleChartDataSet.swift */; }; 55E3565B1ADC63EB00A57971 /* BubbleChartRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 55E3565A1ADC63EB00A57971 /* BubbleChartRenderer.swift */; }; + 5B0032451B6524AD00B6A2FE /* ChartHighlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0032441B6524AD00B6A2FE /* ChartHighlight.swift */; }; + 5B0032471B6524D300B6A2FE /* ChartRange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0032461B6524D300B6A2FE /* ChartRange.swift */; }; + 5B0032491B6525FC00B6A2FE /* ChartHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B0032481B6525FC00B6A2FE /* ChartHighlighter.swift */; }; + 5B00324B1B652BF900B6A2FE /* BarChartHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B00324A1B652BF900B6A2FE /* BarChartHighlighter.swift */; }; + 5B00324D1B65351C00B6A2FE /* HorizontalBarChartHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B00324C1B65351C00B6A2FE /* HorizontalBarChartHighlighter.swift */; }; 5B378F171AD500A4009414A4 /* ChartAnimationEasing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B378F161AD500A4009414A4 /* ChartAnimationEasing.swift */; }; 5B4BCD3E1AA9C0A60063F019 /* ChartFillFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */; }; 5B4BCD401AA9C4930063F019 /* ChartTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */; }; @@ -23,7 +28,6 @@ 5B680D221A9D17C30026A057 /* ChartXAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6D1A9D151C00CE82E1 /* ChartXAxis.swift */; }; 5B680D231A9D17C30026A057 /* ChartYAxis.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC6E1A9D151C00CE82E1 /* ChartYAxis.swift */; }; 5B680D271A9D17C30026A057 /* ChartColorTemplates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */; }; - 5B680D281A9D17C30026A057 /* ChartHighlight.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */; }; 5B680D291A9D17C30026A057 /* ChartSelectionDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7B1A9D151C00CE82E1 /* ChartSelectionDetail.swift */; }; 5B680D2A1A9D17C30026A057 /* ChartUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA8EC7C1A9D151C00CE82E1 /* ChartUtils.swift */; }; 5B680D3D1A9D1AD90026A057 /* Charts.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B680D3C1A9D1AD90026A057 /* Charts.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -92,6 +96,11 @@ 55E356551ADC63CD00A57971 /* BubbleChartDataEntry.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleChartDataEntry.swift; sourceTree = ""; }; 55E356561ADC63CD00A57971 /* BubbleChartDataSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleChartDataSet.swift; sourceTree = ""; }; 55E3565A1ADC63EB00A57971 /* BubbleChartRenderer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BubbleChartRenderer.swift; sourceTree = ""; }; + 5B0032441B6524AD00B6A2FE /* ChartHighlight.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartHighlight.swift; sourceTree = ""; }; + 5B0032461B6524D300B6A2FE /* ChartRange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartRange.swift; sourceTree = ""; }; + 5B0032481B6525FC00B6A2FE /* ChartHighlighter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartHighlighter.swift; sourceTree = ""; }; + 5B00324A1B652BF900B6A2FE /* BarChartHighlighter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarChartHighlighter.swift; sourceTree = ""; }; + 5B00324C1B65351C00B6A2FE /* HorizontalBarChartHighlighter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HorizontalBarChartHighlighter.swift; sourceTree = ""; }; 5B378F161AD500A4009414A4 /* ChartAnimationEasing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartAnimationEasing.swift; sourceTree = ""; }; 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartFillFormatter.swift; sourceTree = ""; }; 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartTransformer.swift; sourceTree = ""; }; @@ -161,7 +170,6 @@ 5BA8EC751A9D151C00CE82E1 /* ChartDataApproximatorFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataApproximatorFilter.swift; sourceTree = ""; }; 5BA8EC761A9D151C00CE82E1 /* ChartDataBaseFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartDataBaseFilter.swift; sourceTree = ""; }; 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartColorTemplates.swift; sourceTree = ""; }; - 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartHighlight.swift; sourceTree = ""; }; 5BA8EC7B1A9D151C00CE82E1 /* ChartSelectionDetail.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartSelectionDetail.swift; sourceTree = ""; }; 5BA8EC7C1A9D151C00CE82E1 /* ChartUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartUtils.swift; sourceTree = ""; }; 5BB6EC1C1ACC28AB006E9C25 /* ChartTransformerHorizontalBarChart.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChartTransformerHorizontalBarChart.swift; sourceTree = ""; }; @@ -180,6 +188,18 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5B0032431B65247600B6A2FE /* Highlight */ = { + isa = PBXGroup; + children = ( + 5B0032441B6524AD00B6A2FE /* ChartHighlight.swift */, + 5B0032461B6524D300B6A2FE /* ChartRange.swift */, + 5B0032481B6525FC00B6A2FE /* ChartHighlighter.swift */, + 5B00324A1B652BF900B6A2FE /* BarChartHighlighter.swift */, + 5B00324C1B65351C00B6A2FE /* HorizontalBarChartHighlighter.swift */, + ); + path = Highlight; + sourceTree = ""; + }; 5B680D1E1A9D170B0026A057 /* Frameworks */ = { isa = PBXGroup; children = ( @@ -259,6 +279,7 @@ 5BA8EC691A9D151C00CE82E1 /* Components */, 5BA8EC6F1A9D151C00CE82E1 /* Data */, 5BA8EC741A9D151C00CE82E1 /* Filters */, + 5B0032431B65247600B6A2FE /* Highlight */, 5B759ED41A9F98A90039D97F /* Renderers */, 5BA8EC781A9D151C00CE82E1 /* Utils */, ); @@ -343,7 +364,6 @@ children = ( 5BA8EC791A9D151C00CE82E1 /* ChartColorTemplates.swift */, 5B4BCD3D1AA9C0A60063F019 /* ChartFillFormatter.swift */, - 5BA8EC7A1A9D151C00CE82E1 /* ChartHighlight.swift */, 5BA8EC7B1A9D151C00CE82E1 /* ChartSelectionDetail.swift */, 5B4BCD3F1AA9C4930063F019 /* ChartTransformer.swift */, 5BB6EC1C1ACC28AB006E9C25 /* ChartTransformerHorizontalBarChart.swift */, @@ -432,7 +452,6 @@ buildActionMask = 2147483647; files = ( 5B680D1F1A9D17C30026A057 /* ChartAxisBase.swift in Sources */, - 5B680D281A9D17C30026A057 /* ChartHighlight.swift in Sources */, 5B4BCD3E1AA9C0A60063F019 /* ChartFillFormatter.swift in Sources */, 5B6A54DE1AA74516000F57C2 /* RadarChartDataSet.swift in Sources */, 5B680D211A9D17C30026A057 /* ChartLimitLine.swift in Sources */, @@ -463,6 +482,7 @@ 5B6A54801AA5DF28000F57C2 /* ChartYAxisRendererHorizontalBarChart.swift in Sources */, 5B6A54D21AA74516000F57C2 /* CandleChartDataEntry.swift in Sources */, 5B6A54CC1AA74516000F57C2 /* BarChartData.swift in Sources */, + 5B00324D1B65351C00B6A2FE /* HorizontalBarChartHighlighter.swift in Sources */, 5B6A54CE1AA74516000F57C2 /* BarChartDataSet.swift in Sources */, 5B6A54871AA669F4000F57C2 /* RadarChartRenderer.swift in Sources */, 5B6A548D1AA66A60000F57C2 /* ChartLegendRenderer.swift in Sources */, @@ -492,7 +512,9 @@ 55E356571ADC63CD00A57971 /* BubbleChartData.swift in Sources */, 5B6A54DF1AA74516000F57C2 /* ScatterChartData.swift in Sources */, 5B6A54D31AA74516000F57C2 /* CandleChartDataSet.swift in Sources */, + 5B0032491B6525FC00B6A2FE /* ChartHighlighter.swift in Sources */, 5B6A54D71AA74516000F57C2 /* ChartDataSet.swift in Sources */, + 5B00324B1B652BF900B6A2FE /* BarChartHighlighter.swift in Sources */, 5B6A54781AA5DEF0000F57C2 /* ChartXAxisRendererRadarChart.swift in Sources */, 5B6A54A71AA66BA7000F57C2 /* PieRadarChartViewBase.swift in Sources */, 5B6A546E1AA5D2DC000F57C2 /* ChartAnimator.swift in Sources */, @@ -504,7 +526,9 @@ 5B6A54CD1AA74516000F57C2 /* BarChartDataEntry.swift in Sources */, 5B6A54D41AA74516000F57C2 /* CombinedChartData.swift in Sources */, 5B680D2A1A9D17C30026A057 /* ChartUtils.swift in Sources */, + 5B0032451B6524AD00B6A2FE /* ChartHighlight.swift in Sources */, 5B680D201A9D17C30026A057 /* ChartLegend.swift in Sources */, + 5B0032471B6524D300B6A2FE /* ChartRange.swift in Sources */, 5BD8F06E1AB89AD800566E05 /* HorizontalBarChartView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Charts/Classes/Charts/BarChartView.swift b/Charts/Classes/Charts/BarChartView.swift index 3d9d637829..f5d19414a0 100644 --- a/Charts/Classes/Charts/BarChartView.swift +++ b/Charts/Classes/Charts/BarChartView.swift @@ -22,9 +22,6 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate /// if set to true, all values are drawn above their bars, instead of below their top private var _drawValueAboveBarEnabled = true - - /// if set to true, all values of a stack are drawn individually, and not just their sum - private var _drawValuesForWholeStackEnabled = true /// if set to true, a grey area is darawn behind each bar that indicates the maximum value private var _drawBarShadowEnabled = false @@ -36,6 +33,8 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate renderer = BarChartRenderer(delegate: self, animator: _animator, viewPortHandler: _viewPortHandler) _xAxisRenderer = ChartXAxisRendererBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self) + _highlighter = BarChartHighlighter(chart: self) + _chartXMin = -0.5 } @@ -56,17 +55,7 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate // extend xDelta to make space for multiple datasets (if ther are one) _deltaX *= CGFloat(_data.dataSetCount) - var maxEntry = 0 - - for (var i = 0, count = barData.dataSetCount; i < count; i++) - { - var set = barData.getDataSetByIndex(i) - - if (maxEntry < set!.entryCount) - { - maxEntry = set!.entryCount - } - } + var maxEntry = barData.xValCount var groupSpace = barData.groupSpace _deltaX += CGFloat(maxEntry) * groupSpace @@ -74,7 +63,7 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate } /// Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the BarChart. - public override func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + public override func getHighlightByTouchPoint(pt: CGPoint) -> ChartHighlight? { if (_dataNotSet || _data === nil) { @@ -82,108 +71,7 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate return nil } - _leftAxisTransformer.pixelToValue(&pt) - - if (pt.x < CGFloat(_chartXMin) || pt.x > CGFloat(_chartXMax)) - { - return nil - } - - return getHighlight(xPosition: pt.x, yPosition: pt.y) - } - - /// Returns the correct Highlight object (including xIndex and dataSet-index) for the specified touch position. - internal func getHighlight(#xPosition: CGFloat, yPosition: CGFloat) -> ChartHighlight! - { - if (_dataNotSet || _data === nil) - { - return nil - } - - var barData = _data as! BarChartData! - - var setCount = barData.dataSetCount - var valCount = barData.xValCount - var dataSetIndex = 0 - var xIndex = 0 - - if (!barData.isGrouped) - { // only one dataset exists - - xIndex = Int(round(xPosition)) - - // check bounds - if (xIndex < 0) - { - xIndex = 0 - } - else if (xIndex >= valCount) - { - xIndex = valCount - 1 - } - } - else - { // if this bardata is grouped into more datasets - - // calculate how often the group-space appears - var steps = Int(xPosition / (CGFloat(setCount) + CGFloat(barData.groupSpace))) - - var groupSpaceSum = barData.groupSpace * CGFloat(steps) - - var baseNoSpace = xPosition - groupSpaceSum - - dataSetIndex = Int(baseNoSpace) % setCount - xIndex = Int(baseNoSpace) / setCount - - // check bounds - if (xIndex < 0) - { - xIndex = 0 - dataSetIndex = 0 - } - else if (xIndex >= valCount) - { - xIndex = valCount - 1 - dataSetIndex = setCount - 1 - } - - // check bounds - if (dataSetIndex < 0) - { - dataSetIndex = 0 - } - else if (dataSetIndex >= setCount) - { - dataSetIndex = setCount - 1 - } - } - - var dataSet = barData.getDataSetByIndex(dataSetIndex) as! BarChartDataSet! - if (!dataSet.isStacked) - { - return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex) - } - else - { - return getStackedHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex, yValue: Double(yPosition)) - } - } - - /// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected. - internal func getStackedHighlight(#xIndex: Int, dataSetIndex: Int, yValue: Double) -> ChartHighlight! - { - var dataSet = _data.getDataSetByIndex(dataSetIndex) - var entry = dataSet.entryForXIndex(xIndex) as! BarChartDataEntry! - - if (entry !== nil) - { - var stackIndex = entry.getClosestIndexAbove(yValue) - return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex, stackIndex: stackIndex) - } - else - { - return nil - } + return _highlighter?.getHighlight(x: Double(pt.x), y: Double(pt.y)) } /// Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be found in the charts data. @@ -261,17 +149,6 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate } } - /// if set to true, all values of a stack are drawn individually, and not just their sum - public var drawValuesForWholeStackEnabled: Bool - { - get { return _drawValuesForWholeStackEnabled; } - set - { - _drawValuesForWholeStackEnabled = newValue - setNeedsDisplay() - } - } - /// if set to true, a grey area is drawn behind each bar that indicates the maximum value public var drawBarShadowEnabled: Bool { @@ -289,9 +166,6 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate /// returns true if drawing values above bars is enabled, false if not public var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled; } - /// returns true if all values of a stack are drawn, and not just their sum - public var isDrawValuesForWholeStackEnabled: Bool { return drawValuesForWholeStackEnabled; } - /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not public var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled; } @@ -347,11 +221,6 @@ public class BarChartView: BarLineChartViewBase, BarChartRendererDelegate return drawValueAboveBarEnabled } - public func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool - { - return drawValuesForWholeStackEnabled - } - public func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool { return drawBarShadowEnabled diff --git a/Charts/Classes/Charts/BarLineChartViewBase.swift b/Charts/Classes/Charts/BarLineChartViewBase.swift index 6f100fc3ae..82674f2515 100644 --- a/Charts/Classes/Charts/BarLineChartViewBase.swift +++ b/Charts/Classes/Charts/BarLineChartViewBase.swift @@ -101,6 +101,8 @@ public class BarLineChartViewBase: ChartViewBase, UIGestureRecognizerDelegate _xAxisRenderer = ChartXAxisRenderer(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer) + _highlighter = ChartHighlighter(chart: self) + _tapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("tapGestureRecognized:")) _doubleTapGestureRecognizer = UITapGestureRecognizer(target: self, action: Selector("doubleTapGestureRecognized:")) _doubleTapGestureRecognizer.numberOfTapsRequired = 2 @@ -325,12 +327,30 @@ public class BarLineChartViewBase: ChartViewBase, UIGestureRecognizerDelegate // consider starting at zero (0) if (_leftAxis.isStartAtZeroEnabled) { - _leftAxis.axisMinimum = 0.0 + if _leftAxis.axisMinimum < 0.0 && _leftAxis.axisMaximum < 0.0 + { + // If the values are all negative, let's stay in the negative zone + _leftAxis.axisMaximum = 0.0 + } + else + { + // We have positive values, stay in the positive zone + _leftAxis.axisMinimum = 0.0 + } } if (_rightAxis.isStartAtZeroEnabled) { - _rightAxis.axisMinimum = 0.0 + if _rightAxis.axisMinimum < 0.0 && _rightAxis.axisMaximum < 0.0 + { + // If the values are all negative, let's stay in the negative zone + _rightAxis.axisMaximum = 0.0 + } + else + { + // We have positive values, stay in the positive zone + _rightAxis.axisMinimum = 0.0 + } } _leftAxis.axisRange = abs(_leftAxis.axisMaximum - _leftAxis.axisMinimum) @@ -437,23 +457,32 @@ public class BarLineChartViewBase: ChartViewBase, UIGestureRecognizerDelegate } } - public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + public override func getMarkerPosition(#entry: ChartDataEntry, highlight: ChartHighlight) -> CGPoint { + let dataSetIndex = highlight.dataSetIndex var xPos = CGFloat(entry.xIndex) + var yPos = entry.value if (self.isKindOfClass(BarChartView)) { var bd = _data as! BarChartData var space = bd.groupSpace - var j = _data.getDataSetByIndex(dataSetIndex)!.entryIndex(entry: entry, isEqual: true) - var x = CGFloat(j * (_data.dataSetCount - 1) + dataSetIndex) + space * CGFloat(j) + space / 2.0 + var x = CGFloat(entry.xIndex * (_data.dataSetCount - 1) + dataSetIndex) + space * CGFloat(entry.xIndex) + space / 2.0 xPos += x + + if let barEntry = entry as? BarChartDataEntry + { + if barEntry.values != nil && highlight.range !== nil + { + yPos = highlight.range!.to + } + } } // position of the marker depends on selected value index and value - var pt = CGPoint(x: xPos, y: CGFloat(entry.value) * _animator.phaseY) + var pt = CGPoint(x: xPos, y: CGFloat(yPos) * _animator.phaseY) getTransformer(_data.getDataSetByIndex(dataSetIndex)!.axisDependency).pointValueToPixel(&pt) @@ -1262,108 +1291,15 @@ public class BarLineChartViewBase: ChartViewBase, UIGestureRecognizerDelegate } /// Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point inside the Line-, Scatter-, or CandleStick-Chart. - public func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + public func getHighlightByTouchPoint(pt: CGPoint) -> ChartHighlight? { if (_dataNotSet || _data === nil) { println("Can't select by touch. No data set.") return nil } - - var valPt = CGPoint() - valPt.x = pt.x - valPt.y = 0.0 - - // take any transformer to determine the x-axis value - _leftAxisTransformer.pixelToValue(&valPt) - - var xTouchVal = valPt.x - var base = floor(xTouchVal) - - var touchOffset = _deltaX * 0.025 - - // touch out of chart - if (xTouchVal < -touchOffset || xTouchVal > _deltaX + touchOffset) - { - return nil - } - - if (base < 0.0) - { - base = 0.0 - } - if (base >= _deltaX) - { - base = _deltaX - 1.0 - } - - var xIndex = Int(base) - - // check if we are more than half of a x-value or not - if (xTouchVal - base > 0.5) - { - xIndex = Int(base + 1.0) - } - - var valsAtIndex = getSelectionDetailsAtIndex(xIndex) - - var leftdist = ChartUtils.getMinimumDistance(valsAtIndex, val: Double(pt.y), axis: .Left) - var rightdist = ChartUtils.getMinimumDistance(valsAtIndex, val: Double(pt.y), axis: .Right) - - if (_data!.getFirstRight() === nil) - { - rightdist = DBL_MAX - } - if (_data!.getFirstLeft() === nil) - { - leftdist = DBL_MAX - } - - var axis: ChartYAxis.AxisDependency = leftdist < rightdist ? .Left : .Right - - var dataSetIndex = ChartUtils.closestDataSetIndex(valsAtIndex, value: Double(pt.y), axis: axis) - - if (dataSetIndex == -1) - { - return nil - } - - return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex) - } - - /// Returns an array of SelectionDetail objects for the given x-index. The SelectionDetail - /// objects give information about the value at the selected index and the - /// DataSet it belongs to. - public func getSelectionDetailsAtIndex(xIndex: Int) -> [ChartSelectionDetail] - { - var vals = [ChartSelectionDetail]() - - var pt = CGPoint() - - for (var i = 0, count = _data.dataSetCount; i < count; i++) - { - var dataSet = _data.getDataSetByIndex(i) - if (dataSet === nil || !dataSet.isHighlightEnabled) - { - continue - } - - // extract all y-values from all DataSets at the given x-index - let yVal = dataSet!.yValForXIndex(xIndex) - if (yVal.isNaN) - { - continue - } - - pt.y = CGFloat(yVal) - - getTransformer(dataSet!.axisDependency).pointValueToPixel(&pt) - - vals.append(ChartSelectionDetail(value: Double(pt.y), dataSetIndex: i, dataSet: dataSet!)) - } - - return vals + return _highlighter?.getHighlight(x: Double(pt.x), y: Double(pt.y)) } /// Returns the x and y values in the chart at the given touch point @@ -1412,7 +1348,7 @@ public class BarLineChartViewBase: ChartViewBase, UIGestureRecognizerDelegate var h = getHighlightByTouchPoint(pt) if (h !== nil) { - return _data.getDataSetByIndex(h.dataSetIndex) as! BarLineScatterCandleChartDataSet! + return _data.getDataSetByIndex(h!.dataSetIndex) as! BarLineScatterCandleChartDataSet! } return nil } diff --git a/Charts/Classes/Charts/ChartViewBase.swift b/Charts/Classes/Charts/ChartViewBase.swift index a1091a14fa..f06b1b7358 100755 --- a/Charts/Classes/Charts/ChartViewBase.swift +++ b/Charts/Classes/Charts/ChartViewBase.swift @@ -94,6 +94,8 @@ public class ChartViewBase: UIView, ChartAnimatorDelegate /// object responsible for rendering the data public var renderer: ChartDataRendererBase? + internal var _highlighter: ChartHighlighter? + /// object that manages the bounds and drawing constraints of the chart internal var _viewPortHandler: ChartViewPortHandler! @@ -460,7 +462,7 @@ public class ChartViewBase: UIView, ChartAnimatorDelegate continue } - var pos = getMarkerPosition(entry: e!, dataSetIndex: dataSetIndex) + var pos = getMarkerPosition(entry: e!, highlight: highlight) // check bounds if (!_viewPortHandler.isInBounds(x: pos.x, y: pos.y)) @@ -469,7 +471,7 @@ public class ChartViewBase: UIView, ChartAnimatorDelegate } // callbacks to update the content - marker!.refreshContent(entry: e!, dataSetIndex: dataSetIndex) + marker!.refreshContent(entry: e!, highlight: highlight) let markerSize = marker!.size if (pos.y - markerSize.height <= 0.0) @@ -486,7 +488,7 @@ public class ChartViewBase: UIView, ChartAnimatorDelegate } /// Returns the actual position in pixels of the MarkerView for the given Entry in the given DataSet. - public func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + public func getMarkerPosition(#entry: ChartDataEntry, highlight: ChartHighlight) -> CGPoint { fatalError("getMarkerPosition() cannot be called on ChartViewBase") } diff --git a/Charts/Classes/Charts/CombinedChartView.swift b/Charts/Classes/Charts/CombinedChartView.swift index 3c723514b7..7baedba269 100644 --- a/Charts/Classes/Charts/CombinedChartView.swift +++ b/Charts/Classes/Charts/CombinedChartView.swift @@ -177,13 +177,6 @@ public class CombinedChartView: BarLineChartViewBase set { (renderer as! CombinedChartRenderer!).drawValueAboveBarEnabled = newValue; } } - /// if set to true, all values of a stack are drawn individually, and not just their sum - public var drawValuesForWholeStackEnabled: Bool - { - get { return (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled; } - set { (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled = newValue; } - } - /// if set to true, a grey area is darawn behind each bar that indicates the maximum value public var drawBarShadowEnabled: Bool { @@ -197,9 +190,6 @@ public class CombinedChartView: BarLineChartViewBase /// returns true if drawing values above bars is enabled, false if not public var isDrawValueAboveBarEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawValueAboveBarEnabled; } - /// returns true if all values of a stack are drawn, and not just their sum - public var isDrawValuesForWholeStackEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawValuesForWholeStackEnabled; } - /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not public var isDrawBarShadowEnabled: Bool { return (renderer as! CombinedChartRenderer!).drawBarShadowEnabled; } diff --git a/Charts/Classes/Charts/HorizontalBarChartView.swift b/Charts/Classes/Charts/HorizontalBarChartView.swift index f52d10b923..241e0c6e84 100644 --- a/Charts/Classes/Charts/HorizontalBarChartView.swift +++ b/Charts/Classes/Charts/HorizontalBarChartView.swift @@ -28,6 +28,8 @@ public class HorizontalBarChartView: BarChartView _leftYAxisRenderer = ChartYAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: _leftAxis, transformer: _leftAxisTransformer) _rightYAxisRenderer = ChartYAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, yAxis: _rightAxis, transformer: _rightAxisTransformer) _xAxisRenderer = ChartXAxisRendererHorizontalBarChart(viewPortHandler: _viewPortHandler, xAxis: _xAxis, transformer: _leftAxisTransformer, chart: self) + + _highlighter = HorizontalBarChartHighlighter(chart: self) } internal override func calculateOffsets() @@ -158,7 +160,7 @@ public class HorizontalBarChartView: BarChartView return vals } - public override func getHighlightByTouchPoint(var pt: CGPoint) -> ChartHighlight! + public override func getHighlightByTouchPoint(pt: CGPoint) -> ChartHighlight? { if (_dataNotSet || _data === nil) { @@ -166,14 +168,7 @@ public class HorizontalBarChartView: BarChartView return nil } - _leftAxisTransformer.pixelToValue(&pt) - - if (pt.y < CGFloat(_chartXMin) || pt.y > CGFloat(_chartXMax)) - { - return nil - } - - return getHighlight(xPosition: pt.y, yPosition: pt.x) + return _highlighter?.getHighlight(x: Double(pt.y), y: Double(pt.x)) } public override var lowestVisibleXIndex: Int diff --git a/Charts/Classes/Charts/PieChartView.swift b/Charts/Classes/Charts/PieChartView.swift index 80bb0ef785..1a9b896b02 100755 --- a/Charts/Classes/Charts/PieChartView.swift +++ b/Charts/Classes/Charts/PieChartView.swift @@ -98,7 +98,7 @@ public class PieChartView: PieRadarChartViewBase calcAngles() } - public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + public override func getMarkerPosition(#entry: ChartDataEntry, highlight: ChartHighlight) -> CGPoint { /// PieChart does not support MarkerView return CGPoint(x: 0.0, y: 0.0) diff --git a/Charts/Classes/Charts/RadarChartView.swift b/Charts/Classes/Charts/RadarChartView.swift index c9a1f9265e..9ea293ede5 100644 --- a/Charts/Classes/Charts/RadarChartView.swift +++ b/Charts/Classes/Charts/RadarChartView.swift @@ -100,7 +100,7 @@ public class RadarChartView: PieRadarChartViewBase _yAxis.axisRange = abs(_yAxis.axisMaximum - _yAxis.axisMinimum) } - public override func getMarkerPosition(#entry: ChartDataEntry, dataSetIndex: Int) -> CGPoint + public override func getMarkerPosition(#entry: ChartDataEntry, highlight: ChartHighlight) -> CGPoint { var angle = self.sliceAngle * CGFloat(entry.xIndex) + self.rotationAngle var val = CGFloat(entry.value) * self.factor diff --git a/Charts/Classes/Components/ChartMarker.swift b/Charts/Classes/Components/ChartMarker.swift index 7781ee0e05..7951128c1b 100644 --- a/Charts/Classes/Components/ChartMarker.swift +++ b/Charts/Classes/Components/ChartMarker.swift @@ -50,7 +50,9 @@ public class ChartMarker: ChartComponentBase } /// This method enables a custom ChartMarker to update it's content everytime the MarkerView is redrawn according to the data entry it points to. - public func refreshContent(#entry: ChartDataEntry, dataSetIndex: Int) + /// + /// :param: highlight the highlight object contains information about the highlighted value such as it's dataset-index, the selected range or stack-index (only stacked bar entries). + public func refreshContent(#entry: ChartDataEntry, highlight: ChartHighlight) { // Do nothing here... } diff --git a/Charts/Classes/Components/ChartYAxis.swift b/Charts/Classes/Components/ChartYAxis.swift index 069a767450..8544053b4c 100644 --- a/Charts/Classes/Components/ChartYAxis.swift +++ b/Charts/Classes/Components/ChartYAxis.swift @@ -53,6 +53,9 @@ public class ChartYAxis: ChartAxisBase /// if true, the y-label entries will always start at zero public var startAtZeroEnabled = true + /// if true, the set number of y-labels will be forced + public var forceLabelsEnabled = true + /// the formatter used to customly format the y-labels public var valueFormatter: NSNumberFormatter? @@ -127,6 +130,22 @@ public class ChartYAxis: ChartAxisBase return _axisDependency } + public func setLabelCount(count: Int, force: Bool) + { + _labelCount = count + + if (_labelCount > 25) + { + _labelCount = 25 + } + if (_labelCount < 2) + { + _labelCount = 2 + } + + forceLabelsEnabled = force + } + /// the number of label entries the y-axis should have /// max = 25, /// min = 2, @@ -140,16 +159,7 @@ public class ChartYAxis: ChartAxisBase } set { - _labelCount = newValue - - if (_labelCount > 25) - { - _labelCount = 25 - } - if (_labelCount < 2) - { - _labelCount = 2 - } + setLabelCount(newValue, force: false); } } @@ -224,7 +234,10 @@ public class ChartYAxis: ChartAxisBase public var isInverted: Bool { return inverted; } public var isStartAtZeroEnabled: Bool { return startAtZeroEnabled; } - + + /// :returns: true if focing the y-label count is enabled. Default: false + public var isForceLabelsEnabled: Bool { return forceLabelsEnabled } + public var isShowOnlyMinMaxEnabled: Bool { return showOnlyMinMaxEnabled; } public var isDrawTopYLabelEntryEnabled: Bool { return drawTopYLabelEntryEnabled; } diff --git a/Charts/Classes/Data/BarChartDataEntry.swift b/Charts/Classes/Data/BarChartDataEntry.swift index 7c52ab1e12..4b2bc54f3d 100644 --- a/Charts/Classes/Data/BarChartDataEntry.swift +++ b/Charts/Classes/Data/BarChartDataEntry.swift @@ -16,13 +16,20 @@ import Foundation public class BarChartDataEntry: ChartDataEntry { /// the values the stacked barchart holds - private var _values: [Double]! + private var _values: [Double]? - /// Constructor for stacked bar entries. + /// the sum of all negative values this entry (if stacked) contains + private var _negativeSum: Double = 0.0 + + /// the sum of all positive values this entry (if stacked) contains + private var _positiveSum: Double = 0.0 + + /// Constructor for stacked bar entries. Don't forget to order the stacked-values in an ascending order e.g. (-2,-1,0,1,2). public init(values: [Double], xIndex: Int) { super.init(value: BarChartDataEntry.calcSum(values), xIndex: xIndex) self.values = values + calcPosNegSum() } /// Constructor for normal bars (not stacked). @@ -43,28 +50,6 @@ public class BarChartDataEntry: ChartDataEntry { super.init(value: value, xIndex: xIndex, data: data) } - - /// Returns the index of the closest value inside the values array (for stacked barchart) - /// to the value given as a parameter. The closest value must be higher - /// (above) the provided value. - public func getClosestIndexAbove(value: Double) -> Int - { - if (values == nil) - { - return 0 - } - - var index = values.count - 1 - var remainder: Double = 0.0 - - while (index > 0 && value > values[index] + remainder) - { - remainder += values[index] - index-- - } - - return index - } public func getBelowSum(stackIndex :Int) -> Double { @@ -74,80 +59,68 @@ public class BarChartDataEntry: ChartDataEntry } var remainder: Double = 0.0 - var index = values.count - 1 + var index = values!.count - 1 while (index > stackIndex && index >= 0) { - remainder += values[index] + remainder += values![index] index-- } return remainder } - - /// Calculates the sum across all values. - private class func calcSum(values: [Double]) -> Double + + /// :returns: the sum of all negative values this entry (if stacked) contains. (this is a positive number) + public var negativeSum: Double { - var sum = Double(0.0) - - for f in values - { - sum += f - } - - return sum + return _negativeSum } + /// :returns: the sum of all positive values this entry (if stacked) contains. public var positiveSum: Double { - if _values == nil - { - return 0.0 - } - - var sum: Double = 0.0 - - for f in _values - { - if f > 0.0 - { - sum += f - } - } - - return sum + return _positiveSum } - - public var negativeSum: Double + + public func calcPosNegSum() { if _values == nil { - return 0.0 + _positiveSum = 0.0 + _negativeSum = 0.0 + return } - var sum: Double = 0.0 + var sumNeg: Double = 0.0 + var sumPos: Double = 0.0 - for f in _values + for f in _values! { if f < 0.0 { - sum += abs(f) + sumNeg += -f + } + else + { + sumPos += f } } - return sum + _negativeSum = sumNeg + _positiveSum = sumPos } // MARK: Accessors /// the values the stacked barchart holds - public var values: [Double]! + public var values: [Double]? { - get { return self._values; } + get { return self._values } set { self.value = BarChartDataEntry.calcSum(newValue) self._values = newValue + calcPosNegSum() } } @@ -157,6 +130,29 @@ public class BarChartDataEntry: ChartDataEntry { var copy = super.copyWithZone(zone) as! BarChartDataEntry copy._values = _values + copy.value = value + copy._negativeSum = _negativeSum return copy } + + /// Calculates the sum across all values of the given stack. + /// + /// :param: vals + /// :returns: + private static func calcSum(vals: [Double]?) -> Double + { + if vals == nil + { + return 0.0 + } + + var sum = 0.0 + + for f in vals! + { + sum += f + } + + return sum + } } \ No newline at end of file diff --git a/Charts/Classes/Data/BarChartDataSet.swift b/Charts/Classes/Data/BarChartDataSet.swift index 6630a00aea..a5f41f3c0e 100644 --- a/Charts/Classes/Data/BarChartDataSet.swift +++ b/Charts/Classes/Data/BarChartDataSet.swift @@ -76,7 +76,7 @@ public class BarChartDataSet: BarLineScatterCandleChartDataSet } else { - _entryCountStacks += vals.count + _entryCountStacks += vals!.count } } } @@ -86,13 +86,81 @@ public class BarChartDataSet: BarLineScatterCandleChartDataSet { for (var i = 0; i < yVals.count; i++) { - var vals = yVals[i].values - - if (vals != nil && vals.count > _stackSize) + if let vals = yVals[i].values + { + if vals.count > _stackSize + { + _stackSize = vals.count + } + } + } + } + + internal override func calcMinMax(#start : Int, end: Int) + { + let yValCount = _yVals.count + + if yValCount == 0 + { + return + } + + var endValue : Int + + if end == 0 || end >= yValCount + { + endValue = yValCount - 1 + } + else + { + endValue = end + } + + _lastStart = start + _lastEnd = endValue + + _yMin = DBL_MAX + _yMax = -DBL_MAX + + for (var i = start; i <= endValue; i++) + { + if let e = _yVals[i] as? BarChartDataEntry { - _stackSize = vals.count + if !e.value.isNaN + { + if e.values == nil + { + if e.value < _yMin + { + _yMin = e.value + } + + if e.value > _yMax + { + _yMax = e.value + } + } + else + { + if -e.negativeSum < _yMin + { + _yMin = -e.negativeSum + } + + if e.positiveSum > _yMax + { + _yMax = e.positiveSum + } + } + } } } + + if (_yMin == DBL_MAX) + { + _yMin = 0.0 + _yMax = 0.0 + } } /// Returns the maximum number of bars that can be stacked upon another in this DataSet. diff --git a/Charts/Classes/Data/ChartData.swift b/Charts/Classes/Data/ChartData.swift index 5c8d268aff..19c660ee08 100644 --- a/Charts/Classes/Data/ChartData.swift +++ b/Charts/Classes/Data/ChartData.swift @@ -155,7 +155,7 @@ public class ChartData: NSObject _lastEnd = end _yMin = DBL_MAX - _yMax = -DBL_MIN + _yMax = -DBL_MAX for (var i = 0; i < _dataSets.count; i++) { diff --git a/Charts/Classes/Data/ChartDataSet.swift b/Charts/Classes/Data/ChartDataSet.swift index ae82517de3..347eb19c2e 100644 --- a/Charts/Classes/Data/ChartDataSet.swift +++ b/Charts/Classes/Data/ChartDataSet.swift @@ -89,16 +89,18 @@ public class ChartDataSet: NSObject internal func calcMinMax(#start : Int, end: Int) { - if _yVals!.count == 0 + let yValCount = _yVals.count + + if yValCount == 0 { return } var endValue : Int - if end == 0 + if end == 0 || end >= yValCount { - endValue = _yVals.count - 1 + endValue = yValCount - 1 } else { @@ -109,7 +111,7 @@ public class ChartDataSet: NSObject _lastEnd = endValue _yMin = DBL_MAX - _yMax = -DBL_MIN + _yMax = -DBL_MAX for (var i = start; i <= endValue; i++) { diff --git a/Charts/Classes/Highlight/BarChartHighlighter.swift b/Charts/Classes/Highlight/BarChartHighlighter.swift new file mode 100644 index 0000000000..047bfa08cd --- /dev/null +++ b/Charts/Classes/Highlight/BarChartHighlighter.swift @@ -0,0 +1,250 @@ +// +// ChartBarHighlighter.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/7/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +internal class BarChartHighlighter: ChartHighlighter +{ + internal init(chart: BarChartView) + { + super.init(chart: chart) + } + + internal override func getHighlight(#x: Double, y: Double) -> ChartHighlight? + { + var h = super.getHighlight(x: x, y: y) + + if h === nil + { + return h + } + else + { + if let set = _chart?.data?.getDataSetByIndex(h!.dataSetIndex) as? BarChartDataSet + { + if set.isStacked + { + // create an array of the touch-point + var pt = CGPoint() + pt.y = CGFloat(y) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(set.axisDependency).pixelToValue(&pt) + + return getStackedHighlight(old: h, set: set, xIndex: h!.xIndex, dataSetIndex: h!.dataSetIndex, yValue: Double(pt.y)) + } + } + + return h + } + } + + internal override func getXIndex(x: Double) -> Int + { + if let barChartData = _chart?.data as? BarChartData + { + if !barChartData.isGrouped + { + return super.getXIndex(x) + } + else + { + let baseNoSpace = getBase(x) + + let setCount = barChartData.dataSetCount + var xIndex = Int(baseNoSpace) / setCount + + let valCount = barChartData.xValCount + + if xIndex < 0 + { + xIndex = 0 + } + else if xIndex >= valCount + { + xIndex = valCount - 1 + } + + return xIndex + } + } + else + { + return 0 + } + } + + internal override func getDataSetIndex(#xIndex: Int, x: Double, y: Double) -> Int + { + if let barChartData = _chart?.data as? BarChartData + { + if !barChartData.isGrouped + { + return 0 + } + else + { + let baseNoSpace = getBase(x) + + let setCount = barChartData.dataSetCount + var dataSetIndex = Int(baseNoSpace) % setCount + + if dataSetIndex < 0 + { + dataSetIndex = 0 + } + else if dataSetIndex >= setCount + { + dataSetIndex = setCount - 1 + } + + return dataSetIndex + } + } + else + { + return 0 + } + } + + /// This method creates the Highlight object that also indicates which value of a stacked BarEntry has been selected. + /// :param: old the old highlight object before looking for stacked values + /// :param: set + /// :param: xIndex + /// :param: dataSetIndex + /// :param: yValue + /// :returns: + internal func getStackedHighlight(#old: ChartHighlight?, set: BarChartDataSet, xIndex: Int, dataSetIndex: Int, yValue: Double) -> ChartHighlight? + { + var entry = set.entryForXIndex(xIndex) as? BarChartDataEntry + + if entry !== nil + { + if entry?.values === nil + { + return old + } + + if let ranges = getRanges(entry: entry!) + { + let stackIndex = getClosestStackIndex(ranges: ranges, value: yValue) + let h = ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex, stackIndex: stackIndex, range: ranges[stackIndex]) + return h + } + return nil + } + else + { + return nil + } + } + + /// Returns the index of the closest value inside the values array / ranges (stacked barchart) to the value given as a parameter. + /// :param: entry + /// :param: value + /// :returns: + internal func getClosestStackIndex(#ranges: [ChartRange]?, value: Double) -> Int + { + if ranges == nil + { + return 0 + } + + var stackIndex = 0 + + for range in ranges! + { + if range.contains(value) + { + return stackIndex + } + else + { + stackIndex++ + } + } + + let length = ranges!.count - 1 + + return (value > ranges![length].to) ? length : 0 + } + + /// Returns the base x-value to the corresponding x-touch value in pixels. + /// :param: x + /// :returns: + internal func getBase(x: Double) -> Double + { + if let barChartData = _chart?.data as? BarChartData + { + // create an array of the touch-point + var pt = CGPoint() + pt.x = CGFloat(x) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(ChartYAxis.AxisDependency.Left).pixelToValue(&pt) + let xVal = Double(pt.x) + + let setCount = barChartData.dataSetCount ?? 0 + + // calculate how often the group-space appears + let steps = Int(xVal / (Double(setCount) + Double(barChartData.groupSpace))) + + let groupSpaceSum = Double(barChartData.groupSpace) * Double(steps) + + let baseNoSpace = xVal - groupSpaceSum + + return baseNoSpace + } + else + { + return 0.0 + } + } + + /// Splits up the stack-values of the given bar-entry into Range objects. + /// :param: entry + /// :returns: + internal func getRanges(#entry: BarChartDataEntry) -> [ChartRange]? + { + let values = entry.values + if (values == nil) + { + return nil + } + + var negRemain = -entry.negativeSum + var posRemain: Double = 0.0 + + var ranges = [ChartRange]() + ranges.reserveCapacity(values!.count) + + for (var i = 0, count = values!.count; i < count; i++) + { + let value = values![i] + + if value < 0 + { + ranges.append(ChartRange(from: negRemain, to: negRemain + abs(value))) + negRemain += abs(value) + } + else + { + ranges.append(ChartRange(from: posRemain, to: posRemain+value)) + posRemain += value + } + } + + return ranges + } +} diff --git a/Charts/Classes/Utils/ChartHighlight.swift b/Charts/Classes/Highlight/ChartHighlight.swift similarity index 73% rename from Charts/Classes/Utils/ChartHighlight.swift rename to Charts/Classes/Highlight/ChartHighlight.swift index 4782f43d05..20dd5338ac 100644 --- a/Charts/Classes/Utils/ChartHighlight.swift +++ b/Charts/Classes/Highlight/ChartHighlight.swift @@ -25,6 +25,9 @@ public class ChartHighlight: NSObject /// index which value of a stacked bar entry is highlighted /// :default: -1 private var _stackIndex = Int(-1) + + /// the range of the bar that is selected (only for stacked-barchart) + private var _range: ChartRange? public override init() { @@ -48,10 +51,26 @@ public class ChartHighlight: NSObject _stackIndex = stackIndex } + /// Constructor, only used for stacked-barchart. + /// + /// :param: x the index of the highlighted value on the x-axis + /// :param: dataSet the index of the DataSet the highlighted value belongs to + /// :param: stackIndex references which value of a stacked-bar entry has been selected + /// :param: range the range the selected stack-value is in + public convenience init(xIndex x: Int, dataSetIndex: Int, stackIndex: Int, range: ChartRange) + { + self.init(xIndex: x, dataSetIndex: dataSetIndex, stackIndex: stackIndex) + + _range = range + } + public var dataSetIndex: Int { return _dataSetIndex; } public var xIndex: Int { return _xIndex; } public var stackIndex: Int { return _stackIndex; } + /// :returns: the range of values the selected value of a stacked bar is in. (this is only relevant for stacked-barchart) + public var range: ChartRange? { return _range } + // MARK: NSObject public override var description: String diff --git a/Charts/Classes/Highlight/ChartHighlighter.swift b/Charts/Classes/Highlight/ChartHighlighter.swift new file mode 100644 index 0000000000..86097458db --- /dev/null +++ b/Charts/Classes/Highlight/ChartHighlighter.swift @@ -0,0 +1,118 @@ +// +// ChartHighlighter.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/7/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +internal class ChartHighlighter +{ + /// instance of the data-provider + internal weak var _chart: BarLineChartViewBase?; + + internal init(chart: BarLineChartViewBase) + { + _chart = chart; + } + + /// Returns a Highlight object corresponding to the given x- and y- touch positions in pixels. + /// :param: x + /// :param: y + /// :returns: + internal func getHighlight(#x: Double, y: Double) -> ChartHighlight? + { + let xIndex = getXIndex(x) + if (xIndex == -Int.max) + { + return nil + } + + let dataSetIndex = getDataSetIndex(xIndex: xIndex, x: x, y: y) + if (dataSetIndex == -Int.max) + { + return nil + } + + return ChartHighlight(xIndex: xIndex, dataSetIndex: dataSetIndex) + } + + /// Returns the corresponding x-index for a given touch-position in pixels. + /// :param: x + /// :returns: + internal func getXIndex(x: Double) -> Int + { + // create an array of the touch-point + var pt = CGPoint(x: x, y: 0.0) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(ChartYAxis.AxisDependency.Left).pixelToValue(&pt) + + return Int(round(pt.x)) + } + + /// Returns the corresponding dataset-index for a given xIndex and xy-touch position in pixels. + /// :param: xIndex + /// :param: x + /// :param: y + /// :returns: + internal func getDataSetIndex(#xIndex: Int, x: Double, y: Double) -> Int + { + let valsAtIndex = getSelectionDetailsAtIndex(xIndex) + + let leftdist = ChartUtils.getMinimumDistance(valsAtIndex, val: y, axis: ChartYAxis.AxisDependency.Left) + let rightdist = ChartUtils.getMinimumDistance(valsAtIndex, val: y, axis: ChartYAxis.AxisDependency.Right) + + let axis = leftdist < rightdist ? ChartYAxis.AxisDependency.Left : ChartYAxis.AxisDependency.Right + + let dataSetIndex = ChartUtils.closestDataSetIndex(valsAtIndex, value: y, axis: axis) + + return dataSetIndex + } + + /// Returns a list of SelectionDetail object corresponding to the given xIndex. + /// :param: xIndex + /// :return: + internal func getSelectionDetailsAtIndex(xIndex: Int) -> [ChartSelectionDetail] + { + var vals = [ChartSelectionDetail]() + var pt = CGPoint() + + for (var i = 0, dataSetCount = _chart?.data?.dataSetCount; i < dataSetCount; i++) + { + var dataSet = _chart!.data!.getDataSetByIndex(i) + + // dont include datasets that cannot be highlighted + if !dataSet.isHighlightEnabled + { + continue + } + + // extract all y-values from all DataSets at the given x-index + let yVal: Double = dataSet.yValForXIndex(xIndex) + if yVal.isNaN + { + continue + } + + pt.y = CGFloat(yVal) + + _chart!.getTransformer(dataSet.axisDependency).pointValueToPixel(&pt) + + if !pt.y.isNaN + { + vals.append(ChartSelectionDetail(value: Double(pt.y), dataSetIndex: i, dataSet: dataSet)) + } + } + + return vals + } +} diff --git a/Charts/Classes/Highlight/ChartRange.swift b/Charts/Classes/Highlight/ChartRange.swift new file mode 100644 index 0000000000..2ffb912933 --- /dev/null +++ b/Charts/Classes/Highlight/ChartRange.swift @@ -0,0 +1,53 @@ +// +// ChartRange.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/7/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +public class ChartRange: NSObject +{ + public var from: Double + public var to: Double + + public init(from: Double, to: Double) + { + self.from = from + self.to = to + + super.init() + } + + /// Returns true if this range contains (if the value is in between) the given value, false if not. + /// :param: value + public func contains(value: Double) -> Bool + { + if value > from && value <= to + { + return true + } + else + { + return false + } + } + + public func isLarger(value: Double) -> Bool + { + return value > to + } + + public func isSmaller(value: Double) -> Bool + { + return value < from + } +} \ No newline at end of file diff --git a/Charts/Classes/Highlight/HorizontalBarChartHighlighter.swift b/Charts/Classes/Highlight/HorizontalBarChartHighlighter.swift new file mode 100644 index 0000000000..7ac4ecc54d --- /dev/null +++ b/Charts/Classes/Highlight/HorizontalBarChartHighlighter.swift @@ -0,0 +1,120 @@ +// +// HorizontalBarChartHighlighter.swift +// Charts +// +// Created by Daniel Cohen Gindi on 26/7/15. + +// +// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda +// A port of MPAndroidChart for iOS +// Licensed under Apache License 2.0 +// +// https://github.com/danielgindi/ios-charts +// + +import Foundation + +internal class HorizontalBarChartHighlighter: BarChartHighlighter +{ + internal override func getHighlight(#x: Double, y: Double) -> ChartHighlight? + { + var h = super.getHighlight(x: x, y: y) + + if h === nil + { + return h + } + else + { + if let set = _chart?.data?.getDataSetByIndex(h!.dataSetIndex) as? BarChartDataSet + { + if set.isStacked + { + // create an array of the touch-point + var pt = CGPoint() + pt.x = CGFloat(y) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(set.axisDependency).pixelToValue(&pt) + + return getStackedHighlight(old: h, set: set, xIndex: h!.xIndex, dataSetIndex: h!.dataSetIndex, yValue: Double(pt.x)) + } + } + + return h + } + } + + internal override func getXIndex(x: Double) -> Int + { + if let barChartData = _chart?.data as? BarChartData + { + if !barChartData.isGrouped + { + // create an array of the touch-point + var pt = CGPoint(x: 0.0, y: x) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(ChartYAxis.AxisDependency.Left).pixelToValue(&pt) + + return Int(round(pt.y)) + } + else + { + let baseNoSpace = getBase(x) + + let setCount = barChartData.dataSetCount + var xIndex = Int(baseNoSpace) / setCount + + let valCount = barChartData.xValCount + + if xIndex < 0 + { + xIndex = 0 + } + else if xIndex >= valCount + { + xIndex = valCount - 1 + } + + return xIndex + } + } + else + { + return 0 + } + } + + /// Returns the base y-value to the corresponding x-touch value in pixels. + /// :param: y + /// :returns: + internal override func getBase(y: Double) -> Double + { + if let barChartData = _chart?.data as? BarChartData + { + // create an array of the touch-point + var pt = CGPoint() + pt.y = CGFloat(y) + + // take any transformer to determine the x-axis value + _chart?.getTransformer(ChartYAxis.AxisDependency.Left).pixelToValue(&pt) + let yVal = Double(pt.y) + + let setCount = barChartData.dataSetCount ?? 0 + + // calculate how often the group-space appears + let steps = Int(yVal / (Double(setCount) + Double(barChartData.groupSpace))) + + let groupSpaceSum = Double(barChartData.groupSpace) * Double(steps) + + let baseNoSpace = yVal - groupSpaceSum + + return baseNoSpace + } + else + { + return 0.0 + } + } +} diff --git a/Charts/Classes/Renderers/BarChartRenderer.swift b/Charts/Classes/Renderers/BarChartRenderer.swift index 83e4e5502a..cdac1ef88c 100644 --- a/Charts/Classes/Renderers/BarChartRenderer.swift +++ b/Charts/Classes/Renderers/BarChartRenderer.swift @@ -28,7 +28,6 @@ public protocol BarChartRendererDelegate func barChartRendererChartXMin(renderer: BarChartRenderer) -> Double func barChartIsDrawHighlightArrowEnabled(renderer: BarChartRenderer) -> Bool func barChartIsDrawValueAboveBarEnabled(renderer: BarChartRenderer) -> Bool - func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool func barChartIsInverted(renderer: BarChartRenderer, axis: ChartYAxis.AxisDependency) -> Bool } @@ -93,8 +92,8 @@ public class BarChartRenderer: ChartDataRendererBase var e = entries[j] // calculate the x-position, depending on datasetcount - var x = CGFloat(e.xIndex + j * dataSetOffset) + CGFloat(index) - + groupSpace * CGFloat(j) + groupSpaceHalf + var x = CGFloat(e.xIndex + e.xIndex * dataSetOffset) + CGFloat(index) + + groupSpace * CGFloat(e.xIndex) + groupSpaceHalf var vals = e.values if (!containsStacks || vals == nil) @@ -151,8 +150,9 @@ public class BarChartRenderer: ChartDataRendererBase } else { - var allPos = e.positiveSum - var allNeg = e.negativeSum + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 // if drawing the bar shadow is enabled if (drawBarShadowEnabled) @@ -191,36 +191,41 @@ public class BarChartRenderer: ChartDataRendererBase } // fill the stack - for (var k = 0; k < vals.count; k++) + for (var k = 0; k < vals!.count; k++) { - let value = vals[k] + let value = vals![k] if value >= 0.0 { - allPos -= value - y = value + allPos + y = posY + yStart = posY + value + posY = yStart } else { - allNeg -= abs(value) - y = value + allNeg + y = negY + yStart = negY + abs(value) + negY += abs(value) } var left = x - barWidth + barSpaceHalf var right = x + barWidth - barSpaceHalf - var top = y >= 0.0 ? CGFloat(y) : 0 - var bottom = y <= 0.0 ? CGFloat(y) : 0 - - // multiply the height of the rect with the phase - if (top > 0) + var top: CGFloat, bottom: CGFloat + if isInverted { - top *= phaseY + bottom = y >= yStart ? CGFloat(y) : CGFloat(yStart) + top = y <= yStart ? CGFloat(y) : CGFloat(yStart) } else { - bottom *= phaseY + top = y >= yStart ? CGFloat(y) : CGFloat(yStart) + bottom = y <= yStart ? CGFloat(y) : CGFloat(yStart) } + // multiply the height of the rect with the phase + top *= phaseY + bottom *= phaseY + barRect.origin.x = left barRect.size.width = right - left barRect.origin.y = top @@ -280,14 +285,13 @@ public class BarChartRenderer: ChartDataRendererBase var dataSets = barData.dataSets var drawValueAboveBar = delegate!.barChartIsDrawValueAboveBarEnabled(self) - var drawValuesForWholeStackEnabled = delegate!.barChartIsDrawValuesForWholeStackEnabled(self) var posOffset: CGFloat var negOffset: CGFloat for (var i = 0, count = barData.dataSetCount; i < count; i++) { - var dataSet = dataSets[i] + var dataSet = dataSets[i] as! BarChartDataSet if (!dataSet.isDrawValuesEnabled) { @@ -297,7 +301,7 @@ public class BarChartRenderer: ChartDataRendererBase var isInverted = delegate!.barChartIsInverted(self, axis: dataSet.axisDependency) // calculate the correct offset depending on the draw position of the value - let valueOffsetPlus: CGFloat = 5.0 + let valueOffsetPlus: CGFloat = 4.5 var valueFont = dataSet.valueFont var valueTextHeight = valueFont.lineHeight posOffset = (drawValueAboveBar ? -(valueTextHeight + valueOffsetPlus) : valueOffsetPlus) @@ -324,7 +328,7 @@ public class BarChartRenderer: ChartDataRendererBase var valuePoints = getTransformedValues(trans: trans, entries: entries, dataSetIndex: i) // if only single values are drawn (sum) - if (!drawValuesForWholeStackEnabled) + if (!dataSet.isStacked) { for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) { @@ -352,16 +356,16 @@ public class BarChartRenderer: ChartDataRendererBase } else { - // if each value of a potential stack should be drawn + // if we have stacks for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) { var e = entries[j] - var vals = e.values + let values = e.values // we still draw stacked bars, but there is one non-stacked in between - if (vals == nil) + if (values == nil) { if (!viewPortHandler.isInBoundsRight(valuePoints[j].x)) { @@ -384,9 +388,13 @@ public class BarChartRenderer: ChartDataRendererBase } else { + // draw stack values + + let vals = values! var transformed = [CGPoint]() - var allPos = e.positiveSum - var allNeg = e.negativeSum + + var posY = 0.0 + var negY = -e.negativeSum for (var k = 0; k < vals.count; k++) { @@ -395,13 +403,13 @@ public class BarChartRenderer: ChartDataRendererBase if value >= 0.0 { - allPos -= value - y = value + allPos + posY += value + y = posY } else { - allNeg -= abs(value) - y = value + allNeg + y = negY + negY -= value } transformed.append(CGPoint(x: 0.0, y: CGFloat(y) * _animator.phaseY)) @@ -507,8 +515,8 @@ public class BarChartRenderer: ChartDataRendererBase if (isStack) { - y1 = e.positiveSum - y2 = -e.negativeSum + y1 = h.range?.from ?? 0.0 + y2 = (h.range?.to ?? 0.0) * Double(_animator.phaseY) } else { diff --git a/Charts/Classes/Renderers/ChartYAxisRenderer.swift b/Charts/Classes/Renderers/ChartYAxisRenderer.swift index 981d4efa9b..b09bbcfcea 100644 --- a/Charts/Classes/Renderers/ChartYAxisRenderer.swift +++ b/Charts/Classes/Renderers/ChartYAxisRenderer.swift @@ -78,37 +78,65 @@ public class ChartYAxisRenderer: ChartAxisRendererBase interval = floor(10.0 * intervalMagnitude) } - // if the labels should only show min and max - if (_yAxis.isShowOnlyMinMaxEnabled) + // force label count + if _yAxis.isForceLabelsEnabled { - _yAxis.entries = [yMin, yMax] - } - else - { - var first = ceil(Double(yMin) / interval) * interval - var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval) + let step = Double(range) / Double(labelCount - 1) - var f: Double - var i: Int - var n = 0 - for (f = first; f <= last; f += interval) + if _yAxis.entries.count < labelCount { - ++n + // Ensure stops contains at least numStops elements. + _yAxis.entries.removeAll(keepCapacity: true) } - - if (_yAxis.entries.count < n) + else { - // Ensure stops contains at least numStops elements. - _yAxis.entries = [Double](count: n, repeatedValue: 0.0) + _yAxis.entries = [Double]() + _yAxis.entries.reserveCapacity(labelCount) } - else if (_yAxis.entries.count > n) + + var v = yMin + + for (var i = 0; i < labelCount; i++) { - _yAxis.entries.removeRange(n..<_yAxis.entries.count) + _yAxis.entries.append(v) + v += step } - for (f = first, i = 0; i < n; f += interval, ++i) + } else { + // no forced count + + // if the labels should only show min and max + if (_yAxis.isShowOnlyMinMaxEnabled) + { + _yAxis.entries = [yMin, yMax] + } + else { - _yAxis.entries[i] = Double(f) + var first = ceil(Double(yMin) / interval) * interval + var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval) + + var f: Double + var i: Int + var n = 0 + for (f = first; f <= last; f += interval) + { + ++n + } + + if (_yAxis.entries.count < n) + { + // Ensure stops contains at least numStops elements. + _yAxis.entries = [Double](count: n, repeatedValue: 0.0) + } + else if (_yAxis.entries.count > n) + { + _yAxis.entries.removeRange(n..<_yAxis.entries.count) + } + + for (f = first, i = 0; i < n; f += interval, ++i) + { + _yAxis.entries[i] = Double(f) + } } } } diff --git a/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift b/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift index 7ec1a3cf98..d034824ba1 100644 --- a/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift +++ b/Charts/Classes/Renderers/ChartYAxisRendererRadarChart.swift @@ -54,52 +54,80 @@ public class ChartYAxisRendererRadarChart: ChartYAxisRenderer interval = floor(10 * intervalMagnitude) } - // clean old values - if (_yAxis.entries.count > 0) + // force label count + if _yAxis.isForceLabelsEnabled { - _yAxis.entries.removeAll(keepCapacity: false) - } - - // if the labels should only show min and max - if (_yAxis.isShowOnlyMinMaxEnabled) - { - _yAxis.entries = [Double]() - _yAxis.entries.append(yMin) - _yAxis.entries.append(yMax) - } - else - { - var first = ceil(Double(yMin) / interval) * interval + let step = Double(range) / Double(labelCount - 1) - if (first == 0.0) - { // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) - first = 0.0 + if _yAxis.entries.count < labelCount + { + // Ensure stops contains at least numStops elements. + _yAxis.entries.removeAll(keepCapacity: true) + } + else + { + _yAxis.entries = [Double]() + _yAxis.entries.reserveCapacity(labelCount) } - var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval) + var v = yMin - var f: Double - var i: Int - var n = 0 - for (f = first; f <= last; f += interval) + for (var i = 0; i < labelCount; i++) { - ++n + _yAxis.entries.append(v) + v += step } - if (isnan(_yAxis.customAxisMax)) + } else { + // no forced count + + // clean old values + if (_yAxis.entries.count > 0) { - n += 1 + _yAxis.entries.removeAll(keepCapacity: false) } - - if (_yAxis.entries.count < n) + + // if the labels should only show min and max + if (_yAxis.isShowOnlyMinMaxEnabled) { - // Ensure stops contains at least numStops elements. - _yAxis.entries = [Double](count: n, repeatedValue: 0.0) + _yAxis.entries = [Double]() + _yAxis.entries.append(yMin) + _yAxis.entries.append(yMax) } - - for (f = first, i = 0; i < n; f += interval, ++i) + else { - _yAxis.entries[i] = Double(f) + var first = ceil(Double(yMin) / interval) * interval + + if (first == 0.0) + { // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) + first = 0.0 + } + + var last = ChartUtils.nextUp(floor(Double(yMax) / interval) * interval) + + var f: Double + var i: Int + var n = 0 + for (f = first; f <= last; f += interval) + { + ++n + } + + if (isnan(_yAxis.customAxisMax)) + { + n += 1 + } + + if (_yAxis.entries.count < n) + { + // Ensure stops contains at least numStops elements. + _yAxis.entries = [Double](count: n, repeatedValue: 0.0) + } + + for (f = first, i = 0; i < n; f += interval, ++i) + { + _yAxis.entries[i] = Double(f) + } } } diff --git a/Charts/Classes/Renderers/CombinedChartRenderer.swift b/Charts/Classes/Renderers/CombinedChartRenderer.swift index 217f7e1094..9312cd5452 100644 --- a/Charts/Classes/Renderers/CombinedChartRenderer.swift +++ b/Charts/Classes/Renderers/CombinedChartRenderer.swift @@ -29,9 +29,6 @@ public class CombinedChartRenderer: ChartDataRendererBase, /// if set to true, all values are drawn above their bars, instead of below their top public var drawValueAboveBarEnabled = true - /// if set to true, all values of a stack are drawn individually, and not just their sum - public var drawValuesForWholeStackEnabled = true - /// if set to true, a grey area is darawn behind each bar that indicates the maximum value public var drawBarShadowEnabled = true @@ -248,11 +245,6 @@ public class CombinedChartRenderer: ChartDataRendererBase, return drawValueAboveBarEnabled } - public func barChartIsDrawValuesForWholeStackEnabled(renderer: BarChartRenderer) -> Bool - { - return drawValuesForWholeStackEnabled - } - public func barChartIsDrawBarShadowEnabled(renderer: BarChartRenderer) -> Bool { return drawBarShadowEnabled @@ -402,9 +394,6 @@ public class CombinedChartRenderer: ChartDataRendererBase, /// returns true if drawing values above bars is enabled, false if not public var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled; } - /// returns true if all values of a stack are drawn, and not just their sum - public var isDrawValuesForWholeStackEnabled: Bool { return drawValuesForWholeStackEnabled; } - /// returns true if drawing shadows (maxvalue) for each bar is enabled, false if not public var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled; } diff --git a/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift b/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift index 10dd9b8e28..e6b4576937 100644 --- a/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift +++ b/Charts/Classes/Renderers/HorizontalBarChartRenderer.swift @@ -17,9 +17,6 @@ import UIKit public class HorizontalBarChartRenderer: BarChartRenderer { - private var xOffset: CGFloat = 0.0 - private var yOffset: CGFloat = 0.0 - public override init(delegate: BarChartRendererDelegate?, animator: ChartAnimator?, viewPortHandler: ChartViewPortHandler) { super.init(delegate: delegate, animator: animator, viewPortHandler: viewPortHandler) @@ -54,11 +51,11 @@ public class HorizontalBarChartRenderer: BarChartRenderer var e = entries[j] // calculate the x-position, depending on datasetcount - var x = CGFloat(e.xIndex + j * dataSetOffset) + CGFloat(index) - + groupSpace * CGFloat(j) + groupSpaceHalf - var vals = e.values + var x = CGFloat(e.xIndex + e.xIndex * dataSetOffset) + CGFloat(index) + + groupSpace * CGFloat(e.xIndex) + groupSpaceHalf + let values = e.values - if (!containsStacks || vals == nil) + if (!containsStacks || values == nil) { y = e.value @@ -112,8 +109,10 @@ public class HorizontalBarChartRenderer: BarChartRenderer } else { - var allPos = e.positiveSum - var allNeg = e.negativeSum + let vals = values! + var posY = 0.0 + var negY = -e.negativeSum + var yStart = 0.0 // if drawing the bar shadow is enabled if (drawBarShadowEnabled) @@ -158,30 +157,35 @@ public class HorizontalBarChartRenderer: BarChartRenderer if value >= 0.0 { - allPos -= value - y = value + allPos + y = posY + yStart = posY + value + posY = yStart } else { - allNeg -= abs(value) - y = value + allNeg + y = negY + yStart = negY + abs(value) + negY += abs(value) } var bottom = x - barWidth + barSpaceHalf var top = x + barWidth - barSpaceHalf - var right = y >= 0.0 ? CGFloat(y) : 0.0 - var left = y <= 0.0 ? CGFloat(y) : 0.0 - - // multiply the height of the rect with the phase - if (right > 0) + var right: CGFloat, left: CGFloat + if isInverted { - right *= phaseY + left = y >= yStart ? CGFloat(y) : CGFloat(yStart) + right = y <= yStart ? CGFloat(y) : CGFloat(yStart) } else { - left *= phaseY + right = y >= yStart ? CGFloat(y) : CGFloat(yStart) + left = y <= yStart ? CGFloat(y) : CGFloat(yStart) } + // multiply the height of the rect with the phase + right *= phaseY + left *= phaseY + barRect.origin.x = left barRect.size.width = right - left barRect.origin.y = top @@ -189,14 +193,14 @@ public class HorizontalBarChartRenderer: BarChartRenderer trans.rectValueToPixel(&barRect) - if (k == 0 && !viewPortHandler.isInBoundsLeft(barRect.origin.x + barRect.size.width)) + if (k == 0 && !viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height)) { // Skip to next bar break } // avoid drawing outofbounds values - if (!viewPortHandler.isInBoundsRight(barRect.origin.x)) + if (!viewPortHandler.isInBoundsBottom(barRect.origin.y)) { break } @@ -245,7 +249,6 @@ public class HorizontalBarChartRenderer: BarChartRenderer var dataSets = barData.dataSets var drawValueAboveBar = delegate!.barChartIsDrawValueAboveBarEnabled(self) - var drawValuesForWholeStackEnabled = delegate!.barChartIsDrawValuesForWholeStackEnabled(self) let textAlign = drawValueAboveBar ? NSTextAlignment.Left : NSTextAlignment.Right @@ -255,7 +258,7 @@ public class HorizontalBarChartRenderer: BarChartRenderer for (var i = 0, count = barData.dataSetCount; i < count; i++) { - var dataSet = dataSets[i] + var dataSet = dataSets[i] as! BarChartDataSet if (!dataSet.isDrawValuesEnabled) { @@ -281,18 +284,18 @@ public class HorizontalBarChartRenderer: BarChartRenderer var valuePoints = getTransformedValues(trans: trans, entries: entries, dataSetIndex: i) // if only single values are drawn (sum) - if (!drawValuesForWholeStackEnabled) + if (!dataSet.isStacked) { for (var j = 0, count = Int(ceil(CGFloat(valuePoints.count) * _animator.phaseX)); j < count; j++) { - if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) + if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) { - continue + break } - if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) + if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) { - break + continue } if (!viewPortHandler.isInBoundsBottom(valuePoints[j].y)) @@ -332,19 +335,19 @@ public class HorizontalBarChartRenderer: BarChartRenderer { var e = entries[j] - var vals = e.values + let values = e.values // we still draw stacked bars, but there is one non-stacked in between - if (vals == nil) + if (values == nil) { - if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) + if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) { - continue + break } - if (!viewPortHandler.isInBoundsTop(valuePoints[j].y)) + if (!viewPortHandler.isInBoundsX(valuePoints[j].x)) { - break + continue } if (!viewPortHandler.isInBoundsBottom(valuePoints[j].y)) @@ -377,9 +380,11 @@ public class HorizontalBarChartRenderer: BarChartRenderer } else { + let vals = values! var transformed = [CGPoint]() - var allPos = e.positiveSum - var allNeg = e.negativeSum + + var posY = 0.0 + var negY = -e.negativeSum for (var k = 0; k < vals.count; k++) { @@ -388,13 +393,13 @@ public class HorizontalBarChartRenderer: BarChartRenderer if value >= 0.0 { - allPos -= value - y = value + allPos + posY += value + y = posY } else { - allNeg -= abs(value) - y = value + allNeg + y = negY + negY -= value } transformed.append(CGPoint(x: CGFloat(y) * _animator.phaseY, y: 0.0)) @@ -421,14 +426,14 @@ public class HorizontalBarChartRenderer: BarChartRenderer var x = transformed[k].x + (val >= 0 ? posOffset : negOffset) var y = valuePoints[j].y - if (!viewPortHandler.isInBoundsX(x)) + if (!viewPortHandler.isInBoundsTop(y)) { - continue + break } - if (!viewPortHandler.isInBoundsTop(y)) + if (!viewPortHandler.isInBoundsX(x)) { - break + continue } if (!viewPortHandler.isInBoundsBottom(y)) diff --git a/Charts/Classes/Renderers/ScatterChartRenderer.swift b/Charts/Classes/Renderers/ScatterChartRenderer.swift index e60183c667..760aa321bc 100644 --- a/Charts/Classes/Renderers/ScatterChartRenderer.swift +++ b/Charts/Classes/Renderers/ScatterChartRenderer.swift @@ -245,6 +245,7 @@ public class ScatterChartRenderer: ChartDataRendererBase { var scatterData = delegate!.scatterChartRendererData(self) var chartXMax = delegate!.scatterChartRendererChartXMax(self) + var chartXMin = delegate!.scatterChartRendererChartXMin(self) var chartYMax = delegate!.scatterChartRendererChartYMax(self) var chartYMin = delegate!.scatterChartRendererChartYMin(self) @@ -289,7 +290,7 @@ public class ScatterChartRenderer: ChartDataRendererBase pts[0] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMax)) pts[1] = CGPoint(x: CGFloat(xIndex), y: CGFloat(chartYMin)) - pts[2] = CGPoint(x: 0.0, y: y) + pts[2] = CGPoint(x: CGFloat(chartXMin), y: y) pts[3] = CGPoint(x: CGFloat(chartXMax), y: y) var trans = delegate!.scatterChartRenderer(self, transformerForAxis: set.axisDependency) diff --git a/Charts/Classes/Utils/ChartTransformer.swift b/Charts/Classes/Utils/ChartTransformer.swift index 815dce70db..a50baac6bb 100644 --- a/Charts/Classes/Utils/ChartTransformer.swift +++ b/Charts/Classes/Utils/ChartTransformer.swift @@ -142,7 +142,7 @@ public class ChartTransformer: NSObject var e = entries[j] // calculate the x-position, depending on datasetcount - var x = CGFloat(e.xIndex + (j * (setCount - 1)) + dataSet) + space * CGFloat(j) + space / 2.0 + var x = CGFloat(e.xIndex + (e.xIndex * (setCount - 1)) + dataSet) + space * CGFloat(e.xIndex) + space / 2.0 var y = e.value valuePoints.append(CGPoint(x: x, y: CGFloat(y) * phaseY)) @@ -167,7 +167,7 @@ public class ChartTransformer: NSObject var e = entries[j] // calculate the x-position, depending on datasetcount - var x = CGFloat(e.xIndex + (j * (setCount - 1)) + dataSet) + space * CGFloat(j) + space / 2.0 + var x = CGFloat(e.xIndex + (e.xIndex * (setCount - 1)) + dataSet) + space * CGFloat(e.xIndex) + space / 2.0 var y = e.value valuePoints.append(CGPoint(x: CGFloat(y) * phaseY, y: x)) diff --git a/Charts/Classes/Utils/ChartUtils.swift b/Charts/Classes/Utils/ChartUtils.swift old mode 100755 new mode 100644 index bcbacdf33c..2c3ec826b2 --- a/Charts/Classes/Utils/ChartUtils.swift +++ b/Charts/Classes/Utils/ChartUtils.swift @@ -63,10 +63,10 @@ internal class ChartUtils } } - /// Returns the index of the DataSet that contains the closest value on the y-axis. This is needed for highlighting. + /// Returns the index of the DataSet that contains the closest value on the y-axis. This will return -Integer.MAX_VALUE if failure. internal class func closestDataSetIndex(valsAtIndex: [ChartSelectionDetail], value: Double, axis: ChartYAxis.AxisDependency?) -> Int { - var index = -1 + var index = -Int.max var distance = DBL_MAX for (var i = 0; i < valsAtIndex.count; i++) diff --git a/Charts/Supporting Files/Info.plist b/Charts/Supporting Files/Info.plist index 9ea742e694..514c36feb3 100644 --- a/Charts/Supporting Files/Info.plist +++ b/Charts/Supporting Files/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.0.9 + 2.1.2 CFBundleSignature ???? CFBundleVersion diff --git a/ChartsDemo/Classes/Components/BalloonMarker.swift b/ChartsDemo/Classes/Components/BalloonMarker.swift index 46aed99a26..daf1348139 100644 --- a/ChartsDemo/Classes/Components/BalloonMarker.swift +++ b/ChartsDemo/Classes/Components/BalloonMarker.swift @@ -95,7 +95,7 @@ public class BalloonMarker: ChartMarker CGContextRestoreGState(context); } - public override func refreshContent(#entry: ChartDataEntry, dataSetIndex: Int) + public override func refreshContent(#entry: ChartDataEntry, highlight: ChartHighlight) { var label = entry.value.description; labelns = label as NSString; diff --git a/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m index cadcb861fc..6ed911b163 100644 --- a/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m +++ b/ChartsDemo/Classes/Demos/MultipleBarChartViewController.m @@ -124,7 +124,7 @@ - (void)setDataCount:(int)count range:(double)range NSMutableArray *dataSets = [[NSMutableArray alloc] init]; [dataSets addObject:set1]; [dataSets addObject:set2]; - //[dataSets addObject:set3]; + [dataSets addObject:set3]; BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; data.groupSpace = 0.8; diff --git a/ChartsDemo/Classes/Demos/NegativeStackedBarChartViewController.m b/ChartsDemo/Classes/Demos/NegativeStackedBarChartViewController.m index 4548ae7334..60bd8a2262 100644 --- a/ChartsDemo/Classes/Demos/NegativeStackedBarChartViewController.m +++ b/ChartsDemo/Classes/Demos/NegativeStackedBarChartViewController.m @@ -26,7 +26,7 @@ - (void)viewDidLoad { [super viewDidLoad]; - self.title = @"Horizontal Bar Chart"; + self.title = @"Stacked Bar Chart Negative"; self.options = @[ @{@"key": @"toggleValues", @"label": @"Toggle Values"}, @@ -56,8 +56,6 @@ - (void)viewDidLoad _chartView.drawBarShadowEnabled = NO; _chartView.drawValueAboveBarEnabled = YES; - // if false values are only drawn for the stack sum, else each value is drawn - _chartView.drawValuesForWholeStackEnabled = YES; // scaling can now only be done on x- and y-axis separately _chartView.pinchZoomEnabled = NO; @@ -101,7 +99,7 @@ - (void)viewDidLoad set.valueFormatter = customFormatter; set.valueFont = [UIFont systemFontOfSize:7.f]; set.axisDependency = AxisDependencyRight; - set.barSpace = 0.5f; + set.barSpace = 0.4f; set.colors = @[ [UIColor colorWithRed:67/255.f green:67/255.f blue:72/255.f alpha:1.f], [UIColor colorWithRed:124/255.f green:181/255.f blue:236/255.f alpha:1.f] @@ -123,36 +121,6 @@ - (void)didReceiveMemoryWarning // Dispose of any resources that can be recreated. } -- (void)setDataCount:(int)count range:(double)range -{ - NSMutableArray *xVals = [[NSMutableArray alloc] init]; - - for (int i = 0; i < count; i++) - { - [xVals addObject:months[i % 12]]; - } - - NSMutableArray *yVals = [[NSMutableArray alloc] init]; - - for (int i = 0; i < count; i++) - { - double mult = (range + 1); - double val = (double) (arc4random_uniform(mult)); - [yVals addObject:[[BarChartDataEntry alloc] initWithValue:val xIndex:i]]; - } - - BarChartDataSet *set1 = [[BarChartDataSet alloc] initWithYVals:yVals label:@"DataSet"]; - set1.barSpace = 0.35; - - NSMutableArray *dataSets = [[NSMutableArray alloc] init]; - [dataSets addObject:set1]; - - BarChartData *data = [[BarChartData alloc] initWithXVals:xVals dataSets:dataSets]; - [data setValueFont:[UIFont fontWithName:@"HelveticaNeue-Light" size:10.f]]; - - _chartView.data = data; -} - - (void)optionTapped:(NSString *)key { if ([key isEqualToString:@"toggleValues"]) @@ -225,7 +193,7 @@ - (void)optionTapped:(NSString *)key - (void)chartValueSelected:(ChartViewBase * __nonnull)chartView entry:(ChartDataEntry * __nonnull)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight * __nonnull)highlight { - NSLog(@"chartValueSelected"); + NSLog(@"chartValueSelected, stack-index %ld", (long)highlight.stackIndex); } - (void)chartValueNothingSelected:(ChartViewBase * __nonnull)chartView diff --git a/ChartsDemo/Classes/Demos/StackedBarChartViewController.m b/ChartsDemo/Classes/Demos/StackedBarChartViewController.m index bd6e5823b1..5f0bb489f8 100644 --- a/ChartsDemo/Classes/Demos/StackedBarChartViewController.m +++ b/ChartsDemo/Classes/Demos/StackedBarChartViewController.m @@ -51,8 +51,8 @@ - (void)viewDidLoad _chartView.noDataTextDescription = @"You need to provide data for the chart."; _chartView.maxVisibleValueCount = 60; - _chartView.drawValuesForWholeStackEnabled = YES; _chartView.pinchZoomEnabled = NO; + _chartView.drawGridBackgroundEnabled = NO; _chartView.drawBarShadowEnabled = NO; _chartView.drawValueAboveBarEnabled = NO; @@ -62,9 +62,7 @@ - (void)viewDidLoad leftAxis.valueFormatter.negativeSuffix = @" $"; leftAxis.valueFormatter.positiveSuffix = @" $"; - ChartYAxis *rightAxis = _chartView.rightAxis; - rightAxis.valueFormatter = leftAxis.valueFormatter; - rightAxis.drawGridLinesEnabled = NO; + _chartView.rightAxis.enabled = NO; ChartXAxis *xAxis = _chartView.xAxis; xAxis.labelPosition = XAxisLabelPositionTop; @@ -209,7 +207,7 @@ - (IBAction)slidersValueChanged:(id)sender - (void)chartValueSelected:(ChartViewBase * __nonnull)chartView entry:(ChartDataEntry * __nonnull)entry dataSetIndex:(NSInteger)dataSetIndex highlight:(ChartHighlight * __nonnull)highlight { - NSLog(@"chartValueSelected"); + NSLog(@"chartValueSelected, stack-index %ld", (long)highlight.stackIndex); } - (void)chartValueNothingSelected:(ChartViewBase * __nonnull)chartView diff --git a/README.md b/README.md index d5d632df4b..d250728673 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -**Version 2.1.0**, synced to [MPAndroidChart #bef5b08](https://github.com/PhilJay/MPAndroidChart/commit/bef5b08) +**Version 2.1.2**, synced to [MPAndroidChart #bef5b08](https://github.com/PhilJay/MPAndroidChart/commit/cd96679) ![alt tag](https://raw.github.com/danielgindi/ios-charts/master/Assets/feature_graphic.png)