TRAVIS_OS_NAME=osx default= <<<<<< ENV Cartfile.private Cartfile.resolved Charts.podspec Charts.xcodeproj/project.pbxproj Charts.xcodeproj/xcshareddata/xcschemes/Charts.xcscheme Charts.xcodeproj/xcshareddata/xcschemes/ChartsTests.xcscheme Charts.xcworkspace/contents.xcworkspacedata Charts.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist ChartsDemo-iOS/ChartsDemo-iOS.xcodeproj/project.pbxproj ChartsDemo-iOS/Objective-C/AppDelegate.h ChartsDemo-iOS/Objective-C/AppDelegate.m ChartsDemo-iOS/Objective-C/Components/BalloonMarker.swift ChartsDemo-iOS/Objective-C/Components/RadarMarkerView.swift ChartsDemo-iOS/Objective-C/Components/XYMarkerView.swift ChartsDemo-iOS/Objective-C/DemoBaseViewController.h ChartsDemo-iOS/Objective-C/DemoBaseViewController.m ChartsDemo-iOS/Objective-C/DemoListViewController.h ChartsDemo-iOS/Objective-C/DemoListViewController.m ChartsDemo-iOS/Objective-C/Demos/AnotherBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/AnotherBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/BarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/BarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/BubbleChartViewController.h ChartsDemo-iOS/Objective-C/Demos/BubbleChartViewController.m ChartsDemo-iOS/Objective-C/Demos/CandleStickChartViewController.h ChartsDemo-iOS/Objective-C/Demos/CandleStickChartViewController.m ChartsDemo-iOS/Objective-C/Demos/ColoredLineChartViewController.h ChartsDemo-iOS/Objective-C/Demos/ColoredLineChartViewController.m ChartsDemo-iOS/Objective-C/Demos/CombinedChartViewController.h ChartsDemo-iOS/Objective-C/Demos/CombinedChartViewController.m ChartsDemo-iOS/Objective-C/Demos/CubicLineChartViewController.h ChartsDemo-iOS/Objective-C/Demos/CubicLineChartViewController.m ChartsDemo-iOS/Objective-C/Demos/HalfPieChartViewController.h ChartsDemo-iOS/Objective-C/Demos/HalfPieChartViewController.m ChartsDemo-iOS/Objective-C/Demos/HorizontalBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/HorizontalBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/LineChart1ViewController.h ChartsDemo-iOS/Objective-C/Demos/LineChart1ViewController.m ChartsDemo-iOS/Objective-C/Demos/LineChart2ViewController.h ChartsDemo-iOS/Objective-C/Demos/LineChart2ViewController.m ChartsDemo-iOS/Objective-C/Demos/LineChartFilledViewController.h ChartsDemo-iOS/Objective-C/Demos/LineChartFilledViewController.m ChartsDemo-iOS/Objective-C/Demos/LineChartTimeViewController.h ChartsDemo-iOS/Objective-C/Demos/LineChartTimeViewController.m ChartsDemo-iOS/Objective-C/Demos/MultipleBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/MultipleBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/MultipleLinesChartViewController.h ChartsDemo-iOS/Objective-C/Demos/MultipleLinesChartViewController.m ChartsDemo-iOS/Objective-C/Demos/NegativeStackedBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/NegativeStackedBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/PieChartViewController.h ChartsDemo-iOS/Objective-C/Demos/PieChartViewController.m ChartsDemo-iOS/Objective-C/Demos/PiePolylineChartViewController.h ChartsDemo-iOS/Objective-C/Demos/PiePolylineChartViewController.m ChartsDemo-iOS/Objective-C/Demos/PositiveNegativeBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/PositiveNegativeBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/RadarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/RadarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/ScatterChartViewController.h ChartsDemo-iOS/Objective-C/Demos/ScatterChartViewController.m ChartsDemo-iOS/Objective-C/Demos/SinusBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/SinusBarChartViewController.m ChartsDemo-iOS/Objective-C/Demos/StackedBarChartViewController.h ChartsDemo-iOS/Objective-C/Demos/StackedBarChartViewController.m ChartsDemo-iOS/Objective-C/Formatters/DateValueFormatter.h ChartsDemo-iOS/Objective-C/Formatters/DateValueFormatter.m ChartsDemo-iOS/Objective-C/Formatters/DayAxisValueFormatter.h ChartsDemo-iOS/Objective-C/Formatters/DayAxisValueFormatter.m ChartsDemo-iOS/Objective-C/Formatters/IntAxisValueFormatter.h ChartsDemo-iOS/Objective-C/Formatters/IntAxisValueFormatter.m ChartsDemo-iOS/Objective-C/Formatters/LargeValueFormatter.swift ChartsDemo-iOS/Resources/Images.xcassets/AppIcon.appiconset/Contents.json ChartsDemo-iOS/Resources/Images.xcassets/Contents.json ChartsDemo-iOS/Resources/Images.xcassets/icon.imageset/Contents.json ChartsDemo-iOS/Resources/Launch Screen.storyboard ChartsDemo-iOS/Supporting Files/ChartsDemo-Bridging-Header.h ChartsDemo-iOS/Supporting Files/Info.plist ChartsDemo-iOS/Supporting Files/main.m ChartsDemo-iOS/Swift/AppDelegate.swift ChartsDemo-iOS/Swift/Components/RadarMarkerView.swift ChartsDemo-iOS/Swift/Components/XYMarkerView.swift ChartsDemo-iOS/Swift/DemoBaseViewController.swift ChartsDemo-iOS/Swift/DemoListViewController.swift ChartsDemo-iOS/Swift/Demos/AnotherBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift ChartsDemo-iOS/Swift/Demos/BubbleChartViewController.swift ChartsDemo-iOS/Swift/Demos/CandleStickChartViewController.swift ChartsDemo-iOS/Swift/Demos/ColoredLineChartViewController.swift ChartsDemo-iOS/Swift/Demos/CombinedChartViewController.swift ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift ChartsDemo-iOS/Swift/Demos/HalfPieChartViewController.swift ChartsDemo-iOS/Swift/Demos/HorizontalBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/LineChart1ViewController.swift ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift ChartsDemo-iOS/Swift/Demos/LineChartFilledViewController.swift ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift ChartsDemo-iOS/Swift/Demos/MultipleBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift ChartsDemo-iOS/Swift/Demos/NegativeStackedBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/PieChartViewController.swift ChartsDemo-iOS/Swift/Demos/PiePolylineChartViewController.swift ChartsDemo-iOS/Swift/Demos/PositiveNegativeBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift ChartsDemo-iOS/Swift/Demos/ScatterChartViewController.swift ChartsDemo-iOS/Swift/Demos/SinusBarChartViewController.swift ChartsDemo-iOS/Swift/Demos/StackedBarChartViewController.swift ChartsDemo-iOS/Swift/Formatters/DateValueFormatter.swift ChartsDemo-iOS/Swift/Formatters/DayAxisValueFormatter.swift ChartsDemo-iOS/Swift/Formatters/IntAxisValueFormatter.swift ChartsDemo-iOS/Swift/Formatters/LargeValueFormatter.swift ChartsDemo-iOS/XIBs/DemoListViewController.xib ChartsDemo-iOS/XIBs/Demos/AnotherBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/BarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/BubbleChartViewController.xib ChartsDemo-iOS/XIBs/Demos/CandleStickChartViewController.xib ChartsDemo-iOS/XIBs/Demos/ColoredLineChartViewController.xib ChartsDemo-iOS/XIBs/Demos/CombinedChartViewController.xib ChartsDemo-iOS/XIBs/Demos/CubicLineChartViewController.xib ChartsDemo-iOS/XIBs/Demos/HalfPieChartViewController.xib ChartsDemo-iOS/XIBs/Demos/HorizontalBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/LineChart1ViewController.xib ChartsDemo-iOS/XIBs/Demos/LineChart2ViewController.xib ChartsDemo-iOS/XIBs/Demos/LineChartFilledViewController.xib ChartsDemo-iOS/XIBs/Demos/LineChartTimeViewController.xib ChartsDemo-iOS/XIBs/Demos/MultipleBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/MultipleLinesChartViewController.xib ChartsDemo-iOS/XIBs/Demos/NegativeStackedBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/PieChartViewController.xib ChartsDemo-iOS/XIBs/Demos/PiePolylineChartViewController.xib ChartsDemo-iOS/XIBs/Demos/PositiveNegativeBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/RadarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/RealmDemosViewController.xib ChartsDemo-iOS/XIBs/Demos/ScatterChartViewController.xib ChartsDemo-iOS/XIBs/Demos/SinusBarChartViewController.xib ChartsDemo-iOS/XIBs/Demos/StackedBarChartViewController.xib ChartsDemo-iOS/XIBs/RadarMarkerView.xib ChartsDemo-macOS/ChartsDemo-macOS.xcodeproj/project.pbxproj ChartsDemo-macOS/ChartsDemo-macOS/AppDelegate.swift ChartsDemo-macOS/ChartsDemo-macOS/Assets.xcassets/AppIcon.appiconset/Contents.json ChartsDemo-macOS/ChartsDemo-macOS/Base.lproj/Main.storyboard ChartsDemo-macOS/ChartsDemo-macOS/Demos/BarDemoViewController.swift ChartsDemo-macOS/ChartsDemo-macOS/Demos/LineDemoViewController.swift ChartsDemo-macOS/ChartsDemo-macOS/Demos/PieDemoViewController.swift ChartsDemo-macOS/ChartsDemo-macOS/Demos/RadarDemoViewController.swift ChartsDemo-macOS/ChartsDemo-macOS/Info.plist ChartsDemo-macOS/PlaygroundChart.playground/Pages/BarChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/BubbleChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/CandleChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/CombinedChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/HorizontalBarChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/LineChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/Menu.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/PieChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/RadarChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/ScatterChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/Pages/StackedBarChart.xcplaygroundpage/Contents.swift ChartsDemo-macOS/PlaygroundChart.playground/contents.xcplayground Gemfile Gemfile.lock LICENSE Package.swift Rakefile Source/Charts/Animation/Animator.swift Source/Charts/Animation/ChartAnimationEasing.swift Source/Charts/Charts/BarChartView.swift Source/Charts/Charts/BarLineChartViewBase.swift Source/Charts/Charts/BubbleChartView.swift Source/Charts/Charts/CandleStickChartView.swift Source/Charts/Charts/ChartViewBase.swift Source/Charts/Charts/CombinedChartView.swift Source/Charts/Charts/HorizontalBarChartView.swift Source/Charts/Charts/LineChartView.swift Source/Charts/Charts/PieChartView.swift Source/Charts/Charts/PieRadarChartViewBase.swift Source/Charts/Charts/RadarChartView.swift Source/Charts/Charts/ScatterChartView.swift Source/Charts/Components/AxisBase.swift Source/Charts/Components/ChartLimitLine.swift Source/Charts/Components/ComponentBase.swift Source/Charts/Components/Description.swift Source/Charts/Components/Legend.swift Source/Charts/Components/LegendEntry.swift Source/Charts/Components/Marker.swift Source/Charts/Components/MarkerImage.swift Source/Charts/Components/MarkerView.swift Source/Charts/Components/XAxis.swift Source/Charts/Components/YAxis.swift Source/Charts/Data/Implementations/ChartBaseDataSet.swift Source/Charts/Data/Implementations/Standard/BarChartData.swift Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift Source/Charts/Data/Implementations/Standard/BubbleChartData.swift Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift Source/Charts/Data/Implementations/Standard/CandleChartData.swift Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift Source/Charts/Data/Implementations/Standard/ChartData.swift Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift Source/Charts/Data/Implementations/Standard/ChartDataSet.swift Source/Charts/Data/Implementations/Standard/CombinedChartData.swift Source/Charts/Data/Implementations/Standard/LineChartData.swift Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift Source/Charts/Data/Implementations/Standard/PieChartData.swift Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift Source/Charts/Data/Implementations/Standard/RadarChartData.swift Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift Source/Charts/Data/Implementations/Standard/ScatterChartData.swift Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift Source/Charts/Data/Interfaces/BarChartDataSetProtocol.swift Source/Charts/Data/Interfaces/BarLineScatterCandleBubbleChartDataSetProtocol.swift Source/Charts/Data/Interfaces/BubbleChartDataSetProtocol.swift Source/Charts/Data/Interfaces/CandleChartDataSetProtocol.swift Source/Charts/Data/Interfaces/ChartDataSetProtocol.swift Source/Charts/Data/Interfaces/LineChartDataSetProtocol.swift Source/Charts/Data/Interfaces/LineRadarChartDataSetProtocol.swift Source/Charts/Data/Interfaces/LineScatterCandleRadarChartDataSetProtocol.swift Source/Charts/Data/Interfaces/PieChartDataSetProtocol.swift Source/Charts/Data/Interfaces/RadarChartDataSetProtocol.swift Source/Charts/Data/Interfaces/ScatterChartDataSetProtocol.swift Source/Charts/Filters/DataApproximator+N.swift Source/Charts/Filters/DataApproximator.swift Source/Charts/Formatters/AxisValueFormatter.swift Source/Charts/Formatters/DefaultAxisValueFormatter.swift Source/Charts/Formatters/DefaultFillFormatter.swift Source/Charts/Formatters/DefaultValueFormatter.swift Source/Charts/Formatters/FillFormatter.swift Source/Charts/Formatters/IndexAxisValueFormatter.swift Source/Charts/Formatters/ValueFormatter.swift Source/Charts/Highlight/BarHighlighter.swift Source/Charts/Highlight/ChartHighlighter.swift Source/Charts/Highlight/CombinedHighlighter.swift Source/Charts/Highlight/Highlight.swift Source/Charts/Highlight/Highlighter.swift Source/Charts/Highlight/HorizontalBarHighlighter.swift Source/Charts/Highlight/PieHighlighter.swift Source/Charts/Highlight/PieRadarHighlighter.swift Source/Charts/Highlight/RadarHighlighter.swift Source/Charts/Highlight/Range.swift Source/Charts/Interfaces/BarChartDataProvider.swift Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift Source/Charts/Interfaces/BubbleChartDataProvider.swift Source/Charts/Interfaces/CandleChartDataProvider.swift Source/Charts/Interfaces/ChartDataProvider.swift Source/Charts/Interfaces/CombinedChartDataProvider.swift Source/Charts/Interfaces/LineChartDataProvider.swift Source/Charts/Interfaces/ScatterChartDataProvider.swift Source/Charts/Jobs/AnimatedMoveViewJob.swift Source/Charts/Jobs/AnimatedViewPortJob.swift Source/Charts/Jobs/AnimatedZoomViewJob.swift Source/Charts/Jobs/MoveViewJob.swift Source/Charts/Jobs/ViewPortJob.swift Source/Charts/Jobs/ZoomViewJob.swift Source/Charts/Renderers/AxisRenderer.swift Source/Charts/Renderers/AxisRendererBase.swift Source/Charts/Renderers/BarChartRenderer.swift Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift Source/Charts/Renderers/BubbleChartRenderer.swift Source/Charts/Renderers/CandleStickChartRenderer.swift Source/Charts/Renderers/CombinedChartRenderer.swift Source/Charts/Renderers/DataRenderer.swift Source/Charts/Renderers/HorizontalBarChartRenderer.swift Source/Charts/Renderers/LegendRenderer.swift Source/Charts/Renderers/LineChartRenderer.swift Source/Charts/Renderers/LineRadarRenderer.swift Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift Source/Charts/Renderers/PieChartRenderer.swift Source/Charts/Renderers/RadarChartRenderer.swift Source/Charts/Renderers/Renderer.swift Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift Source/Charts/Renderers/Scatter/ShapeRenderer.swift Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift Source/Charts/Renderers/Scatter/XShapeRenderer.swift Source/Charts/Renderers/ScatterChartRenderer.swift Source/Charts/Renderers/XAxisRenderer.swift Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift Source/Charts/Renderers/XAxisRendererRadarChart.swift Source/Charts/Renderers/YAxisRenderer.swift Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift Source/Charts/Renderers/YAxisRendererRadarChart.swift Source/Charts/Utils/ChartColorTemplates.swift Source/Charts/Utils/ChartUtils.swift Source/Charts/Utils/Fill.swift Source/Charts/Utils/Platform+Accessibility.swift Source/Charts/Utils/Platform+Color.swift Source/Charts/Utils/Platform+Gestures.swift Source/Charts/Utils/Platform+Graphics.swift Source/Charts/Utils/Platform+Touch Handling.swift Source/Charts/Utils/Platform.swift Source/Charts/Utils/Transformer.swift Source/Charts/Utils/TransformerHorizontalBarChart.swift Source/Charts/Utils/ViewPortHandler.swift Source/Supporting Files/Charts.h Source/Supporting Files/Info.plist Tests/Charts/BarChartTests.swift Tests/Charts/ChartDataTests.swift Tests/Charts/ChartUtilsTests.swift Tests/Charts/CombinedChartTests.swift Tests/Charts/EquatableTests.swift Tests/Charts/HorizontalBarChartTests.swift Tests/Charts/LineChartTests.swift Tests/Charts/PieChartTests.swift Tests/Charts/Snapshot.swift Tests/Supporting Files/Info.plist Tests/Supporting Files/Media.xcassets/Contents.json Tests/Supporting Files/Media.xcassets/icon.imageset/Contents.json carthage.sh scripts/build-dependencies.sh scripts/copy-carthage-frameworks.sh <<<<<< network # path=./Charts.framework.coverage.txt /Users/travis/build/danielgindi/Charts/Source/Charts/Animation/Animator.swift: 1| |// 2| |// Animator.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| |import QuartzCore 15| | 16| |@objc(ChartAnimatorDelegate) 17| |public protocol AnimatorDelegate 18| |{ 19| | /// Called when the Animator has stepped. 20| | func animatorUpdated(_ animator: Animator) 21| | 22| | /// Called when the Animator has stopped. 23| | func animatorStopped(_ animator: Animator) 24| |} 25| | 26| |@objc(ChartAnimator) 27| |open class Animator: NSObject 28| |{ 29| | @objc open weak var delegate: AnimatorDelegate? 30| | @objc open var updateBlock: (() -> Void)? 31| | @objc open var stopBlock: (() -> Void)? 32| | 33| | /// the phase that is animated and influences the drawn values on the x-axis 34| | @objc open var phaseX: Double = 1.0 35| | 36| | /// the phase that is animated and influences the drawn values on the y-axis 37| | @objc open var phaseY: Double = 1.0 38| | 39| | private var _startTimeX: TimeInterval = 0.0 40| | private var _startTimeY: TimeInterval = 0.0 41| | private var _displayLink: NSUIDisplayLink? 42| | 43| | private var _durationX: TimeInterval = 0.0 44| | private var _durationY: TimeInterval = 0.0 45| | 46| | private var _endTimeX: TimeInterval = 0.0 47| | private var _endTimeY: TimeInterval = 0.0 48| | private var _endTime: TimeInterval = 0.0 49| | 50| | private var _enabledX: Bool = false 51| | private var _enabledY: Bool = false 52| | 53| | private var _easingX: ChartEasingFunctionBlock? 54| | private var _easingY: ChartEasingFunctionBlock? 55| | 56| | public override init() 57| 52| { 58| 52| super.init() 59| 52| } 60| | 61| | deinit 62| 37| { 63| 37| stop() 64| 37| } 65| | 66| | @objc open func stop() 67| 37| { 68| 37| guard _displayLink != nil else { return } 69| 0| 70| 0| _displayLink?.remove(from: .main, forMode: RunLoop.Mode.common) 71| 0| _displayLink = nil 72| 0| 73| 0| _enabledX = false 74| 0| _enabledY = false 75| 0| 76| 0| // If we stopped an animation in the middle, we do not want to leave it like this 77| 0| if phaseX != 1.0 || phaseY != 1.0 78| 0| { 79| 0| phaseX = 1.0 80| 0| phaseY = 1.0 81| 0| 82| 0| delegate?.animatorUpdated(self) 83| 0| updateBlock?() 84| 0| } 85| 0| 86| 0| delegate?.animatorStopped(self) 87| 0| stopBlock?() 88| 0| } 89| | 90| | private func updateAnimationPhases(_ currentTime: TimeInterval) 91| 0| { 92| 0| if _enabledX 93| 0| { 94| 0| let elapsedTime: TimeInterval = currentTime - _startTimeX 95| 0| let duration: TimeInterval = _durationX 96| 0| var elapsed: TimeInterval = elapsedTime 97| 0| if elapsed > duration 98| 0| { 99| 0| elapsed = duration 100| 0| } 101| 0| 102| 0| phaseX = _easingX?(elapsed, duration) ?? elapsed / duration 103| 0| } 104| 0| 105| 0| if _enabledY 106| 0| { 107| 0| let elapsedTime: TimeInterval = currentTime - _startTimeY 108| 0| let duration: TimeInterval = _durationY 109| 0| var elapsed: TimeInterval = elapsedTime 110| 0| if elapsed > duration 111| 0| { 112| 0| elapsed = duration 113| 0| } 114| 0| 115| 0| phaseY = _easingY?(elapsed, duration) ?? elapsed / duration 116| 0| } 117| 0| } 118| | 119| | @objc private func animationLoop() 120| 0| { 121| 0| let currentTime: TimeInterval = CACurrentMediaTime() 122| 0| 123| 0| updateAnimationPhases(currentTime) 124| 0| 125| 0| delegate?.animatorUpdated(self) 126| 0| updateBlock?() 127| 0| 128| 0| if currentTime >= _endTime 129| 0| { 130| 0| stop() 131| 0| } 132| 0| } 133| | 134| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 135| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 136| | /// 137| | /// - Parameters: 138| | /// - xAxisDuration: duration for animating the x axis 139| | /// - yAxisDuration: duration for animating the y axis 140| | /// - easingX: an easing function for the animation on the x axis 141| | /// - easingY: an easing function for the animation on the y axis 142| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?) 143| 0| { 144| 0| stop() 145| 0| 146| 0| _startTimeX = CACurrentMediaTime() 147| 0| _startTimeY = _startTimeX 148| 0| _durationX = xAxisDuration 149| 0| _durationY = yAxisDuration 150| 0| _endTimeX = _startTimeX + xAxisDuration 151| 0| _endTimeY = _startTimeY + yAxisDuration 152| 0| _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY 153| 0| _enabledX = xAxisDuration > 0.0 154| 0| _enabledY = yAxisDuration > 0.0 155| 0| 156| 0| _easingX = easingX 157| 0| _easingY = easingY 158| 0| 159| 0| // Take care of the first frame if rendering is already scheduled... 160| 0| updateAnimationPhases(_startTimeX) 161| 0| 162| 0| if _enabledX || _enabledY 163| 0| { 164| 0| _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) 165| 0| _displayLink?.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 166| 0| } 167| 0| } 168| | 169| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 170| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 171| | /// 172| | /// - Parameters: 173| | /// - xAxisDuration: duration for animating the x axis 174| | /// - yAxisDuration: duration for animating the y axis 175| | /// - easingOptionX: the easing function for the animation on the x axis 176| | /// - easingOptionY: the easing function for the animation on the y axis 177| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption) 178| 0| { 179| 0| animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingFunctionFromOption(easingOptionX), easingY: easingFunctionFromOption(easingOptionY)) 180| 0| } 181| | 182| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 183| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 184| | /// 185| | /// - Parameters: 186| | /// - xAxisDuration: duration for animating the x axis 187| | /// - yAxisDuration: duration for animating the y axis 188| | /// - easing: an easing function for the animation 189| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 190| 0| { 191| 0| animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easing, easingY: easing) 192| 0| } 193| | 194| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 195| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 196| | /// 197| | /// - Parameters: 198| | /// - xAxisDuration: duration for animating the x axis 199| | /// - yAxisDuration: duration for animating the y axis 200| | /// - easingOption: the easing function for the animation 201| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) 202| 0| { 203| 0| animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) 204| 0| } 205| | 206| | /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. 207| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 208| | /// 209| | /// - Parameters: 210| | /// - xAxisDuration: duration for animating the x axis 211| | /// - easing: an easing function for the animation 212| | @objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 213| 0| { 214| 0| _startTimeX = CACurrentMediaTime() 215| 0| _durationX = xAxisDuration 216| 0| _endTimeX = _startTimeX + xAxisDuration 217| 0| _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY 218| 0| _enabledX = xAxisDuration > 0.0 219| 0| 220| 0| _easingX = easing 221| 0| 222| 0| // Take care of the first frame if rendering is already scheduled... 223| 0| updateAnimationPhases(_startTimeX) 224| 0| 225| 0| if _enabledX || _enabledY, 226| 0| _displayLink == nil 227| 0| { 228| 0| _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) 229| 0| _displayLink?.add(to: .main, forMode: RunLoop.Mode.common) 230| 0| } 231| 0| } 232| | 233| | /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. 234| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 235| | /// 236| | /// - Parameters: 237| | /// - xAxisDuration: duration for animating the x axis 238| | /// - easingOption: the easing function for the animation 239| | @objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) 240| 0| { 241| 0| animate(xAxisDuration: xAxisDuration, easing: easingFunctionFromOption(easingOption)) 242| 0| } 243| | 244| | /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. 245| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 246| | /// 247| | /// - Parameters: 248| | /// - yAxisDuration: duration for animating the y axis 249| | /// - easing: an easing function for the animation 250| | @objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 251| 0| { 252| 0| _startTimeY = CACurrentMediaTime() 253| 0| _durationY = yAxisDuration 254| 0| _endTimeY = _startTimeY + yAxisDuration 255| 0| _endTime = _endTimeX > _endTimeY ? _endTimeX : _endTimeY 256| 0| _enabledY = yAxisDuration > 0.0 257| 0| 258| 0| _easingY = easing 259| 0| 260| 0| // Take care of the first frame if rendering is already scheduled... 261| 0| updateAnimationPhases(_startTimeY) 262| 0| 263| 0| if _enabledX || _enabledY, 264| 0| _displayLink == nil 265| 0| { 266| 0| _displayLink = NSUIDisplayLink(target: self, selector: #selector(animationLoop)) 267| 0| _displayLink?.add(to: .main, forMode: RunLoop.Mode.common) 268| 0| } 269| 0| } 270| | 271| | /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. 272| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 273| | /// 274| | /// - Parameters: 275| | /// - yAxisDuration: duration for animating the y axis 276| | /// - easingOption: the easing function for the animation 277| | @objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption = .easeInOutSine) 278| 0| { 279| 0| animate(yAxisDuration: yAxisDuration, easing: easingFunctionFromOption(easingOption)) 280| 0| } 281| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Charts/BarLineChartViewBase.swift: 1| |// 2| |// BarLineChartViewBase.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if !os(OSX) 16| | import UIKit 17| |#endif 18| | 19| |/// Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. 20| |open class BarLineChartViewBase: ChartViewBase, BarLineScatterCandleBubbleChartDataProvider, NSUIGestureRecognizerDelegate 21| |{ 22| | /// the maximum number of entries to which values will be drawn 23| | /// (entry numbers greater than this value will cause value-labels to disappear) 24| | internal var _maxVisibleCount = 100 25| | 26| | /// flag that indicates if auto scaling on the y axis is enabled 27| | private var _autoScaleMinMaxEnabled = false 28| | 29| | private var _pinchZoomEnabled = false 30| | private var _doubleTapToZoomEnabled = true 31| | private var _dragXEnabled = true 32| | private var _dragYEnabled = true 33| | 34| | private var _scaleXEnabled = true 35| | private var _scaleYEnabled = true 36| | 37| | /// the color for the background of the chart-drawing area (everything behind the grid lines). 38| 46| @objc open var gridBackgroundColor = NSUIColor(red: 240/255.0, green: 240/255.0, blue: 240/255.0, alpha: 1.0) 39| | 40| 46| @objc open var borderColor = NSUIColor.black 41| | @objc open var borderLineWidth: CGFloat = 1.0 42| | 43| | /// flag indicating if the grid background should be drawn or not 44| | @objc open var drawGridBackgroundEnabled = false 45| | 46| | /// When enabled, the borders rectangle will be rendered. 47| | /// If this is enabled, there is no point drawing the axis-lines of x- and y-axis. 48| | @objc open var drawBordersEnabled = false 49| | 50| | /// When enabled, the values will be clipped to contentRect, otherwise they can bleed outside the content rect. 51| | @objc open var clipValuesToContentEnabled: Bool = false 52| | 53| | /// When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can 54| | /// be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) 55| | /// that there is unwanted clipping. 56| | @objc open var clipDataToContentEnabled: Bool = true 57| | 58| | /// Sets the minimum offset (padding) around the chart, defaults to 10 59| 46| @objc open var minOffset = CGFloat(10.0) 60| | 61| | /// Sets whether the chart should keep its position (zoom / scroll) after a rotation (orientation change) 62| | /// **default**: false 63| | @objc open var keepPositionOnRotation: Bool = false 64| | 65| | /// The left y-axis object. In the horizontal bar-chart, this is the 66| | /// top axis. 67| 46| @objc open internal(set) var leftAxis = YAxis(position: .left) 68| | 69| | /// The right y-axis object. In the horizontal bar-chart, this is the 70| | /// bottom axis. 71| 46| @objc open internal(set) var rightAxis = YAxis(position: .right) 72| | 73| | /// The left Y axis renderer. This is a read-write property so you can set your own custom renderer here. 74| | /// **default**: An instance of YAxisRenderer 75| | @objc open lazy var leftYAxisRenderer = YAxisRenderer(viewPortHandler: viewPortHandler, axis: leftAxis, transformer: _leftAxisTransformer) 76| | 77| | /// The right Y axis renderer. This is a read-write property so you can set your own custom renderer here. 78| | /// **default**: An instance of YAxisRenderer 79| | @objc open lazy var rightYAxisRenderer = YAxisRenderer(viewPortHandler: viewPortHandler, axis: rightAxis, transformer: _rightAxisTransformer) 80| | 81| | internal var _leftAxisTransformer: Transformer! 82| | internal var _rightAxisTransformer: Transformer! 83| | 84| | /// The X axis renderer. This is a read-write property so you can set your own custom renderer here. 85| | /// **default**: An instance of XAxisRenderer 86| | @objc open lazy var xAxisRenderer = XAxisRenderer(viewPortHandler: viewPortHandler, axis: xAxis, transformer: _leftAxisTransformer) 87| | 88| | internal var _tapGestureRecognizer: NSUITapGestureRecognizer! 89| | internal var _doubleTapGestureRecognizer: NSUITapGestureRecognizer! 90| | #if !os(tvOS) 91| | internal var _pinchGestureRecognizer: NSUIPinchGestureRecognizer! 92| | #endif 93| | internal var _panGestureRecognizer: NSUIPanGestureRecognizer! 94| | 95| | /// flag that indicates if a custom viewport offset has been set 96| | private var _customViewPortEnabled = false 97| | 98| | public override init(frame: CGRect) 99| | { 100| | super.init(frame: frame) 101| | } 102| | 103| | public required init?(coder aDecoder: NSCoder) 104| | { 105| | super.init(coder: aDecoder) 106| | } 107| | 108| | deinit 109| | { 110| | stopDeceleration() 111| | } 112| | 113| | internal override func initialize() 114| | { 115| | super.initialize() 116| | 117| | _leftAxisTransformer = Transformer(viewPortHandler: viewPortHandler) 118| | _rightAxisTransformer = Transformer(viewPortHandler: viewPortHandler) 119| | 120| | self.highlighter = ChartHighlighter(chart: self) 121| | 122| | _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) 123| | _doubleTapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(doubleTapGestureRecognized(_:))) 124| | _doubleTapGestureRecognizer.nsuiNumberOfTapsRequired = 2 125| | _panGestureRecognizer = NSUIPanGestureRecognizer(target: self, action: #selector(panGestureRecognized(_:))) 126| | 127| | _panGestureRecognizer.delegate = self 128| | 129| | self.addGestureRecognizer(_tapGestureRecognizer) 130| | self.addGestureRecognizer(_doubleTapGestureRecognizer) 131| | self.addGestureRecognizer(_panGestureRecognizer) 132| | 133| | _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled 134| | _panGestureRecognizer.isEnabled = _dragXEnabled || _dragYEnabled 135| | 136| | #if !os(tvOS) 137| | _pinchGestureRecognizer = NSUIPinchGestureRecognizer(target: self, action: #selector(BarLineChartViewBase.pinchGestureRecognized(_:))) 138| | _pinchGestureRecognizer.delegate = self 139| | self.addGestureRecognizer(_pinchGestureRecognizer) 140| | _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled 141| | #endif 142| | } 143| | 144| | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) 145| | { 146| | // Saving current position of chart. 147| | var oldPoint: CGPoint? 148| | if (keepPositionOnRotation && (keyPath == "frame" || keyPath == "bounds")) 149| | { 150| | oldPoint = viewPortHandler.contentRect.origin 151| | getTransformer(forAxis: .left).pixelToValues(&oldPoint!) 152| | } 153| | 154| | // Superclass transforms chart. 155| | super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context) 156| | 157| | // Restoring old position of chart 158| | if var newPoint = oldPoint , keepPositionOnRotation 159| | { 160| | getTransformer(forAxis: .left).pointValueToPixel(&newPoint) 161| | viewPortHandler.centerViewPort(pt: newPoint, chart: self) 162| | } 163| | else 164| | { 165| | viewPortHandler.refresh(newMatrix: viewPortHandler.touchMatrix, chart: self, invalidate: true) 166| | } 167| | } 168| | 169| | open override func draw(_ rect: CGRect) 170| | { 171| | super.draw(rect) 172| | 173| | guard data != nil, let renderer = renderer else { return } 174| | 175| | let optionalContext = NSUIGraphicsGetCurrentContext() 176| | guard let context = optionalContext else { return } 177| | 178| | // execute all drawing commands 179| | drawGridBackground(context: context) 180| | 181| | 182| | if _autoScaleMinMaxEnabled 183| | { 184| | autoScale() 185| | } 186| | 187| | if leftAxis.isEnabled 188| | { 189| | leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) 190| | } 191| | 192| | if rightAxis.isEnabled 193| | { 194| | rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) 195| | } 196| | 197| | if xAxis.isEnabled 198| | { 199| | xAxisRenderer.computeAxis(min: xAxis._axisMinimum, max: xAxis._axisMaximum, inverted: false) 200| | } 201| | 202| | xAxisRenderer.renderAxisLine(context: context) 203| | leftYAxisRenderer.renderAxisLine(context: context) 204| | rightYAxisRenderer.renderAxisLine(context: context) 205| | 206| | // The renderers are responsible for clipping, to account for line-width center etc. 207| | if xAxis.drawGridLinesBehindDataEnabled 208| | { 209| | xAxisRenderer.renderGridLines(context: context) 210| | leftYAxisRenderer.renderGridLines(context: context) 211| | rightYAxisRenderer.renderGridLines(context: context) 212| | } 213| | 214| | if xAxis.isEnabled && xAxis.isDrawLimitLinesBehindDataEnabled 215| | { 216| | xAxisRenderer.renderLimitLines(context: context) 217| | } 218| | 219| | if leftAxis.isEnabled && leftAxis.isDrawLimitLinesBehindDataEnabled 220| | { 221| | leftYAxisRenderer.renderLimitLines(context: context) 222| | } 223| | 224| | if rightAxis.isEnabled && rightAxis.isDrawLimitLinesBehindDataEnabled 225| | { 226| | rightYAxisRenderer.renderLimitLines(context: context) 227| | } 228| | 229| | context.saveGState() 230| | 231| | // make sure the data cannot be drawn outside the content-rect 232| | if clipDataToContentEnabled { 233| | context.clip(to: viewPortHandler.contentRect) 234| | } 235| | 236| | renderer.drawData(context: context) 237| | 238| | // The renderers are responsible for clipping, to account for line-width center etc. 239| | if !xAxis.drawGridLinesBehindDataEnabled 240| | { 241| | xAxisRenderer.renderGridLines(context: context) 242| | leftYAxisRenderer.renderGridLines(context: context) 243| | rightYAxisRenderer.renderGridLines(context: context) 244| | } 245| | 246| | // if highlighting is enabled 247| | if (valuesToHighlight()) 248| | { 249| | renderer.drawHighlighted(context: context, indices: highlighted) 250| | } 251| | 252| | context.restoreGState() 253| | 254| | renderer.drawExtras(context: context) 255| | 256| | if xAxis.isEnabled && !xAxis.isDrawLimitLinesBehindDataEnabled 257| | { 258| | xAxisRenderer.renderLimitLines(context: context) 259| | } 260| | 261| | if leftAxis.isEnabled && !leftAxis.isDrawLimitLinesBehindDataEnabled 262| | { 263| | leftYAxisRenderer.renderLimitLines(context: context) 264| | } 265| | 266| | if rightAxis.isEnabled && !rightAxis.isDrawLimitLinesBehindDataEnabled 267| | { 268| | rightYAxisRenderer.renderLimitLines(context: context) 269| | } 270| | 271| | xAxisRenderer.renderAxisLabels(context: context) 272| | leftYAxisRenderer.renderAxisLabels(context: context) 273| | rightYAxisRenderer.renderAxisLabels(context: context) 274| | 275| | if clipValuesToContentEnabled 276| | { 277| | context.saveGState() 278| | context.clip(to: viewPortHandler.contentRect) 279| | 280| | renderer.drawValues(context: context) 281| | 282| | context.restoreGState() 283| | } 284| | else 285| | { 286| | renderer.drawValues(context: context) 287| | } 288| | 289| | legendRenderer.renderLegend(context: context) 290| | 291| | drawDescription(in: context) 292| | 293| | drawMarkers(context: context) 294| | } 295| | 296| | private var _autoScaleLastLowestVisibleX: Double? 297| | private var _autoScaleLastHighestVisibleX: Double? 298| | 299| | /// Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. 300| | internal func autoScale() 301| | { 302| | guard let data = data 303| | else { return } 304| | 305| | data.calcMinMaxY(fromX: self.lowestVisibleX, toX: self.highestVisibleX) 306| | 307| | xAxis.calculate(min: data.xMin, max: data.xMax) 308| | 309| | // calculate axis range (min / max) according to provided data 310| | 311| | if leftAxis.isEnabled 312| | { 313| | leftAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left)) 314| | } 315| | 316| | if rightAxis.isEnabled 317| | { 318| | rightAxis.calculate(min: data.getYMin(axis: .right), max: data.getYMax(axis: .right)) 319| | } 320| | 321| | calculateOffsets() 322| | } 323| | 324| | internal func prepareValuePxMatrix() 325| | { 326| | _rightAxisTransformer.prepareMatrixValuePx(chartXMin: xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(rightAxis.axisRange), chartYMin: rightAxis._axisMinimum) 327| | _leftAxisTransformer.prepareMatrixValuePx(chartXMin: xAxis._axisMinimum, deltaX: CGFloat(xAxis.axisRange), deltaY: CGFloat(leftAxis.axisRange), chartYMin: leftAxis._axisMinimum) 328| | } 329| | 330| | internal func prepareOffsetMatrix() 331| | { 332| | _rightAxisTransformer.prepareMatrixOffset(inverted: rightAxis.isInverted) 333| | _leftAxisTransformer.prepareMatrixOffset(inverted: leftAxis.isInverted) 334| | } 335| | 336| | open override func notifyDataSetChanged() 337| | { 338| | renderer?.initBuffers() 339| | 340| | calcMinMax() 341| | 342| | leftYAxisRenderer.computeAxis(min: leftAxis._axisMinimum, max: leftAxis._axisMaximum, inverted: leftAxis.isInverted) 343| | rightYAxisRenderer.computeAxis(min: rightAxis._axisMinimum, max: rightAxis._axisMaximum, inverted: rightAxis.isInverted) 344| | 345| | if let data = data 346| | { 347| | xAxisRenderer.computeAxis( 348| | min: xAxis._axisMinimum, 349| | max: xAxis._axisMaximum, 350| | inverted: false) 351| | 352| | legendRenderer.computeLegend(data: data) 353| | } 354| | 355| | calculateOffsets() 356| | 357| | setNeedsDisplay() 358| | } 359| | 360| | internal override func calcMinMax() 361| | { 362| | // calculate / set x-axis range 363| | xAxis.calculate(min: data?.xMin ?? 0.0, max: data?.xMax ?? 0.0) 364| | 365| | // calculate axis range (min / max) according to provided data 366| | leftAxis.calculate(min: data?.getYMin(axis: .left) ?? 0.0, max: data?.getYMax(axis: .left) ?? 0.0) 367| | rightAxis.calculate(min: data?.getYMin(axis: .right) ?? 0.0, max: data?.getYMax(axis: .right) ?? 0.0) 368| | } 369| | 370| | internal func calculateLegendOffsets(offsetLeft: inout CGFloat, offsetTop: inout CGFloat, offsetRight: inout CGFloat, offsetBottom: inout CGFloat) 371| | { 372| | // setup offsets for legend 373| | if legend.isEnabled, !legend.drawInside 374| | { 375| | switch legend.orientation 376| | { 377| | case .vertical: 378| | 379| | switch legend.horizontalAlignment 380| | { 381| | case .left: 382| | offsetLeft += min(legend.neededWidth, viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset 383| | 384| | case .right: 385| | offsetRight += min(legend.neededWidth, viewPortHandler.chartWidth * legend.maxSizePercent) + legend.xOffset 386| | 387| | case .center: 388| | 389| | switch legend.verticalAlignment 390| | { 391| | case .top: 392| | offsetTop += min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset 393| | 394| | case .bottom: 395| | offsetBottom += min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset 396| | 397| | default: 398| | break 399| | } 400| | } 401| | 402| | case .horizontal: 403| | 404| | switch legend.verticalAlignment 405| | { 406| | case .top: 407| | offsetTop += min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset 408| | 409| | case .bottom: 410| | offsetBottom += min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) + legend.yOffset 411| | 412| | default: 413| | break 414| | } 415| | } 416| | } 417| | } 418| | 419| | internal override func calculateOffsets() 420| | { 421| | if !_customViewPortEnabled 422| | { 423| | var offsetLeft = CGFloat(0.0) 424| | var offsetRight = CGFloat(0.0) 425| | var offsetTop = CGFloat(0.0) 426| | var offsetBottom = CGFloat(0.0) 427| | 428| | calculateLegendOffsets(offsetLeft: &offsetLeft, 429| | offsetTop: &offsetTop, 430| | offsetRight: &offsetRight, 431| | offsetBottom: &offsetBottom) 432| | 433| | // offsets for y-labels 434| | if leftAxis.needsOffset 435| | { 436| | offsetLeft += leftAxis.requiredSize().width 437| | } 438| | 439| | if rightAxis.needsOffset 440| | { 441| | offsetRight += rightAxis.requiredSize().width 442| | } 443| | 444| | if xAxis.isEnabled && xAxis.isDrawLabelsEnabled 445| | { 446| | let xlabelheight = xAxis.labelRotatedHeight + xAxis.yOffset 447| | 448| | // offsets for x-labels 449| | if xAxis.labelPosition == .bottom 450| | { 451| | offsetBottom += xlabelheight 452| | } 453| | else if xAxis.labelPosition == .top 454| | { 455| | offsetTop += xlabelheight 456| | } 457| | else if xAxis.labelPosition == .bothSided 458| | { 459| | offsetBottom += xlabelheight 460| | offsetTop += xlabelheight 461| | } 462| | } 463| | 464| | offsetTop += self.extraTopOffset 465| | offsetRight += self.extraRightOffset 466| | offsetBottom += self.extraBottomOffset 467| | offsetLeft += self.extraLeftOffset 468| | 469| | viewPortHandler.restrainViewPort( 470| | offsetLeft: max(self.minOffset, offsetLeft), 471| | offsetTop: max(self.minOffset, offsetTop), 472| | offsetRight: max(self.minOffset, offsetRight), 473| | offsetBottom: max(self.minOffset, offsetBottom)) 474| | } 475| | 476| | prepareOffsetMatrix() 477| | prepareValuePxMatrix() 478| | } 479| | 480| | /// draws the grid background 481| | internal func drawGridBackground(context: CGContext) 482| | { 483| | if drawGridBackgroundEnabled || drawBordersEnabled 484| | { 485| | context.saveGState() 486| | } 487| | 488| | if drawGridBackgroundEnabled 489| | { 490| | // draw the grid background 491| | context.setFillColor(gridBackgroundColor.cgColor) 492| | context.fill(viewPortHandler.contentRect) 493| | } 494| | 495| | if drawBordersEnabled 496| | { 497| | context.setLineWidth(borderLineWidth) 498| | context.setStrokeColor(borderColor.cgColor) 499| | context.stroke(viewPortHandler.contentRect) 500| | } 501| | 502| | if drawGridBackgroundEnabled || drawBordersEnabled 503| | { 504| | context.restoreGState() 505| | } 506| | } 507| | 508| | // MARK: - Gestures 509| | 510| | private enum GestureScaleAxis 511| | { 512| | case both 513| | case x 514| | case y 515| | } 516| | 517| | private var _isDragging = false 518| | private var _isScaling = false 519| 46| private var _gestureScaleAxis = GestureScaleAxis.both 520| | private var _closestDataSetToTouch: ChartDataSetProtocol! 521| | private var _panGestureReachedEdge: Bool = false 522| | private weak var _outerScrollView: NSUIScrollView? 523| | 524| 46| private var _lastPanPoint = CGPoint() /// This is to prevent using setTranslation which resets velocity 525| | 526| | private var _decelerationLastTime: TimeInterval = 0.0 527| | private var _decelerationDisplayLink: NSUIDisplayLink! 528| 46| private var _decelerationVelocity = CGPoint() 529| | 530| | @objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) 531| | { 532| | if data === nil 533| | { 534| | return 535| | } 536| | 537| | if recognizer.state == NSUIGestureRecognizerState.ended 538| | { 539| | if !isHighLightPerTapEnabled { return } 540| | 541| | let h = getHighlightByTouchPoint(recognizer.location(in: self)) 542| | 543| | if h === nil || h == self.lastHighlighted 544| | { 545| | lastHighlighted = nil 546| | highlightValue(nil, callDelegate: true) 547| | } 548| | else 549| | { 550| | lastHighlighted = h 551| | highlightValue(h, callDelegate: true) 552| | } 553| | } 554| | } 555| | 556| | @objc private func doubleTapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) 557| | { 558| | if data === nil 559| | { 560| | return 561| | } 562| | 563| | if recognizer.state == NSUIGestureRecognizerState.ended 564| | { 565| | if data !== nil && _doubleTapToZoomEnabled && (data?.entryCount ?? 0) > 0 566| | { 567| | var location = recognizer.location(in: self) 568| | location.x = location.x - viewPortHandler.offsetLeft 569| | 570| | if isTouchInverted() 571| | { 572| | location.y = -(location.y - viewPortHandler.offsetTop) 573| | } 574| | else 575| | { 576| | location.y = -(self.bounds.size.height - location.y - viewPortHandler.offsetBottom) 577| | } 578| | 579| | self.zoom(scaleX: isScaleXEnabled ? 1.4 : 1.0, scaleY: isScaleYEnabled ? 1.4 : 1.0, x: location.x, y: location.y) 580| | delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) 581| | } 582| | } 583| | } 584| | 585| | #if !os(tvOS) 586| | @objc private func pinchGestureRecognized(_ recognizer: NSUIPinchGestureRecognizer) 587| | { 588| | if recognizer.state == NSUIGestureRecognizerState.began 589| | { 590| | stopDeceleration() 591| | 592| | if data !== nil && 593| | (_pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled) 594| | { 595| | _isScaling = true 596| | 597| | if _pinchZoomEnabled 598| | { 599| | _gestureScaleAxis = .both 600| | } 601| | else 602| | { 603| | let x = abs(recognizer.location(in: self).x - recognizer.nsuiLocationOfTouch(1, inView: self).x) 604| | let y = abs(recognizer.location(in: self).y - recognizer.nsuiLocationOfTouch(1, inView: self).y) 605| | 606| | if _scaleXEnabled != _scaleYEnabled 607| | { 608| | _gestureScaleAxis = _scaleXEnabled ? .x : .y 609| | } 610| | else 611| | { 612| | _gestureScaleAxis = x > y ? .x : .y 613| | } 614| | } 615| | } 616| | } 617| | else if recognizer.state == NSUIGestureRecognizerState.ended || 618| | recognizer.state == NSUIGestureRecognizerState.cancelled 619| | { 620| | if _isScaling 621| | { 622| | _isScaling = false 623| | 624| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 625| | calculateOffsets() 626| | setNeedsDisplay() 627| | } 628| | } 629| | else if recognizer.state == NSUIGestureRecognizerState.changed 630| | { 631| | let isZoomingOut = (recognizer.nsuiScale < 1) 632| | var canZoomMoreX = isZoomingOut ? viewPortHandler.canZoomOutMoreX : viewPortHandler.canZoomInMoreX 633| | var canZoomMoreY = isZoomingOut ? viewPortHandler.canZoomOutMoreY : viewPortHandler.canZoomInMoreY 634| | 635| | if _isScaling 636| | { 637| | canZoomMoreX = canZoomMoreX && _scaleXEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .x) 638| | canZoomMoreY = canZoomMoreY && _scaleYEnabled && (_gestureScaleAxis == .both || _gestureScaleAxis == .y) 639| | if canZoomMoreX || canZoomMoreY 640| | { 641| | var location = recognizer.location(in: self) 642| | location.x = location.x - viewPortHandler.offsetLeft 643| | 644| | if isTouchInverted() 645| | { 646| | location.y = -(location.y - viewPortHandler.offsetTop) 647| | } 648| | else 649| | { 650| | location.y = -(viewPortHandler.chartHeight - location.y - viewPortHandler.offsetBottom) 651| | } 652| | 653| | let scaleX = canZoomMoreX ? recognizer.nsuiScale : 1.0 654| | let scaleY = canZoomMoreY ? recognizer.nsuiScale : 1.0 655| | 656| | var matrix = CGAffineTransform(translationX: location.x, y: location.y) 657| | matrix = matrix.scaledBy(x: scaleX, y: scaleY) 658| | matrix = matrix.translatedBy(x: -location.x, y: -location.y) 659| | 660| | matrix = viewPortHandler.touchMatrix.concatenating(matrix) 661| | 662| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) 663| | 664| | if delegate !== nil 665| | { 666| | delegate?.chartScaled?(self, scaleX: scaleX, scaleY: scaleY) 667| | } 668| | } 669| | 670| | recognizer.nsuiScale = 1.0 671| | } 672| | } 673| | } 674| | #endif 675| | 676| | @objc private func panGestureRecognized(_ recognizer: NSUIPanGestureRecognizer) 677| | { 678| | if recognizer.state == NSUIGestureRecognizerState.began && recognizer.nsuiNumberOfTouches() > 0 679| | { 680| | stopDeceleration() 681| | 682| | if data === nil || !self.isDragEnabled 683| | { // If we have no data, we have nothing to pan and no data to highlight 684| | return 685| | } 686| | 687| | // If drag is enabled and we are in a position where there's something to drag: 688| | // * If we're zoomed in, then obviously we have something to drag. 689| | // * If we have a drag offset - we always have something to drag 690| | if !self.hasNoDragOffset || !self.isFullyZoomedOut 691| | { 692| | _isDragging = true 693| | 694| | _closestDataSetToTouch = getDataSetByTouchPoint(point: recognizer.nsuiLocationOfTouch(0, inView: self)) 695| | 696| | var translation = recognizer.translation(in: self) 697| | if !self.dragXEnabled 698| | { 699| | translation.x = 0.0 700| | } 701| | else if !self.dragYEnabled 702| | { 703| | translation.y = 0.0 704| | } 705| | 706| | let didUserDrag = translation.x != 0.0 || translation.y != 0.0 707| | 708| | // Check to see if user dragged at all and if so, can the chart be dragged by the given amount 709| | if didUserDrag && !performPanChange(translation: translation) 710| | { 711| | if _outerScrollView !== nil 712| | { 713| | // We can stop dragging right now, and let the scroll view take control 714| | _outerScrollView = nil 715| | _isDragging = false 716| | } 717| | } 718| | else 719| | { 720| | if _outerScrollView !== nil 721| | { 722| | // Prevent the parent scroll view from scrolling 723| | _outerScrollView?.nsuiIsScrollEnabled = false 724| | } 725| | } 726| | 727| | _lastPanPoint = recognizer.translation(in: self) 728| | } 729| | else if self.isHighlightPerDragEnabled 730| | { 731| | // We will only handle highlights on NSUIGestureRecognizerState.Changed 732| | 733| | _isDragging = false 734| | } 735| | } 736| | else if recognizer.state == NSUIGestureRecognizerState.changed 737| | { 738| | if _isDragging 739| | { 740| | let originalTranslation = recognizer.translation(in: self) 741| | var translation = CGPoint(x: originalTranslation.x - _lastPanPoint.x, y: originalTranslation.y - _lastPanPoint.y) 742| | 743| | if !self.dragXEnabled 744| | { 745| | translation.x = 0.0 746| | } 747| | else if !self.dragYEnabled 748| | { 749| | translation.y = 0.0 750| | } 751| | 752| | let _ = performPanChange(translation: translation) 753| | 754| | _lastPanPoint = originalTranslation 755| | } 756| | else if isHighlightPerDragEnabled 757| | { 758| | let h = getHighlightByTouchPoint(recognizer.location(in: self)) 759| | 760| | let lastHighlighted = self.lastHighlighted 761| | 762| | if h != lastHighlighted 763| | { 764| | self.lastHighlighted = h 765| | self.highlightValue(h, callDelegate: true) 766| | } 767| | } 768| | } 769| | else if recognizer.state == NSUIGestureRecognizerState.ended || recognizer.state == NSUIGestureRecognizerState.cancelled 770| | { 771| | if _isDragging 772| | { 773| | if recognizer.state == NSUIGestureRecognizerState.ended && isDragDecelerationEnabled 774| | { 775| | stopDeceleration() 776| | 777| | _decelerationLastTime = CACurrentMediaTime() 778| | _decelerationVelocity = recognizer.velocity(in: self) 779| | 780| | _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(BarLineChartViewBase.decelerationLoop)) 781| | _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 782| | } 783| | 784| | _isDragging = false 785| | 786| | delegate?.chartViewDidEndPanning?(self) 787| | } 788| | 789| | if _outerScrollView !== nil 790| | { 791| | _outerScrollView?.nsuiIsScrollEnabled = true 792| | _outerScrollView = nil 793| | } 794| | } 795| | } 796| | 797| | private func performPanChange(translation: CGPoint) -> Bool 798| | { 799| | var translation = translation 800| | 801| | if isTouchInverted() 802| | { 803| | if self is HorizontalBarChartView 804| | { 805| | translation.x = -translation.x 806| | } 807| | else 808| | { 809| | translation.y = -translation.y 810| | } 811| | } 812| | 813| | let originalMatrix = viewPortHandler.touchMatrix 814| | 815| | var matrix = CGAffineTransform(translationX: translation.x, y: translation.y) 816| | matrix = originalMatrix.concatenating(matrix) 817| | 818| | matrix = viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: true) 819| | 820| | if matrix != originalMatrix 821| | { 822| | delegate?.chartTranslated?(self, dX: translation.x, dY: translation.y) 823| | } 824| | 825| | // Did we managed to actually drag or did we reach the edge? 826| | return matrix.tx != originalMatrix.tx || matrix.ty != originalMatrix.ty 827| | } 828| | 829| | private func isTouchInverted() -> Bool 830| | { 831| | return isAnyAxisInverted && 832| | _closestDataSetToTouch !== nil && 833| | getAxis(_closestDataSetToTouch.axisDependency).isInverted 834| | } 835| | 836| | @objc open func stopDeceleration() 837| | { 838| | if _decelerationDisplayLink !== nil 839| | { 840| | _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) 841| | _decelerationDisplayLink = nil 842| | } 843| | } 844| | 845| | @objc private func decelerationLoop() 846| | { 847| | let currentTime = CACurrentMediaTime() 848| | 849| | _decelerationVelocity.x *= self.dragDecelerationFrictionCoef 850| | _decelerationVelocity.y *= self.dragDecelerationFrictionCoef 851| | 852| | let timeInterval = CGFloat(currentTime - _decelerationLastTime) 853| | 854| | let distance = CGPoint( 855| | x: _decelerationVelocity.x * timeInterval, 856| | y: _decelerationVelocity.y * timeInterval 857| | ) 858| | 859| | if !performPanChange(translation: distance) 860| | { 861| | // We reached the edge, stop 862| | _decelerationVelocity.x = 0.0 863| | _decelerationVelocity.y = 0.0 864| | } 865| | 866| | _decelerationLastTime = currentTime 867| | 868| | if abs(_decelerationVelocity.x) < 0.001 && abs(_decelerationVelocity.y) < 0.001 869| | { 870| | stopDeceleration() 871| | 872| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 873| | calculateOffsets() 874| | setNeedsDisplay() 875| | } 876| | } 877| | 878| | private func nsuiGestureRecognizerShouldBegin(_ gestureRecognizer: NSUIGestureRecognizer) -> Bool 879| | { 880| | if gestureRecognizer == _panGestureRecognizer 881| | { 882| | let velocity = _panGestureRecognizer.velocity(in: self) 883| | if data === nil || !isDragEnabled || 884| | (self.hasNoDragOffset && self.isFullyZoomedOut && !self.isHighlightPerDragEnabled) || 885| | (!_dragYEnabled && abs(velocity.y) > abs(velocity.x)) || 886| | (!_dragXEnabled && abs(velocity.y) < abs(velocity.x)) 887| | { 888| | return false 889| | } 890| | } 891| | else 892| | { 893| | #if !os(tvOS) 894| | if gestureRecognizer == _pinchGestureRecognizer 895| | { 896| | if data === nil || (!_pinchZoomEnabled && !_scaleXEnabled && !_scaleYEnabled) 897| | { 898| | return false 899| | } 900| | } 901| | #endif 902| | } 903| | 904| | return true 905| | } 906| | 907| | #if !os(OSX) 908| | open override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool 909| | { 910| | if !super.gestureRecognizerShouldBegin(gestureRecognizer) 911| | { 912| | return false 913| | } 914| | 915| | return nsuiGestureRecognizerShouldBegin(gestureRecognizer) 916| | } 917| | #endif 918| | 919| | #if os(OSX) 920| | public func gestureRecognizerShouldBegin(gestureRecognizer: NSGestureRecognizer) -> Bool 921| | { 922| | return nsuiGestureRecognizerShouldBegin(gestureRecognizer) 923| | } 924| | #endif 925| | 926| | open func gestureRecognizer(_ gestureRecognizer: NSUIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: NSUIGestureRecognizer) -> Bool 927| | { 928| | #if !os(tvOS) 929| | if ((gestureRecognizer is NSUIPinchGestureRecognizer && otherGestureRecognizer is NSUIPanGestureRecognizer) || 930| | (gestureRecognizer is NSUIPanGestureRecognizer && otherGestureRecognizer is NSUIPinchGestureRecognizer)) 931| | { 932| | return true 933| | } 934| | #endif 935| | 936| | if gestureRecognizer is NSUIPanGestureRecognizer, 937| | otherGestureRecognizer is NSUIPanGestureRecognizer, 938| | gestureRecognizer == _panGestureRecognizer 939| | { 940| | var scrollView = self.superview 941| | while scrollView != nil && !(scrollView is NSUIScrollView) 942| | { 943| | scrollView = scrollView?.superview 944| | } 945| | 946| | // If there is two scrollview together, we pick the superview of the inner scrollview. 947| | // In the case of UITableViewWrepperView, the superview will be UITableView 948| | if let superViewOfScrollView = scrollView?.superview, 949| | superViewOfScrollView is NSUIScrollView 950| | { 951| | scrollView = superViewOfScrollView 952| | } 953| | 954| | var foundScrollView = scrollView as? NSUIScrollView 955| | 956| | if !(foundScrollView?.nsuiIsScrollEnabled ?? true) 957| | { 958| | foundScrollView = nil 959| | } 960| | 961| | let scrollViewPanGestureRecognizer = foundScrollView?.nsuiGestureRecognizers?.first { 962| | $0 is NSUIPanGestureRecognizer 963| | } 964| | 965| | if otherGestureRecognizer === scrollViewPanGestureRecognizer 966| | { 967| | _outerScrollView = foundScrollView 968| | 969| | return true 970| | } 971| | } 972| | 973| | return false 974| | } 975| | 976| | /// MARK: Viewport modifiers 977| | 978| | /// Zooms in by 1.4, into the charts center. 979| | @objc open func zoomIn() 980| | { 981| | let center = viewPortHandler.contentCenter 982| | 983| | let matrix = viewPortHandler.zoomIn(x: center.x, y: -center.y) 984| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 985| | 986| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 987| | calculateOffsets() 988| | setNeedsDisplay() 989| | } 990| | 991| | /// Zooms out by 0.7, from the charts center. 992| | @objc open func zoomOut() 993| | { 994| | let center = viewPortHandler.contentCenter 995| | 996| | let matrix = viewPortHandler.zoomOut(x: center.x, y: -center.y) 997| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 998| | 999| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 1000| | calculateOffsets() 1001| | setNeedsDisplay() 1002| | } 1003| | 1004| | /// Zooms out to original size. 1005| | @objc open func resetZoom() 1006| | { 1007| | let matrix = viewPortHandler.resetZoom() 1008| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 1009| | 1010| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 1011| | calculateOffsets() 1012| | setNeedsDisplay() 1013| | } 1014| | 1015| | /// Zooms in or out by the given scale factor. x and y are the coordinates 1016| | /// (in pixels) of the zoom center. 1017| | /// 1018| | /// - Parameters: 1019| | /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in 1020| | /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in 1021| | /// - x: 1022| | /// - y: 1023| | @objc open func zoom( 1024| | scaleX: CGFloat, 1025| | scaleY: CGFloat, 1026| | x: CGFloat, 1027| | y: CGFloat) 1028| | { 1029| | let matrix = viewPortHandler.zoom(scaleX: scaleX, scaleY: scaleY, x: x, y: -y) 1030| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 1031| | 1032| | // Range might have changed, which means that Y-axis labels could have changed in size, affecting Y-axis size. So we need to recalculate offsets. 1033| | calculateOffsets() 1034| | setNeedsDisplay() 1035| | } 1036| | 1037| | /// Zooms in or out by the given scale factor. 1038| | /// x and y are the values (**not pixels**) of the zoom center. 1039| | /// 1040| | /// - Parameters: 1041| | /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in 1042| | /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in 1043| | /// - xValue: 1044| | /// - yValue: 1045| | /// - axis: 1046| | @objc open func zoom( 1047| | scaleX: CGFloat, 1048| | scaleY: CGFloat, 1049| | xValue: Double, 1050| | yValue: Double, 1051| | axis: YAxis.AxisDependency) 1052| | { 1053| | let job = ZoomViewJob( 1054| | viewPortHandler: viewPortHandler, 1055| | scaleX: scaleX, 1056| | scaleY: scaleY, 1057| | xValue: xValue, 1058| | yValue: yValue, 1059| | transformer: getTransformer(forAxis: axis), 1060| | axis: axis, 1061| | view: self) 1062| | addViewportJob(job) 1063| | } 1064| | 1065| | /// Zooms to the center of the chart with the given scale factor. 1066| | /// 1067| | /// - Parameters: 1068| | /// - scaleX: if < 1 --> zoom out, if > 1 --> zoom in 1069| | /// - scaleY: if < 1 --> zoom out, if > 1 --> zoom in 1070| | /// - xValue: 1071| | /// - yValue: 1072| | /// - axis: 1073| | @objc open func zoomToCenter( 1074| | scaleX: CGFloat, 1075| | scaleY: CGFloat) 1076| | { 1077| | let center = centerOffsets 1078| | let matrix = viewPortHandler.zoom( 1079| | scaleX: scaleX, 1080| | scaleY: scaleY, 1081| | x: center.x, 1082| | y: -center.y) 1083| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 1084| | } 1085| | 1086| | /// Zooms by the specified scale factor to the specified values on the specified axis. 1087| | /// 1088| | /// - Parameters: 1089| | /// - scaleX: 1090| | /// - scaleY: 1091| | /// - xValue: 1092| | /// - yValue: 1093| | /// - axis: which axis should be used as a reference for the y-axis 1094| | /// - duration: the duration of the animation in seconds 1095| | /// - easing: 1096| | @objc open func zoomAndCenterViewAnimated( 1097| | scaleX: CGFloat, 1098| | scaleY: CGFloat, 1099| | xValue: Double, 1100| | yValue: Double, 1101| | axis: YAxis.AxisDependency, 1102| | duration: TimeInterval, 1103| | easing: ChartEasingFunctionBlock?) 1104| | { 1105| | let origin = valueForTouchPoint( 1106| | point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), 1107| | axis: axis) 1108| | 1109| | let job = AnimatedZoomViewJob( 1110| | viewPortHandler: viewPortHandler, 1111| | transformer: getTransformer(forAxis: axis), 1112| | view: self, 1113| | yAxis: getAxis(axis), 1114| | xAxisRange: xAxis.axisRange, 1115| | scaleX: scaleX, 1116| | scaleY: scaleY, 1117| | xOrigin: viewPortHandler.scaleX, 1118| | yOrigin: viewPortHandler.scaleY, 1119| | zoomCenterX: CGFloat(xValue), 1120| | zoomCenterY: CGFloat(yValue), 1121| | zoomOriginX: origin.x, 1122| | zoomOriginY: origin.y, 1123| | duration: duration, 1124| | easing: easing) 1125| | 1126| | addViewportJob(job) 1127| | } 1128| | 1129| | /// Zooms by the specified scale factor to the specified values on the specified axis. 1130| | /// 1131| | /// - Parameters: 1132| | /// - scaleX: 1133| | /// - scaleY: 1134| | /// - xValue: 1135| | /// - yValue: 1136| | /// - axis: which axis should be used as a reference for the y-axis 1137| | /// - duration: the duration of the animation in seconds 1138| | /// - easing: 1139| | @objc open func zoomAndCenterViewAnimated( 1140| | scaleX: CGFloat, 1141| | scaleY: CGFloat, 1142| | xValue: Double, 1143| | yValue: Double, 1144| | axis: YAxis.AxisDependency, 1145| | duration: TimeInterval, 1146| | easingOption: ChartEasingOption) 1147| | { 1148| | zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) 1149| | } 1150| | 1151| | /// Zooms by the specified scale factor to the specified values on the specified axis. 1152| | /// 1153| | /// - Parameters: 1154| | /// - scaleX: 1155| | /// - scaleY: 1156| | /// - xValue: 1157| | /// - yValue: 1158| | /// - axis: which axis should be used as a reference for the y-axis 1159| | /// - duration: the duration of the animation in seconds 1160| | /// - easing: 1161| | @objc open func zoomAndCenterViewAnimated( 1162| | scaleX: CGFloat, 1163| | scaleY: CGFloat, 1164| | xValue: Double, 1165| | yValue: Double, 1166| | axis: YAxis.AxisDependency, 1167| | duration: TimeInterval) 1168| | { 1169| | zoomAndCenterViewAnimated(scaleX: scaleX, scaleY: scaleY, xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) 1170| | } 1171| | 1172| | /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. 1173| | @objc open func fitScreen() 1174| | { 1175| | let matrix = viewPortHandler.fitScreen() 1176| | viewPortHandler.refresh(newMatrix: matrix, chart: self, invalidate: false) 1177| | 1178| | calculateOffsets() 1179| | setNeedsDisplay() 1180| | } 1181| | 1182| | /// Sets the minimum scale value to which can be zoomed out. 1 = fitScreen 1183| | @objc open func setScaleMinima(_ scaleX: CGFloat, scaleY: CGFloat) 1184| | { 1185| | viewPortHandler.setMinimumScaleX(scaleX) 1186| | viewPortHandler.setMinimumScaleY(scaleY) 1187| | } 1188| | 1189| | @objc open var visibleXRange: Double 1190| | { 1191| | return abs(highestVisibleX - lowestVisibleX) 1192| | } 1193| | 1194| | /// Sets the size of the area (range on the x-axis) that should be maximum visible at once (no further zooming out allowed). 1195| | /// 1196| | /// If this is e.g. set to 10, no more than a range of 10 values on the x-axis can be viewed at once without scrolling. 1197| | /// 1198| | /// If you call this method, chart must have data or it has no effect. 1199| | @objc open func setVisibleXRangeMaximum(_ maxXRange: Double) 1200| | { 1201| | let xScale = xAxis.axisRange / maxXRange 1202| | viewPortHandler.setMinimumScaleX(CGFloat(xScale)) 1203| | } 1204| | 1205| | /// Sets the size of the area (range on the x-axis) that should be minimum visible at once (no further zooming in allowed). 1206| | /// 1207| | /// If this is e.g. set to 10, no less than a range of 10 values on the x-axis can be viewed at once without scrolling. 1208| | /// 1209| | /// If you call this method, chart must have data or it has no effect. 1210| | @objc open func setVisibleXRangeMinimum(_ minXRange: Double) 1211| | { 1212| | let xScale = xAxis.axisRange / minXRange 1213| | viewPortHandler.setMaximumScaleX(CGFloat(xScale)) 1214| | } 1215| | 1216| | /// Limits the maximum and minimum value count that can be visible by pinching and zooming. 1217| | /// 1218| | /// e.g. minRange=10, maxRange=100 no less than 10 values and no more that 100 values can be viewed 1219| | /// at once without scrolling. 1220| | /// 1221| | /// If you call this method, chart must have data or it has no effect. 1222| | @objc open func setVisibleXRange(minXRange: Double, maxXRange: Double) 1223| | { 1224| | let minScale = xAxis.axisRange / maxXRange 1225| | let maxScale = xAxis.axisRange / minXRange 1226| | viewPortHandler.setMinMaxScaleX( 1227| | minScaleX: CGFloat(minScale), 1228| | maxScaleX: CGFloat(maxScale)) 1229| | } 1230| | 1231| | /// Sets the size of the area (range on the y-axis) that should be maximum visible at once. 1232| | /// 1233| | /// - Parameters: 1234| | /// - yRange: 1235| | /// - axis: - the axis for which this limit should apply 1236| | @objc open func setVisibleYRangeMaximum(_ maxYRange: Double, axis: YAxis.AxisDependency) 1237| | { 1238| | let yScale = getAxisRange(axis: axis) / maxYRange 1239| | viewPortHandler.setMinimumScaleY(CGFloat(yScale)) 1240| | } 1241| | 1242| | /// Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. 1243| | /// 1244| | /// - Parameters: 1245| | /// - yRange: 1246| | /// - axis: - the axis for which this limit should apply 1247| | @objc open func setVisibleYRangeMinimum(_ minYRange: Double, axis: YAxis.AxisDependency) 1248| | { 1249| | let yScale = getAxisRange(axis: axis) / minYRange 1250| | viewPortHandler.setMaximumScaleY(CGFloat(yScale)) 1251| | } 1252| | 1253| | /// Limits the maximum and minimum y range that can be visible by pinching and zooming. 1254| | /// 1255| | /// - Parameters: 1256| | /// - minYRange: 1257| | /// - maxYRange: 1258| | /// - axis: 1259| | @objc open func setVisibleYRange(minYRange: Double, maxYRange: Double, axis: YAxis.AxisDependency) 1260| | { 1261| | let minScale = getAxisRange(axis: axis) / minYRange 1262| | let maxScale = getAxisRange(axis: axis) / maxYRange 1263| | viewPortHandler.setMinMaxScaleY(minScaleY: CGFloat(minScale), maxScaleY: CGFloat(maxScale)) 1264| | } 1265| | 1266| | /// Moves the left side of the current viewport to the specified x-value. 1267| | /// This also refreshes the chart by calling setNeedsDisplay(). 1268| | @objc open func moveViewToX(_ xValue: Double) 1269| | { 1270| | let job = MoveViewJob( 1271| | viewPortHandler: viewPortHandler, 1272| | xValue: xValue, 1273| | yValue: 0.0, 1274| | transformer: getTransformer(forAxis: .left), 1275| | view: self) 1276| | 1277| | addViewportJob(job) 1278| | } 1279| | 1280| | /// Centers the viewport to the specified y-value on the y-axis. 1281| | /// This also refreshes the chart by calling setNeedsDisplay(). 1282| | /// 1283| | /// - Parameters: 1284| | /// - yValue: 1285| | /// - axis: - which axis should be used as a reference for the y-axis 1286| | @objc open func moveViewToY(_ yValue: Double, axis: YAxis.AxisDependency) 1287| | { 1288| | let yInView = getAxisRange(axis: axis) / Double(viewPortHandler.scaleY) 1289| | 1290| | let job = MoveViewJob( 1291| | viewPortHandler: viewPortHandler, 1292| | xValue: 0.0, 1293| | yValue: yValue + yInView / 2.0, 1294| | transformer: getTransformer(forAxis: axis), 1295| | view: self) 1296| | 1297| | addViewportJob(job) 1298| | } 1299| | 1300| | /// This will move the left side of the current viewport to the specified x-value on the x-axis, and center the viewport to the specified y-value on the y-axis. 1301| | /// This also refreshes the chart by calling setNeedsDisplay(). 1302| | /// 1303| | /// - Parameters: 1304| | /// - xValue: 1305| | /// - yValue: 1306| | /// - axis: - which axis should be used as a reference for the y-axis 1307| | @objc open func moveViewTo(xValue: Double, yValue: Double, axis: YAxis.AxisDependency) 1308| | { 1309| | let yInView = getAxisRange(axis: axis) / Double(viewPortHandler.scaleY) 1310| | 1311| | let job = MoveViewJob( 1312| | viewPortHandler: viewPortHandler, 1313| | xValue: xValue, 1314| | yValue: yValue + yInView / 2.0, 1315| | transformer: getTransformer(forAxis: axis), 1316| | view: self) 1317| | 1318| | addViewportJob(job) 1319| | } 1320| | 1321| | /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. 1322| | /// This also refreshes the chart by calling setNeedsDisplay(). 1323| | /// 1324| | /// - Parameters: 1325| | /// - xValue: 1326| | /// - yValue: 1327| | /// - axis: which axis should be used as a reference for the y-axis 1328| | /// - duration: the duration of the animation in seconds 1329| | /// - easing: 1330| | @objc open func moveViewToAnimated( 1331| | xValue: Double, 1332| | yValue: Double, 1333| | axis: YAxis.AxisDependency, 1334| | duration: TimeInterval, 1335| | easing: ChartEasingFunctionBlock?) 1336| | { 1337| | let bounds = valueForTouchPoint( 1338| | point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), 1339| | axis: axis) 1340| | 1341| | let yInView = getAxisRange(axis: axis) / Double(viewPortHandler.scaleY) 1342| | 1343| | let job = AnimatedMoveViewJob( 1344| | viewPortHandler: viewPortHandler, 1345| | xValue: xValue, 1346| | yValue: yValue + yInView / 2.0, 1347| | transformer: getTransformer(forAxis: axis), 1348| | view: self, 1349| | xOrigin: bounds.x, 1350| | yOrigin: bounds.y, 1351| | duration: duration, 1352| | easing: easing) 1353| | 1354| | addViewportJob(job) 1355| | } 1356| | 1357| | /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. 1358| | /// This also refreshes the chart by calling setNeedsDisplay(). 1359| | /// 1360| | /// - Parameters: 1361| | /// - xValue: 1362| | /// - yValue: 1363| | /// - axis: which axis should be used as a reference for the y-axis 1364| | /// - duration: the duration of the animation in seconds 1365| | /// - easing: 1366| | @objc open func moveViewToAnimated( 1367| | xValue: Double, 1368| | yValue: Double, 1369| | axis: YAxis.AxisDependency, 1370| | duration: TimeInterval, 1371| | easingOption: ChartEasingOption) 1372| | { 1373| | moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) 1374| | } 1375| | 1376| | /// This will move the left side of the current viewport to the specified x-position and center the viewport to the specified y-position animated. 1377| | /// This also refreshes the chart by calling setNeedsDisplay(). 1378| | /// 1379| | /// - Parameters: 1380| | /// - xValue: 1381| | /// - yValue: 1382| | /// - axis: which axis should be used as a reference for the y-axis 1383| | /// - duration: the duration of the animation in seconds 1384| | /// - easing: 1385| | @objc open func moveViewToAnimated( 1386| | xValue: Double, 1387| | yValue: Double, 1388| | axis: YAxis.AxisDependency, 1389| | duration: TimeInterval) 1390| | { 1391| | moveViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) 1392| | } 1393| | 1394| | /// This will move the center of the current viewport to the specified x-value and y-value. 1395| | /// This also refreshes the chart by calling setNeedsDisplay(). 1396| | /// 1397| | /// - Parameters: 1398| | /// - xValue: 1399| | /// - yValue: 1400| | /// - axis: - which axis should be used as a reference for the y-axis 1401| | @objc open func centerViewTo( 1402| | xValue: Double, 1403| | yValue: Double, 1404| | axis: YAxis.AxisDependency) 1405| | { 1406| | let yInView = getAxisRange(axis: axis) / Double(viewPortHandler.scaleY) 1407| | let xInView = xAxis.axisRange / Double(viewPortHandler.scaleX) 1408| | 1409| | let job = MoveViewJob( 1410| | viewPortHandler: viewPortHandler, 1411| | xValue: xValue - xInView / 2.0, 1412| | yValue: yValue + yInView / 2.0, 1413| | transformer: getTransformer(forAxis: axis), 1414| | view: self) 1415| | 1416| | addViewportJob(job) 1417| | } 1418| | 1419| | /// This will move the center of the current viewport to the specified x-value and y-value animated. 1420| | /// 1421| | /// - Parameters: 1422| | /// - xValue: 1423| | /// - yValue: 1424| | /// - axis: which axis should be used as a reference for the y-axis 1425| | /// - duration: the duration of the animation in seconds 1426| | /// - easing: 1427| | @objc open func centerViewToAnimated( 1428| | xValue: Double, 1429| | yValue: Double, 1430| | axis: YAxis.AxisDependency, 1431| | duration: TimeInterval, 1432| | easing: ChartEasingFunctionBlock?) 1433| | { 1434| | let bounds = valueForTouchPoint( 1435| | point: CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop), 1436| | axis: axis) 1437| | 1438| | let yInView = getAxisRange(axis: axis) / Double(viewPortHandler.scaleY) 1439| | let xInView = xAxis.axisRange / Double(viewPortHandler.scaleX) 1440| | 1441| | let job = AnimatedMoveViewJob( 1442| | viewPortHandler: viewPortHandler, 1443| | xValue: xValue - xInView / 2.0, 1444| | yValue: yValue + yInView / 2.0, 1445| | transformer: getTransformer(forAxis: axis), 1446| | view: self, 1447| | xOrigin: bounds.x, 1448| | yOrigin: bounds.y, 1449| | duration: duration, 1450| | easing: easing) 1451| | 1452| | addViewportJob(job) 1453| | } 1454| | 1455| | /// This will move the center of the current viewport to the specified x-value and y-value animated. 1456| | /// 1457| | /// - Parameters: 1458| | /// - xValue: 1459| | /// - yValue: 1460| | /// - axis: which axis should be used as a reference for the y-axis 1461| | /// - duration: the duration of the animation in seconds 1462| | /// - easing: 1463| | @objc open func centerViewToAnimated( 1464| | xValue: Double, 1465| | yValue: Double, 1466| | axis: YAxis.AxisDependency, 1467| | duration: TimeInterval, 1468| | easingOption: ChartEasingOption) 1469| | { 1470| | centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easing: easingFunctionFromOption(easingOption)) 1471| | } 1472| | 1473| | /// This will move the center of the current viewport to the specified x-value and y-value animated. 1474| | /// 1475| | /// - Parameters: 1476| | /// - xValue: 1477| | /// - yValue: 1478| | /// - axis: which axis should be used as a reference for the y-axis 1479| | /// - duration: the duration of the animation in seconds 1480| | /// - easing: 1481| | @objc open func centerViewToAnimated( 1482| | xValue: Double, 1483| | yValue: Double, 1484| | axis: YAxis.AxisDependency, 1485| | duration: TimeInterval) 1486| | { 1487| | centerViewToAnimated(xValue: xValue, yValue: yValue, axis: axis, duration: duration, easingOption: .easeInOutSine) 1488| | } 1489| | 1490| | /// Sets custom offsets for the current `ChartViewPort` (the offsets on the sides of the actual chart window). Setting this will prevent the chart from automatically calculating it's offsets. Use `resetViewPortOffsets()` to undo this. 1491| | /// ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use `setExtraOffsets(...)`. 1492| | @objc open func setViewPortOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) 1493| | { 1494| | _customViewPortEnabled = true 1495| | 1496| | if Thread.isMainThread 1497| | { 1498| | self.viewPortHandler.restrainViewPort(offsetLeft: left, offsetTop: top, offsetRight: right, offsetBottom: bottom) 1499| | prepareOffsetMatrix() 1500| | prepareValuePxMatrix() 1501| | } 1502| | else 1503| | { 1504| | DispatchQueue.main.async(execute: { 1505| | self.setViewPortOffsets(left: left, top: top, right: right, bottom: bottom) 1506| | }) 1507| | } 1508| | } 1509| | 1510| | /// Resets all custom offsets set via `setViewPortOffsets(...)` method. Allows the chart to again calculate all offsets automatically. 1511| | @objc open func resetViewPortOffsets() 1512| | { 1513| | _customViewPortEnabled = false 1514| | calculateOffsets() 1515| | } 1516| | 1517| | // MARK: - Accessors 1518| | 1519| | /// - Returns: The range of the specified axis. 1520| | @objc open func getAxisRange(axis: YAxis.AxisDependency) -> Double 1521| | { 1522| | if axis == .left 1523| | { 1524| | return leftAxis.axisRange 1525| | } 1526| | else 1527| | { 1528| | return rightAxis.axisRange 1529| | } 1530| | } 1531| | 1532| | /// - Returns: The position (in pixels) the provided Entry has inside the chart view 1533| | @objc open func getPosition(entry e: ChartDataEntry, axis: YAxis.AxisDependency) -> CGPoint 1534| | { 1535| | var vals = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y)) 1536| | 1537| | getTransformer(forAxis: axis).pointValueToPixel(&vals) 1538| | 1539| | return vals 1540| | } 1541| | 1542| | /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). 1543| | @objc open var dragEnabled: Bool 1544| | { 1545| | get 1546| | { 1547| | return _dragXEnabled || _dragYEnabled 1548| | } 1549| | set 1550| | { 1551| | _dragYEnabled = newValue 1552| | _dragXEnabled = newValue 1553| | } 1554| | } 1555| | 1556| | /// is dragging enabled? (moving the chart with the finger) for the chart (this does not affect scaling). 1557| | @objc open var isDragEnabled: Bool 1558| | { 1559| | return dragEnabled 1560| | } 1561| | 1562| | /// is dragging on the X axis enabled? 1563| | @objc open var dragXEnabled: Bool 1564| | { 1565| | get 1566| | { 1567| | return _dragXEnabled 1568| | } 1569| | set 1570| | { 1571| | _dragXEnabled = newValue 1572| | } 1573| | } 1574| | 1575| | /// is dragging on the Y axis enabled? 1576| | @objc open var dragYEnabled: Bool 1577| | { 1578| | get 1579| | { 1580| | return _dragYEnabled 1581| | } 1582| | set 1583| | { 1584| | _dragYEnabled = newValue 1585| | } 1586| | } 1587| | 1588| | /// is scaling enabled? (zooming in and out by gesture) for the chart (this does not affect dragging). 1589| | @objc open func setScaleEnabled(_ enabled: Bool) 1590| | { 1591| | if _scaleXEnabled != enabled || _scaleYEnabled != enabled 1592| | { 1593| | _scaleXEnabled = enabled 1594| | _scaleYEnabled = enabled 1595| | #if !os(tvOS) 1596| | _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled 1597| | #endif 1598| | } 1599| | } 1600| | 1601| | @objc open var scaleXEnabled: Bool 1602| | { 1603| | get 1604| | { 1605| | return _scaleXEnabled 1606| | } 1607| | set 1608| | { 1609| | if _scaleXEnabled != newValue 1610| | { 1611| | _scaleXEnabled = newValue 1612| | #if !os(tvOS) 1613| | _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled 1614| | #endif 1615| | } 1616| | } 1617| | } 1618| | 1619| | @objc open var scaleYEnabled: Bool 1620| | { 1621| | get 1622| | { 1623| | return _scaleYEnabled 1624| | } 1625| | set 1626| | { 1627| | if _scaleYEnabled != newValue 1628| | { 1629| | _scaleYEnabled = newValue 1630| | #if !os(tvOS) 1631| | _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled 1632| | #endif 1633| | } 1634| | } 1635| | } 1636| | 1637| | @objc open var isScaleXEnabled: Bool { return scaleXEnabled } 1638| | @objc open var isScaleYEnabled: Bool { return scaleYEnabled } 1639| | 1640| | /// flag that indicates if double tap zoom is enabled or not 1641| | @objc open var doubleTapToZoomEnabled: Bool 1642| | { 1643| | get 1644| | { 1645| | return _doubleTapToZoomEnabled 1646| | } 1647| | set 1648| | { 1649| | if _doubleTapToZoomEnabled != newValue 1650| | { 1651| | _doubleTapToZoomEnabled = newValue 1652| | _doubleTapGestureRecognizer.isEnabled = _doubleTapToZoomEnabled 1653| | } 1654| | } 1655| | } 1656| | 1657| | /// **default**: true 1658| | /// `true` if zooming via double-tap is enabled `false` ifnot. 1659| | @objc open var isDoubleTapToZoomEnabled: Bool 1660| | { 1661| | return doubleTapToZoomEnabled 1662| | } 1663| | 1664| | /// flag that indicates if highlighting per dragging over a fully zoomed out chart is enabled 1665| | @objc open var highlightPerDragEnabled = true 1666| | 1667| | /// If set to true, highlighting per dragging over a fully zoomed out chart is enabled 1668| | /// You might want to disable this when using inside a `NSUIScrollView` 1669| | /// 1670| | /// **default**: true 1671| | @objc open var isHighlightPerDragEnabled: Bool 1672| | { 1673| | return highlightPerDragEnabled 1674| | } 1675| | 1676| | /// **default**: true 1677| | /// `true` if drawing the grid background is enabled, `false` ifnot. 1678| | @objc open var isDrawGridBackgroundEnabled: Bool 1679| | { 1680| | return drawGridBackgroundEnabled 1681| | } 1682| | 1683| | /// **default**: false 1684| | /// `true` if drawing the borders rectangle is enabled, `false` ifnot. 1685| | @objc open var isDrawBordersEnabled: Bool 1686| | { 1687| | return drawBordersEnabled 1688| | } 1689| | 1690| | /// - Returns: The x and y values in the chart at the given touch point 1691| | /// (encapsulated in a `CGPoint`). This method transforms pixel coordinates to 1692| | /// coordinates / values in the chart. This is the opposite method to 1693| | /// `getPixelsForValues(...)`. 1694| | @objc open func valueForTouchPoint(point pt: CGPoint, axis: YAxis.AxisDependency) -> CGPoint 1695| | { 1696| | return getTransformer(forAxis: axis).valueForTouchPoint(pt) 1697| | } 1698| | 1699| | /// Transforms the given chart values into pixels. This is the opposite 1700| | /// method to `valueForTouchPoint(...)`. 1701| | @objc open func pixelForValues(x: Double, y: Double, axis: YAxis.AxisDependency) -> CGPoint 1702| | { 1703| | return getTransformer(forAxis: axis).pixelForValues(x: x, y: y) 1704| | } 1705| | 1706| | /// - Returns: The Entry object displayed at the touched position of the chart 1707| | @objc open func getEntryByTouchPoint(point pt: CGPoint) -> ChartDataEntry! 1708| | { 1709| | if let h = getHighlightByTouchPoint(pt) 1710| | { 1711| | return data!.entry(for: h) 1712| | } 1713| | return nil 1714| | } 1715| | 1716| | /// - Returns: The DataSet object displayed at the touched position of the chart 1717| | @objc open func getDataSetByTouchPoint(point pt: CGPoint) -> BarLineScatterCandleBubbleChartDataSetProtocol? 1718| | { 1719| | guard let h = getHighlightByTouchPoint(pt) else { 1720| | return nil 1721| | } 1722| | 1723| | return data?[h.dataSetIndex] as? BarLineScatterCandleBubbleChartDataSetProtocol 1724| | } 1725| | 1726| | /// The current x-scale factor 1727| | @objc open var scaleX: CGFloat 1728| | { 1729| | return viewPortHandler.scaleX 1730| | } 1731| | 1732| | /// The current y-scale factor 1733| | @objc open var scaleY: CGFloat 1734| | { 1735| | return viewPortHandler.scaleY 1736| | } 1737| | 1738| | /// if the chart is fully zoomed out, return true 1739| | @objc open var isFullyZoomedOut: Bool { return viewPortHandler.isFullyZoomedOut } 1740| | 1741| | /// - Returns: The y-axis object to the corresponding AxisDependency. In the 1742| | /// horizontal bar-chart, LEFT == top, RIGHT == BOTTOM 1743| | @objc open func getAxis(_ axis: YAxis.AxisDependency) -> YAxis 1744| | { 1745| | if axis == .left 1746| | { 1747| | return leftAxis 1748| | } 1749| | else 1750| | { 1751| | return rightAxis 1752| | } 1753| | } 1754| | 1755| | /// flag that indicates if pinch-zoom is enabled. if true, both x and y axis can be scaled simultaneously with 2 fingers, if false, x and y axis can be scaled separately 1756| | @objc open var pinchZoomEnabled: Bool 1757| | { 1758| | get 1759| | { 1760| | return _pinchZoomEnabled 1761| | } 1762| | set 1763| | { 1764| | if _pinchZoomEnabled != newValue 1765| | { 1766| | _pinchZoomEnabled = newValue 1767| | #if !os(tvOS) 1768| | _pinchGestureRecognizer.isEnabled = _pinchZoomEnabled || _scaleXEnabled || _scaleYEnabled 1769| | #endif 1770| | } 1771| | } 1772| | } 1773| | 1774| | /// **default**: false 1775| | /// `true` if pinch-zoom is enabled, `false` ifnot 1776| | @objc open var isPinchZoomEnabled: Bool { return pinchZoomEnabled } 1777| | 1778| | /// Set an offset in dp that allows the user to drag the chart over it's 1779| | /// bounds on the x-axis. 1780| | @objc open func setDragOffsetX(_ offset: CGFloat) 1781| | { 1782| | viewPortHandler.setDragOffsetX(offset) 1783| | } 1784| | 1785| | /// Set an offset in dp that allows the user to drag the chart over it's 1786| | /// bounds on the y-axis. 1787| | @objc open func setDragOffsetY(_ offset: CGFloat) 1788| | { 1789| | viewPortHandler.setDragOffsetY(offset) 1790| | } 1791| | 1792| | /// `true` if both drag offsets (x and y) are zero or smaller. 1793| | @objc open var hasNoDragOffset: Bool { return viewPortHandler.hasNoDragOffset } 1794| | 1795| | open override var chartYMax: Double 1796| | { 1797| | return max(leftAxis._axisMaximum, rightAxis._axisMaximum) 1798| | } 1799| | 1800| | open override var chartYMin: Double 1801| | { 1802| | return min(leftAxis._axisMinimum, rightAxis._axisMinimum) 1803| | } 1804| | 1805| | /// `true` if either the left or the right or both axes are inverted. 1806| | @objc open var isAnyAxisInverted: Bool 1807| | { 1808| | return leftAxis.isInverted || rightAxis.isInverted 1809| | } 1810| | 1811| | /// flag that indicates if auto scaling on the y axis is enabled. 1812| | /// if yes, the y axis automatically adjusts to the min and max y values of the current x axis range whenever the viewport changes 1813| | @objc open var autoScaleMinMaxEnabled: Bool 1814| | { 1815| | get { return _autoScaleMinMaxEnabled } 1816| | set { _autoScaleMinMaxEnabled = newValue } 1817| | } 1818| | 1819| | /// **default**: false 1820| | /// `true` if auto scaling on the y axis is enabled. 1821| | @objc open var isAutoScaleMinMaxEnabled : Bool { return autoScaleMinMaxEnabled } 1822| | 1823| | /// Sets a minimum width to the specified y axis. 1824| | @objc open func setYAxisMinWidth(_ axis: YAxis.AxisDependency, width: CGFloat) 1825| | { 1826| | if axis == .left 1827| | { 1828| | leftAxis.minWidth = width 1829| | } 1830| | else 1831| | { 1832| | rightAxis.minWidth = width 1833| | } 1834| | } 1835| | 1836| | /// **default**: 0.0 1837| | /// 1838| | /// - Returns: The (custom) minimum width of the specified Y axis. 1839| | @objc open func getYAxisMinWidth(_ axis: YAxis.AxisDependency) -> CGFloat 1840| | { 1841| | if axis == .left 1842| | { 1843| | return leftAxis.minWidth 1844| | } 1845| | else 1846| | { 1847| | return rightAxis.minWidth 1848| | } 1849| | } 1850| | /// Sets a maximum width to the specified y axis. 1851| | /// Zero (0.0) means there's no maximum width 1852| | @objc open func setYAxisMaxWidth(_ axis: YAxis.AxisDependency, width: CGFloat) 1853| | { 1854| | if axis == .left 1855| | { 1856| | leftAxis.maxWidth = width 1857| | } 1858| | else 1859| | { 1860| | rightAxis.maxWidth = width 1861| | } 1862| | } 1863| | 1864| | /// Zero (0.0) means there's no maximum width 1865| | /// 1866| | /// **default**: 0.0 (no maximum specified) 1867| | /// 1868| | /// - Returns: The (custom) maximum width of the specified Y axis. 1869| | @objc open func getYAxisMaxWidth(_ axis: YAxis.AxisDependency) -> CGFloat 1870| | { 1871| | if axis == .left 1872| | { 1873| | return leftAxis.maxWidth 1874| | } 1875| | else 1876| | { 1877| | return rightAxis.maxWidth 1878| | } 1879| | } 1880| | 1881| | /// - Returns the width of the specified y axis. 1882| | @objc open func getYAxisWidth(_ axis: YAxis.AxisDependency) -> CGFloat 1883| | { 1884| | if axis == .left 1885| | { 1886| | return leftAxis.requiredSize().width 1887| | } 1888| | else 1889| | { 1890| | return rightAxis.requiredSize().width 1891| | } 1892| | } 1893| | 1894| | // MARK: - BarLineScatterCandleBubbleChartDataProvider 1895| | 1896| | /// - Returns: The Transformer class that contains all matrices and is 1897| | /// responsible for transforming values into pixels on the screen and 1898| | /// backwards. 1899| | open func getTransformer(forAxis axis: YAxis.AxisDependency) -> Transformer 1900| | { 1901| | if axis == .left 1902| | { 1903| | return _leftAxisTransformer 1904| | } 1905| | else 1906| | { 1907| | return _rightAxisTransformer 1908| | } 1909| | } 1910| | 1911| | /// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled 1912| | open override var maxVisibleCount: Int 1913| | { 1914| | get 1915| | { 1916| | return _maxVisibleCount 1917| | } 1918| | set 1919| | { 1920| | _maxVisibleCount = newValue 1921| | } 1922| | } 1923| | 1924| | open func isInverted(axis: YAxis.AxisDependency) -> Bool 1925| | { 1926| | return getAxis(axis).isInverted 1927| | } 1928| | 1929| | /// The lowest x-index (value on the x-axis) that is still visible on he chart. 1930| | open var lowestVisibleX: Double 1931| | { 1932| | var pt = CGPoint( 1933| | x: viewPortHandler.contentLeft, 1934| | y: viewPortHandler.contentBottom) 1935| | 1936| | getTransformer(forAxis: .left).pixelToValues(&pt) 1937| | 1938| | return max(xAxis._axisMinimum, Double(pt.x)) 1939| | } 1940| | 1941| | /// The highest x-index (value on the x-axis) that is still visible on the chart. 1942| | open var highestVisibleX: Double 1943| | { 1944| | var pt = CGPoint( 1945| | x: viewPortHandler.contentRight, 1946| | y: viewPortHandler.contentBottom) 1947| | 1948| | getTransformer(forAxis: .left).pixelToValues(&pt) 1949| | 1950| | return min(xAxis._axisMaximum, Double(pt.x)) 1951| | } 1952| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Charts/ChartViewBase.swift: 1| |// 2| |// ChartViewBase.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| |// Based on https://github.com/PhilJay/MPAndroidChart/commit/c42b880 12| | 13| |import Foundation 14| |import CoreGraphics 15| | 16| |#if !os(OSX) 17| | import UIKit 18| |#endif 19| | 20| |@objc 21| |public protocol ChartViewDelegate 22| |{ 23| | /// Called when a value has been selected inside the chart. 24| | /// 25| | /// - Parameters: 26| | /// - entry: The selected Entry. 27| | /// - highlight: The corresponding highlight object that contains information about the highlighted position such as dataSetIndex etc. 28| | @objc optional func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) 29| | 30| | /// Called when a user stops panning between values on the chart 31| | @objc optional func chartViewDidEndPanning(_ chartView: ChartViewBase) 32| | 33| | // Called when nothing has been selected or an "un-select" has been made. 34| | @objc optional func chartValueNothingSelected(_ chartView: ChartViewBase) 35| | 36| | // Callbacks when the chart is scaled / zoomed via pinch zoom gesture. 37| | @objc optional func chartScaled(_ chartView: ChartViewBase, scaleX: CGFloat, scaleY: CGFloat) 38| | 39| | // Callbacks when the chart is moved / translated via drag gesture. 40| | @objc optional func chartTranslated(_ chartView: ChartViewBase, dX: CGFloat, dY: CGFloat) 41| | 42| | // Callbacks when Animator stops animating 43| | @objc optional func chartView(_ chartView: ChartViewBase, animatorDidStop animator: Animator) 44| |} 45| | 46| |open class ChartViewBase: NSUIView, ChartDataProvider, AnimatorDelegate 47| |{ 48| | // MARK: - Properties 49| | 50| | /// The default IValueFormatter that has been determined by the chart considering the provided minimum and maximum values. 51| | internal lazy var defaultValueFormatter: ValueFormatter = DefaultValueFormatter(decimals: 0) 52| | 53| | /// object that holds all data that was originally set for the chart, before it was modified or any filtering algorithms had been applied 54| | @objc open var data: ChartData? 55| | { 56| | didSet 57| | { 58| | offsetsCalculated = false 59| | 60| | guard let data = data else { return } 61| | 62| | // calculate how many digits are needed 63| | setupDefaultFormatter(min: data.yMin, max: data.yMax) 64| | 65| | for set in data.dataSets 66| | { 67| | if set.valueFormatter is DefaultValueFormatter 68| | { 69| | set.valueFormatter = defaultValueFormatter 70| | } 71| | } 72| | 73| | // let the chart know there is new data 74| | notifyDataSetChanged() 75| | } 76| | } 77| | 78| | /// Flag that indicates if highlighting per tap (touch) is enabled 79| | private var _highlightPerTapEnabled = true 80| | 81| | /// If set to true, chart continues to scroll after touch up 82| | @objc open var dragDecelerationEnabled = true 83| | 84| | /// if true, units are drawn next to the values in the chart 85| | // TODO: This is used nowhere and can't be used by a consumer. Can we remove this property? 86| | internal var _drawUnitInChart = false 87| | 88| | /// The object representing the labels on the x-axis 89| | @objc open internal(set) lazy var xAxis = XAxis() 90| | 91| | /// The `Description` object of the chart. 92| | @objc open lazy var chartDescription = Description() 93| | 94| | /// The legend object containing all data associated with the legend 95| | @objc open internal(set) lazy var legend = Legend() 96| | 97| | /// delegate to receive chart events 98| | @objc open weak var delegate: ChartViewDelegate? 99| | 100| | /// text that is displayed when the chart is empty 101| | @objc open var noDataText = "No chart data available." 102| | 103| | /// Font to be used for the no data text. 104| 52| @objc open var noDataFont = NSUIFont.systemFont(ofSize: 12) 105| | 106| | /// color of the no data text 107| 52| @objc open var noDataTextColor: NSUIColor = .labelOrBlack 108| | 109| | /// alignment of the no data text 110| 52| @objc open var noDataTextAlignment: NSTextAlignment = .left 111| | 112| | /// The renderer object responsible for rendering / drawing the Legend. 113| | @objc open lazy var legendRenderer = LegendRenderer(viewPortHandler: viewPortHandler, legend: legend) 114| | 115| | /// object responsible for rendering the data 116| | @objc open var renderer: DataRenderer? 117| | 118| | @objc open var highlighter: Highlighter? 119| | 120| | /// The ViewPortHandler of the chart that is responsible for the 121| | /// content area of the chart and its offsets and dimensions. 122| | @objc open internal(set) lazy var viewPortHandler = ViewPortHandler(width: bounds.size.width, height: bounds.size.height) 123| | 124| | /// The animator responsible for animating chart values. 125| | @objc open internal(set) lazy var chartAnimator: Animator = { 126| | let animator = Animator() 127| | animator.delegate = self 128| | return animator 129| | }() 130| | 131| | /// flag that indicates if offsets calculation has already been done or not 132| | private var offsetsCalculated = false 133| | 134| | /// The array of currently highlighted values. This might an empty if nothing is highlighted. 135| 52| @objc open internal(set) var highlighted = [Highlight]() 136| | 137| | /// `true` if drawing the marker is enabled when tapping on values 138| | /// (use the `marker` property to specify a marker) 139| | @objc open var drawMarkers = true 140| | 141| | /// - Returns: `true` if drawing the marker is enabled when tapping on values 142| | /// (use the `marker` property to specify a marker) 143| | @objc open var isDrawMarkersEnabled: Bool { return drawMarkers } 144| | 145| | /// The marker that is displayed when a value is clicked on the chart 146| | @objc open var marker: Marker? 147| | 148| | // TODO: There is no way to modify this value. Should it exist? 149| | private let interceptTouchEvents = false 150| | 151| | /// An extra offset to be appended to the viewport's top 152| | @objc open var extraTopOffset: CGFloat = 0.0 153| | 154| | /// An extra offset to be appended to the viewport's right 155| | @objc open var extraRightOffset: CGFloat = 0.0 156| | 157| | /// An extra offset to be appended to the viewport's bottom 158| | @objc open var extraBottomOffset: CGFloat = 0.0 159| | 160| | /// An extra offset to be appended to the viewport's left 161| | @objc open var extraLeftOffset: CGFloat = 0.0 162| | 163| | @objc open func setExtraOffsets(left: CGFloat, top: CGFloat, right: CGFloat, bottom: CGFloat) 164| | { 165| | extraLeftOffset = left 166| | extraTopOffset = top 167| | extraRightOffset = right 168| | extraBottomOffset = bottom 169| | } 170| | 171| | // MARK: - Initializers 172| | 173| | public override init(frame: CGRect) 174| | { 175| | super.init(frame: frame) 176| | initialize() 177| | } 178| | 179| | public required init?(coder aDecoder: NSCoder) 180| | { 181| | super.init(coder: aDecoder) 182| | initialize() 183| | } 184| | 185| | deinit 186| | { 187| | removeObserver(self, forKeyPath: "bounds") 188| | removeObserver(self, forKeyPath: "frame") 189| | } 190| | 191| | internal func initialize() 192| | { 193| | #if os(iOS) 194| | self.backgroundColor = .clear 195| | #endif 196| | 197| | addObserver(self, forKeyPath: "bounds", options: .new, context: nil) 198| | addObserver(self, forKeyPath: "frame", options: .new, context: nil) 199| | } 200| | 201| | // MARK: - ChartViewBase 202| | 203| | /// Clears the chart from all data (sets it to null) and refreshes it (by calling setNeedsDisplay()). 204| | @objc open func clear() 205| | { 206| | data = nil 207| | offsetsCalculated = false 208| | highlighted.removeAll() 209| | lastHighlighted = nil 210| | 211| | setNeedsDisplay() 212| | } 213| | 214| | /// Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to nil. Also refreshes the chart by calling setNeedsDisplay(). 215| | @objc open func clearValues() 216| | { 217| | data?.clearValues() 218| | setNeedsDisplay() 219| | } 220| | 221| | /// - Returns: `true` if the chart is empty (meaning it's data object is either null or contains no entries). 222| | @objc open func isEmpty() -> Bool 223| | { 224| | return data?.isEmpty ?? true 225| | } 226| | 227| | /// Lets the chart know its underlying data has changed and should perform all necessary recalculations. 228| | /// It is crucial that this method is called everytime data is changed dynamically. Not calling this method can lead to crashes or unexpected behaviour. 229| | @objc open func notifyDataSetChanged() 230| | { 231| | fatalError("notifyDataSetChanged() cannot be called on ChartViewBase") 232| | } 233| | 234| | /// Calculates the offsets of the chart to the border depending on the position of an eventual legend or depending on the length of the y-axis and x-axis labels and their position 235| | internal func calculateOffsets() 236| | { 237| | fatalError("calculateOffsets() cannot be called on ChartViewBase") 238| | } 239| | 240| | /// calcualtes the y-min and y-max value and the y-delta and x-delta value 241| | internal func calcMinMax() 242| | { 243| | fatalError("calcMinMax() cannot be called on ChartViewBase") 244| | } 245| | 246| | /// calculates the required number of digits for the values that might be drawn in the chart (if enabled), and creates the default value formatter 247| | internal func setupDefaultFormatter(min: Double, max: Double) 248| | { 249| | // check if a custom formatter is set or not 250| | var reference = 0.0 251| | 252| | if let data = data , data.entryCount >= 2 253| | { 254| | reference = fabs(max - min) 255| | } 256| | else 257| | { 258| | let absMin = fabs(min) 259| | let absMax = fabs(max) 260| | reference = absMin > absMax ? absMin : absMax 261| | } 262| | 263| | 264| | if let formatter = defaultValueFormatter as? DefaultValueFormatter 265| | { 266| | // setup the formatter with a new number of digits 267| | let digits = reference.decimalPlaces 268| | formatter.decimals = digits 269| | } 270| | } 271| | 272| | open override func draw(_ rect: CGRect) 273| | { 274| | let optionalContext = NSUIGraphicsGetCurrentContext() 275| | guard let context = optionalContext else { return } 276| | 277| | if data === nil && !noDataText.isEmpty 278| | { 279| | context.saveGState() 280| | defer { context.restoreGState() } 281| | 282| | let paragraphStyle = NSMutableParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 283| | paragraphStyle.minimumLineHeight = noDataFont.lineHeight 284| | paragraphStyle.lineBreakMode = .byWordWrapping 285| | paragraphStyle.alignment = noDataTextAlignment 286| | 287| | context.drawMultilineText(noDataText, 288| | at: CGPoint(x: bounds.width / 2.0, y: bounds.height / 2.0), 289| | constrainedTo: bounds.size, 290| | anchor: CGPoint(x: 0.5, y: 0.5), 291| | angleRadians: 0.0, 292| | attributes: [.font: noDataFont, 293| | .foregroundColor: noDataTextColor, 294| | .paragraphStyle: paragraphStyle]) 295| | 296| | return 297| | } 298| | 299| | if !offsetsCalculated 300| | { 301| | calculateOffsets() 302| | offsetsCalculated = true 303| | } 304| | } 305| | 306| | /// Draws the description text in the bottom right corner of the chart (per default) 307| | internal func drawDescription(in context: CGContext) 308| | { 309| | let description = chartDescription 310| | 311| | // check if description should be drawn 312| | guard 313| | description.isEnabled, 314| | let descriptionText = description.text, 315| | !descriptionText.isEmpty 316| | else { return } 317| | 318| | let position = description.position ?? CGPoint(x: bounds.width - viewPortHandler.offsetRight - description.xOffset, 319| | y: bounds.height - viewPortHandler.offsetBottom - description.yOffset - description.font.lineHeight) 320| | 321| | let attrs: [NSAttributedString.Key : Any] = [ 322| | .font: description.font, 323| | .foregroundColor: description.textColor 324| | ] 325| | 326| | context.drawText(descriptionText, 327| | at: position, 328| | align: description.textAlign, 329| | attributes: attrs) 330| | } 331| | 332| | // MARK: - Accessibility 333| | 334| | open override func accessibilityChildren() -> [Any]? { 335| | return renderer?.accessibleChartElements 336| | } 337| | 338| | // MARK: - Highlighting 339| | 340| | /// Set this to false to prevent values from being highlighted by tap gesture. 341| | /// Values can still be highlighted via drag or programmatically. 342| | /// **default**: true 343| | @objc open var highlightPerTapEnabled: Bool 344| | { 345| | get { return _highlightPerTapEnabled } 346| | set { _highlightPerTapEnabled = newValue } 347| | } 348| | 349| | /// `true` if values can be highlighted via tap gesture, `false` ifnot. 350| | @objc open var isHighLightPerTapEnabled: Bool 351| | { 352| | return highlightPerTapEnabled 353| | } 354| | 355| | /// Checks if the highlight array is null, has a length of zero or if the first object is null. 356| | /// 357| | /// - Returns: `true` if there are values to highlight, `false` ifthere are no values to highlight. 358| | @objc open func valuesToHighlight() -> Bool 359| | { 360| | return !highlighted.isEmpty 361| | } 362| | 363| | /// Highlights the values at the given indices in the given DataSets. Provide 364| | /// null or an empty array to undo all highlighting. 365| | /// This should be used to programmatically highlight values. 366| | /// This method *will not* call the delegate. 367| | @objc open func highlightValues(_ highs: [Highlight]?) 368| | { 369| | // set the indices to highlight 370| | highlighted = highs ?? [] 371| | 372| | lastHighlighted = highlighted.isEmpty 373| | ? nil 374| | : highlighted[0] 375| | 376| | 377| | // redraw the chart 378| | setNeedsDisplay() 379| | } 380| | 381| | /// Highlights any y-value at the given x-value in the given DataSet. 382| | /// Provide -1 as the dataSetIndex to undo all highlighting. 383| | /// This method will call the delegate. 384| | /// 385| | /// - Parameters: 386| | /// - x: The x-value to highlight 387| | /// - dataSetIndex: The dataset index to search in 388| | /// - dataIndex: The data index to search in (only used in CombinedChartView currently) 389| | @objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1) 390| | { 391| | highlightValue(x: x, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true) 392| | } 393| | 394| | /// Highlights the value at the given x-value and y-value in the given DataSet. 395| | /// Provide -1 as the dataSetIndex to undo all highlighting. 396| | /// This method will call the delegate. 397| | /// 398| | /// - Parameters: 399| | /// - x: The x-value to highlight 400| | /// - y: The y-value to highlight. Supply `NaN` for "any" 401| | /// - dataSetIndex: The dataset index to search in 402| | /// - dataIndex: The data index to search in (only used in CombinedChartView currently) 403| | @objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1) 404| | { 405| | highlightValue(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: true) 406| | } 407| | 408| | /// Highlights any y-value at the given x-value in the given DataSet. 409| | /// Provide -1 as the dataSetIndex to undo all highlighting. 410| | /// 411| | /// - Parameters: 412| | /// - x: The x-value to highlight 413| | /// - dataSetIndex: The dataset index to search in 414| | /// - dataIndex: The data index to search in (only used in CombinedChartView currently) 415| | /// - callDelegate: Should the delegate be called for this change 416| | @objc open func highlightValue(x: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool) 417| | { 418| | highlightValue(x: x, y: .nan, dataSetIndex: dataSetIndex, dataIndex: dataIndex, callDelegate: callDelegate) 419| | } 420| | 421| | /// Highlights the value at the given x-value and y-value in the given DataSet. 422| | /// Provide -1 as the dataSetIndex to undo all highlighting. 423| | /// 424| | /// - Parameters: 425| | /// - x: The x-value to highlight 426| | /// - y: The y-value to highlight. Supply `NaN` for "any" 427| | /// - dataSetIndex: The dataset index to search in 428| | /// - dataIndex: The data index to search in (only used in CombinedChartView currently) 429| | /// - callDelegate: Should the delegate be called for this change 430| | @objc open func highlightValue(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1, callDelegate: Bool) 431| | { 432| | guard let data = data else 433| | { 434| | Swift.print("Value not highlighted because data is nil") 435| | return 436| | } 437| | 438| | if data.indices.contains(dataSetIndex) 439| | { 440| | highlightValue(Highlight(x: x, y: y, dataSetIndex: dataSetIndex, dataIndex: dataIndex), callDelegate: callDelegate) 441| | } 442| | else 443| | { 444| | highlightValue(nil, callDelegate: callDelegate) 445| | } 446| | } 447| | 448| | /// Highlights the values represented by the provided Highlight object 449| | /// This method *will not* call the delegate. 450| | /// 451| | /// - Parameters: 452| | /// - highlight: contains information about which entry should be highlighted 453| | @objc open func highlightValue(_ highlight: Highlight?) 454| | { 455| | highlightValue(highlight, callDelegate: false) 456| | } 457| | 458| | /// Highlights the value selected by touch gesture. 459| | @objc open func highlightValue(_ highlight: Highlight?, callDelegate: Bool) 460| | { 461| | var high = highlight 462| | guard 463| | let h = high, 464| | let entry = data?.entry(for: h) 465| | else 466| | { 467| | high = nil 468| | highlighted.removeAll(keepingCapacity: false) 469| | if callDelegate 470| | { 471| | delegate?.chartValueNothingSelected?(self) 472| | } 473| | return 474| | } 475| | 476| | // set the indices to highlight 477| | highlighted = [h] 478| | 479| | if callDelegate 480| | { 481| | // notify the listener 482| | delegate?.chartValueSelected?(self, entry: entry, highlight: h) 483| | } 484| | 485| | // redraw the chart 486| | setNeedsDisplay() 487| | } 488| | 489| | /// - Returns: The Highlight object (contains x-index and DataSet index) of the 490| | /// selected value at the given touch point inside the Line-, Scatter-, or 491| | /// CandleStick-Chart. 492| | @objc open func getHighlightByTouchPoint(_ pt: CGPoint) -> Highlight? 493| | { 494| | guard data != nil else 495| | { 496| | Swift.print("Can't select by touch. No data set.") 497| | return nil 498| | } 499| | 500| | return self.highlighter?.getHighlight(x: pt.x, y: pt.y) 501| | } 502| | 503| | /// The last value that was highlighted via touch. 504| | @objc open var lastHighlighted: Highlight? 505| | 506| | // MARK: - Markers 507| | 508| | /// draws all MarkerViews on the highlighted positions 509| | internal func drawMarkers(context: CGContext) 510| | { 511| | // if there is no marker view or drawing marker is disabled 512| | guard 513| | let marker = marker, 514| | isDrawMarkersEnabled, 515| | valuesToHighlight() 516| | else { return } 517| | 518| | for highlight in highlighted 519| | { 520| | guard 521| | let set = data?[highlight.dataSetIndex], 522| | let e = data?.entry(for: highlight) 523| | else { continue } 524| | 525| | let entryIndex = set.entryIndex(entry: e) 526| | guard entryIndex <= Int(Double(set.entryCount) * chartAnimator.phaseX) else { continue } 527| | 528| | let pos = getMarkerPosition(highlight: highlight) 529| | 530| | // check bounds 531| | guard viewPortHandler.isInBounds(x: pos.x, y: pos.y) else { continue } 532| | 533| | // callbacks to update the content 534| | marker.refreshContent(entry: e, highlight: highlight) 535| | 536| | // draw the marker 537| | marker.draw(context: context, point: pos) 538| | } 539| | } 540| | 541| | /// - Returns: The actual position in pixels of the MarkerView for the given Entry in the given DataSet. 542| | @objc open func getMarkerPosition(highlight: Highlight) -> CGPoint 543| | { 544| | return CGPoint(x: highlight.drawX, y: highlight.drawY) 545| | } 546| | 547| | // MARK: - Animation 548| | 549| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 550| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 551| | /// 552| | /// - Parameters: 553| | /// - xAxisDuration: duration for animating the x axis 554| | /// - yAxisDuration: duration for animating the y axis 555| | /// - easingX: an easing function for the animation on the x axis 556| | /// - easingY: an easing function for the animation on the y axis 557| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingX: ChartEasingFunctionBlock?, easingY: ChartEasingFunctionBlock?) 558| | { 559| | chartAnimator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingX: easingX, easingY: easingY) 560| | } 561| | 562| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 563| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 564| | /// 565| | /// - Parameters: 566| | /// - xAxisDuration: duration for animating the x axis 567| | /// - yAxisDuration: duration for animating the y axis 568| | /// - easingOptionX: the easing function for the animation on the x axis 569| | /// - easingOptionY: the easing function for the animation on the y axis 570| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOptionX: ChartEasingOption, easingOptionY: ChartEasingOption) 571| | { 572| | chartAnimator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOptionX: easingOptionX, easingOptionY: easingOptionY) 573| | } 574| | 575| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 576| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 577| | /// 578| | /// - Parameters: 579| | /// - xAxisDuration: duration for animating the x axis 580| | /// - yAxisDuration: duration for animating the y axis 581| | /// - easing: an easing function for the animation 582| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 583| | { 584| | chartAnimator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easing: easing) 585| | } 586| | 587| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 588| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 589| | /// 590| | /// - Parameters: 591| | /// - xAxisDuration: duration for animating the x axis 592| | /// - yAxisDuration: duration for animating the y axis 593| | /// - easingOption: the easing function for the animation 594| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval, easingOption: ChartEasingOption) 595| | { 596| | chartAnimator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration, easingOption: easingOption) 597| | } 598| | 599| | /// Animates the drawing / rendering of the chart on both x- and y-axis with the specified animation time. 600| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 601| | /// 602| | /// - Parameters: 603| | /// - xAxisDuration: duration for animating the x axis 604| | /// - yAxisDuration: duration for animating the y axis 605| | @objc open func animate(xAxisDuration: TimeInterval, yAxisDuration: TimeInterval) 606| | { 607| | chartAnimator.animate(xAxisDuration: xAxisDuration, yAxisDuration: yAxisDuration) 608| | } 609| | 610| | /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. 611| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 612| | /// 613| | /// - Parameters: 614| | /// - xAxisDuration: duration for animating the x axis 615| | /// - easing: an easing function for the animation 616| | @objc open func animate(xAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 617| | { 618| | chartAnimator.animate(xAxisDuration: xAxisDuration, easing: easing) 619| | } 620| | 621| | /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. 622| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 623| | /// 624| | /// - Parameters: 625| | /// - xAxisDuration: duration for animating the x axis 626| | /// - easingOption: the easing function for the animation 627| | @objc open func animate(xAxisDuration: TimeInterval, easingOption: ChartEasingOption) 628| | { 629| | chartAnimator.animate(xAxisDuration: xAxisDuration, easingOption: easingOption) 630| | } 631| | 632| | /// Animates the drawing / rendering of the chart the x-axis with the specified animation time. 633| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 634| | /// 635| | /// - Parameters: 636| | /// - xAxisDuration: duration for animating the x axis 637| | @objc open func animate(xAxisDuration: TimeInterval) 638| | { 639| | chartAnimator.animate(xAxisDuration: xAxisDuration) 640| | } 641| | 642| | /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. 643| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 644| | /// 645| | /// - Parameters: 646| | /// - yAxisDuration: duration for animating the y axis 647| | /// - easing: an easing function for the animation 648| | @objc open func animate(yAxisDuration: TimeInterval, easing: ChartEasingFunctionBlock?) 649| | { 650| | chartAnimator.animate(yAxisDuration: yAxisDuration, easing: easing) 651| | } 652| | 653| | /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. 654| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 655| | /// 656| | /// - Parameters: 657| | /// - yAxisDuration: duration for animating the y axis 658| | /// - easingOption: the easing function for the animation 659| | @objc open func animate(yAxisDuration: TimeInterval, easingOption: ChartEasingOption) 660| | { 661| | chartAnimator.animate(yAxisDuration: yAxisDuration, easingOption: easingOption) 662| | } 663| | 664| | /// Animates the drawing / rendering of the chart the y-axis with the specified animation time. 665| | /// If `animate(...)` is called, no further calling of `invalidate()` is necessary to refresh the chart. 666| | /// 667| | /// - Parameters: 668| | /// - yAxisDuration: duration for animating the y axis 669| | @objc open func animate(yAxisDuration: TimeInterval) 670| | { 671| | chartAnimator.animate(yAxisDuration: yAxisDuration) 672| | } 673| | 674| | // MARK: - Accessors 675| | 676| | /// The current y-max value across all DataSets 677| | open var chartYMax: Double 678| | { 679| | return data?.yMax ?? 0.0 680| | } 681| | 682| | /// The current y-min value across all DataSets 683| | open var chartYMin: Double 684| | { 685| | return data?.yMin ?? 0.0 686| | } 687| | 688| | open var chartXMax: Double 689| | { 690| | return xAxis._axisMaximum 691| | } 692| | 693| | open var chartXMin: Double 694| | { 695| | return xAxis._axisMinimum 696| | } 697| | 698| | open var xRange: Double 699| | { 700| | return xAxis.axisRange 701| | } 702| | 703| | /// - Note: (Equivalent of getCenter() in MPAndroidChart, as center is already a standard in iOS that returns the center point relative to superview, and MPAndroidChart returns relative to self)* 704| | /// The center point of the chart (the whole View) in pixels. 705| | @objc open var midPoint: CGPoint 706| | { 707| | let bounds = self.bounds 708| | return CGPoint(x: bounds.origin.x + bounds.size.width / 2.0, y: bounds.origin.y + bounds.size.height / 2.0) 709| | } 710| | 711| | /// The center of the chart taking offsets under consideration. (returns the center of the content rectangle) 712| | open var centerOffsets: CGPoint 713| | { 714| | return viewPortHandler.contentCenter 715| | } 716| | 717| | /// The rectangle that defines the borders of the chart-value surface (into which the actual values are drawn). 718| | @objc open var contentRect: CGRect 719| | { 720| | return viewPortHandler.contentRect 721| | } 722| | 723| | /// - Returns: The bitmap that represents the chart. 724| | @objc open func getChartImage(transparent: Bool) -> NSUIImage? 725| | { 726| | NSUIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque || !transparent, NSUIMainScreen()?.nsuiScale ?? 1.0) 727| | 728| | guard let context = NSUIGraphicsGetCurrentContext() 729| | else { return nil } 730| | 731| | let rect = CGRect(origin: .zero, size: bounds.size) 732| | 733| | if isOpaque || !transparent 734| | { 735| | // Background color may be partially transparent, we must fill with white if we want to output an opaque image 736| | context.setFillColor(NSUIColor.white.cgColor) 737| | context.fill(rect) 738| | 739| | if let backgroundColor = self.backgroundColor 740| | { 741| | context.setFillColor(backgroundColor.cgColor) 742| | context.fill(rect) 743| | } 744| | } 745| | 746| | nsuiLayer?.render(in: context) 747| | 748| | let image = NSUIGraphicsGetImageFromCurrentImageContext() 749| | 750| | NSUIGraphicsEndImageContext() 751| | 752| | return image 753| | } 754| | 755| | public enum ImageFormat 756| | { 757| | case jpeg 758| | case png 759| | } 760| | 761| | /// Saves the current chart state with the given name to the given path on 762| | /// the sdcard leaving the path empty "" will put the saved file directly on 763| | /// the SD card chart is saved as a PNG image, example: 764| | /// saveToPath("myfilename", "foldername1/foldername2") 765| | /// 766| | /// - Parameters: 767| | /// - to: path to the image to save 768| | /// - format: the format to save 769| | /// - compressionQuality: compression quality for lossless formats (JPEG) 770| | /// - Returns: `true` if the image was saved successfully 771| | open func save(to path: String, format: ImageFormat, compressionQuality: Double) -> Bool 772| | { 773| | guard let image = getChartImage(transparent: format != .jpeg) else { return false } 774| | 775| | let imageData: Data? 776| | switch (format) 777| | { 778| | case .png: imageData = NSUIImagePNGRepresentation(image) 779| | case .jpeg: imageData = NSUIImageJPEGRepresentation(image, CGFloat(compressionQuality)) 780| | } 781| | 782| | guard let data = imageData else { return false } 783| | 784| | do 785| | { 786| | try data.write(to: URL(fileURLWithPath: path), options: .atomic) 787| | } 788| | catch 789| | { 790| | return false 791| | } 792| | 793| | return true 794| | } 795| | 796| 52| internal var _viewportJobs = [ViewPortJob]() 797| | 798| | open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) 799| | { 800| | if keyPath == "bounds" || keyPath == "frame" 801| | { 802| | let bounds = self.bounds 803| | 804| | if ((bounds.size.width != viewPortHandler.chartWidth || 805| | bounds.size.height != viewPortHandler.chartHeight)) 806| | { 807| | viewPortHandler.setChartDimens(width: bounds.size.width, height: bounds.size.height) 808| | 809| | // This may cause the chart view to mutate properties affecting the view port -- lets do this 810| | // before we try to run any pending jobs on the view port itself 811| | notifyDataSetChanged() 812| | 813| | // Finish any pending viewport changes 814| | while (!_viewportJobs.isEmpty) 815| | { 816| | let job = _viewportJobs.remove(at: 0) 817| | job.doJob() 818| | } 819| | } 820| | } 821| | } 822| | 823| | @objc open func removeViewportJob(_ job: ViewPortJob) 824| | { 825| | if let index = _viewportJobs.firstIndex(where: { $0 === job }) 826| | { 827| | _viewportJobs.remove(at: index) 828| | } 829| | } 830| | 831| | @objc open func clearAllViewportJobs() 832| | { 833| | _viewportJobs.removeAll(keepingCapacity: false) 834| | } 835| | 836| | @objc open func addViewportJob(_ job: ViewPortJob) 837| | { 838| | if viewPortHandler.hasChartDimens 839| | { 840| | job.doJob() 841| | } 842| | else 843| | { 844| | _viewportJobs.append(job) 845| | } 846| | } 847| | 848| | /// **default**: true 849| | /// `true` if chart continues to scroll after touch up, `false` ifnot. 850| | @objc open var isDragDecelerationEnabled: Bool 851| | { 852| | return dragDecelerationEnabled 853| | } 854| | 855| | /// Deceleration friction coefficient in [0 ; 1] interval, higher values indicate that speed will decrease slowly, for example if it set to 0, it will stop immediately. 856| | /// 1 is an invalid value, and will be converted to 0.999 automatically. 857| | @objc open var dragDecelerationFrictionCoef: CGFloat 858| | { 859| | get 860| | { 861| | return _dragDecelerationFrictionCoef 862| | } 863| | set 864| | { 865| | switch newValue 866| | { 867| | case ..<0.0: _dragDecelerationFrictionCoef = 0 868| | case 1.0...: _dragDecelerationFrictionCoef = 0.999 869| | default: _dragDecelerationFrictionCoef = newValue 870| | } 871| | } 872| | } 873| | private var _dragDecelerationFrictionCoef: CGFloat = 0.9 874| | 875| | /// The maximum distance in screen pixels away from an entry causing it to highlight. 876| | /// **default**: 500.0 877| | open var maxHighlightDistance: CGFloat = 500.0 878| | 879| | /// the number of maximum visible drawn values on the chart only active when `drawValuesEnabled` is enabled 880| | open var maxVisibleCount: Int 881| | { 882| | return .max 883| | } 884| | 885| | // MARK: - AnimatorDelegate 886| | 887| | open func animatorUpdated(_ chartAnimator: Animator) 888| | { 889| | setNeedsDisplay() 890| | } 891| | 892| | open func animatorStopped(_ chartAnimator: Animator) 893| | { 894| | delegate?.chartView?(self, animatorDidStop: chartAnimator) 895| | } 896| | 897| | // MARK: - Touches 898| | 899| | open override func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) 900| | { 901| | if !interceptTouchEvents 902| | { 903| | super.nsuiTouchesBegan(touches, withEvent: event) 904| | } 905| | } 906| | 907| | open override func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) 908| | { 909| | if !interceptTouchEvents 910| | { 911| | super.nsuiTouchesMoved(touches, withEvent: event) 912| | } 913| | } 914| | 915| | open override func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) 916| | { 917| | if !interceptTouchEvents 918| | { 919| | super.nsuiTouchesEnded(touches, withEvent: event) 920| | } 921| | } 922| | 923| | open override func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) 924| | { 925| | if !interceptTouchEvents 926| | { 927| | super.nsuiTouchesCancelled(touches, withEvent: event) 928| | } 929| | } 930| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Charts/PieChartView.swift: 1| |// 2| |// PieChartView.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if !os(OSX) 16| | import UIKit 17| |#endif 18| | 19| |/// View that represents a pie chart. Draws cake like slices. 20| |open class PieChartView: PieRadarChartViewBase 21| |{ 22| | /// rect object that represents the bounds of the piechart, needed for drawing the circle 23| 6| private var _circleBox = CGRect() 24| | 25| | /// flag indicating if entry labels should be drawn or not 26| | private var _drawEntryLabelsEnabled = true 27| | 28| | /// array that holds the width of each pie-slice in degrees 29| 6| private var _drawAngles = [CGFloat]() 30| | 31| | /// array that holds the absolute angle in degrees of each slice 32| 6| private var _absoluteAngles = [CGFloat]() 33| | 34| | /// if true, the hole inside the chart will be drawn 35| | private var _drawHoleEnabled = true 36| | 37| 6| private var _holeColor: NSUIColor? = NSUIColor.white 38| | 39| | /// Sets the color the entry labels are drawn with. 40| 6| private var _entryLabelColor: NSUIColor? = NSUIColor.white 41| | 42| | /// Sets the font the entry labels are drawn with. 43| 6| private var _entryLabelFont: NSUIFont? = NSUIFont(name: "HelveticaNeue", size: 13.0) 44| | 45| | /// if true, the hole will see-through to the inner tips of the slices 46| | private var _drawSlicesUnderHoleEnabled = false 47| | 48| | /// if true, the values inside the piechart are drawn as percent values 49| | private var _usePercentValuesEnabled = false 50| | 51| | /// variable for the text that is drawn in the center of the pie-chart 52| | private var _centerAttributedText: NSAttributedString? 53| | 54| | /// the offset on the x- and y-axis the center text has in dp. 55| 6| private var _centerTextOffset: CGPoint = CGPoint() 56| | 57| | /// indicates the size of the hole in the center of the piechart 58| | /// 59| | /// **default**: `0.5` 60| 6| private var _holeRadiusPercent = CGFloat(0.5) 61| | 62| 6| private var _transparentCircleColor: NSUIColor? = NSUIColor(white: 1.0, alpha: 105.0/255.0) 63| | 64| | /// the radius of the transparent circle next to the chart-hole in the center 65| 6| private var _transparentCircleRadiusPercent = CGFloat(0.55) 66| | 67| | /// if enabled, centertext is drawn 68| | private var _drawCenterTextEnabled = true 69| | 70| | private var _centerTextRadiusPercent: CGFloat = 1.0 71| | 72| | /// maximum angle for this pie 73| | private var _maxAngle: CGFloat = 360.0 74| | 75| | public override init(frame: CGRect) 76| | { 77| | super.init(frame: frame) 78| | } 79| | 80| | public required init?(coder aDecoder: NSCoder) 81| | { 82| | super.init(coder: aDecoder) 83| | } 84| | 85| | internal override func initialize() 86| | { 87| | super.initialize() 88| | 89| | renderer = PieChartRenderer(chart: self, animator: chartAnimator, viewPortHandler: viewPortHandler) 90| | 91| | self.highlighter = PieHighlighter(chart: self) 92| | } 93| | 94| | open override func draw(_ rect: CGRect) 95| | { 96| | super.draw(rect) 97| | 98| | if data === nil 99| | { 100| | return 101| | } 102| | 103| | let optionalContext = NSUIGraphicsGetCurrentContext() 104| | guard let context = optionalContext, let renderer = renderer else 105| | { 106| | return 107| | } 108| | 109| | renderer.drawData(context: context) 110| | 111| | if (valuesToHighlight()) 112| | { 113| | renderer.drawHighlighted(context: context, indices: highlighted) 114| | } 115| | 116| | renderer.drawExtras(context: context) 117| | 118| | renderer.drawValues(context: context) 119| | 120| | legendRenderer.renderLegend(context: context) 121| | 122| | drawDescription(in: context) 123| | 124| | drawMarkers(context: context) 125| | } 126| | 127| | /// if width is larger than height 128| | private var widthLarger: Bool 129| | { 130| | return viewPortHandler.contentRect.orientation == .landscape 131| | } 132| | 133| | /// adjusted radius. Use diameter when it's half pie and width is larger 134| | private var adjustedRadius: CGFloat 135| | { 136| | return maxAngle <= 180 && widthLarger ? diameter : diameter / 2.0 137| | } 138| | 139| | /// true centerOffsets considering half pie & width is larger 140| | private func adjustedCenterOffsets() -> CGPoint 141| | { 142| | var c = self.centerOffsets 143| | c.y = maxAngle <= 180 && widthLarger ? c.y + adjustedRadius / 2 : c.y 144| | return c 145| | } 146| | 147| | internal override func calculateOffsets() 148| | { 149| | super.calculateOffsets() 150| | 151| | // prevent nullpointer when no data set 152| | if data === nil 153| | { 154| | return 155| | } 156| | 157| | let radius = adjustedRadius 158| | 159| | let c = adjustedCenterOffsets() 160| | 161| | let shift = (data as? PieChartData)?.dataSet?.selectionShift ?? 0.0 162| | 163| | // create the circle box that will contain the pie-chart (the bounds of the pie-chart) 164| | _circleBox.origin.x = (c.x - radius) + shift 165| | _circleBox.origin.y = (c.y - radius) + shift 166| | _circleBox.size.width = radius * 2 - shift * 2.0 167| | _circleBox.size.height = radius * 2 - shift * 2.0 168| | 169| | } 170| | 171| | internal override func calcMinMax() 172| | { 173| | calcAngles() 174| | } 175| | 176| | @objc open override func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat 177| | { 178| | let c = adjustedCenterOffsets() 179| | 180| | let tx = Double(x - c.x) 181| | let ty = Double(y - c.y) 182| | let length = sqrt(tx * tx + ty * ty) 183| | let r = acos(ty / length) 184| | 185| | var angle = r.RAD2DEG 186| | 187| | if x > c.x 188| | { 189| | angle = 360.0 - angle 190| | } 191| | 192| | // add 90° because chart starts EAST 193| | angle = angle + 90.0 194| | 195| | // neutralize overflow 196| | if angle > 360.0 197| | { 198| | angle = angle - 360.0 199| | } 200| | 201| | return CGFloat(angle) 202| | } 203| | 204| | /// - Returns: The distance of a certain point on the chart to the center of the chart. 205| | @objc open override func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat 206| | { 207| | let c = adjustedCenterOffsets() 208| | var dist = CGFloat(0.0) 209| | 210| | let xDist = x > c.x ? x - c.x : c.x - x 211| | let yDist = y > c.y ? y - c.y : c.y - y 212| | 213| | // pythagoras 214| | dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0)) 215| | 216| | return dist 217| | } 218| | 219| | open override func getMarkerPosition(highlight: Highlight) -> CGPoint 220| | { 221| | let center = self.centerCircleBox 222| | var r = self.radius 223| | 224| | var off = r / 10.0 * 3.6 225| | 226| | if self.isDrawHoleEnabled 227| | { 228| | off = (r - (r * self.holeRadiusPercent)) / 2.0 229| | } 230| | 231| | r -= off // offset to keep things inside the chart 232| | 233| | let rotationAngle = self.rotationAngle 234| | 235| | let entryIndex = Int(highlight.x) 236| | 237| | // offset needed to center the drawn text in the slice 238| | let offset = drawAngles[entryIndex] / 2.0 239| | 240| | // calculate the text position 241| | let x = (r * cos(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(chartAnimator.phaseY)).DEG2RAD) + center.x) 242| | let y = (r * sin(((rotationAngle + absoluteAngles[entryIndex] - offset) * CGFloat(chartAnimator.phaseY)).DEG2RAD) + center.y) 243| | 244| | return CGPoint(x: x, y: y) 245| | } 246| | 247| | /// calculates the needed angles for the chart slices 248| | private func calcAngles() 249| | { 250| | _drawAngles = [CGFloat]() 251| | _absoluteAngles = [CGFloat]() 252| | 253| | guard let data = data else { return } 254| | 255| | let entryCount = data.entryCount 256| | 257| | _drawAngles.reserveCapacity(entryCount) 258| | _absoluteAngles.reserveCapacity(entryCount) 259| | 260| | let yValueSum = (data as! PieChartData).yValueSum 261| | 262| | var cnt = 0 263| | 264| | for set in data 265| | { 266| | for j in 0 ..< set.entryCount 267| | { 268| | guard let e = set.entryForIndex(j) else { continue } 269| | 270| | _drawAngles.append(calcAngle(value: abs(e.y), yValueSum: yValueSum)) 271| | 272| | if cnt == 0 273| | { 274| | _absoluteAngles.append(_drawAngles[cnt]) 275| | } 276| | else 277| | { 278| | _absoluteAngles.append(_absoluteAngles[cnt - 1] + _drawAngles[cnt]) 279| | } 280| | 281| | cnt += 1 282| | } 283| | } 284| | } 285| | 286| | /// Checks if the given index is set to be highlighted. 287| | @objc open func needsHighlight(index: Int) -> Bool 288| | { 289| | return highlighted.contains { Int($0.x) == index } 290| | } 291| | 292| | /// calculates the needed angle for a given value 293| | private func calcAngle(_ value: Double) -> CGFloat 294| | { 295| | return calcAngle(value: value, yValueSum: (data as! PieChartData).yValueSum) 296| | } 297| | 298| | /// calculates the needed angle for a given value 299| | private func calcAngle(value: Double, yValueSum: Double) -> CGFloat 300| | { 301| | return CGFloat(value) / CGFloat(yValueSum) * _maxAngle 302| | } 303| | 304| | /// This will throw an exception, PieChart has no XAxis object. 305| | open override var xAxis: XAxis 306| | { 307| | get { fatalError("PieChart has no XAxis") } 308| | set { fatalError("PieChart has no XAxis") } 309| | } 310| | 311| | open override func indexForAngle(_ angle: CGFloat) -> Int 312| | { 313| | // TODO: Return nil instead of -1 314| | // take the current angle of the chart into consideration 315| | let a = (angle - self.rotationAngle).normalizedAngle 316| | return _absoluteAngles.firstIndex { $0 > a } ?? -1 317| | } 318| | 319| | /// - Returns: The index of the DataSet this x-index belongs to. 320| | @objc open func dataSetIndexForIndex(_ xValue: Double) -> Int 321| | { 322| | // TODO: Return nil instead of -1 323| | return data?.dataSets.firstIndex { 324| | $0.entryForXValue(xValue, closestToY: .nan) != nil 325| | } ?? -1 326| | } 327| | 328| | /// - Returns: An integer array of all the different angles the chart slices 329| | /// have the angles in the returned array determine how much space (of 360°) 330| | /// each slice takes 331| | @objc open var drawAngles: [CGFloat] 332| | { 333| | return _drawAngles 334| | } 335| | 336| | /// - Returns: The absolute angles of the different chart slices (where the 337| | /// slices end) 338| | @objc open var absoluteAngles: [CGFloat] 339| | { 340| | return _absoluteAngles 341| | } 342| | 343| | /// The color for the hole that is drawn in the center of the PieChart (if enabled). 344| | /// 345| | /// - Note: Use holeTransparent with holeColor = nil to make the hole transparent.* 346| | @objc open var holeColor: NSUIColor? 347| | { 348| | get 349| | { 350| | return _holeColor 351| | } 352| | set 353| | { 354| | _holeColor = newValue 355| | setNeedsDisplay() 356| | } 357| | } 358| | 359| | /// if true, the hole will see-through to the inner tips of the slices 360| | /// 361| | /// **default**: `false` 362| | @objc open var drawSlicesUnderHoleEnabled: Bool 363| | { 364| | get 365| | { 366| | return _drawSlicesUnderHoleEnabled 367| | } 368| | set 369| | { 370| | _drawSlicesUnderHoleEnabled = newValue 371| | setNeedsDisplay() 372| | } 373| | } 374| | 375| | /// `true` if the inner tips of the slices are visible behind the hole, `false` if not. 376| | @objc open var isDrawSlicesUnderHoleEnabled: Bool 377| | { 378| | return drawSlicesUnderHoleEnabled 379| | } 380| | 381| | /// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot 382| | @objc open var drawHoleEnabled: Bool 383| | { 384| | get 385| | { 386| | return _drawHoleEnabled 387| | } 388| | set 389| | { 390| | _drawHoleEnabled = newValue 391| | setNeedsDisplay() 392| | } 393| | } 394| | 395| | /// `true` if the hole in the center of the pie-chart is set to be visible, `false` ifnot 396| | @objc open var isDrawHoleEnabled: Bool 397| | { 398| | get 399| | { 400| | return drawHoleEnabled 401| | } 402| | } 403| | 404| | /// the text that is displayed in the center of the pie-chart 405| | @objc open var centerText: String? 406| | { 407| | get 408| | { 409| | return self.centerAttributedText?.string 410| | } 411| | set 412| | { 413| | var attrString: NSMutableAttributedString? 414| | if newValue == nil 415| | { 416| | attrString = nil 417| | } 418| | else 419| | { 420| | let paragraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle 421| | paragraphStyle.lineBreakMode = .byTruncatingTail 422| | paragraphStyle.alignment = .center 423| | 424| | attrString = NSMutableAttributedString(string: newValue!) 425| | attrString?.setAttributes([ 426| | .foregroundColor: NSUIColor.labelOrBlack, 427| | .font: NSUIFont.systemFont(ofSize: 12.0), 428| | .paragraphStyle: paragraphStyle 429| | ], range: NSMakeRange(0, attrString!.length)) 430| | } 431| | self.centerAttributedText = attrString 432| | } 433| | } 434| | 435| | /// the text that is displayed in the center of the pie-chart 436| | @objc open var centerAttributedText: NSAttributedString? 437| | { 438| | get 439| | { 440| | return _centerAttributedText 441| | } 442| | set 443| | { 444| | _centerAttributedText = newValue 445| | setNeedsDisplay() 446| | } 447| | } 448| | 449| | /// Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0 450| | @objc open var centerTextOffset: CGPoint 451| | { 452| | get 453| | { 454| | return _centerTextOffset 455| | } 456| | set 457| | { 458| | _centerTextOffset = newValue 459| | setNeedsDisplay() 460| | } 461| | } 462| | 463| | /// `true` if drawing the center text is enabled 464| | @objc open var drawCenterTextEnabled: Bool 465| | { 466| | get 467| | { 468| | return _drawCenterTextEnabled 469| | } 470| | set 471| | { 472| | _drawCenterTextEnabled = newValue 473| | setNeedsDisplay() 474| | } 475| | } 476| | 477| | /// `true` if drawing the center text is enabled 478| | @objc open var isDrawCenterTextEnabled: Bool 479| | { 480| | get 481| | { 482| | return drawCenterTextEnabled 483| | } 484| | } 485| | 486| | internal override var requiredLegendOffset: CGFloat 487| | { 488| | return legend.font.pointSize * 2.0 489| | } 490| | 491| | internal override var requiredBaseOffset: CGFloat 492| | { 493| | return 0.0 494| | } 495| | 496| | open override var radius: CGFloat 497| | { 498| | return _circleBox.width / 2.0 499| | } 500| | 501| | /// The circlebox, the boundingbox of the pie-chart slices 502| | @objc open var circleBox: CGRect 503| | { 504| | return _circleBox 505| | } 506| | 507| | /// The center of the circlebox 508| | @objc open var centerCircleBox: CGPoint 509| | { 510| | return CGPoint(x: _circleBox.midX, y: _circleBox.midY) 511| | } 512| | 513| | /// the radius of the hole in the center of the piechart in percent of the maximum radius (max = the radius of the whole chart) 514| | /// 515| | /// **default**: 0.5 (50%) (half the pie) 516| | @objc open var holeRadiusPercent: CGFloat 517| | { 518| | get 519| | { 520| | return _holeRadiusPercent 521| | } 522| | set 523| | { 524| | _holeRadiusPercent = newValue 525| | setNeedsDisplay() 526| | } 527| | } 528| | 529| | /// The color that the transparent-circle should have. 530| | /// 531| | /// **default**: `nil` 532| | @objc open var transparentCircleColor: NSUIColor? 533| | { 534| | get 535| | { 536| | return _transparentCircleColor 537| | } 538| | set 539| | { 540| | _transparentCircleColor = newValue 541| | setNeedsDisplay() 542| | } 543| | } 544| | 545| | /// the radius of the transparent circle that is drawn next to the hole in the piechart in percent of the maximum radius (max = the radius of the whole chart) 546| | /// 547| | /// **default**: 0.55 (55%) -> means 5% larger than the center-hole by default 548| | @objc open var transparentCircleRadiusPercent: CGFloat 549| | { 550| | get 551| | { 552| | return _transparentCircleRadiusPercent 553| | } 554| | set 555| | { 556| | _transparentCircleRadiusPercent = newValue 557| | setNeedsDisplay() 558| | } 559| | } 560| | 561| | /// The color the entry labels are drawn with. 562| | @objc open var entryLabelColor: NSUIColor? 563| | { 564| | get { return _entryLabelColor } 565| | set 566| | { 567| | _entryLabelColor = newValue 568| | setNeedsDisplay() 569| | } 570| | } 571| | 572| | /// The font the entry labels are drawn with. 573| | @objc open var entryLabelFont: NSUIFont? 574| | { 575| | get { return _entryLabelFont } 576| | set 577| | { 578| | _entryLabelFont = newValue 579| | setNeedsDisplay() 580| | } 581| | } 582| | 583| | /// Set this to true to draw the enrty labels into the pie slices 584| | @objc open var drawEntryLabelsEnabled: Bool 585| | { 586| | get 587| | { 588| | return _drawEntryLabelsEnabled 589| | } 590| | set 591| | { 592| | _drawEntryLabelsEnabled = newValue 593| | setNeedsDisplay() 594| | } 595| | } 596| | 597| | /// `true` if drawing entry labels is enabled, `false` ifnot 598| | @objc open var isDrawEntryLabelsEnabled: Bool 599| | { 600| | get 601| | { 602| | return drawEntryLabelsEnabled 603| | } 604| | } 605| | 606| | /// If this is enabled, values inside the PieChart are drawn in percent and not with their original value. Values provided for the ValueFormatter to format are then provided in percent. 607| | @objc open var usePercentValuesEnabled: Bool 608| | { 609| | get 610| | { 611| | return _usePercentValuesEnabled 612| | } 613| | set 614| | { 615| | _usePercentValuesEnabled = newValue 616| | setNeedsDisplay() 617| | } 618| | } 619| | 620| | /// `true` if drawing x-values is enabled, `false` ifnot 621| | @objc open var isUsePercentValuesEnabled: Bool 622| | { 623| | get 624| | { 625| | return usePercentValuesEnabled 626| | } 627| | } 628| | 629| | /// the rectangular radius of the bounding box for the center text, as a percentage of the pie hole 630| | @objc open var centerTextRadiusPercent: CGFloat 631| | { 632| | get 633| | { 634| | return _centerTextRadiusPercent 635| | } 636| | set 637| | { 638| | _centerTextRadiusPercent = newValue 639| | setNeedsDisplay() 640| | } 641| | } 642| | 643| | /// The max angle that is used for calculating the pie-circle. 644| | /// 360 means it's a full pie-chart, 180 results in a half-pie-chart. 645| | /// **default**: 360.0 646| | @objc open var maxAngle: CGFloat 647| | { 648| | get 649| | { 650| | return _maxAngle 651| | } 652| | set 653| | { 654| | _maxAngle = newValue 655| | 656| | if _maxAngle > 360.0 657| | { 658| | _maxAngle = 360.0 659| | } 660| | 661| | if _maxAngle < 90.0 662| | { 663| | _maxAngle = 90.0 664| | } 665| | } 666| | } 667| | 668| | /// smallest pie slice angle that will have a label drawn in degrees, 0 by default 669| | @objc open var sliceTextDrawingThreshold: CGFloat = 0.0 670| | { 671| | didSet { 672| | setNeedsDisplay() 673| | } 674| | } 675| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Charts/PieRadarChartViewBase.swift: 1| |// 2| |// PieRadarChartViewBase.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| |import QuartzCore 15| | 16| |#if canImport(AppKit) 17| |import AppKit 18| |#endif 19| | 20| | 21| |/// Base class of PieChartView and RadarChartView. 22| |open class PieRadarChartViewBase: ChartViewBase 23| |{ 24| | /// holds the normalized version of the current rotation angle of the chart 25| 6| private var _rotationAngle = CGFloat(270.0) 26| | 27| | /// holds the raw version of the current rotation angle of the chart 28| 6| private var _rawRotationAngle = CGFloat(270.0) 29| | 30| | /// flag that indicates if rotation is enabled or not 31| | @objc open var rotationEnabled = true 32| | 33| | /// Sets the minimum offset (padding) around the chart, defaults to 0.0 34| 6| @objc open var minOffset = CGFloat(0.0) 35| | 36| | /// iOS && OSX only: Enabled multi-touch rotation using two fingers. 37| | private var _rotationWithTwoFingers = false 38| | 39| | private var _tapGestureRecognizer: NSUITapGestureRecognizer! 40| | #if !os(tvOS) 41| | private var _rotationGestureRecognizer: NSUIRotationGestureRecognizer! 42| | #endif 43| | 44| | public override init(frame: CGRect) 45| | { 46| | super.init(frame: frame) 47| | } 48| | 49| | public required init?(coder aDecoder: NSCoder) 50| | { 51| | super.init(coder: aDecoder) 52| | } 53| | 54| | deinit 55| | { 56| | stopDeceleration() 57| | } 58| | 59| | internal override func initialize() 60| | { 61| | super.initialize() 62| | 63| | _tapGestureRecognizer = NSUITapGestureRecognizer(target: self, action: #selector(tapGestureRecognized(_:))) 64| | 65| | self.addGestureRecognizer(_tapGestureRecognizer) 66| | 67| | #if !os(tvOS) 68| | _rotationGestureRecognizer = NSUIRotationGestureRecognizer(target: self, action: #selector(rotationGestureRecognized(_:))) 69| | self.addGestureRecognizer(_rotationGestureRecognizer) 70| | _rotationGestureRecognizer.isEnabled = rotationWithTwoFingers 71| | #endif 72| | } 73| | 74| | internal override func calcMinMax() 75| | { 76| | /*_xAxis.axisRange = Double((data?.xVals.count ?? 0) - 1)*/ 77| | } 78| | 79| | open override var maxVisibleCount: Int 80| | { 81| | get 82| | { 83| | return data?.entryCount ?? 0 84| | } 85| | } 86| | 87| | open override func notifyDataSetChanged() 88| | { 89| | calcMinMax() 90| | 91| | if let data = data 92| | { 93| | legendRenderer.computeLegend(data: data) 94| | } 95| | 96| | calculateOffsets() 97| | 98| | setNeedsDisplay() 99| | } 100| | 101| | internal override func calculateOffsets() 102| | { 103| | var legendLeft = CGFloat(0.0) 104| | var legendRight = CGFloat(0.0) 105| | var legendBottom = CGFloat(0.0) 106| | var legendTop = CGFloat(0.0) 107| | 108| | if legend.enabled && !legend.drawInside 109| | { 110| | let fullLegendWidth = min(legend.neededWidth, viewPortHandler.chartWidth * legend.maxSizePercent) 111| | 112| | switch legend.orientation 113| | { 114| | case .vertical: 115| | 116| | var xLegendOffset: CGFloat = 0.0 117| | 118| | if legend.horizontalAlignment == .left 119| | || legend.horizontalAlignment == .right 120| | { 121| | if legend.verticalAlignment == .center 122| | { 123| | // this is the space between the legend and the chart 124| | let spacing = CGFloat(13.0) 125| | 126| | xLegendOffset = fullLegendWidth + spacing 127| | } 128| | else 129| | { 130| | // this is the space between the legend and the chart 131| | let spacing = CGFloat(8.0) 132| | 133| | let legendWidth = fullLegendWidth + spacing 134| | let legendHeight = legend.neededHeight + legend.textHeightMax 135| | 136| | let c = self.midPoint 137| | 138| | let bottomX = legend.horizontalAlignment == .right 139| | ? self.bounds.width - legendWidth + 15.0 140| | : legendWidth - 15.0 141| | let bottomY = legendHeight + 15 142| | let distLegend = distanceToCenter(x: bottomX, y: bottomY) 143| | 144| | let reference = getPosition(center: c, dist: self.radius, 145| | angle: angleForPoint(x: bottomX, y: bottomY)) 146| | 147| | let distReference = distanceToCenter(x: reference.x, y: reference.y) 148| | let minOffset = CGFloat(5.0) 149| | 150| | if bottomY >= c.y 151| | && self.bounds.height - legendWidth > self.bounds.width 152| | { 153| | xLegendOffset = legendWidth 154| | } 155| | else if distLegend < distReference 156| | { 157| | let diff = distReference - distLegend 158| | xLegendOffset = minOffset + diff 159| | } 160| | } 161| | } 162| | 163| | switch legend.horizontalAlignment 164| | { 165| | case .left: 166| | legendLeft = xLegendOffset 167| | 168| | case .right: 169| | legendRight = xLegendOffset 170| | 171| | case .center: 172| | 173| | switch legend.verticalAlignment 174| | { 175| | case .top: 176| | legendTop = min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) 177| | 178| | case .bottom: 179| | legendBottom = min(legend.neededHeight, viewPortHandler.chartHeight * legend.maxSizePercent) 180| | 181| | default: 182| | break 183| | } 184| | } 185| | 186| | case .horizontal: 187| | 188| | var yLegendOffset: CGFloat = 0.0 189| | 190| | if legend.verticalAlignment == .top 191| | || legend.verticalAlignment == .bottom 192| | { 193| | // It's possible that we do not need this offset anymore as it 194| | // is available through the extraOffsets, but changing it can mean 195| | // changing default visibility for existing apps. 196| | let yOffset = self.requiredLegendOffset 197| | 198| | yLegendOffset = min( 199| | legend.neededHeight + yOffset, 200| | viewPortHandler.chartHeight * legend.maxSizePercent) 201| | } 202| | 203| | switch legend.verticalAlignment 204| | { 205| | case .top: 206| | 207| | legendTop = yLegendOffset 208| | 209| | case .bottom: 210| | 211| | legendBottom = yLegendOffset 212| | 213| | default: 214| | break 215| | } 216| | } 217| | 218| | legendLeft += self.requiredBaseOffset 219| | legendRight += self.requiredBaseOffset 220| | legendTop += self.requiredBaseOffset 221| | legendBottom += self.requiredBaseOffset 222| | } 223| | 224| | legendTop += self.extraTopOffset 225| | legendRight += self.extraRightOffset 226| | legendBottom += self.extraBottomOffset 227| | legendLeft += self.extraLeftOffset 228| | 229| | var minOffset = self.minOffset 230| | 231| | if self is RadarChartView 232| | { 233| | let x = self.xAxis 234| | 235| | if x.isEnabled && x.drawLabelsEnabled 236| | { 237| | minOffset = max(minOffset, x.labelRotatedWidth) 238| | } 239| | } 240| | 241| | let offsetLeft = max(minOffset, legendLeft) 242| | let offsetTop = max(minOffset, legendTop) 243| | let offsetRight = max(minOffset, legendRight) 244| | let offsetBottom = max(minOffset, max(self.requiredBaseOffset, legendBottom)) 245| | 246| | viewPortHandler.restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom) 247| | } 248| | 249| | /// - Returns: The angle relative to the chart center for the given point on the chart in degrees. 250| | /// The angle is always between 0 and 360°, 0° is NORTH, 90° is EAST, ... 251| | @objc open func angleForPoint(x: CGFloat, y: CGFloat) -> CGFloat 252| | { 253| | let c = centerOffsets 254| | 255| | let tx = Double(x - c.x) 256| | let ty = Double(y - c.y) 257| | let length = sqrt(tx * tx + ty * ty) 258| | let r = acos(ty / length) 259| | 260| | var angle = r.RAD2DEG 261| | 262| | if x > c.x 263| | { 264| | angle = 360.0 - angle 265| | } 266| | 267| | // add 90° because chart starts EAST 268| | angle = angle + 90.0 269| | 270| | // neutralize overflow 271| | if angle > 360.0 272| | { 273| | angle = angle - 360.0 274| | } 275| | 276| | return CGFloat(angle) 277| | } 278| | 279| | /// Calculates the position around a center point, depending on the distance 280| | /// from the center, and the angle of the position around the center. 281| | @objc open func getPosition(center: CGPoint, dist: CGFloat, angle: CGFloat) -> CGPoint 282| | { 283| | return CGPoint(x: center.x + dist * cos(angle.DEG2RAD), 284| | y: center.y + dist * sin(angle.DEG2RAD)) 285| | } 286| | 287| | /// - Returns: The distance of a certain point on the chart to the center of the chart. 288| | @objc open func distanceToCenter(x: CGFloat, y: CGFloat) -> CGFloat 289| | { 290| | let c = self.centerOffsets 291| | 292| | var dist = CGFloat(0.0) 293| | 294| | var xDist = CGFloat(0.0) 295| | var yDist = CGFloat(0.0) 296| | 297| | if x > c.x 298| | { 299| | xDist = x - c.x 300| | } 301| | else 302| | { 303| | xDist = c.x - x 304| | } 305| | 306| | if y > c.y 307| | { 308| | yDist = y - c.y 309| | } 310| | else 311| | { 312| | yDist = c.y - y 313| | } 314| | 315| | // pythagoras 316| | dist = sqrt(pow(xDist, 2.0) + pow(yDist, 2.0)) 317| | 318| | return dist 319| | } 320| | 321| | /// - Returns: The xIndex for the given angle around the center of the chart. 322| | /// -1 if not found / outofbounds. 323| | @objc open func indexForAngle(_ angle: CGFloat) -> Int 324| | { 325| | fatalError("indexForAngle() cannot be called on PieRadarChartViewBase") 326| | } 327| | 328| | /// current rotation angle of the pie chart 329| | /// 330| | /// **default**: 270 --> top (NORTH) 331| | /// Will always return a normalized value, which will be between 0.0 < 360.0 332| | @objc open var rotationAngle: CGFloat 333| | { 334| | get 335| | { 336| | return _rotationAngle 337| | } 338| | set 339| | { 340| | _rawRotationAngle = newValue 341| | _rotationAngle = newValue.normalizedAngle 342| | setNeedsDisplay() 343| | } 344| | } 345| | 346| | /// gets the raw version of the current rotation angle of the pie chart the returned value could be any value, negative or positive, outside of the 360 degrees. 347| | /// this is used when working with rotation direction, mainly by gestures and animations. 348| | @objc open var rawRotationAngle: CGFloat 349| | { 350| | return _rawRotationAngle 351| | } 352| | 353| | /// The diameter of the pie- or radar-chart 354| | @objc open var diameter: CGFloat 355| | { 356| | var content = viewPortHandler.contentRect 357| | content.origin.x += extraLeftOffset 358| | content.origin.y += extraTopOffset 359| | content.size.width -= extraLeftOffset + extraRightOffset 360| | content.size.height -= extraTopOffset + extraBottomOffset 361| | return min(content.width, content.height) 362| | } 363| | 364| | /// The radius of the chart in pixels. 365| | @objc open var radius: CGFloat 366| | { 367| | fatalError("radius cannot be called on PieRadarChartViewBase") 368| | } 369| | 370| | /// The required offset for the chart legend. 371| | internal var requiredLegendOffset: CGFloat 372| | { 373| | fatalError("requiredLegendOffset cannot be called on PieRadarChartViewBase") 374| | } 375| | 376| | /// - Returns: The base offset needed for the chart without calculating the 377| | /// legend size. 378| | internal var requiredBaseOffset: CGFloat 379| | { 380| | fatalError("requiredBaseOffset cannot be called on PieRadarChartViewBase") 381| | } 382| | 383| | open override var chartYMax: Double 384| | { 385| | return 0.0 386| | } 387| | 388| | open override var chartYMin: Double 389| | { 390| | return 0.0 391| | } 392| | 393| | @objc open var isRotationEnabled: Bool { return rotationEnabled } 394| | 395| | /// flag that indicates if rotation is done with two fingers or one. 396| | /// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events. 397| | /// 398| | /// On iOS this will disable one-finger rotation. 399| | /// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation. 400| | /// 401| | /// **default**: false 402| | @objc open var rotationWithTwoFingers: Bool 403| | { 404| | get 405| | { 406| | return _rotationWithTwoFingers 407| | } 408| | set 409| | { 410| | _rotationWithTwoFingers = newValue 411| | #if !os(tvOS) 412| | _rotationGestureRecognizer.isEnabled = _rotationWithTwoFingers 413| | #endif 414| | } 415| | } 416| | 417| | /// flag that indicates if rotation is done with two fingers or one. 418| | /// when the chart is inside a scrollview, you need a two-finger rotation because a one-finger rotation eats up all touch events. 419| | /// 420| | /// On iOS this will disable one-finger rotation. 421| | /// On OSX this will keep two-finger multitouch rotation, and one-pointer mouse rotation. 422| | /// 423| | /// **default**: false 424| | @objc open var isRotationWithTwoFingers: Bool 425| | { 426| | return _rotationWithTwoFingers 427| | } 428| | 429| | // MARK: - Animation 430| | 431| | private var _spinAnimator: Animator! 432| | 433| | /// Applys a spin animation to the Chart. 434| | @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easing: ChartEasingFunctionBlock?) 435| | { 436| | if _spinAnimator != nil 437| | { 438| | _spinAnimator.stop() 439| | } 440| | 441| | _spinAnimator = Animator() 442| | _spinAnimator.updateBlock = { 443| | self.rotationAngle = (toAngle - fromAngle) * CGFloat(self._spinAnimator.phaseX) + fromAngle 444| | } 445| | _spinAnimator.stopBlock = { self._spinAnimator = nil } 446| | 447| | _spinAnimator.animate(xAxisDuration: duration, easing: easing) 448| | } 449| | 450| | @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat, easingOption: ChartEasingOption) 451| | { 452| | spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: easingFunctionFromOption(easingOption)) 453| | } 454| | 455| | @objc open func spin(duration: TimeInterval, fromAngle: CGFloat, toAngle: CGFloat) 456| | { 457| | spin(duration: duration, fromAngle: fromAngle, toAngle: toAngle, easing: nil) 458| | } 459| | 460| | @objc open func stopSpinAnimation() 461| | { 462| | if _spinAnimator != nil 463| | { 464| | _spinAnimator.stop() 465| | } 466| | } 467| | 468| | // MARK: - Gestures 469| | 470| | private var _rotationGestureStartPoint: CGPoint! 471| | private var _isRotating = false 472| 6| private var _startAngle = CGFloat(0.0) 473| | 474| | private struct AngularVelocitySample 475| | { 476| | var time: TimeInterval 477| | var angle: CGFloat 478| | } 479| | 480| 6| private var velocitySamples = [AngularVelocitySample]() 481| | 482| | private var _decelerationLastTime: TimeInterval = 0.0 483| | private var _decelerationDisplayLink: NSUIDisplayLink! 484| | private var _decelerationAngularVelocity: CGFloat = 0.0 485| | 486| | internal final func processRotationGestureBegan(location: CGPoint) 487| | { 488| | self.resetVelocity() 489| | 490| | if rotationEnabled 491| | { 492| | self.sampleVelocity(touchLocation: location) 493| | } 494| | 495| | self.setGestureStartAngle(x: location.x, y: location.y) 496| | 497| | _rotationGestureStartPoint = location 498| | } 499| | 500| | internal final func processRotationGestureMoved(location: CGPoint) 501| | { 502| | if isDragDecelerationEnabled 503| | { 504| | sampleVelocity(touchLocation: location) 505| | } 506| | 507| | if !_isRotating && 508| | distance( 509| | eventX: location.x, 510| | startX: _rotationGestureStartPoint.x, 511| | eventY: location.y, 512| | startY: _rotationGestureStartPoint.y) > CGFloat(8.0) 513| | { 514| | _isRotating = true 515| | } 516| | else 517| | { 518| | self.updateGestureRotation(x: location.x, y: location.y) 519| | setNeedsDisplay() 520| | } 521| | } 522| | 523| | internal final func processRotationGestureEnded(location: CGPoint) 524| | { 525| | if isDragDecelerationEnabled 526| | { 527| | stopDeceleration() 528| | 529| | sampleVelocity(touchLocation: location) 530| | 531| | _decelerationAngularVelocity = calculateVelocity() 532| | 533| | if _decelerationAngularVelocity != 0.0 534| | { 535| | _decelerationLastTime = CACurrentMediaTime() 536| | _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop)) 537| | _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 538| | } 539| | } 540| | } 541| | 542| | internal final func processRotationGestureCancelled() 543| | { 544| | if _isRotating 545| | { 546| | _isRotating = false 547| | } 548| | } 549| | 550| | #if !os(OSX) 551| | open override func nsuiTouchesBegan(_ touches: Set, withEvent event: NSUIEvent?) 552| | { 553| | // if rotation by touch is enabled 554| | if rotationEnabled 555| | { 556| | stopDeceleration() 557| | 558| | if !rotationWithTwoFingers, let touchLocation = touches.first?.location(in: self) 559| | { 560| | processRotationGestureBegan(location: touchLocation) 561| | } 562| | } 563| | 564| | if !_isRotating 565| | { 566| | super.nsuiTouchesBegan(touches, withEvent: event) 567| | } 568| | } 569| | 570| | open override func nsuiTouchesMoved(_ touches: Set, withEvent event: NSUIEvent?) 571| | { 572| | if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first 573| | { 574| | let touchLocation = touch.location(in: self) 575| | processRotationGestureMoved(location: touchLocation) 576| | } 577| | 578| | if !_isRotating 579| | { 580| | super.nsuiTouchesMoved(touches, withEvent: event) 581| | } 582| | } 583| | 584| | open override func nsuiTouchesEnded(_ touches: Set, withEvent event: NSUIEvent?) 585| | { 586| | if !_isRotating 587| | { 588| | super.nsuiTouchesEnded(touches, withEvent: event) 589| | } 590| | 591| | if rotationEnabled && !rotationWithTwoFingers, let touch = touches.first 592| | { 593| | let touchLocation = touch.location(in: self) 594| | processRotationGestureEnded(location: touchLocation) 595| | } 596| | 597| | if _isRotating 598| | { 599| | _isRotating = false 600| | } 601| | } 602| | 603| | open override func nsuiTouchesCancelled(_ touches: Set?, withEvent event: NSUIEvent?) 604| | { 605| | super.nsuiTouchesCancelled(touches, withEvent: event) 606| | 607| | processRotationGestureCancelled() 608| | } 609| | #endif 610| | 611| | #if os(OSX) 612| | open override func mouseDown(with theEvent: NSEvent) 613| | { 614| | // if rotation by touch is enabled 615| | if rotationEnabled 616| | { 617| | stopDeceleration() 618| | 619| | let location = self.convert(theEvent.locationInWindow, from: nil) 620| | 621| | processRotationGestureBegan(location: location) 622| | } 623| | 624| | if !_isRotating 625| | { 626| | super.mouseDown(with: theEvent) 627| | } 628| | } 629| | 630| | open override func mouseDragged(with theEvent: NSEvent) 631| | { 632| | if rotationEnabled 633| | { 634| | let location = self.convert(theEvent.locationInWindow, from: nil) 635| | 636| | processRotationGestureMoved(location: location) 637| | } 638| | 639| | if !_isRotating 640| | { 641| | super.mouseDragged(with: theEvent) 642| | } 643| | } 644| | 645| | open override func mouseUp(with theEvent: NSEvent) 646| | { 647| | if !_isRotating 648| | { 649| | super.mouseUp(with: theEvent) 650| | } 651| | 652| | if rotationEnabled 653| | { 654| | let location = self.convert(theEvent.locationInWindow, from: nil) 655| | 656| | processRotationGestureEnded(location: location) 657| | } 658| | 659| | if _isRotating 660| | { 661| | _isRotating = false 662| | } 663| | } 664| | #endif 665| | 666| | private func resetVelocity() 667| | { 668| | velocitySamples.removeAll(keepingCapacity: false) 669| | } 670| | 671| | private func sampleVelocity(touchLocation: CGPoint) 672| | { 673| | let currentSample: AngularVelocitySample = { 674| | let time = CACurrentMediaTime() 675| | let angle = angleForPoint(x: touchLocation.x, y: touchLocation.y) 676| | return AngularVelocitySample(time: time, angle: angle) 677| | }() 678| | 679| | // Remove samples older than our sample time - 1 seconds 680| | // while keeping at least one sample 681| | var i = 0, count = velocitySamples.count 682| | while (i < count - 2) 683| | { 684| | if currentSample.time - velocitySamples[i].time > 1.0 685| | { 686| | velocitySamples.remove(at: 0) 687| | i -= 1 688| | count -= 1 689| | } 690| | else 691| | { 692| | break 693| | } 694| | 695| | i += 1 696| | } 697| | 698| | velocitySamples.append(currentSample) 699| | } 700| | 701| | private func calculateVelocity() -> CGFloat 702| | { 703| | guard var firstSample = velocitySamples.first, 704| | var lastSample = velocitySamples.last 705| | else { return 0 } 706| | 707| | // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction 708| | let beforeLastSample = velocitySamples.last { $0.angle != lastSample.angle } 709| | ?? firstSample 710| | 711| | // Calculate the sampling time 712| | let timeDelta: CGFloat = { 713| | let delta = CGFloat(lastSample.time - firstSample.time) 714| | return delta == 0 ? 0.1 : delta 715| | }() 716| | 717| | // Calculate clockwise/ccw by choosing two values that should be closest to each other, 718| | // so if the angles are two far from each other we know they are inverted "for sure" 719| | let isClockwise: Bool = { 720| | let isClockwise = lastSample.angle >= beforeLastSample.angle 721| | let isInverted = abs(lastSample.angle - beforeLastSample.angle) > 270.0 722| | return isInverted ? !isClockwise : isClockwise 723| | }() 724| | 725| | // Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point 726| | if lastSample.angle - firstSample.angle > 180.0 727| | { 728| | firstSample.angle += 360.0 729| | } 730| | else if firstSample.angle - lastSample.angle > 180.0 731| | { 732| | lastSample.angle += 360.0 733| | } 734| | 735| | // The velocity 736| | let velocity = abs((lastSample.angle - firstSample.angle) / timeDelta) 737| | return isClockwise ? velocity : -velocity 738| | } 739| | 740| | /// sets the starting angle of the rotation, this is only used by the touch listener, x and y is the touch position 741| | private func setGestureStartAngle(x: CGFloat, y: CGFloat) 742| | { 743| | _startAngle = angleForPoint(x: x, y: y) 744| | 745| | // take the current angle into consideration when starting a new drag 746| | _startAngle -= _rotationAngle 747| | } 748| | 749| | /// updates the view rotation depending on the given touch position, also takes the starting angle into consideration 750| | private func updateGestureRotation(x: CGFloat, y: CGFloat) 751| | { 752| | self.rotationAngle = angleForPoint(x: x, y: y) - _startAngle 753| | } 754| | 755| | @objc open func stopDeceleration() 756| | { 757| | if _decelerationDisplayLink !== nil 758| | { 759| | _decelerationDisplayLink.remove(from: RunLoop.main, forMode: RunLoop.Mode.common) 760| | _decelerationDisplayLink = nil 761| | } 762| | } 763| | 764| | @objc private func decelerationLoop() 765| | { 766| | let currentTime = CACurrentMediaTime() 767| | 768| | _decelerationAngularVelocity *= self.dragDecelerationFrictionCoef 769| | 770| | let timeInterval = CGFloat(currentTime - _decelerationLastTime) 771| | 772| | self.rotationAngle += _decelerationAngularVelocity * timeInterval 773| | 774| | _decelerationLastTime = currentTime 775| | 776| | if(abs(_decelerationAngularVelocity) < 0.001) 777| | { 778| | stopDeceleration() 779| | } 780| | } 781| | 782| | /// - Returns: The distance between two points 783| | private func distance(eventX: CGFloat, startX: CGFloat, eventY: CGFloat, startY: CGFloat) -> CGFloat 784| | { 785| | let dx = eventX - startX 786| | let dy = eventY - startY 787| | return sqrt(dx * dx + dy * dy) 788| | } 789| | 790| | /// - Returns: The distance between two points 791| | private func distance(from: CGPoint, to: CGPoint) -> CGFloat 792| | { 793| | let dx = from.x - to.x 794| | let dy = from.y - to.y 795| | return sqrt(dx * dx + dy * dy) 796| | } 797| | 798| | /// reference to the last highlighted object 799| | private var _lastHighlight: Highlight! 800| | 801| | @objc private func tapGestureRecognized(_ recognizer: NSUITapGestureRecognizer) 802| | { 803| | if recognizer.state == NSUIGestureRecognizerState.ended 804| | { 805| | if !self.isHighLightPerTapEnabled { return } 806| | 807| | let location = recognizer.location(in: self) 808| | 809| | let high = self.getHighlightByTouchPoint(location) 810| | self.highlightValue(high, callDelegate: true) 811| | } 812| | } 813| | 814| | #if !os(tvOS) 815| | @objc private func rotationGestureRecognized(_ recognizer: NSUIRotationGestureRecognizer) 816| | { 817| | if recognizer.state == NSUIGestureRecognizerState.began 818| | { 819| | stopDeceleration() 820| | 821| | _startAngle = self.rawRotationAngle 822| | } 823| | 824| | if recognizer.state == NSUIGestureRecognizerState.began || recognizer.state == NSUIGestureRecognizerState.changed 825| | { 826| | let angle = recognizer.nsuiRotation.RAD2DEG 827| | 828| | self.rotationAngle = _startAngle + angle 829| | setNeedsDisplay() 830| | } 831| | else if recognizer.state == NSUIGestureRecognizerState.ended 832| | { 833| | let angle = recognizer.nsuiRotation.RAD2DEG 834| | 835| | self.rotationAngle = _startAngle + angle 836| | setNeedsDisplay() 837| | 838| | if isDragDecelerationEnabled 839| | { 840| | stopDeceleration() 841| | 842| | _decelerationAngularVelocity = recognizer.velocity.RAD2DEG 843| | 844| | if _decelerationAngularVelocity != 0.0 845| | { 846| | _decelerationLastTime = CACurrentMediaTime() 847| | _decelerationDisplayLink = NSUIDisplayLink(target: self, selector: #selector(PieRadarChartViewBase.decelerationLoop)) 848| | _decelerationDisplayLink.add(to: RunLoop.main, forMode: RunLoop.Mode.common) 849| | } 850| | } 851| | } 852| | } 853| | #endif 854| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Charts/RadarChartView.swift: 1| |// 2| |// RadarChartView.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |/// Implementation of the RadarChart, a "spidernet"-like chart. It works best 17| |/// when displaying 5-10 entries per DataSet. 18| |open class RadarChartView: PieRadarChartViewBase 19| |{ 20| | /// width of the web lines that come from the center. 21| 0| @objc open var webLineWidth = CGFloat(1.5) 22| | 23| | /// width of the web lines that are in between the lines coming from the center 24| 0| @objc open var innerWebLineWidth = CGFloat(0.75) 25| | 26| | /// color for the web lines that come from the center 27| 0| @objc open var webColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) 28| | 29| | /// color for the web lines in between the lines that come from the center. 30| 0| @objc open var innerWebColor = NSUIColor(red: 122/255.0, green: 122/255.0, blue: 122.0/255.0, alpha: 1.0) 31| | 32| | /// transparency the grid is drawn with (0.0 - 1.0) 33| 0| @objc open var webAlpha: CGFloat = 150.0 / 255.0 34| | 35| | /// flag indicating if the web lines should be drawn or not 36| | @objc open var drawWeb = true 37| | 38| | /// modulus that determines how many labels and web-lines are skipped before the next is drawn 39| | private var _skipWebLineCount = 0 40| | 41| | /// the object reprsenting the y-axis labels 42| | private var _yAxis: YAxis! 43| | 44| | internal var _yAxisRenderer: YAxisRendererRadarChart! 45| | internal var _xAxisRenderer: XAxisRendererRadarChart! 46| | 47| | public override init(frame: CGRect) 48| | { 49| | super.init(frame: frame) 50| | } 51| | 52| | public required init?(coder aDecoder: NSCoder) 53| | { 54| | super.init(coder: aDecoder) 55| | } 56| | 57| | internal override func initialize() 58| | { 59| | super.initialize() 60| | 61| | _yAxis = YAxis(position: .left) 62| | _yAxis.labelXOffset = 10.0 63| | 64| | renderer = RadarChartRenderer(chart: self, animator: chartAnimator, viewPortHandler: viewPortHandler) 65| | 66| | _yAxisRenderer = YAxisRendererRadarChart(viewPortHandler: viewPortHandler, axis: _yAxis, chart: self) 67| | _xAxisRenderer = XAxisRendererRadarChart(viewPortHandler: viewPortHandler, axis: xAxis, chart: self) 68| | 69| | self.highlighter = RadarHighlighter(chart: self) 70| | } 71| | 72| | internal override func calcMinMax() 73| | { 74| | super.calcMinMax() 75| | 76| | guard let data = data else { return } 77| | 78| | _yAxis.calculate(min: data.getYMin(axis: .left), max: data.getYMax(axis: .left)) 79| | xAxis.calculate(min: 0.0, max: Double(data.maxEntryCountSet?.entryCount ?? 0)) 80| | } 81| | 82| | open override func notifyDataSetChanged() 83| | { 84| | calcMinMax() 85| | 86| | _yAxisRenderer?.computeAxis(min: _yAxis._axisMinimum, max: _yAxis._axisMaximum, inverted: _yAxis.isInverted) 87| | _xAxisRenderer?.computeAxis(min: xAxis._axisMinimum, max: xAxis._axisMaximum, inverted: false) 88| | 89| | if let data = data, 90| | !legend.isLegendCustom 91| | { 92| | legendRenderer.computeLegend(data: data) 93| | } 94| | 95| | calculateOffsets() 96| | 97| | setNeedsDisplay() 98| | } 99| | 100| | open override func draw(_ rect: CGRect) 101| | { 102| | super.draw(rect) 103| | 104| | guard data != nil, let renderer = renderer else { return } 105| | 106| | let optionalContext = NSUIGraphicsGetCurrentContext() 107| | guard let context = optionalContext else { return } 108| | 109| | if xAxis.isEnabled 110| | { 111| | _xAxisRenderer.computeAxis(min: xAxis._axisMinimum, max: xAxis._axisMaximum, inverted: false) 112| | } 113| | 114| | _xAxisRenderer?.renderAxisLabels(context: context) 115| | 116| | if drawWeb 117| | { 118| | renderer.drawExtras(context: context) 119| | } 120| | 121| | if _yAxis.isEnabled && _yAxis.isDrawLimitLinesBehindDataEnabled 122| | { 123| | _yAxisRenderer.renderLimitLines(context: context) 124| | } 125| | 126| | renderer.drawData(context: context) 127| | 128| | if valuesToHighlight() 129| | { 130| | renderer.drawHighlighted(context: context, indices: highlighted) 131| | } 132| | 133| | if _yAxis.isEnabled && !_yAxis.isDrawLimitLinesBehindDataEnabled 134| | { 135| | _yAxisRenderer.renderLimitLines(context: context) 136| | } 137| | 138| | _yAxisRenderer.renderAxisLabels(context: context) 139| | 140| | renderer.drawValues(context: context) 141| | 142| | legendRenderer.renderLegend(context: context) 143| | 144| | drawDescription(in: context) 145| | 146| | drawMarkers(context: context) 147| | } 148| | 149| | /// The factor that is needed to transform values into pixels. 150| | @objc open var factor: CGFloat 151| | { 152| | let content = viewPortHandler.contentRect 153| | return min(content.width / 2.0, content.height / 2.0) 154| | / CGFloat(_yAxis.axisRange) 155| | } 156| | 157| | /// The angle that each slice in the radar chart occupies. 158| | @objc open var sliceAngle: CGFloat 159| | { 160| | return 360.0 / CGFloat(data?.maxEntryCountSet?.entryCount ?? 0) 161| | } 162| | 163| | open override func indexForAngle(_ angle: CGFloat) -> Int 164| | { 165| | // take the current angle of the chart into consideration 166| | let a = (angle - self.rotationAngle).normalizedAngle 167| | 168| | let sliceAngle = self.sliceAngle 169| | 170| | let max = data?.maxEntryCountSet?.entryCount ?? 0 171| | return (0.. a 173| | } ?? 0 174| | } 175| | 176| | /// The object that represents all y-labels of the RadarChart. 177| | @objc open var yAxis: YAxis 178| | { 179| | return _yAxis 180| | } 181| | 182| | /// Sets the number of web-lines that should be skipped on chart web before the next one is drawn. This targets the lines that come from the center of the RadarChart. 183| | /// if count = 1 -> 1 line is skipped in between 184| | @objc open var skipWebLineCount: Int 185| | { 186| | get 187| | { 188| | return _skipWebLineCount 189| | } 190| | set 191| | { 192| | _skipWebLineCount = max(0, newValue) 193| | } 194| | } 195| | 196| | internal override var requiredLegendOffset: CGFloat 197| | { 198| | return legend.font.pointSize * 4.0 199| | } 200| | 201| | internal override var requiredBaseOffset: CGFloat 202| | { 203| | return xAxis.isEnabled && xAxis.isDrawLabelsEnabled ? xAxis.labelRotatedWidth : 10.0 204| | } 205| | 206| | open override var radius: CGFloat 207| | { 208| | let content = viewPortHandler.contentRect 209| | return min(content.width / 2.0, content.height / 2.0) 210| | } 211| | 212| | /// The maximum value this chart can display on it's y-axis. 213| | open override var chartYMax: Double { return _yAxis._axisMaximum } 214| | 215| | /// The minimum value this chart can display on it's y-axis. 216| | open override var chartYMin: Double { return _yAxis._axisMinimum } 217| | 218| | /// The range of y-values this chart can display. 219| | @objc open var yRange: Double { return _yAxis.axisRange } 220| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/AxisBase.swift: 1| |// 2| |// AxisBase.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |/// Base class for all axes 16| |@objc(ChartAxisBase) 17| |open class AxisBase: ComponentBase 18| |{ 19| | public override init() 20| | { 21| | super.init() 22| | } 23| | 24| | /// Custom formatter that is used instead of the auto-formatter if set 25| | private lazy var _axisValueFormatter: AxisValueFormatter = DefaultAxisValueFormatter(decimals: decimals) 26| | 27| 138| @objc open var labelFont = NSUIFont.systemFont(ofSize: 10.0) 28| 138| @objc open var labelTextColor = NSUIColor.labelOrBlack 29| | 30| 138| @objc open var axisLineColor = NSUIColor.gray 31| 138| @objc open var axisLineWidth = CGFloat(0.5) 32| 138| @objc open var axisLineDashPhase = CGFloat(0.0) 33| | @objc open var axisLineDashLengths: [CGFloat]! 34| | 35| 138| @objc open var gridColor = NSUIColor.gray.withAlphaComponent(0.9) 36| 138| @objc open var gridLineWidth = CGFloat(0.5) 37| 138| @objc open var gridLineDashPhase = CGFloat(0.0) 38| | @objc open var gridLineDashLengths: [CGFloat]! 39| 138| @objc open var gridLineCap = CGLineCap.butt 40| | 41| | @objc open var drawGridLinesEnabled = true 42| | @objc open var drawAxisLineEnabled = true 43| | 44| | /// flag that indicates of the labels of this axis should be drawn or not 45| | @objc open var drawLabelsEnabled = true 46| | 47| | private var _centerAxisLabelsEnabled = false 48| | 49| | /// Centers the axis labels instead of drawing them at their original position. 50| | /// This is useful especially for grouped BarChart. 51| | @objc open var centerAxisLabelsEnabled: Bool 52| | { 53| | get { return _centerAxisLabelsEnabled && entryCount > 0 } 54| | set { _centerAxisLabelsEnabled = newValue } 55| | } 56| | 57| | @objc open var isCenterAxisLabelsEnabled: Bool 58| | { 59| | get { return centerAxisLabelsEnabled } 60| | } 61| | 62| | /// array of limitlines that can be set for the axis 63| 138| private var _limitLines = [ChartLimitLine]() 64| | 65| | /// Are the LimitLines drawn behind the data or in front of the data? 66| | /// 67| | /// **default**: false 68| | @objc open var drawLimitLinesBehindDataEnabled = false 69| | 70| | /// Are the grid lines drawn behind the data or in front of the data? 71| | /// 72| | /// **default**: true 73| | @objc open var drawGridLinesBehindDataEnabled = true 74| | 75| | /// the flag can be used to turn off the antialias for grid lines 76| | @objc open var gridAntialiasEnabled = true 77| | 78| | /// the actual array of entries 79| 138| @objc open var entries = [Double]() 80| | 81| | /// axis label entries only used for centered labels 82| 138| @objc open var centeredEntries = [Double]() 83| | 84| | /// the number of entries the legend contains 85| | @objc open var entryCount: Int { return entries.count } 86| | 87| | /// the number of label entries the axis should have 88| | /// 89| | /// **default**: 6 90| 138| private var _labelCount = Int(6) 91| | 92| | /// the number of decimal digits to use (for the default formatter 93| | @objc open var decimals: Int = 0 94| | 95| | /// When true, axis labels are controlled by the `granularity` property. 96| | /// When false, axis values could possibly be repeated. 97| | /// This could happen if two adjacent axis values are rounded to same value. 98| | /// If using granularity this could be avoided by having fewer axis values visible. 99| | @objc open var granularityEnabled = false 100| | 101| 138| private var _granularity = Double(1.0) 102| | 103| | /// The minimum interval between axis values. 104| | /// This can be used to avoid label duplicating when zooming in. 105| | /// 106| | /// **default**: 1.0 107| | @objc open var granularity: Double 108| | { 109| | get 110| | { 111| | return _granularity 112| | } 113| | set 114| | { 115| | _granularity = newValue 116| | 117| | // set this to `true` if it was disabled, as it makes no sense to set this property with granularity disabled 118| | granularityEnabled = true 119| | } 120| | } 121| | 122| | /// The minimum interval between axis values. 123| | @objc open var isGranularityEnabled: Bool 124| | { 125| | get 126| | { 127| | return granularityEnabled 128| | } 129| | } 130| | 131| | /// if true, the set number of y-labels will be forced 132| | @objc open var forceLabelsEnabled = false 133| | 134| | @objc open func getLongestLabel() -> String 135| | { 136| | var longest = "" 137| | 138| | for i in entries.indices 139| | { 140| | let text = getFormattedLabel(i) 141| | 142| | if longest.count < text.count 143| | { 144| | longest = text 145| | } 146| | } 147| | 148| | return longest 149| | } 150| | 151| | /// - Returns: The formatted label at the specified index. This will either use the auto-formatter or the custom formatter (if one is set). 152| | @objc open func getFormattedLabel(_ index: Int) -> String 153| | { 154| | guard entries.indices.contains(index) else { return "" } 155| | 156| | return valueFormatter?.stringForValue(entries[index], axis: self) ?? "" 157| | } 158| | 159| | /// Sets the formatter to be used for formatting the axis labels. 160| | /// If no formatter is set, the chart will automatically determine a reasonable formatting (concerning decimals) for all the values that are drawn inside the chart. 161| | /// Use `nil` to use the formatter calculated by the chart. 162| | @objc open var valueFormatter: AxisValueFormatter? 163| | { 164| | get 165| | { 166| | if _axisValueFormatter is DefaultAxisValueFormatter && 167| | (_axisValueFormatter as! DefaultAxisValueFormatter).hasAutoDecimals && 168| | (_axisValueFormatter as! DefaultAxisValueFormatter).decimals != decimals 169| | { 170| | (self._axisValueFormatter as! DefaultAxisValueFormatter).decimals = self.decimals 171| | } 172| | 173| | return _axisValueFormatter 174| | } 175| | set 176| | { 177| | _axisValueFormatter = newValue ?? DefaultAxisValueFormatter(decimals: decimals) 178| | } 179| | } 180| | 181| | @objc open var isDrawGridLinesEnabled: Bool { return drawGridLinesEnabled } 182| | 183| | @objc open var isDrawAxisLineEnabled: Bool { return drawAxisLineEnabled } 184| | 185| | @objc open var isDrawLabelsEnabled: Bool { return drawLabelsEnabled } 186| | 187| | /// Are the LimitLines drawn behind the data or in front of the data? 188| | /// 189| | /// **default**: false 190| | @objc open var isDrawLimitLinesBehindDataEnabled: Bool { return drawLimitLinesBehindDataEnabled } 191| | 192| | /// Are the grid lines drawn behind the data or in front of the data? 193| | /// 194| | /// **default**: true 195| | @objc open var isDrawGridLinesBehindDataEnabled: Bool { return drawGridLinesBehindDataEnabled } 196| | 197| | /// Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` 198| | @objc open var spaceMin: Double = 0.0 199| | 200| | /// Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` 201| | @objc open var spaceMax: Double = 0.0 202| | 203| | /// Flag indicating that the axis-min value has been customized 204| | internal var _customAxisMin: Bool = false 205| | 206| | /// Flag indicating that the axis-max value has been customized 207| | internal var _customAxisMax: Bool = false 208| | 209| | /// Do not touch this directly, instead, use axisMinimum. 210| | /// This is automatically calculated to represent the real min value, 211| | /// and is used when calculating the effective minimum. 212| 138| internal var _axisMinimum = Double(0) 213| | 214| | /// Do not touch this directly, instead, use axisMaximum. 215| | /// This is automatically calculated to represent the real max value, 216| | /// and is used when calculating the effective maximum. 217| 138| internal var _axisMaximum = Double(0) 218| | 219| | /// the total range of values this axis covers 220| 138| @objc open var axisRange = Double(0) 221| | 222| | /// The minumum number of labels on the axis 223| 138| @objc open var axisMinLabels = Int(2) { 224| | didSet { axisMinLabels = axisMinLabels > 0 ? axisMinLabels : oldValue } 225| | } 226| | 227| | /// The maximum number of labels on the axis 228| 138| @objc open var axisMaxLabels = Int(25) { 229| | didSet { axisMaxLabels = axisMaxLabels > 0 ? axisMaxLabels : oldValue } 230| | } 231| | 232| | /// the number of label entries the axis should have 233| | /// max = 25, 234| | /// min = 2, 235| | /// default = 6, 236| | /// be aware that this number is not fixed and can only be approximated 237| | @objc open var labelCount: Int 238| | { 239| | get 240| | { 241| | return _labelCount 242| | } 243| | set 244| | { 245| | let range = axisMinLabels...axisMaxLabels as ClosedRange 246| | _labelCount = newValue.clamped(to: range) 247| | 248| | forceLabelsEnabled = false 249| | } 250| | } 251| | 252| | @objc open func setLabelCount(_ count: Int, force: Bool) 253| | { 254| | self.labelCount = count 255| | forceLabelsEnabled = force 256| | } 257| | 258| | /// `true` if focing the y-label count is enabled. Default: false 259| | @objc open var isForceLabelsEnabled: Bool { return forceLabelsEnabled } 260| | 261| | /// Adds a new ChartLimitLine to this axis. 262| | @objc open func addLimitLine(_ line: ChartLimitLine) 263| | { 264| | _limitLines.append(line) 265| | } 266| | 267| | /// Removes the specified ChartLimitLine from the axis. 268| | @objc open func removeLimitLine(_ line: ChartLimitLine) 269| | { 270| | guard let i = _limitLines.firstIndex(of: line) else { return } 271| | _limitLines.remove(at: i) 272| | } 273| | 274| | /// Removes all LimitLines from the axis. 275| | @objc open func removeAllLimitLines() 276| | { 277| | _limitLines.removeAll(keepingCapacity: false) 278| | } 279| | 280| | /// The LimitLines of this axis. 281| | @objc open var limitLines : [ChartLimitLine] 282| | { 283| | return _limitLines 284| | } 285| | 286| | // MARK: Custom axis ranges 287| | 288| | /// By calling this method, any custom minimum value that has been previously set is reseted, and the calculation is done automatically. 289| | @objc open func resetCustomAxisMin() 290| | { 291| | _customAxisMin = false 292| | } 293| | 294| | @objc open var isAxisMinCustom: Bool { return _customAxisMin } 295| | 296| | /// By calling this method, any custom maximum value that has been previously set is reseted, and the calculation is done automatically. 297| | @objc open func resetCustomAxisMax() 298| | { 299| | _customAxisMax = false 300| | } 301| | 302| | @objc open var isAxisMaxCustom: Bool { return _customAxisMax } 303| | 304| | /// The minimum value for this axis. 305| | /// If set, this value will not be calculated automatically depending on the provided data. 306| | /// Use `resetCustomAxisMin()` to undo this. 307| | @objc open var axisMinimum: Double 308| | { 309| | get 310| | { 311| | return _axisMinimum 312| | } 313| | set 314| | { 315| | _customAxisMin = true 316| | _axisMinimum = newValue 317| | axisRange = abs(_axisMaximum - newValue) 318| | } 319| | } 320| | 321| | /// The maximum value for this axis. 322| | /// If set, this value will not be calculated automatically depending on the provided data. 323| | /// Use `resetCustomAxisMax()` to undo this. 324| | @objc open var axisMaximum: Double 325| | { 326| | get 327| | { 328| | return _axisMaximum 329| | } 330| | set 331| | { 332| | _customAxisMax = true 333| | _axisMaximum = newValue 334| | axisRange = abs(newValue - _axisMinimum) 335| | } 336| | } 337| | 338| | /// Calculates the minimum, maximum and range values of the YAxis with the given minimum and maximum values from the chart data. 339| | /// 340| | /// - Parameters: 341| | /// - dataMin: the y-min value according to chart data 342| | /// - dataMax: the y-max value according to chart 343| | @objc open func calculate(min dataMin: Double, max dataMax: Double) 344| | { 345| | // if custom, use value as is, else use data value 346| | var min = _customAxisMin ? _axisMinimum : (dataMin - spaceMin) 347| | var max = _customAxisMax ? _axisMaximum : (dataMax + spaceMax) 348| | 349| | // temporary range (before calculations) 350| | let range = abs(max - min) 351| | 352| | // in case all values are equal 353| | if range == 0.0 354| | { 355| | max = max + 1.0 356| | min = min - 1.0 357| | } 358| | 359| | _axisMinimum = min 360| | _axisMaximum = max 361| | 362| | // actual range 363| | axisRange = abs(max - min) 364| | } 365| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/ChartLimitLine.swift: 1| |// 2| |// ChartLimitLine.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |/// The limit line is an additional feature for all Line, Bar and ScatterCharts. 17| |/// It allows the displaying of an additional line in the chart that marks a certain maximum / limit on the specified axis (x- or y-axis). 18| |open class ChartLimitLine: ComponentBase 19| |{ 20| | @objc(ChartLimitLabelPosition) 21| | public enum LabelPosition: Int 22| | { 23| | case leftTop 24| | case leftBottom 25| | case rightTop 26| | case rightBottom 27| | } 28| | 29| | /// limit / maximum (the y-value or xIndex) 30| 0| @objc open var limit = Double(0.0) 31| | 32| 0| private var _lineWidth = CGFloat(2.0) 33| 0| @objc open var lineColor = NSUIColor(red: 237.0/255.0, green: 91.0/255.0, blue: 91.0/255.0, alpha: 1.0) 34| 0| @objc open var lineDashPhase = CGFloat(0.0) 35| | @objc open var lineDashLengths: [CGFloat]? 36| | 37| 0| @objc open var valueTextColor = NSUIColor.labelOrBlack 38| 0| @objc open var valueFont = NSUIFont.systemFont(ofSize: 13.0) 39| | 40| | @objc open var drawLabelEnabled = true 41| | @objc open var label = "" 42| 0| @objc open var labelPosition = LabelPosition.rightTop 43| | 44| | public override init() 45| | { 46| | super.init() 47| | } 48| | 49| | @objc public init(limit: Double) 50| | { 51| | super.init() 52| | self.limit = limit 53| | } 54| | 55| | @objc public init(limit: Double, label: String) 56| | { 57| | super.init() 58| | self.limit = limit 59| | self.label = label 60| | } 61| | 62| | /// set the line width of the chart (min = 0.2, max = 12); default 2 63| | @objc open var lineWidth: CGFloat 64| | { 65| | get 66| | { 67| | return _lineWidth 68| | } 69| | set 70| | { 71| | _lineWidth = newValue.clamped(to: 0.2...12) 72| | } 73| | } 74| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/ComponentBase.swift: 1| |// 2| |// ComponentBase.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |/// This class encapsulates everything both Axis, Legend and LimitLines have in common 17| |@objc(ChartComponentBase) 18| |open class ComponentBase: NSObject 19| |{ 20| | /// flag that indicates if this component is enabled or not 21| | @objc open var enabled = true 22| | 23| | /// The offset this component has on the x-axis 24| | /// **default**: 5.0 25| 242| @objc open var xOffset = CGFloat(5.0) 26| | 27| | /// The offset this component has on the x-axis 28| | /// **default**: 5.0 (or 0.0 on ChartYAxis) 29| 242| @objc open var yOffset = CGFloat(5.0) 30| | 31| | public override init() 32| | { 33| | super.init() 34| | } 35| | 36| | @objc open var isEnabled: Bool { return enabled } 37| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/Description.swift: 1| |// 2| |// Description.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if canImport(UIKit) 16| | import UIKit 17| |#endif 18| | 19| |#if canImport(Cocoa) 20| |import Cocoa 21| |#endif 22| | 23| |@objc(ChartDescription) 24| |open class Description: ComponentBase 25| |{ 26| | public override init() 27| | { 28| | #if os(tvOS) 29| | // 23 is the smallest recommended font size on the TV 30| | font = .systemFont(ofSize: 23) 31| | #elseif os(OSX) 32| | font = .systemFont(ofSize: NSUIFont.systemFontSize) 33| | #else 34| | font = .systemFont(ofSize: 8.0) 35| | #endif 36| | 37| | super.init() 38| | } 39| | 40| | /// The text to be shown as the description. 41| | @objc open var text: String? 42| | 43| | /// Custom position for the description text in pixels on the screen. 44| | open var position: CGPoint? = nil 45| | 46| | /// The text alignment of the description text. Default RIGHT. 47| 52| @objc open var textAlign: NSTextAlignment = NSTextAlignment.right 48| | 49| | /// Font object used for drawing the description text. 50| | @objc open var font: NSUIFont 51| | 52| | /// Text color used for drawing the description text 53| 52| @objc open var textColor = NSUIColor.labelOrBlack 54| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/Legend.swift: 1| |// 2| |// Legend.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartLegend) 16| |open class Legend: ComponentBase 17| |{ 18| | @objc(ChartLegendForm) 19| | public enum Form: Int 20| | { 21| | /// Avoid drawing a form 22| | case none 23| | 24| | /// Do not draw the a form, but leave space for it 25| | case empty 26| | 27| | /// Use default (default dataset's form to the legend's form) 28| | case `default` 29| | 30| | /// Draw a square 31| | case square 32| | 33| | /// Draw a circle 34| | case circle 35| | 36| | /// Draw a horizontal line 37| | case line 38| | } 39| | 40| | @objc(ChartLegendHorizontalAlignment) 41| | public enum HorizontalAlignment: Int 42| | { 43| | case left 44| | case center 45| | case right 46| | } 47| | 48| | @objc(ChartLegendVerticalAlignment) 49| | public enum VerticalAlignment: Int 50| | { 51| | case top 52| | case center 53| | case bottom 54| | } 55| | 56| | @objc(ChartLegendOrientation) 57| | public enum Orientation: Int 58| | { 59| | case horizontal 60| | case vertical 61| | } 62| | 63| | @objc(ChartLegendDirection) 64| | public enum Direction: Int 65| | { 66| | case leftToRight 67| | case rightToLeft 68| | } 69| | 70| | /// The legend entries array 71| 52| @objc open var entries = [LegendEntry]() 72| | 73| | /// Entries that will be appended to the end of the auto calculated entries after calculating the legend. 74| | /// (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect) 75| 52| @objc open var extraEntries = [LegendEntry]() 76| | 77| | /// Are the legend labels/colors a custom value or auto calculated? If false, then it's auto, if true, then custom. 78| | /// 79| | /// **default**: false (automatic legend) 80| | private var _isLegendCustom = false 81| | 82| | /// The horizontal alignment of the legend 83| 52| @objc open var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.left 84| | 85| | /// The vertical alignment of the legend 86| 52| @objc open var verticalAlignment: VerticalAlignment = VerticalAlignment.bottom 87| | 88| | /// The orientation of the legend 89| 52| @objc open var orientation: Orientation = Orientation.horizontal 90| | 91| | /// Flag indicating whether the legend will draw inside the chart or outside 92| | @objc open var drawInside: Bool = false 93| | 94| | /// Flag indicating whether the legend will draw inside the chart or outside 95| | @objc open var isDrawInsideEnabled: Bool { return drawInside } 96| | 97| | /// The text direction of the legend 98| 52| @objc open var direction: Direction = Direction.leftToRight 99| | 100| 52| @objc open var font: NSUIFont = NSUIFont.systemFont(ofSize: 10.0) 101| 52| @objc open var textColor = NSUIColor.labelOrBlack 102| | 103| | /// The form/shape of the legend forms 104| 52| @objc open var form = Form.square 105| | 106| | /// The size of the legend forms 107| 52| @objc open var formSize = CGFloat(8.0) 108| | 109| | /// The line width for forms that consist of lines 110| 52| @objc open var formLineWidth = CGFloat(3.0) 111| | 112| | /// Line dash configuration for shapes that consist of lines. 113| | /// 114| | /// This is how much (in pixels) into the dash pattern are we starting from. 115| | @objc open var formLineDashPhase: CGFloat = 0.0 116| | 117| | /// Line dash configuration for shapes that consist of lines. 118| | /// 119| | /// This is the actual dash pattern. 120| | /// I.e. [2, 3] will paint [-- -- ] 121| | /// [1, 3, 4, 2] will paint [- ---- - ---- ] 122| | @objc open var formLineDashLengths: [CGFloat]? 123| | 124| 52| @objc open var xEntrySpace = CGFloat(6.0) 125| 52| @objc open var yEntrySpace = CGFloat(0.0) 126| 52| @objc open var formToTextSpace = CGFloat(5.0) 127| 52| @objc open var stackSpace = CGFloat(3.0) 128| | 129| 52| @objc open var calculatedLabelSizes = [CGSize]() 130| 52| @objc open var calculatedLabelBreakPoints = [Bool]() 131| 52| @objc open var calculatedLineSizes = [CGSize]() 132| | 133| | public override init() 134| | { 135| | super.init() 136| | 137| | self.xOffset = 5.0 138| | self.yOffset = 3.0 139| | } 140| | 141| | @objc public init(entries: [LegendEntry]) 142| | { 143| | super.init() 144| | 145| | self.entries = entries 146| | } 147| | 148| | @objc open func getMaximumEntrySize(withFont font: NSUIFont) -> CGSize 149| | { 150| | var maxW = CGFloat(0.0) 151| | var maxH = CGFloat(0.0) 152| | 153| | var maxFormSize: CGFloat = 0.0 154| | 155| | for entry in entries 156| | { 157| | let formSize = entry.formSize.isNaN ? self.formSize : entry.formSize 158| | if formSize > maxFormSize 159| | { 160| | maxFormSize = formSize 161| | } 162| | 163| | guard let label = entry.label 164| | else { continue } 165| | 166| | let size = (label as NSString).size(withAttributes: [.font: font]) 167| | 168| | if size.width > maxW 169| | { 170| | maxW = size.width 171| | } 172| | if size.height > maxH 173| | { 174| | maxH = size.height 175| | } 176| | } 177| | 178| | return CGSize( 179| | width: maxW + maxFormSize + formToTextSpace, 180| | height: maxH 181| | ) 182| | } 183| | 184| 52| @objc open var neededWidth = CGFloat(0.0) 185| 52| @objc open var neededHeight = CGFloat(0.0) 186| 52| @objc open var textWidthMax = CGFloat(0.0) 187| 52| @objc open var textHeightMax = CGFloat(0.0) 188| | 189| | /// flag that indicates if word wrapping is enabled 190| | /// this is currently supported only for `orientation == Horizontal`. 191| | /// you may want to set maxSizePercent when word wrapping, to set the point where the text wraps. 192| | /// 193| | /// **default**: true 194| | @objc open var wordWrapEnabled = true 195| | 196| | /// if this is set, then word wrapping the legend is enabled. 197| | @objc open var isWordWrapEnabled: Bool { return wordWrapEnabled } 198| | 199| | /// The maximum relative size out of the whole chart view in percent. 200| | /// If the legend is to the right/left of the chart, then this affects the width of the legend. 201| | /// If the legend is to the top/bottom of the chart, then this affects the height of the legend. 202| | /// 203| | /// **default**: 0.95 (95%) 204| | @objc open var maxSizePercent: CGFloat = 0.95 205| | 206| | @objc open func calculateDimensions(labelFont: NSUIFont, viewPortHandler: ViewPortHandler) 207| | { 208| | let maxEntrySize = getMaximumEntrySize(withFont: labelFont) 209| | let defaultFormSize = self.formSize 210| | let stackSpace = self.stackSpace 211| | let formToTextSpace = self.formToTextSpace 212| | let xEntrySpace = self.xEntrySpace 213| | let yEntrySpace = self.yEntrySpace 214| | let wordWrapEnabled = self.wordWrapEnabled 215| | let entries = self.entries 216| | let entryCount = entries.count 217| | 218| | textWidthMax = maxEntrySize.width 219| | textHeightMax = maxEntrySize.height 220| | 221| | switch orientation 222| | { 223| | case .vertical: 224| | 225| | var maxWidth = CGFloat(0.0) 226| | var width = CGFloat(0.0) 227| | var maxHeight = CGFloat(0.0) 228| | let labelLineHeight = labelFont.lineHeight 229| | 230| | var wasStacked = false 231| | 232| | for i in entries.indices 233| | { 234| | let e = entries[i] 235| | let drawingForm = e.form != .none 236| | let formSize = e.formSize.isNaN ? defaultFormSize : e.formSize 237| | 238| | if !wasStacked 239| | { 240| | width = 0.0 241| | } 242| | 243| | if drawingForm 244| | { 245| | if wasStacked 246| | { 247| | width += stackSpace 248| | } 249| | width += formSize 250| | } 251| | 252| | if let label = e.label 253| | { 254| | let size = (label as NSString).size(withAttributes: [.font: labelFont]) 255| | 256| | if drawingForm && !wasStacked 257| | { 258| | width += formToTextSpace 259| | } 260| | else if wasStacked 261| | { 262| | maxWidth = max(maxWidth, width) 263| | maxHeight += labelLineHeight + yEntrySpace 264| | width = 0.0 265| | wasStacked = false 266| | } 267| | 268| | width += size.width 269| | maxHeight += labelLineHeight + yEntrySpace 270| | } 271| | else 272| | { 273| | wasStacked = true 274| | width += formSize 275| | 276| | if i < entryCount - 1 277| | { 278| | width += stackSpace 279| | } 280| | } 281| | 282| | maxWidth = max(maxWidth, width) 283| | } 284| | 285| | neededWidth = maxWidth 286| | neededHeight = maxHeight 287| | 288| | case .horizontal: 289| | 290| | let labelLineHeight = labelFont.lineHeight 291| | 292| | let contentWidth: CGFloat = viewPortHandler.contentWidth * maxSizePercent 293| | 294| | // Prepare arrays for calculated layout 295| | if calculatedLabelSizes.count != entryCount 296| | { 297| | calculatedLabelSizes = [CGSize](repeating: CGSize(), count: entryCount) 298| | } 299| | 300| | if calculatedLabelBreakPoints.count != entryCount 301| | { 302| | calculatedLabelBreakPoints = [Bool](repeating: false, count: entryCount) 303| | } 304| | 305| | calculatedLineSizes.removeAll(keepingCapacity: true) 306| | 307| | // Start calculating layout 308| | 309| | var maxLineWidth: CGFloat = 0.0 310| | var currentLineWidth: CGFloat = 0.0 311| | var requiredWidth: CGFloat = 0.0 312| | var stackedStartIndex: Int = -1 313| | 314| | for i in entries.indices 315| | { 316| | let e = entries[i] 317| | let drawingForm = e.form != .none 318| | let label = e.label 319| | 320| | calculatedLabelBreakPoints[i] = false 321| | 322| | if stackedStartIndex == -1 323| | { 324| | // we are not stacking, so required width is for this label only 325| | requiredWidth = 0.0 326| | } 327| | else 328| | { 329| | // add the spacing appropriate for stacked labels/forms 330| | requiredWidth += stackSpace 331| | } 332| | 333| | // grouped forms have null labels 334| | if let label = label 335| | { 336| | calculatedLabelSizes[i] = (label as NSString).size(withAttributes: [.font: labelFont]) 337| | requiredWidth += drawingForm ? formToTextSpace + formSize : 0.0 338| | requiredWidth += calculatedLabelSizes[i].width 339| | } 340| | else 341| | { 342| | calculatedLabelSizes[i] = CGSize() 343| | requiredWidth += drawingForm ? formSize : 0.0 344| | 345| | if stackedStartIndex == -1 346| | { 347| | // mark this index as we might want to break here later 348| | stackedStartIndex = i 349| | } 350| | } 351| | 352| | if label != nil || i == entryCount - 1 353| | { 354| | let requiredSpacing = currentLineWidth == 0.0 ? 0.0 : xEntrySpace 355| | 356| | if (!wordWrapEnabled || // No word wrapping, it must fit. 357| | currentLineWidth == 0.0 || // The line is empty, it must fit. 358| | (contentWidth - currentLineWidth >= requiredSpacing + requiredWidth)) // It simply fits 359| | { 360| | // Expand current line 361| | currentLineWidth += requiredSpacing + requiredWidth 362| | } 363| | else 364| | { // It doesn't fit, we need to wrap a line 365| | 366| | // Add current line size to array 367| | calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight)) 368| | maxLineWidth = max(maxLineWidth, currentLineWidth) 369| | 370| | // Start a new line 371| | calculatedLabelBreakPoints[stackedStartIndex > -1 ? stackedStartIndex : i] = true 372| | currentLineWidth = requiredWidth 373| | } 374| | 375| | if i == entryCount - 1 376| | { // Add last line size to array 377| | calculatedLineSizes.append(CGSize(width: currentLineWidth, height: labelLineHeight)) 378| | maxLineWidth = max(maxLineWidth, currentLineWidth) 379| | } 380| | } 381| | 382| | stackedStartIndex = label != nil ? -1 : stackedStartIndex 383| | } 384| | 385| | neededWidth = maxLineWidth 386| | neededHeight = labelLineHeight * CGFloat(calculatedLineSizes.count) + 387| | yEntrySpace * CGFloat(calculatedLineSizes.isEmpty ? 0 : (calculatedLineSizes.count - 1)) 388| | } 389| | 390| | neededWidth += xOffset 391| | neededHeight += yOffset 392| | } 393| | 394| | /// MARK: - Custom legend 395| | 396| | /// Sets a custom legend's entries array. 397| | /// * A nil label will start a group. 398| | /// This will disable the feature that automatically calculates the legend entries from the datasets. 399| | /// Call `resetCustom(...)` to re-enable automatic calculation (and then `notifyDataSetChanged()` is needed). 400| | @objc open func setCustom(entries: [LegendEntry]) 401| | { 402| | self.entries = entries 403| | _isLegendCustom = true 404| | } 405| | 406| | /// Calling this will disable the custom legend entries (set by `setLegend(...)`). Instead, the entries will again be calculated automatically (after `notifyDataSetChanged()` is called). 407| | @objc open func resetCustom() 408| | { 409| | _isLegendCustom = false 410| | } 411| | 412| | /// **default**: false (automatic legend) 413| | /// `true` if a custom legend entries has been set 414| | @objc open var isLegendCustom: Bool 415| | { 416| | return _isLegendCustom 417| | } 418| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/LegendEntry.swift: 1| |// 2| |// LegendEntry.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartLegendEntry) 16| |open class LegendEntry: NSObject 17| |{ 18| | public override init() 19| | { 20| | super.init() 21| | } 22| | 23| | /// - Parameters: 24| | /// - label: The legend entry text. 25| | /// A `nil` label will start a group. 26| | @objc public init(label: String?) 27| | { 28| | self.label = label 29| | } 30| | 31| | /// The legend entry text. 32| | /// A `nil` label will start a group. 33| | @objc open var label: String? 34| | 35| | /// The color for drawing the label 36| | @objc open var labelColor: NSUIColor? 37| | 38| | /// The form to draw for this entry. 39| | /// 40| | /// `None` will avoid drawing a form, and any related space. 41| | /// `Empty` will avoid drawing a form, but keep its space. 42| | /// `Default` will use the Legend's default. 43| 180| @objc open var form: Legend.Form = .default 44| | 45| | /// Form size will be considered except for when .None is used 46| | /// 47| | /// Set as NaN to use the legend's default 48| 180| @objc open var formSize: CGFloat = CGFloat.nan 49| | 50| | /// Line width used for shapes that consist of lines. 51| | /// 52| | /// Set to NaN to use the legend's default. 53| 180| @objc open var formLineWidth: CGFloat = CGFloat.nan 54| | 55| | /// Line dash configuration for shapes that consist of lines. 56| | /// 57| | /// This is how much (in pixels) into the dash pattern are we starting from. 58| | /// 59| | /// Set to NaN to use the legend's default. 60| | @objc open var formLineDashPhase: CGFloat = 0.0 61| | 62| | /// Line dash configuration for shapes that consist of lines. 63| | /// 64| | /// This is the actual dash pattern. 65| | /// I.e. [2, 3] will paint [-- -- ] 66| | /// [1, 3, 4, 2] will paint [- ---- - ---- ] 67| | /// 68| | /// Set to nil to use the legend's default. 69| | @objc open var formLineDashLengths: [CGFloat]? 70| | 71| | /// The color for drawing the form 72| | @objc open var formColor: NSUIColor? 73| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/MarkerImage.swift: 1| |// 2| |// ChartMarkerImage.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartMarkerImage) 16| |open class MarkerImage: NSObject, Marker 17| |{ 18| | /// The marker image to render 19| | @objc open var image: NSUIImage? 20| | 21| 0| open var offset: CGPoint = CGPoint() 22| | 23| | @objc open weak var chartView: ChartViewBase? 24| | 25| | /// As long as size is 0.0/0.0 - it will default to the image's size 26| 0| @objc open var size: CGSize = CGSize() 27| | 28| | public override init() 29| | { 30| | super.init() 31| | } 32| | 33| | open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint 34| | { 35| | var offset = self.offset 36| | 37| | let chart = self.chartView 38| | 39| | var size = self.size 40| | 41| | if size.width == 0.0 && image != nil 42| | { 43| | size.width = image?.size.width ?? 0.0 44| | } 45| | if size.height == 0.0 && image != nil 46| | { 47| | size.height = image?.size.height ?? 0.0 48| | } 49| | 50| | let width = size.width 51| | let height = size.height 52| | 53| | if point.x + offset.x < 0.0 54| | { 55| | offset.x = -point.x 56| | } 57| | else if chart != nil && point.x + width + offset.x > chart!.bounds.size.width 58| | { 59| | offset.x = chart!.bounds.size.width - point.x - width 60| | } 61| | 62| | if point.y + offset.y < 0 63| | { 64| | offset.y = -point.y 65| | } 66| | else if chart != nil && point.y + height + offset.y > chart!.bounds.size.height 67| | { 68| | offset.y = chart!.bounds.size.height - point.y - height 69| | } 70| | 71| | return offset 72| | } 73| | 74| | open func refreshContent(entry: ChartDataEntry, highlight: Highlight) 75| | { 76| | // Do nothing here... 77| | } 78| | 79| | open func draw(context: CGContext, point: CGPoint) 80| | { 81| | guard let image = image else { return } 82| | 83| | let offset = offsetForDrawing(atPoint: point) 84| | 85| | var size = self.size 86| | 87| | if size.width == 0.0 88| | { 89| | size.width = image.size.width 90| | } 91| | if size.height == 0.0 92| | { 93| | size.height = image.size.height 94| | } 95| | 96| | let rect = CGRect( 97| | x: point.x + offset.x, 98| | y: point.y + offset.y, 99| | width: size.width, 100| | height: size.height) 101| | 102| | NSUIGraphicsPushContext(context) 103| | image.draw(in: rect) 104| | NSUIGraphicsPopContext() 105| | } 106| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/MarkerView.swift: 1| |// 2| |// ChartMarkerView.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if canImport(AppKit) 16| |import AppKit 17| |#endif 18| | 19| |@objc(ChartMarkerView) 20| |open class MarkerView: NSUIView, Marker 21| |{ 22| 0| open var offset: CGPoint = CGPoint() 23| | 24| | @objc open weak var chartView: ChartViewBase? 25| | 26| | open func offsetForDrawing(atPoint point: CGPoint) -> CGPoint 27| | { 28| | guard let chart = chartView else { return self.offset } 29| | 30| | var offset = self.offset 31| | 32| | let width = self.bounds.size.width 33| | let height = self.bounds.size.height 34| | 35| | if point.x + offset.x < 0.0 36| | { 37| | offset.x = -point.x 38| | } 39| | else if point.x + width + offset.x > chart.bounds.size.width 40| | { 41| | offset.x = chart.bounds.size.width - point.x - width 42| | } 43| | 44| | if point.y + offset.y < 0 45| | { 46| | offset.y = -point.y 47| | } 48| | else if point.y + height + offset.y > chart.bounds.size.height 49| | { 50| | offset.y = chart.bounds.size.height - point.y - height 51| | } 52| | 53| | return offset 54| | } 55| | 56| | open func refreshContent(entry: ChartDataEntry, highlight: Highlight) 57| | { 58| | // Do nothing here... 59| | } 60| | 61| | open func draw(context: CGContext, point: CGPoint) 62| | { 63| | let offset = self.offsetForDrawing(atPoint: point) 64| | 65| | context.saveGState() 66| | context.translateBy(x: point.x + offset.x, 67| | y: point.y + offset.y) 68| | NSUIGraphicsPushContext(context) 69| | self.nsuiLayer?.render(in: context) 70| | NSUIGraphicsPopContext() 71| | context.restoreGState() 72| | } 73| | 74| | @objc 75| | open class func viewFromXib(in bundle: Bundle = .main) -> MarkerView? 76| | { 77| | #if !os(OSX) 78| | 79| | return bundle.loadNibNamed( 80| | String(describing: self), 81| | owner: nil, 82| | options: nil)?[0] as? MarkerView 83| | #else 84| | 85| | var loadedObjects = NSArray() 86| | let loadedObjectsPointer = AutoreleasingUnsafeMutablePointer(&loadedObjects) 87| | 88| | if bundle.loadNibNamed( 89| | NSNib.Name(String(describing: self)), 90| | owner: nil, 91| | topLevelObjects: loadedObjectsPointer) 92| | { 93| | return loadedObjects[0] as? MarkerView 94| | } 95| | 96| | return nil 97| | #endif 98| | } 99| | 100| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/XAxis.swift: 1| |// 2| |// XAxis.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartXAxis) 16| |open class XAxis: AxisBase 17| |{ 18| | @objc(XAxisLabelPosition) 19| | public enum LabelPosition: Int 20| | { 21| | case top 22| | case bottom 23| | case bothSided 24| | case topInside 25| | case bottomInside 26| | } 27| | 28| | /// width of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers 29| 46| @objc open var labelWidth = CGFloat(1.0) 30| | 31| | /// height of the x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers 32| 46| @objc open var labelHeight = CGFloat(1.0) 33| | 34| | /// width of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers 35| 46| @objc open var labelRotatedWidth = CGFloat(1.0) 36| | 37| | /// height of the (rotated) x-axis labels in pixels - this is automatically calculated by the `computeSize()` methods in the renderers 38| 46| @objc open var labelRotatedHeight = CGFloat(1.0) 39| | 40| | /// This is the angle for drawing the X axis labels (in degrees) 41| 46| @objc open var labelRotationAngle = CGFloat(0.0) 42| | 43| | /// if set to true, the chart will avoid that the first and last label entry in the chart "clip" off the edge of the chart 44| | @objc open var avoidFirstLastClippingEnabled = false 45| | 46| | /// the position of the x-labels relative to the chart 47| 46| @objc open var labelPosition = LabelPosition.top 48| | 49| | /// if set to true, word wrapping the labels will be enabled. 50| | /// word wrapping is done using `(value width * labelRotatedWidth)` 51| | /// 52| | /// - Note: currently supports all charts except pie/radar/horizontal-bar* 53| | @objc open var wordWrapEnabled = false 54| | 55| | /// `true` if word wrapping the labels is enabled 56| | @objc open var isWordWrapEnabled: Bool { return wordWrapEnabled } 57| | 58| | /// the width for wrapping the labels, as percentage out of one value width. 59| | /// used only when isWordWrapEnabled = true. 60| | /// 61| | /// **default**: 1.0 62| | @objc open var wordWrapWidthPercent: CGFloat = 1.0 63| | 64| | public override init() 65| | { 66| | super.init() 67| | 68| | self.yOffset = 4.0 69| | } 70| | 71| | @objc open var isAvoidFirstLastClippingEnabled: Bool 72| | { 73| | return avoidFirstLastClippingEnabled 74| | } 75| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Components/YAxis.swift: 1| |// 2| |// YAxis.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if canImport(UIKit) 16| | import UIKit 17| |#endif 18| | 19| |#if canImport(Cocoa) 20| |import Cocoa 21| |#endif 22| | 23| | 24| |/// Class representing the y-axis labels settings and its entries. 25| |/// Be aware that not all features the YLabels class provides are suitable for the RadarChart. 26| |/// Customizations that affect the value range of the axis need to be applied before setting data for the chart. 27| |@objc(ChartYAxis) 28| |open class YAxis: AxisBase 29| |{ 30| | @objc(YAxisLabelPosition) 31| | public enum LabelPosition: Int 32| | { 33| | case outsideChart 34| | case insideChart 35| | } 36| | 37| | /// Enum that specifies the axis a DataSet should be plotted against, either Left or Right. 38| | @objc 39| | public enum AxisDependency: Int 40| | { 41| | case left 42| | case right 43| | } 44| | 45| | /// indicates if the bottom y-label entry is drawn or not 46| | @objc open var drawBottomYLabelEntryEnabled = true 47| | 48| | /// indicates if the top y-label entry is drawn or not 49| | @objc open var drawTopYLabelEntryEnabled = true 50| | 51| | /// flag that indicates if the axis is inverted or not 52| | @objc open var inverted = false 53| | 54| | /// flag that indicates if the zero-line should be drawn regardless of other grid lines 55| | @objc open var drawZeroLineEnabled = false 56| | 57| | /// Color of the zero line 58| 92| @objc open var zeroLineColor: NSUIColor? = NSUIColor.gray 59| | 60| | /// Width of the zero line 61| | @objc open var zeroLineWidth: CGFloat = 1.0 62| | 63| | /// This is how much (in pixels) into the dash pattern are we starting from. 64| 92| @objc open var zeroLineDashPhase = CGFloat(0.0) 65| | 66| | /// This is the actual dash pattern. 67| | /// I.e. [2, 3] will paint [-- -- ] 68| | /// [1, 3, 4, 2] will paint [- ---- - ---- ] 69| | @objc open var zeroLineDashLengths: [CGFloat]? 70| | 71| | /// axis space from the largest value to the top in percent of the total axis range 72| 92| @objc open var spaceTop = CGFloat(0.1) 73| | 74| | /// axis space from the smallest value to the bottom in percent of the total axis range 75| 92| @objc open var spaceBottom = CGFloat(0.1) 76| | 77| | /// the position of the y-labels relative to the chart 78| 92| @objc open var labelPosition = LabelPosition.outsideChart 79| | 80| | /// the alignment of the text in the y-label 81| 92| @objc open var labelAlignment: NSTextAlignment = .left 82| | 83| | /// the horizontal offset of the y-label 84| | @objc open var labelXOffset: CGFloat = 0.0 85| | 86| | /// the side this axis object represents 87| 92| private var _axisDependency = AxisDependency.left 88| | 89| | /// the minimum width that the axis should take 90| | /// 91| | /// **default**: 0.0 92| 92| @objc open var minWidth = CGFloat(0) 93| | 94| | /// the maximum width that the axis can take. 95| | /// use Infinity for disabling the maximum. 96| | /// 97| | /// **default**: CGFloat.infinity 98| 92| @objc open var maxWidth = CGFloat(CGFloat.infinity) 99| | 100| | public override init() 101| | { 102| | super.init() 103| | 104| | self.yOffset = 0.0 105| | } 106| | 107| | @objc public init(position: AxisDependency) 108| | { 109| | super.init() 110| | 111| | _axisDependency = position 112| | 113| | self.yOffset = 0.0 114| | } 115| | 116| | @objc open var axisDependency: AxisDependency 117| | { 118| | return _axisDependency 119| | } 120| | 121| | @objc open func requiredSize() -> CGSize 122| | { 123| | let label = getLongestLabel() as NSString 124| | var size = label.size(withAttributes: [.font: labelFont]) 125| | size.width += xOffset * 2.0 126| | size.height += yOffset * 2.0 127| | size.width = max(minWidth, min(size.width, maxWidth > 0.0 ? maxWidth : size.width)) 128| | return size 129| | } 130| | 131| | @objc open func getRequiredHeightSpace() -> CGFloat 132| | { 133| | return requiredSize().height 134| | } 135| | 136| | /// `true` if this axis needs horizontal offset, `false` ifno offset is needed. 137| | @objc open var needsOffset: Bool 138| | { 139| | if isEnabled && isDrawLabelsEnabled && labelPosition == .outsideChart 140| | { 141| | return true 142| | } 143| | else 144| | { 145| | return false 146| | } 147| | } 148| | 149| | @objc open var isInverted: Bool { return inverted } 150| | 151| | open override func calculate(min dataMin: Double, max dataMax: Double) 152| | { 153| | // if custom, use value as is, else use data value 154| | var min = _customAxisMin ? _axisMinimum : dataMin 155| | var max = _customAxisMax ? _axisMaximum : dataMax 156| | 157| | // Make sure max is greater than min 158| | // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 159| | if min > max 160| | { 161| | switch(_customAxisMax, _customAxisMin) 162| | { 163| | case(true, true): 164| | (min, max) = (max, min) 165| | case(true, false): 166| | min = max < 0 ? max * 1.5 : max * 0.5 167| | case(false, true): 168| | max = min < 0 ? min * 0.5 : min * 1.5 169| | case(false, false): 170| | break 171| | } 172| | } 173| | 174| | // temporary range (before calculations) 175| | let range = abs(max - min) 176| | 177| | // in case all values are equal 178| | if range == 0.0 179| | { 180| | max = max + 1.0 181| | min = min - 1.0 182| | } 183| | 184| | // bottom-space only effects non-custom min 185| | if !_customAxisMin 186| | { 187| | let bottomSpace = range * Double(spaceBottom) 188| | _axisMinimum = (min - bottomSpace) 189| | } 190| | 191| | // top-space only effects non-custom max 192| | if !_customAxisMax 193| | { 194| | let topSpace = range * Double(spaceTop) 195| | _axisMaximum = (max + topSpace) 196| | } 197| | 198| | // calc actual range 199| | axisRange = abs(_axisMaximum - _axisMinimum) 200| | } 201| | 202| | @objc open var isDrawBottomYLabelEntryEnabled: Bool { return drawBottomYLabelEntryEnabled } 203| | 204| | @objc open var isDrawTopYLabelEntryEnabled: Bool { return drawTopYLabelEntryEnabled } 205| | 206| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/ChartBaseDataSet.swift: 1| |// 2| |// BaseDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class ChartBaseDataSet: NSObject, ChartDataSetProtocol, NSCopying 17| |{ 18| | public required override init() 19| | { 20| | super.init() 21| | 22| | // default color 23| | colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 24| | valueColors.append(.labelOrBlack) 25| | } 26| | 27| | @objc public init(label: String) 28| | { 29| | super.init() 30| | 31| | // default color 32| | colors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 33| | valueColors.append(.labelOrBlack) 34| | 35| | self.label = label 36| | } 37| | 38| | // MARK: - Data functions and accessors 39| | 40| | /// Use this method to tell the data set that the underlying data has changed 41| | open func notifyDataSetChanged() 42| | { 43| | calcMinMax() 44| | } 45| | 46| | open func calcMinMax() 47| | { 48| | fatalError("calcMinMax is not implemented in ChartBaseDataSet") 49| | } 50| | 51| | open func calcMinMaxY(fromX: Double, toX: Double) 52| | { 53| | fatalError("calcMinMaxY(fromX:, toX:) is not implemented in ChartBaseDataSet") 54| | } 55| | 56| | open var yMin: Double 57| | { 58| | fatalError("yMin is not implemented in ChartBaseDataSet") 59| | } 60| | 61| | open var yMax: Double 62| | { 63| | fatalError("yMax is not implemented in ChartBaseDataSet") 64| | } 65| | 66| | open var xMin: Double 67| | { 68| | fatalError("xMin is not implemented in ChartBaseDataSet") 69| | } 70| | 71| | open var xMax: Double 72| | { 73| | fatalError("xMax is not implemented in ChartBaseDataSet") 74| | } 75| | 76| | open var entryCount: Int 77| | { 78| | fatalError("entryCount is not implemented in ChartBaseDataSet") 79| | } 80| | 81| | open func entryForIndex(_ i: Int) -> ChartDataEntry? 82| | { 83| | fatalError("entryForIndex is not implemented in ChartBaseDataSet") 84| | } 85| | 86| | open func entryForXValue( 87| | _ x: Double, 88| | closestToY y: Double, 89| | rounding: ChartDataSetRounding) -> ChartDataEntry? 90| | { 91| | fatalError("entryForXValue(x, closestToY, rounding) is not implemented in ChartBaseDataSet") 92| | } 93| | 94| | open func entryForXValue( 95| | _ x: Double, 96| | closestToY y: Double) -> ChartDataEntry? 97| | { 98| | fatalError("entryForXValue(x, closestToY) is not implemented in ChartBaseDataSet") 99| | } 100| | 101| | open func entriesForXValue(_ x: Double) -> [ChartDataEntry] 102| | { 103| | fatalError("entriesForXValue is not implemented in ChartBaseDataSet") 104| | } 105| | 106| | open func entryIndex( 107| | x xValue: Double, 108| | closestToY y: Double, 109| | rounding: ChartDataSetRounding) -> Int 110| | { 111| | fatalError("entryIndex(x, closestToY, rounding) is not implemented in ChartBaseDataSet") 112| | } 113| | 114| | open func entryIndex(entry e: ChartDataEntry) -> Int 115| | { 116| | fatalError("entryIndex(entry) is not implemented in ChartBaseDataSet") 117| | } 118| | 119| | open func addEntry(_ e: ChartDataEntry) -> Bool 120| | { 121| | fatalError("addEntry is not implemented in ChartBaseDataSet") 122| | } 123| | 124| | open func addEntryOrdered(_ e: ChartDataEntry) -> Bool 125| | { 126| | fatalError("addEntryOrdered is not implemented in ChartBaseDataSet") 127| | } 128| | 129| | @discardableResult open func removeEntry(_ entry: ChartDataEntry) -> Bool 130| | { 131| | fatalError("removeEntry is not implemented in ChartBaseDataSet") 132| | } 133| | 134| | @discardableResult open func removeEntry(index: Int) -> Bool 135| | { 136| | if let entry = entryForIndex(index) 137| | { 138| | return removeEntry(entry) 139| | } 140| | return false 141| | } 142| | 143| | @discardableResult open func removeEntry(x: Double) -> Bool 144| | { 145| | if let entry = entryForXValue(x, closestToY: Double.nan) 146| | { 147| | return removeEntry(entry) 148| | } 149| | return false 150| | } 151| | 152| | @discardableResult open func removeFirst() -> Bool 153| | { 154| | if entryCount > 0 155| | { 156| | if let entry = entryForIndex(0) 157| | { 158| | return removeEntry(entry) 159| | } 160| | } 161| | return false 162| | } 163| | 164| | @discardableResult open func removeLast() -> Bool 165| | { 166| | if entryCount > 0 167| | { 168| | if let entry = entryForIndex(entryCount - 1) 169| | { 170| | return removeEntry(entry) 171| | } 172| | } 173| | return false 174| | } 175| | 176| | open func contains(_ e: ChartDataEntry) -> Bool 177| | { 178| | fatalError("removeEntry is not implemented in ChartBaseDataSet") 179| | } 180| | 181| | open func clear() 182| | { 183| | fatalError("clear is not implemented in ChartBaseDataSet") 184| | } 185| | 186| | // MARK: - Styling functions and accessors 187| | 188| | /// All the colors that are used for this DataSet. 189| | /// Colors are reused as soon as the number of Entries the DataSet represents is higher than the size of the colors array. 190| 64| open var colors = [NSUIColor]() 191| | 192| | /// List representing all colors that are used for drawing the actual values for this DataSet 193| 64| open var valueColors = [NSUIColor]() 194| | 195| | /// The label string that describes the DataSet. 196| 64| open var label: String? = "DataSet" 197| | 198| | /// The axis this DataSet should be plotted against. 199| 64| open var axisDependency = YAxis.AxisDependency.left 200| | 201| | /// - Returns: The color at the given index of the DataSet's color array. 202| | /// This prevents out-of-bounds by performing a modulus on the color index, so colours will repeat themselves. 203| | open func color(atIndex index: Int) -> NSUIColor 204| | { 205| | var index = index 206| | if index < 0 207| | { 208| | index = 0 209| | } 210| | return colors[index % colors.count] 211| | } 212| | 213| | /// Resets all colors of this DataSet and recreates the colors array. 214| | open func resetColors() 215| | { 216| | colors.removeAll(keepingCapacity: false) 217| | } 218| | 219| | /// Adds a new color to the colors array of the DataSet. 220| | /// 221| | /// - Parameters: 222| | /// - color: the color to add 223| | open func addColor(_ color: NSUIColor) 224| | { 225| | colors.append(color) 226| | } 227| | 228| | /// Sets the one and **only** color that should be used for this DataSet. 229| | /// Internally, this recreates the colors array and adds the specified color. 230| | /// 231| | /// - Parameters: 232| | /// - color: the color to set 233| | open func setColor(_ color: NSUIColor) 234| | { 235| | colors.removeAll(keepingCapacity: false) 236| | colors.append(color) 237| | } 238| | 239| | /// Sets colors to a single color a specific alpha value. 240| | /// 241| | /// - Parameters: 242| | /// - color: the color to set 243| | /// - alpha: alpha to apply to the set `color` 244| | @objc open func setColor(_ color: NSUIColor, alpha: CGFloat) 245| | { 246| | setColor(color.withAlphaComponent(alpha)) 247| | } 248| | 249| | /// Sets colors with a specific alpha value. 250| | /// 251| | /// - Parameters: 252| | /// - colors: the colors to set 253| | /// - alpha: alpha to apply to the set `colors` 254| | @objc open func setColors(_ colors: [NSUIColor], alpha: CGFloat) 255| | { 256| | self.colors = colors.map { $0.withAlphaComponent(alpha) } 257| | } 258| | 259| | /// Sets colors with a specific alpha value. 260| | /// 261| | /// - Parameters: 262| | /// - colors: the colors to set 263| | /// - alpha: alpha to apply to the set `colors` 264| | open func setColors(_ colors: NSUIColor...) 265| | { 266| | self.colors = colors 267| | } 268| | 269| | /// if true, value highlighting is enabled 270| | open var highlightEnabled = true 271| | 272| | /// `true` if value highlighting is enabled for this dataset 273| | open var isHighlightEnabled: Bool { return highlightEnabled } 274| | 275| | /// Custom formatter that is used instead of the auto-formatter if set 276| | open lazy var valueFormatter: ValueFormatter = DefaultValueFormatter() 277| | 278| | /// Sets/get a single color for value text. 279| | /// Setting the color clears the colors array and adds a single color. 280| | /// Getting will return the first color in the array. 281| | open var valueTextColor: NSUIColor 282| | { 283| | get 284| | { 285| | return valueColors[0] 286| | } 287| | set 288| | { 289| | valueColors.removeAll(keepingCapacity: false) 290| | valueColors.append(newValue) 291| | } 292| | } 293| | 294| | /// - Returns: The color at the specified index that is used for drawing the values inside the chart. Uses modulus internally. 295| | open func valueTextColorAt(_ index: Int) -> NSUIColor 296| | { 297| | var index = index 298| | if index < 0 299| | { 300| | index = 0 301| | } 302| | return valueColors[index % valueColors.count] 303| | } 304| | 305| | /// the font for the value-text labels 306| 64| open var valueFont: NSUIFont = NSUIFont.systemFont(ofSize: 7.0) 307| | 308| | /// The rotation angle (in degrees) for value-text labels 309| 64| open var valueLabelAngle: CGFloat = CGFloat(0.0) 310| | 311| | /// The form to draw for this dataset in the legend. 312| 64| open var form = Legend.Form.default 313| | 314| | /// The form size to draw for this dataset in the legend. 315| | /// 316| | /// Return `NaN` to use the default legend form size. 317| 64| open var formSize: CGFloat = CGFloat.nan 318| | 319| | /// The line width for drawing the form of this dataset in the legend 320| | /// 321| | /// Return `NaN` to use the default legend form line width. 322| 64| open var formLineWidth: CGFloat = CGFloat.nan 323| | 324| | /// Line dash configuration for legend shapes that consist of lines. 325| | /// 326| | /// This is how much (in pixels) into the dash pattern are we starting from. 327| | open var formLineDashPhase: CGFloat = 0.0 328| | 329| | /// Line dash configuration for legend shapes that consist of lines. 330| | /// 331| | /// This is the actual dash pattern. 332| | /// I.e. [2, 3] will paint [-- -- ] 333| | /// [1, 3, 4, 2] will paint [- ---- - ---- ] 334| | open var formLineDashLengths: [CGFloat]? = nil 335| | 336| | /// Set this to true to draw y-values on the chart. 337| | /// 338| | /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no values will be drawn even if this is enabled. 339| | open var drawValuesEnabled = true 340| | 341| | /// `true` if y-value drawing is enabled, `false` ifnot 342| | open var isDrawValuesEnabled: Bool 343| | { 344| | return drawValuesEnabled 345| | } 346| | 347| | /// Set this to true to draw y-icons on the chart. 348| | /// 349| | /// - Note: For bar and line charts: if `maxVisibleCount` is reached, no icons will be drawn even if this is enabled. 350| | open var drawIconsEnabled = true 351| | 352| | /// Returns true if y-icon drawing is enabled, false if not 353| | open var isDrawIconsEnabled: Bool 354| | { 355| | return drawIconsEnabled 356| | } 357| | 358| | /// Offset of icons drawn on the chart. 359| | /// 360| | /// For all charts except Pie and Radar it will be ordinary (x offset, y offset). 361| | /// 362| | /// For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint. 363| 64| open var iconsOffset = CGPoint(x: 0, y: 0) 364| | 365| | /// Set the visibility of this DataSet. If not visible, the DataSet will not be drawn to the chart upon refreshing it. 366| | open var visible = true 367| | 368| | /// `true` if this DataSet is visible inside the chart, or `false` ifit is currently hidden. 369| | open var isVisible: Bool 370| | { 371| | return visible 372| | } 373| | 374| | // MARK: - NSObject 375| | 376| | open override var description: String 377| | { 378| | return String(format: "%@, label: %@, %i entries", arguments: [NSStringFromClass(type(of: self)), self.label ?? "", self.entryCount]) 379| | } 380| | 381| | open override var debugDescription: String 382| | { 383| | return (0.. Any 391| | { 392| | let copy = type(of: self).init() 393| | 394| | copy.colors = colors 395| | copy.valueColors = valueColors 396| | copy.label = label 397| | copy.axisDependency = axisDependency 398| | copy.highlightEnabled = highlightEnabled 399| | copy.valueFormatter = valueFormatter 400| | copy.valueFont = valueFont 401| | copy.form = form 402| | copy.formSize = formSize 403| | copy.formLineWidth = formLineWidth 404| | copy.formLineDashPhase = formLineDashPhase 405| | copy.formLineDashLengths = formLineDashLengths 406| | copy.drawValuesEnabled = drawValuesEnabled 407| | copy.drawValuesEnabled = drawValuesEnabled 408| | copy.iconsOffset = iconsOffset 409| | copy.visible = visible 410| | 411| | return copy 412| | } 413| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/BarChartData.swift: 1| |// 2| |// BarChartData.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class BarChartData: BarLineScatterCandleBubbleChartData 16| |{ 17| | public required init() 18| | { 19| | super.init() 20| | } 21| | 22| | public override init(dataSets: [ChartDataSetProtocol]) 23| | { 24| | super.init(dataSets: dataSets) 25| | } 26| | 27| | public required init(arrayLiteral elements: ChartDataSetProtocol...) 28| | { 29| | super.init(dataSets: elements) 30| | } 31| | 32| | /// The width of the bars on the x-axis, in values (not pixels) 33| | /// 34| | /// **default**: 0.85 35| 40| @objc open var barWidth = Double(0.85) 36| | 37| | /// Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. 38| | /// Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified by the parameters. 39| | /// Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method. 40| | /// 41| | /// - Parameters: 42| | /// - fromX: the starting point on the x-axis where the grouping should begin 43| | /// - groupSpace: The space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f 44| | /// - barSpace: The space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f 45| | @objc open func groupBars(fromX: Double, groupSpace: Double, barSpace: Double) 46| | { 47| | guard !isEmpty else { 48| | print("BarData needs to hold at least 2 BarDataSets to allow grouping.", terminator: "\n") 49| | return 50| | } 51| | 52| | let max = maxEntryCountSet 53| | let maxEntryCount = max?.entryCount ?? 0 54| | 55| | let groupSpaceWidthHalf = groupSpace / 2.0 56| | let barSpaceHalf = barSpace / 2.0 57| | let barWidthHalf = self.barWidth / 2.0 58| | 59| | var fromX = fromX 60| | 61| | let interval = groupWidth(groupSpace: groupSpace, barSpace: barSpace) 62| | 63| | for i in 0.. 0 || diff < 0 91| | { 92| | fromX += diff 93| | } 94| | 95| | } 96| | 97| | notifyDataChanged() 98| | } 99| | 100| | /// In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis. 101| | /// 102| | /// - Parameters: 103| | /// - groupSpace: 104| | /// - barSpace: 105| | @objc open func groupWidth(groupSpace: Double, barSpace: Double) -> Double 106| | { 107| | return Double(count) * (self.barWidth + barSpace) + groupSpace 108| | } 109| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift: 1| |// 2| |// BarChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class BarChartDataSet: BarLineScatterCandleBubbleChartDataSet, BarChartDataSetProtocol 17| |{ 18| | private func initialize() 19| | { 20| | self.highlightColor = NSUIColor.black 21| | 22| | self.calcStackSize(entries: entries as! [BarChartDataEntry]) 23| | self.calcEntryCountIncludingStacks(entries: entries as! [BarChartDataEntry]) 24| | } 25| | 26| | public required init() 27| | { 28| | super.init() 29| | initialize() 30| | } 31| | 32| | public override init(entries: [ChartDataEntry], label: String) 33| | { 34| | super.init(entries: entries, label: label) 35| | initialize() 36| | } 37| | 38| | // MARK: - Data functions and accessors 39| | 40| | /// the maximum number of bars that are stacked upon each other, this value 41| | /// is calculated from the Entries that are added to the DataSet 42| | private var _stackSize = 1 43| | 44| | /// the overall entry count, including counting each stack-value individually 45| | private var _entryCountStacks = 0 46| | 47| | /// Calculates the total number of entries this DataSet represents, including 48| | /// stacks. All values belonging to a stack are calculated separately. 49| | private func calcEntryCountIncludingStacks(entries: [BarChartDataEntry]) 50| | { 51| | _entryCountStacks = 0 52| | 53| | entries.forEach { _entryCountStacks += $0.yValues?.count ?? 1 } 54| | } 55| | 56| | /// calculates the maximum stacksize that occurs in the Entries array of this DataSet 57| | private func calcStackSize(entries: [BarChartDataEntry]) 58| | { 59| | for e in entries where (e.yValues?.count ?? 0) > _stackSize 60| | { 61| | _stackSize = e.yValues!.count 62| | } 63| | } 64| | 65| | open override func calcMinMax(entry e: ChartDataEntry) 66| | { 67| | guard let e = e as? BarChartDataEntry, 68| | !e.y.isNaN 69| | else { return } 70| | 71| | if e.yValues == nil 72| | { 73| | _yMin = Swift.min(e.y, _yMin) 74| | _yMax = Swift.max(e.y, _yMax) 75| | } 76| | else 77| | { 78| | _yMin = Swift.min(-e.negativeSum, _yMin) 79| | _yMax = Swift.max(e.positiveSum, _yMax) 80| | } 81| | 82| | calcMinMaxX(entry: e) 83| | } 84| | 85| | /// The maximum number of bars that can be stacked upon another in this DataSet. 86| | open var stackSize: Int 87| | { 88| | return _stackSize 89| | } 90| | 91| | /// `true` if this DataSet is stacked (stacksize > 1) or not. 92| | open var isStacked: Bool 93| | { 94| | return _stackSize > 1 ? true : false 95| | } 96| | 97| | /// The overall entry count, including counting each stack-value individually 98| | @objc open var entryCountStacks: Int 99| | { 100| | return _entryCountStacks 101| | } 102| | 103| | /// array of labels used to describe the different values of the stacked bars 104| 40| open var stackLabels: [String] = [] 105| | 106| | // MARK: - Styling functions and accessors 107| | 108| | /// the color used for drawing the bar-shadows. The bar shadows is a surface behind the bar that indicates the maximum value 109| 40| open var barShadowColor = NSUIColor(red: 215.0/255.0, green: 215.0/255.0, blue: 215.0/255.0, alpha: 1.0) 110| | 111| | /// the width used for drawing borders around the bars. If borderWidth == 0, no border will be drawn. 112| | open var barBorderWidth : CGFloat = 0.0 113| | 114| | /// the color drawing borders around the bars. 115| 40| open var barBorderColor = NSUIColor.black 116| | 117| | /// the alpha value (transparency) that is used for drawing the highlight indicator bar. min = 0.0 (fully transparent), max = 1.0 (fully opaque) 118| 40| open var highlightAlpha = CGFloat(120.0 / 255.0) 119| | 120| | // MARK: - NSCopying 121| | 122| | open override func copy(with zone: NSZone? = nil) -> Any 123| | { 124| | let copy = super.copy(with: zone) as! BarChartDataSet 125| | copy._stackSize = _stackSize 126| | copy._entryCountStacks = _entryCountStacks 127| | copy.stackLabels = stackLabels 128| | 129| | copy.barShadowColor = barShadowColor 130| | copy.barBorderWidth = barBorderWidth 131| | copy.barBorderColor = barBorderColor 132| | copy.highlightAlpha = highlightAlpha 133| | return copy 134| | } 135| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift: 1| |// 2| |// BarLineScatterCandleBubbleChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class BarLineScatterCandleBubbleChartDataSet: ChartDataSet, BarLineScatterCandleBubbleChartDataSetProtocol 17| |{ 18| | // MARK: - Data functions and accessors 19| | 20| | // MARK: - Styling functions and accessors 21| | 22| 58| open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) 23| 58| open var highlightLineWidth = CGFloat(0.5) 24| 58| open var highlightLineDashPhase = CGFloat(0.0) 25| | open var highlightLineDashLengths: [CGFloat]? 26| | 27| | // MARK: - NSCopying 28| | 29| | open override func copy(with zone: NSZone? = nil) -> Any 30| | { 31| | let copy = super.copy(with: zone) as! BarLineScatterCandleBubbleChartDataSet 32| | copy.highlightColor = highlightColor 33| | copy.highlightLineWidth = highlightLineWidth 34| | copy.highlightLineDashPhase = highlightLineDashPhase 35| | copy.highlightLineDashLengths = highlightLineDashLengths 36| | return copy 37| | } 38| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift: 1| |// 2| |// BubbleDataEntry.swift 3| |// Charts 4| |// 5| |// Bubble chart implementation: 6| |// Copyright 2015 Pierre-Marc Airoldi 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class BubbleChartDataEntry: ChartDataEntry 16| |{ 17| | /// The size of the bubble. 18| 0| @objc open var size = CGFloat(0.0) 19| | 20| | public required init() 21| | { 22| | super.init() 23| | } 24| | 25| | /// - Parameters: 26| | /// - x: The index on the x-axis. 27| | /// - y: The value on the y-axis. 28| | /// - size: The size of the bubble. 29| | @objc public init(x: Double, y: Double, size: CGFloat) 30| | { 31| | super.init(x: x, y: y) 32| | 33| | self.size = size 34| | } 35| | 36| | /// - Parameters: 37| | /// - x: The index on the x-axis. 38| | /// - y: The value on the y-axis. 39| | /// - size: The size of the bubble. 40| | /// - data: Spot for additional data this Entry represents. 41| | @objc public convenience init(x: Double, y: Double, size: CGFloat, data: Any?) 42| | { 43| | self.init(x: x, y: y, size: size) 44| | self.data = data 45| | } 46| | 47| | /// - Parameters: 48| | /// - x: The index on the x-axis. 49| | /// - y: The value on the y-axis. 50| | /// - size: The size of the bubble. 51| | /// - icon: icon image 52| | @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?) 53| | { 54| | self.init(x: x, y: y, size: size) 55| | self.icon = icon 56| | } 57| | 58| | /// - Parameters: 59| | /// - x: The index on the x-axis. 60| | /// - y: The value on the y-axis. 61| | /// - size: The size of the bubble. 62| | /// - icon: icon image 63| | /// - data: Spot for additional data this Entry represents. 64| | @objc public convenience init(x: Double, y: Double, size: CGFloat, icon: NSUIImage?, data: Any?) 65| | { 66| | self.init(x: x, y: y, size: size) 67| | self.icon = icon 68| | self.data = data 69| | } 70| | 71| | // MARK: NSCopying 72| | 73| | open override func copy(with zone: NSZone? = nil) -> Any 74| | { 75| | let copy = super.copy(with: zone) as! BubbleChartDataEntry 76| | copy.size = size 77| | return copy 78| | } 79| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift: 1| |// 2| |// BubbleChartDataSet.swift 3| |// Charts 4| |// 5| |// Bubble chart implementation: 6| |// Copyright 2015 Pierre-Marc Airoldi 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class BubbleChartDataSet: BarLineScatterCandleBubbleChartDataSet, BubbleChartDataSetProtocol 17| |{ 18| | // MARK: - Data functions and accessors 19| | 20| 0| internal var _maxSize = CGFloat(0.0) 21| | 22| | open var maxSize: CGFloat { return _maxSize } 23| | @objc open var normalizeSizeEnabled: Bool = true 24| | open var isNormalizeSizeEnabled: Bool { return normalizeSizeEnabled } 25| | 26| | open override func calcMinMax(entry e: ChartDataEntry) 27| | { 28| | guard let e = e as? BubbleChartDataEntry 29| | else { return } 30| | 31| | super.calcMinMax(entry: e) 32| | 33| | _maxSize = Swift.max(e.size, maxSize) 34| | } 35| | 36| | // MARK: - Styling functions and accessors 37| | 38| | /// Sets/gets the width of the circle that surrounds the bubble when highlighted 39| | open var highlightCircleWidth: CGFloat = 2.5 40| | 41| | // MARK: - NSCopying 42| | 43| | open override func copy(with zone: NSZone? = nil) -> Any 44| | { 45| | let copy = super.copy(with: zone) as! BubbleChartDataSet 46| | copy._xMin = _xMin 47| | copy._xMax = _xMax 48| | copy._maxSize = _maxSize 49| | copy.normalizeSizeEnabled = normalizeSizeEnabled 50| | copy.highlightCircleWidth = highlightCircleWidth 51| | return copy 52| | } 53| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift: 1| |// 2| |// CandleChartDataEntry.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| | 14| |open class CandleChartDataEntry: ChartDataEntry 15| |{ 16| | /// shadow-high value 17| 0| @objc open var high = Double(0.0) 18| | 19| | /// shadow-low value 20| 0| @objc open var low = Double(0.0) 21| | 22| | /// close value 23| 0| @objc open var close = Double(0.0) 24| | 25| | /// open value 26| 0| @objc open var open = Double(0.0) 27| | 28| | public required init() 29| | { 30| | super.init() 31| | } 32| | 33| | @objc public init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double) 34| | { 35| | super.init(x: x, y: (shadowH + shadowL) / 2.0) 36| | 37| | self.high = shadowH 38| | self.low = shadowL 39| | self.open = open 40| | self.close = close 41| | } 42| | 43| | @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?) 44| | { 45| | self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) 46| | self.icon = icon 47| | } 48| | 49| | @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, data: Any?) 50| | { 51| | self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) 52| | self.data = data 53| | } 54| | 55| | @objc public convenience init(x: Double, shadowH: Double, shadowL: Double, open: Double, close: Double, icon: NSUIImage?, data: Any?) 56| | { 57| | self.init(x: x, shadowH: shadowH, shadowL: shadowL, open: open, close: close) 58| | self.icon = icon 59| | self.data = data 60| | } 61| | 62| | /// The overall range (difference) between shadow-high and shadow-low. 63| | @objc open var shadowRange: Double 64| | { 65| | return abs(high - low) 66| | } 67| | 68| | /// The body size (difference between open and close). 69| | @objc open var bodyRange: Double 70| | { 71| | return abs(open - close) 72| | } 73| | 74| | /// the center value of the candle. (Middle value between high and low) 75| | open override var y: Double 76| | { 77| | get 78| | { 79| | return super.y 80| | } 81| | set 82| | { 83| | super.y = (high + low) / 2.0 84| | } 85| | } 86| | 87| | // MARK: NSCopying 88| | 89| | open override func copy(with zone: NSZone? = nil) -> Any 90| | { 91| | let copy = super.copy(with: zone) as! CandleChartDataEntry 92| | copy.high = high 93| | copy.low = low 94| | copy.open = open 95| | copy.close = close 96| | return copy 97| | } 98| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift: 1| |// 2| |// CandleChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class CandleChartDataSet: LineScatterCandleRadarChartDataSet, CandleChartDataSetProtocol 17| |{ 18| | 19| | public required init() 20| | { 21| | super.init() 22| | } 23| | 24| | public override init(entries: [ChartDataEntry], label: String) 25| | { 26| | super.init(entries: entries, label: label) 27| | } 28| | 29| | // MARK: - Data functions and accessors 30| | 31| | open override func calcMinMax(entry e: ChartDataEntry) 32| | { 33| | guard let e = e as? CandleChartDataEntry 34| | else { return } 35| | 36| | _yMin = Swift.min(e.low, _yMin) 37| | _yMax = Swift.max(e.high, _yMax) 38| | 39| | calcMinMaxX(entry: e) 40| | } 41| | 42| | open override func calcMinMaxY(entry e: ChartDataEntry) 43| | { 44| | guard let e = e as? CandleChartDataEntry 45| | else { return } 46| | 47| | _yMin = Swift.min(e.low, _yMin) 48| | _yMax = Swift.max(e.high, _yMin) 49| | 50| | _yMin = Swift.min(e.low, _yMax) 51| | _yMax = Swift.max(e.high, _yMax) 52| | } 53| | 54| | // MARK: - Styling functions and accessors 55| | 56| | /// the space between the candle entries 57| | /// 58| | /// **default**: 0.1 (10%) 59| | private var _barSpace: CGFloat = 0.1 60| | 61| | /// the space that is left out on the left and right side of each candle, 62| | /// **default**: 0.1 (10%), max 0.45, min 0.0 63| | open var barSpace: CGFloat 64| | { 65| | get 66| | { 67| | return _barSpace 68| | } 69| | set 70| | { 71| | _barSpace = newValue.clamped(to: 0...0.45) 72| | } 73| | } 74| | 75| | /// should the candle bars show? 76| | /// when false, only "ticks" will show 77| | /// 78| | /// **default**: true 79| | open var showCandleBar: Bool = true 80| | 81| | /// the width of the candle-shadow-line in pixels. 82| | /// 83| | /// **default**: 1.5 84| 0| open var shadowWidth = CGFloat(1.5) 85| | 86| | /// the color of the shadow line 87| | open var shadowColor: NSUIColor? 88| | 89| | /// use candle color for the shadow 90| | open var shadowColorSameAsCandle = false 91| | 92| | /// Is the shadow color same as the candle color? 93| | open var isShadowColorSameAsCandle: Bool { return shadowColorSameAsCandle } 94| | 95| | /// color for open == close 96| | open var neutralColor: NSUIColor? 97| | 98| | /// color for open > close 99| | open var increasingColor: NSUIColor? 100| | 101| | /// color for open < close 102| | open var decreasingColor: NSUIColor? 103| | 104| | /// Are increasing values drawn as filled? 105| | /// increasing candlesticks are traditionally hollow 106| | open var increasingFilled = false 107| | 108| | /// Are increasing values drawn as filled? 109| | open var isIncreasingFilled: Bool { return increasingFilled } 110| | 111| | /// Are decreasing values drawn as filled? 112| | /// descreasing candlesticks are traditionally filled 113| | open var decreasingFilled = true 114| | 115| | /// Are decreasing values drawn as filled? 116| | open var isDecreasingFilled: Bool { return decreasingFilled } 117| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/ChartData.swift: 1| |// 2| |// ChartData.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| | 14| |open class ChartData: NSObject, ExpressibleByArrayLiteral 15| |{ 16| | 17| 61| @objc public internal(set) var xMax = -Double.greatestFiniteMagnitude 18| 61| @objc public internal(set) var xMin = Double.greatestFiniteMagnitude 19| 61| @objc public internal(set) var yMax = -Double.greatestFiniteMagnitude 20| 61| @objc public internal(set) var yMin = Double.greatestFiniteMagnitude 21| 61| var leftAxisMax = -Double.greatestFiniteMagnitude 22| 61| var leftAxisMin = Double.greatestFiniteMagnitude 23| 61| var rightAxisMax = -Double.greatestFiniteMagnitude 24| 61| var rightAxisMin = Double.greatestFiniteMagnitude 25| | 26| | // MARK: - Accessibility 27| | 28| | /// When the data entry labels are generated identifiers, set this property to prepend a string before each identifier 29| | /// 30| | /// For example, if a label is "#3", settings this property to "Item" allows it to be spoken as "Item #3" 31| | @objc open var accessibilityEntryLabelPrefix: String? 32| | 33| | /// When the data entry value requires a unit, use this property to append the string representation of the unit to the value 34| | /// 35| | /// For example, if a value is "44.1", setting this property to "m" allows it to be spoken as "44.1 m" 36| | @objc open var accessibilityEntryLabelSuffix: String? 37| | 38| | /// If the data entry value is a count, set this to true to allow plurals and other grammatical changes 39| | /// **default**: false 40| | @objc open var accessibilityEntryLabelSuffixIsCount: Bool = false 41| | 42| 61| var _dataSets = [Element]() 43| | 44| | public override required init() 45| | { 46| | super.init() 47| | } 48| | 49| | public required init(arrayLiteral elements: Element...) 50| | { 51| | super.init() 52| | self.dataSets = elements 53| | } 54| | 55| | @objc public init(dataSets: [Element]) 56| | { 57| | super.init() 58| | self.dataSets = dataSets 59| | } 60| | 61| | @objc public convenience init(dataSet: Element) 62| | { 63| | self.init(dataSets: [dataSet]) 64| | } 65| | 66| | /// Call this method to let the ChartData know that the underlying data has changed. 67| | /// Calling this performs all necessary recalculations needed when the contained data has changed. 68| | @objc open func notifyDataChanged() 69| | { 70| | calcMinMax() 71| | } 72| | 73| | @objc open func calcMinMaxY(fromX: Double, toX: Double) 74| | { 75| | forEach { $0.calcMinMaxY(fromX: fromX, toX: toX) } 76| | 77| | // apply the new data 78| | calcMinMax() 79| | } 80| | 81| | /// calc minimum and maximum y value over all datasets 82| | @objc open func calcMinMax() 83| | { 84| | leftAxisMax = -.greatestFiniteMagnitude 85| | leftAxisMin = .greatestFiniteMagnitude 86| | rightAxisMax = -.greatestFiniteMagnitude 87| | rightAxisMin = .greatestFiniteMagnitude 88| | yMax = -.greatestFiniteMagnitude 89| | yMin = .greatestFiniteMagnitude 90| | xMax = -.greatestFiniteMagnitude 91| | xMin = .greatestFiniteMagnitude 92| | 93| | forEach { calcMinMax(dataSet: $0) } 94| | 95| | // left axis 96| | let firstLeft = getFirstLeft(dataSets: dataSets) 97| | 98| | if firstLeft !== nil 99| | { 100| | leftAxisMax = firstLeft!.yMax 101| | leftAxisMin = firstLeft!.yMin 102| | 103| | for dataSet in _dataSets where dataSet.axisDependency == .left 104| | { 105| | if dataSet.yMin < leftAxisMin 106| | { 107| | leftAxisMin = dataSet.yMin 108| | } 109| | 110| | if dataSet.yMax > leftAxisMax 111| | { 112| | leftAxisMax = dataSet.yMax 113| | } 114| | } 115| | } 116| | 117| | // right axis 118| | let firstRight = getFirstRight(dataSets: dataSets) 119| | 120| | if firstRight !== nil 121| | { 122| | rightAxisMax = firstRight!.yMax 123| | rightAxisMin = firstRight!.yMin 124| | 125| | for dataSet in _dataSets where dataSet.axisDependency == .right 126| | { 127| | if dataSet.yMin < rightAxisMin 128| | { 129| | rightAxisMin = dataSet.yMin 130| | } 131| | 132| | if dataSet.yMax > rightAxisMax 133| | { 134| | rightAxisMax = dataSet.yMax 135| | } 136| | } 137| | } 138| | } 139| | 140| | /// Adjusts the current minimum and maximum values based on the provided Entry object. 141| | @objc open func calcMinMax(entry e: ChartDataEntry, axis: YAxis.AxisDependency) 142| | { 143| | xMax = Swift.max(xMax, e.x) 144| | xMin = Swift.min(xMin, e.x) 145| | yMax = Swift.max(yMax, e.y) 146| | yMin = Swift.min(yMin, e.y) 147| | 148| | switch axis 149| | { 150| | case .left: 151| | leftAxisMax = Swift.max(leftAxisMax, e.y) 152| | leftAxisMin = Swift.min(leftAxisMin, e.y) 153| | 154| | case .right: 155| | rightAxisMax = Swift.max(rightAxisMax, e.y) 156| | rightAxisMin = Swift.min(rightAxisMin, e.y) 157| | } 158| | } 159| | 160| | /// Adjusts the minimum and maximum values based on the given DataSet. 161| | @objc open func calcMinMax(dataSet d: Element) 162| | { 163| | xMax = Swift.max(xMax, d.xMax) 164| | xMin = Swift.min(xMin, d.xMin) 165| | yMax = Swift.max(yMax, d.yMax) 166| | yMin = Swift.min(yMin, d.yMin) 167| | 168| | switch d.axisDependency 169| | { 170| | case .left: 171| | leftAxisMax = Swift.max(leftAxisMax, d.yMax) 172| | leftAxisMin = Swift.min(leftAxisMin, d.yMin) 173| | 174| | case .right: 175| | rightAxisMax = Swift.max(rightAxisMax, d.yMax) 176| | rightAxisMin = Swift.min(rightAxisMin, d.yMin) 177| | } 178| | } 179| | 180| | /// The number of LineDataSets this object contains 181| | // exists only for objc compatibility 182| | @objc open var dataSetCount: Int 183| | { 184| | return dataSets.count 185| | } 186| | 187| | @objc open func getYMin(axis: YAxis.AxisDependency) -> Double 188| | { 189| | // TODO: Why does it make sense to return the other axisMin if there is none for the one requested? 190| | switch axis 191| | { 192| | case .left: 193| | if leftAxisMin == .greatestFiniteMagnitude 194| | { 195| | return rightAxisMin 196| | } 197| | else 198| | { 199| | return leftAxisMin 200| | } 201| | 202| | case .right: 203| | if rightAxisMin == .greatestFiniteMagnitude 204| | { 205| | return leftAxisMin 206| | } 207| | else 208| | { 209| | return rightAxisMin 210| | } 211| | } 212| | } 213| | 214| | @objc open func getYMax(axis: YAxis.AxisDependency) -> Double 215| | { 216| | if axis == .left 217| | { 218| | if leftAxisMax == -.greatestFiniteMagnitude 219| | { 220| | return rightAxisMax 221| | } 222| | else 223| | { 224| | return leftAxisMax 225| | } 226| | } 227| | else 228| | { 229| | if rightAxisMax == -.greatestFiniteMagnitude 230| | { 231| | return leftAxisMax 232| | } 233| | else 234| | { 235| | return rightAxisMax 236| | } 237| | } 238| | } 239| | 240| | /// All DataSet objects this ChartData object holds. 241| | @objc open var dataSets: [Element] 242| | { 243| | get 244| | { 245| | return _dataSets 246| | } 247| | set 248| | { 249| | _dataSets = newValue 250| | notifyDataChanged() 251| | } 252| | } 253| | 254| | /// Get the Entry for a corresponding highlight object 255| | /// 256| | /// - Parameters: 257| | /// - highlight: 258| | /// - Returns: The entry that is highlighted 259| | @objc open func entry(for highlight: Highlight) -> ChartDataEntry? 260| | { 261| | guard highlight.dataSetIndex < dataSets.endIndex else { return nil } 262| | return self[highlight.dataSetIndex].entryForXValue(highlight.x, closestToY: highlight.y) 263| | } 264| | 265| | /// **IMPORTANT: This method does calculations at runtime. Use with care in performance critical situations.** 266| | /// 267| | /// - Parameters: 268| | /// - label: 269| | /// - ignorecase: 270| | /// - Returns: The DataSet Object with the given label. Sensitive or not. 271| | @objc open func dataSet(forLabel label: String, ignorecase: Bool) -> Element? 272| | { 273| | guard let index = index(forLabel: label, ignoreCase: ignorecase) else { return nil } 274| | return self[index] 275| | } 276| | 277| | @objc(dataSetAtIndex:) 278| | open func dataSet(at index: Index) -> Element? 279| | { 280| | guard dataSets.indices.contains(index) else { return nil } 281| | return self[index] 282| | } 283| | 284| | /// Removes the given DataSet from this data object. 285| | /// Also recalculates all minimum and maximum values. 286| | /// 287| | /// - Returns: `true` if a DataSet was removed, `false` ifno DataSet could be removed. 288| | @objc @discardableResult open func removeDataSet(_ dataSet: Element) -> Element? 289| | { 290| | guard let index = firstIndex(where: { $0 === dataSet }) else { return nil } 291| | return remove(at: index) 292| | } 293| | 294| | /// Adds an Entry to the DataSet at the specified index. Entries are added to the end of the list. 295| | @objc(addEntry:dataSetIndex:) 296| | open func appendEntry(_ e: ChartDataEntry, toDataSet dataSetIndex: Index) 297| | { 298| | guard dataSets.indices.contains(dataSetIndex) else { 299| | return print("ChartData.addEntry() - Cannot add Entry because dataSetIndex too high or too low.", terminator: "\n") 300| | } 301| | 302| | let set = self[dataSetIndex] 303| | if !set.addEntry(e) { return } 304| | calcMinMax(entry: e, axis: set.axisDependency) 305| | } 306| | 307| | /// Removes the given Entry object from the DataSet at the specified index. 308| | @objc @discardableResult open func removeEntry(_ entry: ChartDataEntry, dataSetIndex: Index) -> Bool 309| | { 310| | guard dataSets.indices.contains(dataSetIndex) else { return false } 311| | 312| | // remove the entry from the dataset 313| | let removed = self[dataSetIndex].removeEntry(entry) 314| | 315| | if removed 316| | { 317| | calcMinMax() 318| | } 319| | 320| | return removed 321| | } 322| | 323| | /// Removes the Entry object closest to the given xIndex from the ChartDataSet at the 324| | /// specified index. 325| | /// 326| | /// - Returns: `true` if an entry was removed, `false` ifno Entry was found that meets the specified requirements. 327| | @objc @discardableResult open func removeEntry(xValue: Double, dataSetIndex: Index) -> Bool 328| | { 329| | guard 330| | dataSets.indices.contains(dataSetIndex), 331| | let entry = self[dataSetIndex].entryForXValue(xValue, closestToY: .nan) 332| | else { return false } 333| | 334| | return removeEntry(entry, dataSetIndex: dataSetIndex) 335| | } 336| | 337| | /// - Returns: The DataSet that contains the provided Entry, or null, if no DataSet contains this entry. 338| | @objc open func getDataSetForEntry(_ e: ChartDataEntry) -> Element? 339| | { 340| | return first { $0.entryForXValue(e.x, closestToY: e.y) === e } 341| | } 342| | 343| | /// - Returns: The index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. 344| | @objc open func index(of dataSet: Element) -> Index 345| | { 346| | // TODO: Return nil instead of -1 347| | return firstIndex(where: { $0 === dataSet }) ?? -1 348| | } 349| | 350| | /// - Returns: The first DataSet from the datasets-array that has it's dependency on the left axis. Returns null if no DataSet with left dependency could be found. 351| | @objc open func getFirstLeft(dataSets: [Element]) -> Element? 352| | { 353| | return first { $0.axisDependency == .left } 354| | } 355| | 356| | /// - Returns: The first DataSet from the datasets-array that has it's dependency on the right axis. Returns null if no DataSet with right dependency could be found. 357| | @objc open func getFirstRight(dataSets: [Element]) -> Element? 358| | { 359| | return first { $0.axisDependency == .right } 360| | } 361| | 362| | /// - Returns: All colors used across all DataSet objects this object represents. 363| | @objc open var colors: [NSUIColor] 364| | { 365| | // TODO: Don't return nil 366| | return reduce(into: []) { $0 += $1.colors } 367| | } 368| | 369| | /// Sets a custom ValueFormatter for all DataSets this data object contains. 370| | @objc open func setValueFormatter(_ formatter: ValueFormatter) 371| | { 372| | forEach { $0.valueFormatter = formatter } 373| | } 374| | 375| | /// Sets the color of the value-text (color in which the value-labels are drawn) for all DataSets this data object contains. 376| | @objc open func setValueTextColor(_ color: NSUIColor) 377| | { 378| | forEach { $0.valueTextColor = color } 379| | } 380| | 381| | /// Sets the font for all value-labels for all DataSets this data object contains. 382| | @objc open func setValueFont(_ font: NSUIFont) 383| | { 384| | forEach { $0.valueFont = font } 385| | } 386| | 387| | /// Enables / disables drawing values (value-text) for all DataSets this data object contains. 388| | @objc open func setDrawValues(_ enabled: Bool) 389| | { 390| | forEach { $0.drawValuesEnabled = enabled } 391| | } 392| | 393| | /// Enables / disables highlighting values for all DataSets this data object contains. 394| | /// If set to true, this means that values can be highlighted programmatically or by touch gesture. 395| | @objc open var isHighlightEnabled: Bool 396| | { 397| | get { return allSatisfy { $0.isHighlightEnabled } } 398| | set { forEach { $0.highlightEnabled = newValue } } 399| | } 400| | 401| | /// Clears this data object from all DataSets and removes all Entries. 402| | /// Don't forget to invalidate the chart after this. 403| | @objc open func clearValues() 404| | { 405| | removeAll(keepingCapacity: false) 406| | } 407| | 408| | /// Checks if this data object contains the specified DataSet. 409| | /// 410| | /// - Returns: `true` if so, `false` ifnot. 411| | @objc open func contains(dataSet: Element) -> Bool 412| | { 413| | return contains { $0 === dataSet } 414| | } 415| | 416| | /// The total entry count across all DataSet objects this data object contains. 417| | @objc open var entryCount: Int 418| | { 419| | return reduce(0) { return $0 + $1.entryCount } 420| | } 421| | 422| | /// The DataSet object with the maximum number of entries or null if there are no DataSets. 423| | @objc open var maxEntryCountSet: Element? 424| | { 425| | return self.max { $0.entryCount > $1.entryCount } 426| | } 427| |} 428| | 429| |// MARK: MutableCollection 430| |extension ChartData: MutableCollection 431| |{ 432| | public typealias Index = Int 433| | public typealias Element = ChartDataSetProtocol 434| | 435| | public var startIndex: Index 436| | { 437| | return dataSets.startIndex 438| | } 439| | 440| | public var endIndex: Index 441| | { 442| | return dataSets.endIndex 443| | } 444| | 445| | public func index(after: Index) -> Index 446| | { 447| | return dataSets.index(after: after) 448| | } 449| | 450| | public subscript(position: Index) -> Element 451| | { 452| | get { return dataSets[position] } 453| | set { self._dataSets[position] = newValue } 454| | } 455| |} 456| | 457| |// MARK: RandomAccessCollection 458| |extension ChartData: RandomAccessCollection 459| |{ 460| | public func index(before: Index) -> Index 461| | { 462| | return dataSets.index(before: before) 463| | } 464| |} 465| | 466| |// TODO: Conform when dropping Objective-C support 467| |// MARK: RangeReplaceableCollection 468| |extension ChartData//: RangeReplaceableCollection 469| |{ 470| | @objc(addDataSet:) 471| | public func append(_ newElement: Element) 472| | { 473| | _dataSets.append(newElement) 474| | calcMinMax(dataSet: newElement) 475| | } 476| | 477| | @objc(removeDataSetByIndex:) 478| | public func remove(at position: Index) -> Element 479| | { 480| | let element = _dataSets.remove(at: position) 481| | calcMinMax() 482| | return element 483| | } 484| | 485| | public func removeFirst() -> Element 486| | { 487| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 488| | 489| | let element = _dataSets.removeFirst() 490| | notifyDataChanged() 491| | return element 492| | } 493| | 494| | public func removeFirst(_ n: Int) 495| | { 496| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 497| | 498| | _dataSets.removeFirst(n) 499| | notifyDataChanged() 500| | } 501| | 502| | public func removeLast() -> Element 503| | { 504| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 505| | 506| | let element = _dataSets.removeLast() 507| | notifyDataChanged() 508| | return element 509| | } 510| | 511| | public func removeLast(_ n: Int) 512| | { 513| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 514| | 515| | _dataSets.removeLast(n) 516| | notifyDataChanged() 517| | } 518| | 519| | public func removeSubrange(_ bounds: R) where R : RangeExpression, Index == R.Bound 520| | { 521| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 522| | 523| | _dataSets.removeSubrange(bounds) 524| | notifyDataChanged() 525| | } 526| | 527| | public func removeAll(keepingCapacity keepCapacity: Bool) 528| | { 529| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 530| | 531| | _dataSets.removeAll(keepingCapacity: keepCapacity) 532| | notifyDataChanged() 533| | } 534| | 535| | public func replaceSubrange(_ subrange: Swift.Range, with newElements: C) where C : Collection, Element == C.Element 536| | { 537| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 538| | 539| | _dataSets.replaceSubrange(subrange, with: newElements) 540| | newElements.forEach { self.calcMinMax(dataSet: $0) } 541| | } 542| |} 543| | 544| |// MARK: Swift Accessors 545| |extension ChartData 546| |{ 547| | /// Retrieve the index of a ChartDataSet with a specific label from the ChartData. Search can be case sensitive or not. 548| | /// **IMPORTANT: This method does calculations at runtime, do not over-use in performance critical situations.** 549| | /// 550| | /// - Parameters: 551| | /// - label: The label to search for 552| | /// - ignoreCase: if true, the search is not case-sensitive 553| | /// - Returns: The index of the DataSet Object with the given label. `nil` if not found 554| | public func index(forLabel label: String, ignoreCase: Bool) -> Index? 555| | { 556| | return ignoreCase 557| | ? firstIndex { $0.label?.caseInsensitiveCompare(label) == .orderedSame } 558| | : firstIndex { $0.label == label } 559| | } 560| | 561| | public subscript(label label: String, ignoreCase ignoreCase: Bool) -> Element? 562| | { 563| | guard let index = index(forLabel: label, ignoreCase: ignoreCase) else { return nil } 564| | return self[index] 565| | } 566| | 567| | public subscript(entry entry: ChartDataEntry) -> Element? 568| | { 569| | assert(!(self is CombinedChartData), "\(#function) not supported for CombinedData") 570| | 571| | guard let index = firstIndex(where: { $0.entryForXValue(entry.x, closestToY: entry.y) === entry }) else { return nil } 572| | return self[index] 573| | } 574| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/ChartDataSet.swift: 1| |// 2| |// ChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| | 14| |/// Determines how to round DataSet index values for `ChartDataSet.entryIndex(x, rounding)` when an exact x-value is not found. 15| |@objc 16| |public enum ChartDataSetRounding: Int 17| |{ 18| | case up = 0 19| | case down = 1 20| | case closest = 2 21| |} 22| | 23| |/// The DataSet class represents one group or type of entries (Entry) in the Chart that belong together. 24| |/// It is designed to logically separate different groups of values inside the Chart (e.g. the values for a specific line in the LineChart, or the values of a specific group of bars in the BarChart). 25| |open class ChartDataSet: ChartBaseDataSet 26| |{ 27| | public required init() 28| | { 29| | entries = [] 30| | 31| | super.init() 32| | } 33| | 34| | public override convenience init(label: String) 35| | { 36| | self.init(entries: [], label: label) 37| | } 38| | 39| | @objc public init(entries: [ChartDataEntry], label: String) 40| | { 41| | self.entries = entries 42| | 43| | super.init(label: label) 44| | 45| | self.calcMinMax() 46| | } 47| | 48| | @objc public convenience init(entries: [ChartDataEntry]) 49| | { 50| | self.init(entries: entries, label: "DataSet") 51| | } 52| | 53| | // MARK: - Data functions and accessors 54| | 55| | /// - Note: Calls `notifyDataSetChanged()` after setting a new value. 56| | /// - Returns: The array of y-values that this DataSet represents. 57| | /// the entries that this dataset represents / holds together 58| | @objc 59| | open private(set) var entries: [ChartDataEntry] 60| | 61| | /// Used to replace all entries of a data set while retaining styling properties. 62| | /// This is a separate method from a setter on `entries` to encourage usage 63| | /// of `Collection` conformances. 64| | /// 65| | /// - Parameter entries: new entries to replace existing entries in the dataset 66| | @objc 67| | public func replaceEntries(_ entries: [ChartDataEntry]) { 68| | self.entries = entries 69| | notifyDataSetChanged() 70| | } 71| | 72| | /// maximum y-value in the value array 73| 64| internal var _yMax: Double = -Double.greatestFiniteMagnitude 74| | 75| | /// minimum y-value in the value array 76| 64| internal var _yMin: Double = Double.greatestFiniteMagnitude 77| | 78| | /// maximum x-value in the value array 79| 64| internal var _xMax: Double = -Double.greatestFiniteMagnitude 80| | 81| | /// minimum x-value in the value array 82| 64| internal var _xMin: Double = Double.greatestFiniteMagnitude 83| | 84| | open override func calcMinMax() 85| | { 86| | _yMax = -Double.greatestFiniteMagnitude 87| | _yMin = Double.greatestFiniteMagnitude 88| | _xMax = -Double.greatestFiniteMagnitude 89| | _xMin = Double.greatestFiniteMagnitude 90| | 91| | guard !isEmpty else { return } 92| | 93| | forEach(calcMinMax) 94| | } 95| | 96| | open override func calcMinMaxY(fromX: Double, toX: Double) 97| | { 98| | _yMax = -Double.greatestFiniteMagnitude 99| | _yMin = Double.greatestFiniteMagnitude 100| | 101| | guard !isEmpty else { return } 102| | 103| | let indexFrom = entryIndex(x: fromX, closestToY: .nan, rounding: .down) 104| | let indexTo = entryIndex(x: toX, closestToY: .nan, rounding: .up) 105| | 106| | guard indexTo >= indexFrom else { return } 107| | // only recalculate y 108| | self[indexFrom...indexTo].forEach(calcMinMaxY) 109| | } 110| | 111| | @objc open func calcMinMaxX(entry e: ChartDataEntry) 112| | { 113| | _xMin = Swift.min(e.x, _xMin) 114| | _xMax = Swift.max(e.x, _xMax) 115| | } 116| | 117| | @objc open func calcMinMaxY(entry e: ChartDataEntry) 118| | { 119| | _yMin = Swift.min(e.y, _yMin) 120| | _yMax = Swift.max(e.y, _yMax) 121| | } 122| | 123| | /// Updates the min and max x and y value of this DataSet based on the given Entry. 124| | /// 125| | /// - Parameters: 126| | /// - e: 127| | internal func calcMinMax(entry e: ChartDataEntry) 128| | { 129| | calcMinMaxX(entry: e) 130| | calcMinMaxY(entry: e) 131| | } 132| | 133| | /// The minimum y-value this DataSet holds 134| | @objc open override var yMin: Double { return _yMin } 135| | 136| | /// The maximum y-value this DataSet holds 137| | @objc open override var yMax: Double { return _yMax } 138| | 139| | /// The minimum x-value this DataSet holds 140| | @objc open override var xMin: Double { return _xMin } 141| | 142| | /// The maximum x-value this DataSet holds 143| | @objc open override var xMax: Double { return _xMax } 144| | 145| | /// The number of y-values this DataSet represents 146| | @available(*, deprecated, message: "Use `count` instead") 147| | open override var entryCount: Int { return count } 148| | 149| | /// - Throws: out of bounds 150| | /// if `i` is out of bounds, it may throw an out-of-bounds exception 151| | /// - Returns: The entry object found at the given index (not x-value!) 152| | @available(*, deprecated, message: "Use `subscript(index:)` instead.") 153| | open override func entryForIndex(_ i: Int) -> ChartDataEntry? 154| | { 155| | guard indices.contains(i) else { 156| | return nil 157| | } 158| | return self[i] 159| | } 160| | 161| | /// - Parameters: 162| | /// - xValue: the x-value 163| | /// - closestToY: If there are multiple y-values for the specified x-value, 164| | /// - rounding: determine whether to round up/down/closest if there is no Entry matching the provided x-value 165| | /// - Returns: The first Entry object found at the given x-value with binary search. 166| | /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value according to the rounding. 167| | /// nil if no Entry object at that x-value. 168| | open override func entryForXValue( 169| | _ xValue: Double, 170| | closestToY yValue: Double, 171| | rounding: ChartDataSetRounding) -> ChartDataEntry? 172| | { 173| | let index = entryIndex(x: xValue, closestToY: yValue, rounding: rounding) 174| | if index > -1 175| | { 176| | return self[index] 177| | } 178| | return nil 179| | } 180| | 181| | /// - Parameters: 182| | /// - xValue: the x-value 183| | /// - closestToY: If there are multiple y-values for the specified x-value, 184| | /// - Returns: The first Entry object found at the given x-value with binary search. 185| | /// If the no Entry at the specified x-value is found, this method returns the Entry at the closest x-value. 186| | /// nil if no Entry object at that x-value. 187| | open override func entryForXValue( 188| | _ xValue: Double, 189| | closestToY yValue: Double) -> ChartDataEntry? 190| | { 191| | return entryForXValue(xValue, closestToY: yValue, rounding: .closest) 192| | } 193| | 194| | /// - Returns: All Entry objects found at the given xIndex with binary search. 195| | /// An empty array if no Entry object at that index. 196| | open override func entriesForXValue(_ xValue: Double) -> [ChartDataEntry] 197| | { 198| | var entries = [ChartDataEntry]() 199| | 200| | var low = startIndex 201| | var high = endIndex - 1 202| | 203| | while low <= high 204| | { 205| | var m = (high + low) / 2 206| | var entry = self[m] 207| | 208| | // if we have a match 209| | if xValue == entry.x 210| | { 211| | while m > 0 && self[m - 1].x == xValue 212| | { 213| | m -= 1 214| | } 215| | 216| | high = endIndex 217| | 218| | // loop over all "equal" entries 219| | while m < high 220| | { 221| | entry = self[m] 222| | if entry.x == xValue 223| | { 224| | entries.append(entry) 225| | } 226| | else 227| | { 228| | break 229| | } 230| | 231| | m += 1 232| | } 233| | 234| | break 235| | } 236| | else 237| | { 238| | if xValue > entry.x 239| | { 240| | low = m + 1 241| | } 242| | else 243| | { 244| | high = m - 1 245| | } 246| | } 247| | } 248| | 249| | return entries 250| | } 251| | 252| | /// - Parameters: 253| | /// - xValue: x-value of the entry to search for 254| | /// - closestToY: If there are multiple y-values for the specified x-value, 255| | /// - rounding: Rounding method if exact value was not found 256| | /// - Returns: The array-index of the specified entry. 257| | /// If the no Entry at the specified x-value is found, this method returns the index of the Entry at the closest x-value according to the rounding. 258| | open override func entryIndex( 259| | x xValue: Double, 260| | closestToY yValue: Double, 261| | rounding: ChartDataSetRounding) -> Int 262| | { 263| | var low = startIndex 264| | var high = endIndex - 1 265| | var closest = high 266| | 267| | while low < high 268| | { 269| | let m = (low + high) / 2 270| | 271| | let d1 = self[m].x - xValue 272| | let d2 = self[m + 1].x - xValue 273| | let ad1 = abs(d1), ad2 = abs(d2) 274| | 275| | if ad2 < ad1 276| | { 277| | // [m + 1] is closer to xValue 278| | // Search in an higher place 279| | low = m + 1 280| | } 281| | else if ad1 < ad2 282| | { 283| | // [m] is closer to xValue 284| | // Search in a lower place 285| | high = m 286| | } 287| | else 288| | { 289| | // We have multiple sequential x-value with same distance 290| | 291| | if d1 >= 0.0 292| | { 293| | // Search in a lower place 294| | high = m 295| | } 296| | else if d1 < 0.0 297| | { 298| | // Search in an higher place 299| | low = m + 1 300| | } 301| | } 302| | 303| | closest = high 304| | } 305| | 306| | if closest != -1 307| | { 308| | let closestXValue = self[closest].x 309| | 310| | if rounding == .up 311| | { 312| | // If rounding up, and found x-value is lower than specified x, and we can go upper... 313| | if closestXValue < xValue && closest < endIndex - 1 314| | { 315| | closest += 1 316| | } 317| | } 318| | else if rounding == .down 319| | { 320| | // If rounding down, and found x-value is upper than specified x, and we can go lower... 321| | if closestXValue > xValue && closest > 0 322| | { 323| | closest -= 1 324| | } 325| | } 326| | 327| | // Search by closest to y-value 328| | if !yValue.isNaN 329| | { 330| | while closest > 0 && self[closest - 1].x == closestXValue 331| | { 332| | closest -= 1 333| | } 334| | 335| | var closestYValue = self[closest].y 336| | var closestYIndex = closest 337| | 338| | while true 339| | { 340| | closest += 1 341| | if closest >= endIndex { break } 342| | 343| | let value = self[closest] 344| | 345| | if value.x != closestXValue { break } 346| | if abs(value.y - yValue) <= abs(closestYValue - yValue) 347| | { 348| | closestYValue = yValue 349| | closestYIndex = closest 350| | } 351| | } 352| | 353| | closest = closestYIndex 354| | } 355| | } 356| | 357| | return closest 358| | } 359| | 360| | /// - Parameters: 361| | /// - e: the entry to search for 362| | /// - Returns: The array-index of the specified entry 363| | // TODO: Should be returning `nil` to follow Swift convention 364| | @available(*, deprecated, message: "Use `firstIndex(of:)` or `lastIndex(of:)`") 365| | open override func entryIndex(entry e: ChartDataEntry) -> Int 366| | { 367| | return firstIndex(of: e) ?? -1 368| | } 369| | 370| | /// Adds an Entry to the DataSet dynamically. 371| | /// Entries are added to the end of the list. 372| | /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. 373| | /// 374| | /// - Parameters: 375| | /// - e: the entry to add 376| | /// - Returns: True 377| | // TODO: This should return `Void` to follow Swift convention 378| | @available(*, deprecated, message: "Use `append(_:)` instead") 379| | open override func addEntry(_ e: ChartDataEntry) -> Bool 380| | { 381| | append(e) 382| | return true 383| | } 384| | 385| | /// Adds an Entry to the DataSet dynamically. 386| | /// Entries are added to their appropriate index respective to it's x-index. 387| | /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. 388| | /// 389| | /// - Parameters: 390| | /// - e: the entry to add 391| | /// - Returns: True 392| | // TODO: This should return `Void` to follow Swift convention 393| | open override func addEntryOrdered(_ e: ChartDataEntry) -> Bool 394| | { 395| | if let last = last, last.x > e.x 396| | { 397| | let startIndex = entryIndex(x: e.x, closestToY: e.y, rounding: .up) 398| | let closestIndex = self[startIndex...].lastIndex { $0.x < e.x } 399| | ?? startIndex 400| | calcMinMax(entry: e) 401| | entries.insert(e, at: closestIndex) 402| | } 403| | else 404| | { 405| | append(e) 406| | } 407| | 408| | return true 409| | } 410| | 411| | @available(*, renamed: "remove(_:)") 412| | open override func removeEntry(_ entry: ChartDataEntry) -> Bool 413| | { 414| | remove(entry) 415| | } 416| | 417| | /// Removes an Entry from the DataSet dynamically. 418| | /// This will also recalculate the current minimum and maximum values of the DataSet and the value-sum. 419| | /// 420| | /// - Parameters: 421| | /// - entry: the entry to remove 422| | /// - Returns: `true` if the entry was removed successfully, else if the entry does not exist 423| | open func remove(_ entry: ChartDataEntry) -> Bool 424| | { 425| | guard let index = firstIndex(of: entry) else { return false } 426| | _ = remove(at: index) 427| | return true 428| | } 429| | 430| | /// Removes the first Entry (at index 0) of this DataSet from the entries array. 431| | /// 432| | /// - Returns: `true` if successful, `false` if not. 433| | // TODO: This should return the removed entry to follow Swift convention. 434| | @available(*, deprecated, message: "Use `func removeFirst() -> ChartDataEntry` instead.") 435| | open override func removeFirst() -> Bool 436| | { 437| | let entry: ChartDataEntry? = isEmpty ? nil : removeFirst() 438| | return entry != nil 439| | } 440| | 441| | /// Removes the last Entry (at index size-1) of this DataSet from the entries array. 442| | /// 443| | /// - Returns: `true` if successful, `false` if not. 444| | // TODO: This should return the removed entry to follow Swift convention. 445| | @available(*, deprecated, message: "Use `func removeLast() -> ChartDataEntry` instead.") 446| | open override func removeLast() -> Bool 447| | { 448| | let entry: ChartDataEntry? = isEmpty ? nil : removeLast() 449| | return entry != nil 450| | } 451| | 452| | /// Removes all values from this DataSet and recalculates min and max value. 453| | @available(*, deprecated, message: "Use `removeAll(keepingCapacity:)` instead.") 454| | open override func clear() 455| | { 456| | removeAll(keepingCapacity: true) 457| | } 458| | 459| | // MARK: - Data functions and accessors 460| | 461| | // MARK: - NSCopying 462| | 463| | open override func copy(with zone: NSZone? = nil) -> Any 464| | { 465| | let copy = super.copy(with: zone) as! ChartDataSet 466| | 467| | copy.entries = entries 468| | copy._yMax = _yMax 469| | copy._yMin = _yMin 470| | copy._xMax = _xMax 471| | copy._xMin = _xMin 472| | 473| | return copy 474| | } 475| |} 476| | 477| |// MARK: MutableCollection 478| |extension ChartDataSet: MutableCollection { 479| | public typealias Index = Int 480| | public typealias Element = ChartDataEntry 481| | 482| | public var startIndex: Index { 483| | return entries.startIndex 484| | } 485| | 486| | public var endIndex: Index { 487| | return entries.endIndex 488| | } 489| | 490| | public func index(after: Index) -> Index { 491| | return entries.index(after: after) 492| | } 493| | 494| | @objc 495| | public subscript(position: Index) -> Element { 496| | get { 497| | // This is intentionally not a safe subscript to mirror 498| | // the behaviour of the built in Swift Collection Types 499| | return entries[position] 500| | } 501| | set { 502| | calcMinMax(entry: newValue) 503| | entries[position] = newValue 504| | } 505| | } 506| |} 507| | 508| |// MARK: RandomAccessCollection 509| |extension ChartDataSet: RandomAccessCollection { 510| | public func index(before: Index) -> Index { 511| | return entries.index(before: before) 512| | } 513| |} 514| | 515| |// MARK: RangeReplaceableCollection 516| |extension ChartDataSet: RangeReplaceableCollection { 517| | public func append(_ newElement: Element) { 518| | calcMinMax(entry: newElement) 519| | entries.append(newElement) 520| | } 521| | 522| | public func remove(at position: Index) -> Element { 523| | let element = entries.remove(at: position) 524| | notifyDataSetChanged() 525| | return element 526| | } 527| | 528| | public func removeFirst() -> Element { 529| | let element = entries.removeFirst() 530| | notifyDataSetChanged() 531| | return element 532| | } 533| | 534| | public func removeFirst(_ n: Int) { 535| | entries.removeFirst(n) 536| | notifyDataSetChanged() 537| | } 538| | 539| | public func removeLast() -> Element { 540| | let element = entries.removeLast() 541| | notifyDataSetChanged() 542| | return element 543| | } 544| | 545| | public func removeLast(_ n: Int) { 546| | entries.removeLast(n) 547| | notifyDataSetChanged() 548| | } 549| | 550| | public func removeSubrange(_ bounds: R) where R : RangeExpression, Index == R.Bound { 551| | entries.removeSubrange(bounds) 552| | notifyDataSetChanged() 553| | } 554| | 555| | @objc 556| | public func removeAll(keepingCapacity keepCapacity: Bool) { 557| | entries.removeAll(keepingCapacity: keepCapacity) 558| | notifyDataSetChanged() 559| | } 560| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift: 1| |// 2| |// LineChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class LineChartDataSet: LineRadarChartDataSet, LineChartDataSetProtocol 17| |{ 18| | @objc(LineChartMode) 19| | public enum Mode: Int 20| | { 21| | case linear 22| | case stepped 23| | case cubicBezier 24| | case horizontalBezier 25| | } 26| | 27| | private func initialize() 28| | { 29| | // default color 30| | circleColors.append(NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0)) 31| | } 32| | 33| | public required init() 34| | { 35| | super.init() 36| | initialize() 37| | } 38| | 39| | public override init(entries: [ChartDataEntry], label: String) 40| | { 41| | super.init(entries: entries, label: label) 42| | initialize() 43| | } 44| | 45| | // MARK: - Data functions and accessors 46| | 47| | // MARK: - Styling functions and accessors 48| | 49| | /// The drawing mode for this line dataset 50| | /// 51| | /// **default**: Linear 52| 9| open var mode: Mode = Mode.linear 53| | 54| 9| private var _cubicIntensity = CGFloat(0.2) 55| | 56| | /// Intensity for cubic lines (min = 0.05, max = 1) 57| | /// 58| | /// **default**: 0.2 59| | open var cubicIntensity: CGFloat 60| | { 61| | get 62| | { 63| | return _cubicIntensity 64| | } 65| | set 66| | { 67| | _cubicIntensity = newValue.clamped(to: 0.05...1) 68| | } 69| | } 70| | 71| | open var isDrawLineWithGradientEnabled = false 72| | 73| | open var gradientPositions: [CGFloat]? 74| | 75| | /// The radius of the drawn circles. 76| 9| open var circleRadius = CGFloat(8.0) 77| | 78| | /// The hole radius of the drawn circles 79| 9| open var circleHoleRadius = CGFloat(4.0) 80| | 81| 9| open var circleColors = [NSUIColor]() 82| | 83| | /// - Returns: The color at the given index of the DataSet's circle-color array. 84| | /// Performs a IndexOutOfBounds check by modulus. 85| | open func getCircleColor(atIndex index: Int) -> NSUIColor? 86| | { 87| | let size = circleColors.count 88| | let index = index % size 89| | if index >= size 90| | { 91| | return nil 92| | } 93| | return circleColors[index] 94| | } 95| | 96| | /// Sets the one and ONLY color that should be used for this DataSet. 97| | /// Internally, this recreates the colors array and adds the specified color. 98| | open func setCircleColor(_ color: NSUIColor) 99| | { 100| | circleColors.removeAll(keepingCapacity: false) 101| | circleColors.append(color) 102| | } 103| | 104| | open func setCircleColors(_ colors: NSUIColor...) 105| | { 106| | circleColors.removeAll(keepingCapacity: false) 107| | circleColors.append(contentsOf: colors) 108| | } 109| | 110| | /// Resets the circle-colors array and creates a new one 111| | open func resetCircleColors(_ index: Int) 112| | { 113| | circleColors.removeAll(keepingCapacity: false) 114| | } 115| | 116| | /// If true, drawing circles is enabled 117| | open var drawCirclesEnabled = true 118| | 119| | /// `true` if drawing circles for this DataSet is enabled, `false` ifnot 120| | open var isDrawCirclesEnabled: Bool { return drawCirclesEnabled } 121| | 122| | /// The color of the inner circle (the circle-hole). 123| 9| open var circleHoleColor: NSUIColor? = NSUIColor.white 124| | 125| | /// `true` if drawing circles for this DataSet is enabled, `false` ifnot 126| | open var drawCircleHoleEnabled = true 127| | 128| | /// `true` if drawing the circle-holes is enabled, `false` ifnot. 129| | open var isDrawCircleHoleEnabled: Bool { return drawCircleHoleEnabled } 130| | 131| | /// This is how much (in pixels) into the dash pattern are we starting from. 132| 9| open var lineDashPhase = CGFloat(0.0) 133| | 134| | /// This is the actual dash pattern. 135| | /// I.e. [2, 3] will paint [-- -- ] 136| | /// [1, 3, 4, 2] will paint [- ---- - ---- ] 137| | open var lineDashLengths: [CGFloat]? 138| | 139| | /// Line cap type, default is CGLineCap.Butt 140| 9| open var lineCapType = CGLineCap.butt 141| | 142| | /// formatter for customizing the position of the fill-line 143| 9| private var _fillFormatter: FillFormatter = DefaultFillFormatter() 144| | 145| | /// Sets a custom FillFormatterProtocol to the chart that handles the position of the filled-line for each DataSet. Set this to null to use the default logic. 146| | open var fillFormatter: FillFormatter? 147| | { 148| | get 149| | { 150| | return _fillFormatter 151| | } 152| | set 153| | { 154| | _fillFormatter = newValue ?? DefaultFillFormatter() 155| | } 156| | } 157| | 158| | // MARK: NSCopying 159| | 160| | open override func copy(with zone: NSZone? = nil) -> Any 161| | { 162| | let copy = super.copy(with: zone) as! LineChartDataSet 163| | copy.circleColors = circleColors 164| | copy.circleHoleColor = circleHoleColor 165| | copy.circleRadius = circleRadius 166| | copy.circleHoleRadius = circleHoleRadius 167| | copy.cubicIntensity = cubicIntensity 168| | copy.lineDashPhase = lineDashPhase 169| | copy.lineDashLengths = lineDashLengths 170| | copy.lineCapType = lineCapType 171| | copy.drawCirclesEnabled = drawCirclesEnabled 172| | copy.drawCircleHoleEnabled = drawCircleHoleEnabled 173| | copy.mode = mode 174| | copy._fillFormatter = _fillFormatter 175| | return copy 176| | } 177| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift: 1| |// 2| |// LineRadarChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class LineRadarChartDataSet: LineScatterCandleRadarChartDataSet, LineRadarChartDataSetProtocol 17| |{ 18| | // MARK: - Data functions and accessors 19| | 20| | // MARK: - Styling functions and accessors 21| | 22| | /// The color that is used for filling the line surface area. 23| 9| private var _fillColor = NSUIColor(red: 140.0/255.0, green: 234.0/255.0, blue: 255.0/255.0, alpha: 1.0) 24| | 25| | /// The color that is used for filling the line surface area. 26| | open var fillColor: NSUIColor 27| | { 28| | get { return _fillColor } 29| | set 30| | { 31| | _fillColor = newValue 32| | fill = nil 33| | } 34| | } 35| | 36| | /// The object that is used for filling the area below the line. 37| | /// **default**: nil 38| | open var fill: Fill? 39| | 40| | /// The alpha value that is used for filling the line surface, 41| | /// **default**: 0.33 42| 9| open var fillAlpha = CGFloat(0.33) 43| | 44| 9| private var _lineWidth = CGFloat(1.0) 45| | 46| | /// line width of the chart (min = 0.0, max = 10) 47| | /// 48| | /// **default**: 1 49| | open var lineWidth: CGFloat 50| | { 51| | get 52| | { 53| | return _lineWidth 54| | } 55| | set 56| | { 57| | _lineWidth = newValue.clamped(to: 0...10) 58| | } 59| | } 60| | 61| | /// Set to `true` if the DataSet should be drawn filled (surface), and not just as a line. 62| | /// Disabling this will give great performance boost. 63| | /// Please note that this method uses the path clipping for drawing the filled area (with images, gradients and layers). 64| | open var drawFilledEnabled = false 65| | 66| | /// `true` if filled drawing is enabled, `false` ifnot 67| | open var isDrawFilledEnabled: Bool 68| | { 69| | return drawFilledEnabled 70| | } 71| | 72| | // MARK: NSCopying 73| | 74| | open override func copy(with zone: NSZone? = nil) -> Any 75| | { 76| | let copy = super.copy(with: zone) as! LineRadarChartDataSet 77| | copy.fill = fill 78| | copy.fillAlpha = fillAlpha 79| | copy._fillColor = _fillColor 80| | copy._lineWidth = _lineWidth 81| | copy.drawFilledEnabled = drawFilledEnabled 82| | return copy 83| | } 84| | 85| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift: 1| |// 2| |// PieChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class PieChartDataSet: ChartDataSet, PieChartDataSetProtocol 16| |{ 17| | @objc(PieChartValuePosition) 18| | public enum ValuePosition: Int 19| | { 20| | case insideSlice 21| | case outsideSlice 22| | } 23| | 24| | private func initialize() 25| | { 26| | self.valueTextColor = NSUIColor.white 27| | self.valueFont = NSUIFont.systemFont(ofSize: 13.0) 28| | } 29| | 30| | public required init() 31| | { 32| | super.init() 33| | initialize() 34| | } 35| | 36| | public override init(entries: [ChartDataEntry], label: String) 37| | { 38| | super.init(entries: entries, label: label) 39| | initialize() 40| | } 41| | 42| | internal override func calcMinMax(entry e: ChartDataEntry) 43| | { 44| | calcMinMaxY(entry: e) 45| | } 46| | 47| | // MARK: - Styling functions and accessors 48| | 49| 6| private var _sliceSpace = CGFloat(0.0) 50| | 51| | /// the space in pixels between the pie-slices 52| | /// **default**: 0 53| | /// **maximum**: 20 54| | open var sliceSpace: CGFloat 55| | { 56| | get 57| | { 58| | return _sliceSpace 59| | } 60| | set 61| | { 62| | switch newValue { 63| | case ..<0.0: _sliceSpace = 0.0 64| | case 20.0...: _sliceSpace = 20.0 65| | default: _sliceSpace = newValue 66| | } 67| | } 68| | } 69| | 70| | /// When enabled, slice spacing will be 0.0 when the smallest value is going to be smaller than the slice spacing itself. 71| | open var automaticallyDisableSliceSpacing: Bool = false 72| | 73| | /// indicates the selection distance of a pie slice 74| 6| open var selectionShift = CGFloat(18.0) 75| | 76| 6| open var xValuePosition: ValuePosition = .insideSlice 77| 6| open var yValuePosition: ValuePosition = .insideSlice 78| | 79| | /// When valuePosition is OutsideSlice, indicates line color 80| 6| open var valueLineColor: NSUIColor? = NSUIColor.black 81| | 82| | /// When valuePosition is OutsideSlice and enabled, line will have the same color as the slice 83| | open var useValueColorForLine: Bool = false 84| | 85| | /// When valuePosition is OutsideSlice, indicates line width 86| | open var valueLineWidth: CGFloat = 1.0 87| | 88| | /// When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size 89| | open var valueLinePart1OffsetPercentage: CGFloat = 0.75 90| | 91| | /// When valuePosition is OutsideSlice, indicates length of first half of the line 92| | open var valueLinePart1Length: CGFloat = 0.3 93| | 94| | /// When valuePosition is OutsideSlice, indicates length of second half of the line 95| | open var valueLinePart2Length: CGFloat = 0.4 96| | 97| | /// When valuePosition is OutsideSlice, this allows variable line length 98| | open var valueLineVariableLength: Bool = true 99| | 100| | /// the font for the slice-text labels 101| | open var entryLabelFont: NSUIFont? = nil 102| | 103| | /// the color for the slice-text labels 104| | open var entryLabelColor: NSUIColor? = nil 105| | 106| | /// the color for the highlighted sector 107| | open var highlightColor: NSUIColor? = nil 108| | 109| | // MARK: - NSCopying 110| | 111| | open override func copy(with zone: NSZone? = nil) -> Any 112| | { 113| | let copy = super.copy(with: zone) as! PieChartDataSet 114| | copy._sliceSpace = _sliceSpace 115| | copy.automaticallyDisableSliceSpacing = automaticallyDisableSliceSpacing 116| | copy.selectionShift = selectionShift 117| | copy.xValuePosition = xValuePosition 118| | copy.yValuePosition = yValuePosition 119| | copy.valueLineColor = valueLineColor 120| | copy.valueLineWidth = valueLineWidth 121| | copy.valueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage 122| | copy.valueLinePart1Length = valueLinePart1Length 123| | copy.valueLinePart2Length = valueLinePart2Length 124| | copy.valueLineVariableLength = valueLineVariableLength 125| | copy.entryLabelFont = entryLabelFont 126| | copy.entryLabelColor = entryLabelColor 127| | copy.highlightColor = highlightColor 128| | return copy 129| | } 130| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/RadarChartData.swift: 1| |// 2| |// RadarChartData.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class RadarChartData: ChartData 17| |{ 18| 0| @objc open var highlightColor = NSUIColor(red: 255.0/255.0, green: 187.0/255.0, blue: 115.0/255.0, alpha: 1.0) 19| 0| @objc open var highlightLineWidth = CGFloat(1.0) 20| 0| @objc open var highlightLineDashPhase = CGFloat(0.0) 21| | @objc open var highlightLineDashLengths: [CGFloat]? 22| | 23| | /// Sets labels that should be drawn around the RadarChart at the end of each web line. 24| 0| @objc open var labels = [String]() 25| | 26| | /// Sets the labels that should be drawn around the RadarChart at the end of each web line. 27| | open func setLabels(_ labels: String...) 28| | { 29| | self.labels = labels 30| | } 31| | 32| | public required init() 33| | { 34| | super.init() 35| | } 36| | 37| | public override init(dataSets: [ChartDataSetProtocol]) 38| | { 39| | super.init(dataSets: dataSets) 40| | } 41| | 42| | public required init(arrayLiteral elements: ChartDataSetProtocol...) 43| | { 44| | super.init(dataSets: elements) 45| | } 46| | 47| | @objc open override func entry(for highlight: Highlight) -> ChartDataEntry? 48| | { 49| | return self[highlight.dataSetIndex].entryForIndex(Int(highlight.x)) 50| | } 51| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift: 1| |// 2| |// RadarChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |open class RadarChartDataSet: LineRadarChartDataSet, RadarChartDataSetProtocol 17| |{ 18| | private func initialize() 19| | { 20| | self.valueFont = NSUIFont.systemFont(ofSize: 13.0) 21| | } 22| | 23| | public required init() 24| | { 25| | super.init() 26| | initialize() 27| | } 28| | 29| | public required override init(entries: [ChartDataEntry], label: String) 30| | { 31| | super.init(entries: entries, label: label) 32| | initialize() 33| | } 34| | 35| | // MARK: - Data functions and accessors 36| | 37| | // MARK: - Styling functions and accessors 38| | 39| | /// flag indicating whether highlight circle should be drawn or not 40| | /// **default**: false 41| | open var drawHighlightCircleEnabled: Bool = false 42| | 43| | /// `true` if highlight circle should be drawn, `false` ifnot 44| | open var isDrawHighlightCircleEnabled: Bool { return drawHighlightCircleEnabled } 45| | 46| 0| open var highlightCircleFillColor: NSUIColor? = NSUIColor.white 47| | 48| | /// The stroke color for highlight circle. 49| | /// If `nil`, the color of the dataset is taken. 50| | open var highlightCircleStrokeColor: NSUIColor? 51| | 52| | open var highlightCircleStrokeAlpha: CGFloat = 0.3 53| | 54| | open var highlightCircleInnerRadius: CGFloat = 3.0 55| | 56| | open var highlightCircleOuterRadius: CGFloat = 4.0 57| | 58| | open var highlightCircleStrokeWidth: CGFloat = 2.0 59| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift: 1| |// 2| |// ScatterChartDataSet.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class ScatterChartDataSet: LineScatterCandleRadarChartDataSet, ScatterChartDataSetProtocol 16| |{ 17| | 18| | @objc(ScatterShape) 19| | public enum Shape: Int 20| | { 21| | case square 22| | case circle 23| | case triangle 24| | case cross 25| | case x 26| | case chevronUp 27| | case chevronDown 28| | } 29| | 30| | /// The size the scatter shape will have 31| 9| open var scatterShapeSize = CGFloat(10.0) 32| | 33| | /// The radius of the hole in the shape (applies to Square, Circle and Triangle) 34| | /// **default**: 0.0 35| | open var scatterShapeHoleRadius: CGFloat = 0.0 36| | 37| | /// Color for the hole in the shape. Setting to `nil` will behave as transparent. 38| | /// **default**: nil 39| | open var scatterShapeHoleColor: NSUIColor? = nil 40| | 41| | /// Sets the ScatterShape this DataSet should be drawn with. 42| | /// This will search for an available ShapeRenderer and set this renderer for the DataSet 43| | @objc open func setScatterShape(_ shape: Shape) 44| | { 45| | self.shapeRenderer = ScatterChartDataSet.renderer(forShape: shape) 46| | } 47| | 48| | /// The IShapeRenderer responsible for rendering this DataSet. 49| | /// This can also be used to set a custom IShapeRenderer aside from the default ones. 50| | /// **default**: `SquareShapeRenderer` 51| 9| open var shapeRenderer: ShapeRenderer? = SquareShapeRenderer() 52| | 53| | @objc open class func renderer(forShape shape: Shape) -> ShapeRenderer 54| | { 55| | switch shape 56| | { 57| | case .square: return SquareShapeRenderer() 58| | case .circle: return CircleShapeRenderer() 59| | case .triangle: return TriangleShapeRenderer() 60| | case .cross: return CrossShapeRenderer() 61| | case .x: return XShapeRenderer() 62| | case .chevronUp: return ChevronUpShapeRenderer() 63| | case .chevronDown: return ChevronDownShapeRenderer() 64| | } 65| | } 66| | 67| | // MARK: NSCopying 68| | 69| | open override func copy(with zone: NSZone? = nil) -> Any 70| | { 71| | let copy = super.copy(with: zone) as! ScatterChartDataSet 72| | copy.scatterShapeSize = scatterShapeSize 73| | copy.scatterShapeHoleRadius = scatterShapeHoleRadius 74| | copy.scatterShapeHoleColor = scatterShapeHoleColor 75| | copy.shapeRenderer = shapeRenderer 76| | return copy 77| | } 78| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Formatters/IndexAxisValueFormatter.swift: 1| |// 2| |// IndexAxisValueFormatter.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| | 14| |/// This formatter is used for passing an array of x-axis labels, on whole x steps. 15| |@objc(ChartIndexAxisValueFormatter) 16| |open class IndexAxisValueFormatter: NSObject, AxisValueFormatter 17| |{ 18| 0| @objc public var values: [String] = [String]() 19| | 20| | public override init() 21| | { 22| | super.init() 23| | 24| | } 25| | 26| | @objc public init(values: [String]) 27| | { 28| | super.init() 29| | 30| | self.values = values 31| | } 32| | 33| | @objc public static func with(values: [String]) -> IndexAxisValueFormatter? 34| | { 35| | return IndexAxisValueFormatter(values: values) 36| | } 37| | 38| | open func stringForValue(_ value: Double, 39| | axis: AxisBase?) -> String 40| | { 41| | let index = Int(value.rounded()) 42| | guard values.indices.contains(index), index == Int(value) else { return "" } 43| | return values[index] 44| | } 45| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Highlight/Highlight.swift: 1| |// 2| |// Highlight.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartHighlight) 16| |open class Highlight: NSObject 17| |{ 18| | /// the x-value of the highlighted value 19| 6| fileprivate var _x = Double.nan 20| | 21| | /// the y-value of the highlighted value 22| 6| fileprivate var _y = Double.nan 23| | 24| | /// the x-pixel of the highlight 25| 6| private var _xPx = CGFloat.nan 26| | 27| | /// the y-pixel of the highlight 28| 6| private var _yPx = CGFloat.nan 29| | 30| | /// the index of the data object - in case it refers to more than one 31| 6| @objc open var dataIndex = Int(-1) 32| | 33| | /// the index of the dataset the highlighted value is in 34| 6| fileprivate var _dataSetIndex = Int(0) 35| | 36| | /// index which value of a stacked bar entry is highlighted 37| | /// 38| | /// **default**: -1 39| 6| fileprivate var _stackIndex = Int(-1) 40| | 41| | /// the axis the highlighted value belongs to 42| 6| private var _axis: YAxis.AxisDependency = YAxis.AxisDependency.left 43| | 44| | /// the x-position (pixels) on which this highlight object was last drawn 45| | @objc open var drawX: CGFloat = 0.0 46| | 47| | /// the y-position (pixels) on which this highlight object was last drawn 48| | @objc open var drawY: CGFloat = 0.0 49| | 50| | public override init() 51| | { 52| | super.init() 53| | } 54| | 55| | /// - Parameters: 56| | /// - x: the x-value of the highlighted value 57| | /// - y: the y-value of the highlighted value 58| | /// - xPx: the x-pixel of the highlighted value 59| | /// - yPx: the y-pixel of the highlighted value 60| | /// - dataIndex: the index of the Data the highlighted value belongs to 61| | /// - dataSetIndex: the index of the DataSet the highlighted value belongs to 62| | /// - stackIndex: references which value of a stacked-bar entry has been selected 63| | /// - axis: the axis the highlighted value belongs to 64| | @objc public init( 65| | x: Double, y: Double, 66| | xPx: CGFloat, yPx: CGFloat, 67| | dataIndex: Int, 68| | dataSetIndex: Int, 69| | stackIndex: Int, 70| | axis: YAxis.AxisDependency) 71| | { 72| | super.init() 73| | 74| | _x = x 75| | _y = y 76| | _xPx = xPx 77| | _yPx = yPx 78| | self.dataIndex = dataIndex 79| | _dataSetIndex = dataSetIndex 80| | _stackIndex = stackIndex 81| | _axis = axis 82| | } 83| | 84| | /// - Parameters: 85| | /// - x: the x-value of the highlighted value 86| | /// - y: the y-value of the highlighted value 87| | /// - xPx: the x-pixel of the highlighted value 88| | /// - yPx: the y-pixel of the highlighted value 89| | /// - dataSetIndex: the index of the DataSet the highlighted value belongs to 90| | /// - stackIndex: references which value of a stacked-bar entry has been selected 91| | /// - axis: the axis the highlighted value belongs to 92| | @objc public convenience init( 93| | x: Double, y: Double, 94| | xPx: CGFloat, yPx: CGFloat, 95| | dataSetIndex: Int, 96| | stackIndex: Int, 97| | axis: YAxis.AxisDependency) 98| | { 99| | self.init(x: x, y: y, xPx: xPx, yPx: yPx, 100| | dataIndex: 0, 101| | dataSetIndex: dataSetIndex, 102| | stackIndex: stackIndex, 103| | axis: axis) 104| | } 105| | 106| | /// - Parameters: 107| | /// - x: the x-value of the highlighted value 108| | /// - y: the y-value of the highlighted value 109| | /// - xPx: the x-pixel of the highlighted value 110| | /// - yPx: the y-pixel of the highlighted value 111| | /// - dataIndex: the index of the Data the highlighted value belongs to 112| | /// - dataSetIndex: the index of the DataSet the highlighted value belongs to 113| | /// - stackIndex: references which value of a stacked-bar entry has been selected 114| | /// - axis: the axis the highlighted value belongs to 115| | @objc public init( 116| | x: Double, y: Double, 117| | xPx: CGFloat, yPx: CGFloat, 118| | dataSetIndex: Int, 119| | axis: YAxis.AxisDependency) 120| | { 121| | super.init() 122| | 123| | _x = x 124| | _y = y 125| | _xPx = xPx 126| | _yPx = yPx 127| | _dataSetIndex = dataSetIndex 128| | _axis = axis 129| | } 130| | 131| | /// - Parameters: 132| | /// - x: the x-value of the highlighted value 133| | /// - y: the y-value of the highlighted value 134| | /// - dataSetIndex: the index of the DataSet the highlighted value belongs to 135| | /// - dataIndex: The data index to search in (only used in CombinedChartView currently) 136| | @objc public init(x: Double, y: Double, dataSetIndex: Int, dataIndex: Int = -1) 137| | { 138| | _x = x 139| | _y = y 140| | _dataSetIndex = dataSetIndex 141| | self.dataIndex = dataIndex 142| | } 143| | 144| | /// - Parameters: 145| | /// - x: the x-value of the highlighted value 146| | /// - dataSetIndex: the index of the DataSet the highlighted value belongs to 147| | /// - stackIndex: references which value of a stacked-bar entry has been selected 148| | @objc public convenience init(x: Double, dataSetIndex: Int, stackIndex: Int) 149| | { 150| | self.init(x: x, y: Double.nan, dataSetIndex: dataSetIndex) 151| | _stackIndex = stackIndex 152| | } 153| | 154| | @objc open var x: Double { return _x } 155| | @objc open var y: Double { return _y } 156| | @objc open var xPx: CGFloat { return _xPx } 157| | @objc open var yPx: CGFloat { return _yPx } 158| | @objc open var dataSetIndex: Int { return _dataSetIndex } 159| | @objc open var stackIndex: Int { return _stackIndex } 160| | @objc open var axis: YAxis.AxisDependency { return _axis } 161| | 162| | @objc open var isStacked: Bool { return _stackIndex >= 0 } 163| | 164| | /// Sets the x- and y-position (pixels) where this highlight was last drawn. 165| | @objc open func setDraw(x: CGFloat, y: CGFloat) 166| | { 167| | self.drawX = x 168| | self.drawY = y 169| | } 170| | 171| | /// Sets the x- and y-position (pixels) where this highlight was last drawn. 172| | @objc open func setDraw(pt: CGPoint) 173| | { 174| | self.drawX = pt.x 175| | self.drawY = pt.y 176| | } 177| | 178| | // MARK: NSObject 179| | 180| | open override var description: String 181| | { 182| | return "Highlight, x: \(_x), y: \(_y), dataIndex (combined charts): \(dataIndex), dataSetIndex: \(_dataSetIndex), stackIndex (only stacked barentry): \(_stackIndex)" 183| | } 184| |} 185| | 186| | 187| |// MARK: Equatable 188| |extension Highlight /*: Equatable*/ { 189| | open override func isEqual(_ object: Any?) -> Bool { 190| | guard let object = object as? Highlight else { return false } 191| | 192| | if self === object 193| | { 194| | return true 195| | } 196| | 197| | return _x == object._x 198| | && _y == object._y 199| | && dataIndex == object.dataIndex 200| | && _dataSetIndex == object._dataSetIndex 201| | && _stackIndex == object._stackIndex 202| | } 203| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Jobs/ViewPortJob.swift: 1| |// 2| |// ViewPortJob.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |// This defines a viewport modification job, used for delaying or animating viewport changes 16| |@objc(ChartViewPortJob) 17| |open class ViewPortJob: NSObject 18| |{ 19| 0| internal var point: CGPoint = .zero 20| | internal unowned var viewPortHandler: ViewPortHandler 21| | internal var xValue = 0.0 22| | internal var yValue = 0.0 23| | internal unowned var transformer: Transformer 24| | internal unowned var view: ChartViewBase 25| | 26| | @objc public init( 27| | viewPortHandler: ViewPortHandler, 28| | xValue: Double, 29| | yValue: Double, 30| | transformer: Transformer, 31| | view: ChartViewBase) 32| | { 33| | self.viewPortHandler = viewPortHandler 34| | self.xValue = xValue 35| | self.yValue = yValue 36| | self.transformer = transformer 37| | self.view = view 38| | 39| | super.init() 40| | } 41| | 42| | @objc open func doJob() 43| | { 44| | fatalError("`doJob()` must be overridden by subclasses") 45| | } 46| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Jobs/ZoomViewJob.swift: 1| |// 2| |// ZoomViewJob.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ZoomChartViewJob) 16| |open class ZoomViewJob: ViewPortJob 17| |{ 18| | internal var scaleX: CGFloat = 0.0 19| | internal var scaleY: CGFloat = 0.0 20| 0| internal var axisDependency: YAxis.AxisDependency = .left 21| | 22| | @objc public init( 23| | viewPortHandler: ViewPortHandler, 24| | scaleX: CGFloat, 25| | scaleY: CGFloat, 26| | xValue: Double, 27| | yValue: Double, 28| | transformer: Transformer, 29| | axis: YAxis.AxisDependency, 30| | view: ChartViewBase) 31| | { 32| | self.scaleX = scaleX 33| | self.scaleY = scaleY 34| | self.axisDependency = axis 35| | 36| | super.init( 37| | viewPortHandler: viewPortHandler, 38| | xValue: xValue, 39| | yValue: yValue, 40| | transformer: transformer, 41| | view: view) 42| | 43| | } 44| | 45| | open override func doJob() 46| | { 47| | var matrix = viewPortHandler.setZoom(scaleX: scaleX, scaleY: scaleY) 48| | viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) 49| | 50| | let yValsInView = (view as! BarLineChartViewBase).getAxis(axisDependency).axisRange / Double(viewPortHandler.scaleY) 51| | let xValsInView = (view as! BarLineChartViewBase).xAxis.axisRange / Double(viewPortHandler.scaleX) 52| | 53| | var pt = CGPoint( 54| | x: CGFloat(xValue - xValsInView / 2.0), 55| | y: CGFloat(yValue + yValsInView / 2.0) 56| | ) 57| | 58| | transformer.pointValueToPixel(&pt) 59| | 60| | matrix = viewPortHandler.translate(pt: pt) 61| | viewPortHandler.refresh(newMatrix: matrix, chart: view, invalidate: false) 62| | 63| | (view as! BarLineChartViewBase).calculateOffsets() 64| | view.setNeedsDisplay() 65| | } 66| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/BarChartRenderer.swift: 1| |// 2| |// BarChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if !os(OSX) 16| | import UIKit 17| |#endif 18| | 19| |open class BarChartRenderer: BarLineScatterCandleBubbleRenderer 20| |{ 21| | /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver 22| | /// 23| | /// Its use is apparent when there are multiple data sets, since we want to read bars in left to right order, 24| | /// irrespective of dataset. However, drawing is done per dataset, so using this array and then flattening it prevents us from needing to 25| | /// re-render for the sake of accessibility. 26| | /// 27| | /// In practise, its structure is: 28| | /// 29| | /// ```` 30| | /// [ 31| | /// [dataset1 element1, dataset2 element1], 32| | /// [dataset1 element2, dataset2 element2], 33| | /// [dataset1 element3, dataset2 element3] 34| | /// ... 35| | /// ] 36| | /// ```` 37| | /// This is done to provide numerical inference across datasets to a screenreader user, in the same way that a sighted individual 38| | /// uses a multi-dataset bar chart. 39| | /// 40| | /// The ````internal```` specifier is to allow subclasses (HorizontalBar) to populate the same array 41| | internal lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() 42| | 43| | private typealias Buffer = [CGRect] 44| | 45| | @objc open weak var dataProvider: BarChartDataProvider? 46| | 47| | @objc public init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) 48| | { 49| | super.init(animator: animator, viewPortHandler: viewPortHandler) 50| | 51| | self.dataProvider = dataProvider 52| | } 53| | 54| | // [CGRect] per dataset 55| 46| private var _buffers = [Buffer]() 56| | 57| | open override func initBuffers() 58| | { 59| | guard let barData = dataProvider?.barData else { return _buffers.removeAll() } 60| | 61| | // Match buffers count to dataset count 62| | if _buffers.count != barData.count 63| | { 64| | while _buffers.count < barData.count 65| | { 66| | _buffers.append(Buffer()) 67| | } 68| | while _buffers.count > barData.count 69| | { 70| | _buffers.removeLast() 71| | } 72| | } 73| | 74| | _buffers = zip(_buffers, barData.dataSets).map { buffer, set -> Buffer in 75| | let set = set as! BarChartDataSetProtocol 76| | let size = set.entryCount * (set.isStacked ? set.stackSize : 1) 77| | return buffer.count == size 78| | ? buffer 79| | : Buffer(repeating: .zero, count: size) 80| | } 81| | } 82| | 83| | private func prepareBuffer(dataSet: BarChartDataSetProtocol, index: Int) 84| | { 85| | guard 86| | let dataProvider = dataProvider, 87| | let barData = dataProvider.barData 88| | else { return } 89| | 90| | let barWidthHalf = CGFloat(barData.barWidth / 2.0) 91| | 92| | var bufferIndex = 0 93| | let containsStacks = dataSet.isStacked 94| | 95| | let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) 96| | let phaseY = CGFloat(animator.phaseY) 97| | 98| | for i in (0..= 0.0 124| | { 125| | y = posY 126| | yStart = posY + value 127| | posY = yStart 128| | } 129| | else 130| | { 131| | y = negY 132| | yStart = negY + abs(value) 133| | negY += abs(value) 134| | } 135| | 136| | var top = isInverted 137| | ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) 138| | : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) 139| | var bottom = isInverted 140| | ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) 141| | : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) 142| | 143| | // multiply the height of the rect with the phase 144| | top *= phaseY 145| | bottom *= phaseY 146| | 147| | let barRect = CGRect(x: left, y: top, 148| | width: right - left, 149| | height: bottom - top) 150| | _buffers[index][bufferIndex] = barRect 151| | bufferIndex += 1 152| | } 153| | } 154| | else 155| | { 156| | var top = isInverted 157| | ? (y <= 0.0 ? CGFloat(y) : 0) 158| | : (y >= 0.0 ? CGFloat(y) : 0) 159| | var bottom = isInverted 160| | ? (y >= 0.0 ? CGFloat(y) : 0) 161| | : (y <= 0.0 ? CGFloat(y) : 0) 162| | 163| | /* When drawing each bar, the renderer actually draws each bar from 0 to the required value. 164| | * This drawn bar is then clipped to the visible chart rect in BarLineChartViewBase's draw(rect:) using clipDataToContent. 165| | * While this works fine when calculating the bar rects for drawing, it causes the accessibilityFrames to be oversized in some cases. 166| | * This offset attempts to undo that unnecessary drawing when calculating barRects 167| | * 168| | * +---------------------------------------------------------------+---------------------------------------------------------------+ 169| | * | Situation 1: (!inverted && y >= 0) | Situation 3: (inverted && y >= 0) | 170| | * | | | 171| | * | y -> +--+ <- top | 0 -> ---+--+---+--+------ <- top | 172| | * | |//| } topOffset = y - max | | | |//| } topOffset = min | 173| | * | max -> +---------+--+----+ <- top - topOffset | min -> +--+--+---+--+----+ <- top + topOffset | 174| | * | | +--+ |//| | | | | | |//| | | 175| | * | | | | |//| | | | +--+ |//| | | 176| | * | | | | |//| | | | |//| | | 177| | * | min -> +--+--+---+--+----+ <- bottom + bottomOffset | max -> +---------+--+----+ <- bottom - bottomOffset | 178| | * | | | |//| } bottomOffset = min | |//| } bottomOffset = y - max | 179| | * | 0 -> ---+--+---+--+----- <- bottom | y -> +--+ <- bottom | 180| | * | | | 181| | * +---------------------------------------------------------------+---------------------------------------------------------------+ 182| | * | Situation 2: (!inverted && y < 0) | Situation 4: (inverted && y < 0) | 183| | * | | | 184| | * | 0 -> ---+--+---+--+----- <- top | y -> +--+ <- top | 185| | * | | | |//| } topOffset = -max | |//| } topOffset = min - y | 186| | * | max -> +--+--+---+--+----+ <- top - topOffset | min -> +---------+--+----+ <- top + topOffset | 187| | * | | | | |//| | | | +--+ |//| | | 188| | * | | +--+ |//| | | | | | |//| | | 189| | * | | |//| | | | | | |//| | | 190| | * | min -> +---------+--+----+ <- bottom + bottomOffset | max -> +--+--+---+--+----+ <- bottom - bottomOffset | 191| | * | |//| } bottomOffset = min - y | | | |//| } bottomOffset = -max | 192| | * | y -> +--+ <- bottom | 0 -> ---+--+---+--+------- <- bottom | 193| | * | | | 194| | * +---------------------------------------------------------------+---------------------------------------------------------------+ 195| | */ 196| | var topOffset: CGFloat = 0.0 197| | var bottomOffset: CGFloat = 0.0 198| | if let offsetView = dataProvider as? BarChartView 199| | { 200| | let offsetAxis = offsetView.getAxis(dataSet.axisDependency) 201| | if y >= 0 202| | { 203| | // situation 1 204| | if offsetAxis.axisMaximum < y 205| | { 206| | topOffset = CGFloat(y - offsetAxis.axisMaximum) 207| | } 208| | if offsetAxis.axisMinimum > 0 209| | { 210| | bottomOffset = CGFloat(offsetAxis.axisMinimum) 211| | } 212| | } 213| | else // y < 0 214| | { 215| | //situation 2 216| | if offsetAxis.axisMaximum < 0 217| | { 218| | topOffset = CGFloat(offsetAxis.axisMaximum * -1) 219| | } 220| | if offsetAxis.axisMinimum > y 221| | { 222| | bottomOffset = CGFloat(offsetAxis.axisMinimum - y) 223| | } 224| | } 225| | if isInverted 226| | { 227| | // situation 3 and 4 228| | // exchange topOffset/bottomOffset based on 1 and 2 229| | // see diagram above 230| | (topOffset, bottomOffset) = (bottomOffset, topOffset) 231| | } 232| | } 233| | //apply offset 234| | top = isInverted ? top + topOffset : top - topOffset 235| | bottom = isInverted ? bottom - bottomOffset : bottom + bottomOffset 236| | 237| | // multiply the height of the rect with the phase 238| | // explicitly add 0 + topOffset to indicate this is changed after adding accessibility support (#3650, #3520) 239| | if top > 0 + topOffset 240| | { 241| | top *= phaseY 242| | } 243| | else 244| | { 245| | bottom *= phaseY 246| | } 247| | 248| | let barRect = CGRect(x: left, y: top, 249| | width: right - left, 250| | height: bottom - top) 251| | _buffers[index][bufferIndex] = barRect 252| | bufferIndex += 1 253| | } 254| | } 255| | } 256| | 257| | open override func drawData(context: CGContext) 258| | { 259| | guard 260| | let dataProvider = dataProvider, 261| | let barData = dataProvider.barData 262| | else { return } 263| | 264| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 265| | accessibleChartElements.removeAll() 266| | accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() 267| | 268| | // Make the chart header the first element in the accessible elements array 269| | if let chart = dataProvider as? BarChartView { 270| | let element = createAccessibleHeader(usingChart: chart, 271| | andData: barData, 272| | withDefaultDescription: "Bar Chart") 273| | accessibleChartElements.append(element) 274| | } 275| | 276| | // Populate logically ordered nested elements into accessibilityOrderedElements in drawDataSet() 277| | for i in barData.indices 278| | { 279| | guard let set = barData[i] as? BarChartDataSetProtocol else { 280| | fatalError("Datasets for BarChartRenderer must conform to IBarChartDataset") 281| | } 282| | 283| | guard set.isVisible else { continue } 284| | 285| | drawDataSet(context: context, dataSet: set, index: i) 286| | } 287| | 288| | // Merge nested ordered arrays into the single accessibleChartElements. 289| | accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) 290| | accessibilityPostLayoutChangedNotification() 291| | } 292| | 293| 46| private var _barShadowRectBuffer: CGRect = CGRect() 294| | 295| | @objc open func drawDataSet(context: CGContext, dataSet: BarChartDataSetProtocol, index: Int) 296| | { 297| | guard let dataProvider = dataProvider else { return } 298| | 299| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 300| | 301| | prepareBuffer(dataSet: dataSet, index: index) 302| | trans.rectValuesToPixel(&_buffers[index]) 303| | 304| | let borderWidth = dataSet.barBorderWidth 305| | let borderColor = dataSet.barBorderColor 306| | let drawBorder = borderWidth > 0.0 307| | 308| | context.saveGState() 309| | defer { context.restoreGState() } 310| | 311| | // draw the bar shadow before the values 312| | if dataProvider.isDrawBarShadowEnabled 313| | { 314| | guard let barData = dataProvider.barData else { return } 315| | 316| | let barWidth = barData.barWidth 317| | let barWidthHalf = barWidth / 2.0 318| | var x: Double = 0.0 319| | 320| | let range = (0..= 0.0 510| | ? (rect.origin.y + posOffset) 511| | : (rect.origin.y + rect.size.height + negOffset), 512| | font: valueFont, 513| | align: .center, 514| | color: dataSet.valueTextColorAt(j), 515| | anchor: CGPoint(x: 0.5, y: 0.5), 516| | angleRadians: angleRadians) 517| | } 518| | 519| | if let icon = e.icon, dataSet.isDrawIconsEnabled 520| | { 521| | var px = x 522| | var py = val >= 0.0 523| | ? (rect.origin.y + posOffset) 524| | : (rect.origin.y + rect.size.height + negOffset) 525| | 526| | px += iconsOffset.x 527| | py += iconsOffset.y 528| | 529| | context.drawImage(icon, 530| | atCenter: CGPoint(x: px, y: py), 531| | size: icon.size) 532| | } 533| | 534| | } 535| | } 536| | else 537| | { 538| | // if we have stacks 539| | 540| | var bufferIndex = 0 541| | 542| | for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) 543| | { 544| | guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } 545| | 546| | let vals = e.yValues 547| | 548| | let rect = buffer[bufferIndex] 549| | 550| | let x = rect.origin.x + rect.size.width / 2.0 551| | 552| | // we still draw stacked bars, but there is one non-stacked in between 553| | if let vals = vals 554| | { 555| | // draw stack values 556| | var transformed = [CGPoint]() 557| | 558| | var posY = 0.0 559| | var negY = -e.negativeSum 560| | 561| | for k in vals.indices 562| | { 563| | let value = vals[k] 564| | var y: Double 565| | 566| | if value == 0.0 && (posY == 0.0 || negY == 0.0) 567| | { 568| | // Take care of the situation of a 0.0 value, which overlaps a non-zero bar 569| | y = value 570| | } 571| | else if value >= 0.0 572| | { 573| | posY += value 574| | y = posY 575| | } 576| | else 577| | { 578| | y = negY 579| | negY -= value 580| | } 581| | 582| | transformed.append(CGPoint(x: 0.0, y: CGFloat(y * phaseY))) 583| | } 584| | 585| | trans.pointValuesToPixel(&transformed) 586| | 587| | for (val, transformed) in zip(vals, transformed) 588| | { 589| | let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 590| | let y = transformed.y + (drawBelow ? negOffset : posOffset) 591| | 592| | guard viewPortHandler.isInBoundsRight(x) else { break } 593| | guard viewPortHandler.isInBoundsY(y), 594| | viewPortHandler.isInBoundsLeft(x) 595| | else { continue } 596| | 597| | if dataSet.isDrawValuesEnabled 598| | { 599| | drawValue( 600| | context: context, 601| | value: formatter.stringForValue( 602| | val, 603| | entry: e, 604| | dataSetIndex: dataSetIndex, 605| | viewPortHandler: viewPortHandler), 606| | xPos: x, 607| | yPos: y, 608| | font: valueFont, 609| | align: .center, 610| | color: dataSet.valueTextColorAt(index), 611| | anchor: CGPoint(x: 0.5, y: 0.5), 612| | angleRadians: angleRadians) 613| | } 614| | 615| | if let icon = e.icon, dataSet.isDrawIconsEnabled 616| | { 617| | context.drawImage(icon, 618| | atCenter: CGPoint(x: x + iconsOffset.x, 619| | y: y + iconsOffset.y), 620| | size: icon.size) 621| | } 622| | } 623| | } 624| | else 625| | { 626| | guard viewPortHandler.isInBoundsRight(x) else { break } 627| | guard viewPortHandler.isInBoundsY(rect.origin.y), 628| | viewPortHandler.isInBoundsLeft(x) else { continue } 629| | 630| | if dataSet.isDrawValuesEnabled 631| | { 632| | drawValue( 633| | context: context, 634| | value: formatter.stringForValue( 635| | e.y, 636| | entry: e, 637| | dataSetIndex: dataSetIndex, 638| | viewPortHandler: viewPortHandler), 639| | xPos: x, 640| | yPos: rect.origin.y + 641| | (e.y >= 0 ? posOffset : negOffset), 642| | font: valueFont, 643| | align: .center, 644| | color: dataSet.valueTextColorAt(index), 645| | anchor: CGPoint(x: 0.5, y: 0.5), 646| | angleRadians: angleRadians) 647| | } 648| | 649| | if let icon = e.icon, dataSet.isDrawIconsEnabled 650| | { 651| | var px = x 652| | var py = rect.origin.y + 653| | (e.y >= 0 ? posOffset : negOffset) 654| | 655| | px += iconsOffset.x 656| | py += iconsOffset.y 657| | 658| | context.drawImage(icon, 659| | atCenter: CGPoint(x: px, y: py), 660| | size: icon.size) 661| | } 662| | } 663| | 664| | bufferIndex += vals?.count ?? 1 665| | } 666| | } 667| | } 668| | } 669| | } 670| | 671| | /// Draws a value at the specified x and y position. 672| | @objc open func drawValue(context: CGContext, value: String, xPos: CGFloat, yPos: CGFloat, font: NSUIFont, align: NSTextAlignment, color: NSUIColor, anchor: CGPoint, angleRadians: CGFloat) 673| | { 674| | if (angleRadians == 0.0) 675| | { 676| | context.drawText(value, at: CGPoint(x: xPos, y: yPos), align: align, attributes: [.font: font, .foregroundColor: color]) 677| | } 678| | else 679| | { 680| | // align left to center text with rotation 681| | context.drawText(value, at: CGPoint(x: xPos, y: yPos), align: align, anchor: anchor, angleRadians: angleRadians, attributes: [.font: font, .foregroundColor: color]) 682| | } 683| | } 684| | 685| | 686| | open override func drawExtras(context: CGContext) 687| | { 688| | 689| | } 690| | 691| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 692| | { 693| | guard 694| | let dataProvider = dataProvider, 695| | let barData = dataProvider.barData 696| | else { return } 697| | 698| | context.saveGState() 699| | defer { context.restoreGState() } 700| | var barRect = CGRect() 701| | 702| | for high in indices 703| | { 704| | guard 705| | let set = barData[high.dataSetIndex] as? BarChartDataSetProtocol, 706| | set.isHighlightEnabled 707| | else { continue } 708| | 709| | if let e = set.entryForXValue(high.x, closestToY: high.y) as? BarChartDataEntry 710| | { 711| | guard isInBoundsX(entry: e, dataSet: set) else { continue } 712| | 713| | let trans = dataProvider.getTransformer(forAxis: set.axisDependency) 714| | 715| | context.setFillColor(set.highlightColor.cgColor) 716| | context.setAlpha(set.highlightAlpha) 717| | 718| | let isStack = high.stackIndex >= 0 && e.isStacked 719| | 720| | let y1: Double 721| | let y2: Double 722| | 723| | if isStack 724| | { 725| | if dataProvider.isHighlightFullBarEnabled 726| | { 727| | y1 = e.positiveSum 728| | y2 = -e.negativeSum 729| | } 730| | else 731| | { 732| | let range = e.ranges?[high.stackIndex] 733| | 734| | y1 = range?.from ?? 0.0 735| | y2 = range?.to ?? 0.0 736| | } 737| | } 738| | else 739| | { 740| | y1 = e.y 741| | y2 = 0.0 742| | } 743| | 744| | prepareBarHighlight(x: e.x, y1: y1, y2: y2, barWidthHalf: barData.barWidth / 2.0, trans: trans, rect: &barRect) 745| | 746| | setHighlightDrawPos(highlight: high, barRect: barRect) 747| | 748| | context.fill(barRect) 749| | } 750| | } 751| | } 752| | 753| | /// Sets the drawing position of the highlight object based on the given bar-rect. 754| | internal func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) 755| | { 756| | high.setDraw(x: barRect.midX, y: barRect.origin.y) 757| | } 758| | 759| | /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. 760| | /// This is marked internal to support HorizontalBarChartRenderer as well. 761| | internal func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] 762| | { 763| | guard let chart = dataProvider as? BarChartView else { return [] } 764| | 765| | // Unlike Bubble & Line charts, here we use the maximum entry count to account for stacked bars 766| | let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 767| | 768| | return Array(repeating: [NSUIAccessibilityElement](), 769| | count: maxEntryCount) 770| | } 771| | 772| | /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart 773| | /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. 774| | /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. 775| | internal func createAccessibleElement(withIndex idx: Int, 776| | container: BarChartView, 777| | dataSet: BarChartDataSetProtocol, 778| | dataSetIndex: Int, 779| | stackSize: Int, 780| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement 781| | { 782| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 783| | let xAxis = container.xAxis 784| | 785| | guard let e = dataSet.entryForIndex(idx/stackSize) as? BarChartDataEntry else { return element } 786| | guard let dataProvider = dataProvider else { return element } 787| | 788| | // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. 789| | // i.e. due to the Double conversion, if there are more than one data set that are grouped, 790| | // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. 791| | let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" 792| | 793| | var elementValueText = dataSet.valueFormatter.stringForValue( 794| | e.y, 795| | entry: e, 796| | dataSetIndex: dataSetIndex, 797| | viewPortHandler: viewPortHandler) 798| | 799| | if dataSet.isStacked, let vals = e.yValues 800| | { 801| | let labelCount = min(dataSet.colors.count, stackSize) 802| | 803| | let stackLabel: String? 804| | if (!dataSet.stackLabels.isEmpty && labelCount > 0) { 805| | let labelIndex = idx % labelCount 806| | stackLabel = dataSet.stackLabels.indices.contains(labelIndex) ? dataSet.stackLabels[labelIndex] : nil 807| | } else { 808| | stackLabel = nil 809| | } 810| | 811| | //Handles empty array of yValues 812| | let yValue = vals.isEmpty ? 0.0 : vals[idx % vals.count] 813| | 814| | elementValueText = dataSet.valueFormatter.stringForValue( 815| | yValue, 816| | entry: e, 817| | dataSetIndex: dataSetIndex, 818| | viewPortHandler: viewPortHandler) 819| | 820| | if let stackLabel = stackLabel { 821| | elementValueText = stackLabel + " \(elementValueText)" 822| | } else { 823| | elementValueText = "\(elementValueText)" 824| | } 825| | } 826| | 827| | let dataSetCount = dataProvider.barData?.dataSetCount ?? -1 828| | let doesContainMultipleDataSets = dataSetCount > 1 829| | 830| | element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" 831| | 832| | modifier(element) 833| | 834| | return element 835| | } 836| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift: 1| |// 2| |// BarLineScatterCandleBubbleRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(BarLineScatterCandleBubbleChartRenderer) 16| |open class BarLineScatterCandleBubbleRenderer: NSObject, DataRenderer 17| |{ 18| | public let viewPortHandler: ViewPortHandler 19| | 20| 55| public final var accessibleChartElements: [NSUIAccessibilityElement] = [] 21| | 22| | public let animator: Animator 23| | 24| 55| internal var _xBounds = XBounds() // Reusable XBounds object 25| | 26| | public init(animator: Animator, viewPortHandler: ViewPortHandler) 27| | { 28| | self.viewPortHandler = viewPortHandler 29| | self.animator = animator 30| | 31| | super.init() 32| | } 33| | 34| | open func drawData(context: CGContext) { } 35| | 36| | open func drawValues(context: CGContext) { } 37| | 38| | open func drawExtras(context: CGContext) { } 39| | 40| | open func drawHighlighted(context: CGContext, indices: [Highlight]) { } 41| | 42| | /// Checks if the provided entry object is in bounds for drawing considering the current animation phase. 43| | internal func isInBoundsX(entry e: ChartDataEntry, dataSet: BarLineScatterCandleBubbleChartDataSetProtocol) -> Bool 44| | { 45| | let entryIndex = dataSet.entryIndex(entry: e) 46| | return Double(entryIndex) < Double(dataSet.entryCount) * animator.phaseX 47| | } 48| | 49| | /// Calculates and returns the x-bounds for the given DataSet in terms of index in their values array. 50| | /// This includes minimum and maximum visible x, as well as range. 51| | internal func xBounds(chart: BarLineScatterCandleBubbleChartDataProvider, 52| | dataSet: BarLineScatterCandleBubbleChartDataSetProtocol, 53| | animator: Animator?) -> XBounds 54| | { 55| | return XBounds(chart: chart, dataSet: dataSet, animator: animator) 56| | } 57| | 58| | /// - Returns: `true` if the DataSet values should be drawn, `false` if not. 59| | internal func shouldDrawValues(forDataSet set: ChartDataSetProtocol) -> Bool 60| | { 61| | return set.isVisible && (set.isDrawValuesEnabled || set.isDrawIconsEnabled) 62| | } 63| | 64| | open func initBuffers() { } 65| | 66| | open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool 67| | { 68| | guard let data = dataProvider?.data else { return false } 69| | return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleX) 70| | } 71| | 72| | /// Class representing the bounds of the current viewport in terms of indices in the values array of a DataSet. 73| | open class XBounds 74| | { 75| | /// minimum visible entry index 76| | open var min: Int = 0 77| | 78| | /// maximum visible entry index 79| | open var max: Int = 0 80| | 81| | /// range of visible entry indices 82| | open var range: Int = 0 83| | 84| | public init() 85| | { 86| | 87| | } 88| | 89| | public init(chart: BarLineScatterCandleBubbleChartDataProvider, 90| | dataSet: BarLineScatterCandleBubbleChartDataSetProtocol, 91| | animator: Animator?) 92| | { 93| | self.set(chart: chart, dataSet: dataSet, animator: animator) 94| | } 95| | 96| | /// Calculates the minimum and maximum x values as well as the range between them. 97| | open func set(chart: BarLineScatterCandleBubbleChartDataProvider, 98| | dataSet: BarLineScatterCandleBubbleChartDataSetProtocol, 99| | animator: Animator?) 100| | { 101| | let phaseX = Swift.max(0.0, Swift.min(1.0, animator?.phaseX ?? 1.0)) 102| | 103| | let low = chart.lowestVisibleX 104| | let high = chart.highestVisibleX 105| | 106| | let entryFrom = dataSet.entryForXValue(low, closestToY: .nan, rounding: .down) 107| | let entryTo = dataSet.entryForXValue(high, closestToY: .nan, rounding: .up) 108| | 109| | self.min = entryFrom == nil ? 0 : dataSet.entryIndex(entry: entryFrom!) 110| | self.max = entryTo == nil ? 0 : dataSet.entryIndex(entry: entryTo!) 111| | range = Int(Double(self.max - self.min) * phaseX) 112| | } 113| | } 114| | 115| | public func createAccessibleHeader(usingChart chart: ChartViewBase, andData data: ChartData, withDefaultDescription defaultDescription: String) -> NSUIAccessibilityElement { 116| | return AccessibleHeader.create(usingChart: chart, andData: data, withDefaultDescription: defaultDescription) 117| | } 118| |} 119| | 120| |extension BarLineScatterCandleBubbleRenderer.XBounds: RangeExpression { 121| | public func relative(to collection: C) -> Swift.Range 122| | where C : Collection, Bound == C.Index 123| | { 124| | return Swift.Range(min...min + range) 125| | } 126| | 127| | public func contains(_ element: Int) -> Bool { 128| | return (min...min + range).contains(element) 129| | } 130| |} 131| | 132| |extension BarLineScatterCandleBubbleRenderer.XBounds: Sequence { 133| | public struct Iterator: IteratorProtocol { 134| | private var iterator: IndexingIterator> 135| | 136| | fileprivate init(min: Int, max: Int) { 137| | self.iterator = (min...max).makeIterator() 138| | } 139| | 140| | public mutating func next() -> Int? { 141| | return self.iterator.next() 142| | } 143| | } 144| | 145| | public func makeIterator() -> Iterator { 146| | return Iterator(min: self.min, max: self.min + self.range) 147| | } 148| |} 149| | 150| |extension BarLineScatterCandleBubbleRenderer.XBounds: CustomDebugStringConvertible 151| |{ 152| | public var debugDescription: String 153| | { 154| | return "min:\(self.min), max:\(self.max), range:\(self.range)" 155| | } 156| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/BubbleChartRenderer.swift: 1| |// 2| |// BubbleChartRenderer.swift 3| |// Charts 4| |// 5| |// Bubble chart implementation: 6| |// Copyright 2015 Pierre-Marc Airoldi 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class BubbleChartRenderer: BarLineScatterCandleBubbleRenderer 16| |{ 17| | /// A nested array of elements ordered logically (i.e not in visual/drawing order) for use with VoiceOver. 18| | private lazy var accessibilityOrderedElements: [[NSUIAccessibilityElement]] = accessibilityCreateEmptyOrderedElements() 19| | 20| | @objc open weak var dataProvider: BubbleChartDataProvider? 21| | 22| | @objc public init(dataProvider: BubbleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) 23| | { 24| | super.init(animator: animator, viewPortHandler: viewPortHandler) 25| | 26| | self.dataProvider = dataProvider 27| | } 28| | 29| | open override func drawData(context: CGContext) 30| | { 31| | guard 32| | let dataProvider = dataProvider, 33| | let bubbleData = dataProvider.bubbleData 34| | else { return } 35| | 36| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 37| | accessibleChartElements.removeAll() 38| | accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() 39| | 40| | // Make the chart header the first element in the accessible elements array 41| | if let chart = dataProvider as? BubbleChartView { 42| | let element = createAccessibleHeader(usingChart: chart, 43| | andData: bubbleData, 44| | withDefaultDescription: "Bubble Chart") 45| | accessibleChartElements.append(element) 46| | } 47| | 48| | for (i, set) in (bubbleData.dataSets as! [BubbleChartDataSetProtocol]).enumerated() where set.isVisible 49| | { 50| | drawDataSet(context: context, dataSet: set, dataSetIndex: i) 51| | } 52| | 53| | // Merge nested ordered arrays into the single accessibleChartElements. 54| | accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) 55| | accessibilityPostLayoutChangedNotification() 56| | } 57| | 58| | private func getShapeSize( 59| | entrySize: CGFloat, 60| | maxSize: CGFloat, 61| | reference: CGFloat, 62| | normalizeSize: Bool) -> CGFloat 63| | { 64| | let factor: CGFloat = normalizeSize 65| | ? ((maxSize == 0.0) ? 1.0 : sqrt(entrySize / maxSize)) 66| | : entrySize 67| | let shapeSize: CGFloat = reference * factor 68| | return shapeSize 69| | } 70| | 71| 0| private var _pointBuffer = CGPoint() 72| 0| private var _sizeBuffer = [CGPoint](repeating: CGPoint(), count: 2) 73| | 74| | @objc open func drawDataSet(context: CGContext, dataSet: BubbleChartDataSetProtocol, dataSetIndex: Int) 75| | { 76| | guard let dataProvider = dataProvider else { return } 77| | 78| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 79| | 80| | let phaseY = animator.phaseY 81| | 82| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 83| | 84| | let valueToPixelMatrix = trans.valueToPixelMatrix 85| | 86| | _sizeBuffer[0].x = 0.0 87| | _sizeBuffer[0].y = 0.0 88| | _sizeBuffer[1].x = 1.0 89| | _sizeBuffer[1].y = 0.0 90| | 91| | trans.pointValuesToPixel(&_sizeBuffer) 92| | 93| | context.saveGState() 94| | defer { context.restoreGState() } 95| | 96| | let normalizeSize = dataSet.isNormalizeSizeEnabled 97| | 98| | // calcualte the full width of 1 step on the x-axis 99| | let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x) 100| | let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop) 101| | let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth) 102| | 103| | for j in _xBounds 104| | { 105| | guard let entry = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { continue } 106| | 107| | _pointBuffer.x = CGFloat(entry.x) 108| | _pointBuffer.y = CGFloat(entry.y * phaseY) 109| | _pointBuffer = _pointBuffer.applying(valueToPixelMatrix) 110| | 111| | let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize) 112| | let shapeHalf = shapeSize / 2.0 113| | 114| | guard 115| | viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf), 116| | viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf), 117| | viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf) 118| | else { continue } 119| | 120| | guard viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf) else { break } 121| | 122| | let color = dataSet.color(atIndex: j) 123| | 124| | let rect = CGRect( 125| | x: _pointBuffer.x - shapeHalf, 126| | y: _pointBuffer.y - shapeHalf, 127| | width: shapeSize, 128| | height: shapeSize 129| | ) 130| | 131| | context.setFillColor(color.cgColor) 132| | context.fillEllipse(in: rect) 133| | 134| | // Create and append the corresponding accessibility element to accessibilityOrderedElements 135| | if let chart = dataProvider as? BubbleChartView 136| | { 137| | let element = createAccessibleElement(withIndex: j, 138| | container: chart, 139| | dataSet: dataSet, 140| | dataSetIndex: dataSetIndex, 141| | shapeSize: shapeSize) 142| | { (element) in 143| | element.accessibilityFrame = rect 144| | } 145| | 146| | accessibilityOrderedElements[dataSetIndex].append(element) 147| | } 148| | } 149| | } 150| | 151| | open override func drawValues(context: CGContext) 152| | { 153| | guard let 154| | dataProvider = dataProvider, 155| | let bubbleData = dataProvider.bubbleData, 156| | isDrawingValuesAllowed(dataProvider: dataProvider), 157| | let dataSets = bubbleData.dataSets as? [BubbleChartDataSetProtocol] 158| | else { return } 159| | 160| | let phaseX = max(0.0, min(1.0, animator.phaseX)) 161| | let phaseY = animator.phaseY 162| | 163| | var pt = CGPoint() 164| | 165| | for i in dataSets.indices 166| | { 167| | let dataSet = dataSets[i] 168| | 169| | guard shouldDrawValues(forDataSet: dataSet) else { continue } 170| | 171| | let formatter = dataSet.valueFormatter 172| | let alpha = phaseX == 1 ? phaseY : phaseX 173| | 174| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 175| | 176| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 177| | let valueToPixelMatrix = trans.valueToPixelMatrix 178| | 179| | let iconsOffset = dataSet.iconsOffset 180| | 181| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 182| | 183| | for j in _xBounds 184| | { 185| | guard let e = dataSet.entryForIndex(j) as? BubbleChartDataEntry else { break } 186| | 187| | let valueTextColor = dataSet.valueTextColorAt(j).withAlphaComponent(CGFloat(alpha)) 188| | 189| | pt.x = CGFloat(e.x) 190| | pt.y = CGFloat(e.y * phaseY) 191| | pt = pt.applying(valueToPixelMatrix) 192| | 193| | guard viewPortHandler.isInBoundsRight(pt.x) else { break } 194| | 195| | guard 196| | viewPortHandler.isInBoundsLeft(pt.x), 197| | viewPortHandler.isInBoundsY(pt.y) 198| | else { continue } 199| | 200| | let text = formatter.stringForValue( 201| | Double(e.size), 202| | entry: e, 203| | dataSetIndex: i, 204| | viewPortHandler: viewPortHandler) 205| | 206| | // Larger font for larger bubbles? 207| | let valueFont = dataSet.valueFont 208| | let lineHeight = valueFont.lineHeight 209| | 210| | if dataSet.isDrawValuesEnabled 211| | { 212| | context.drawText(text, 213| | at: CGPoint(x: pt.x, 214| | y: pt.y - (0.5 * lineHeight)), 215| | align: .center, 216| | angleRadians: angleRadians, 217| | attributes: [.font: valueFont, 218| | .foregroundColor: valueTextColor]) 219| | } 220| | 221| | if let icon = e.icon, dataSet.isDrawIconsEnabled 222| | { 223| | context.drawImage(icon, 224| | atCenter: CGPoint(x: pt.x + iconsOffset.x, 225| | y: pt.y + iconsOffset.y), 226| | size: icon.size) 227| | } 228| | } 229| | } 230| | } 231| | 232| | open override func drawExtras(context: CGContext) 233| | { 234| | 235| | } 236| | 237| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 238| | { 239| | guard 240| | let dataProvider = dataProvider, 241| | let bubbleData = dataProvider.bubbleData 242| | else { return } 243| | 244| | context.saveGState() 245| | defer { context.restoreGState() } 246| | 247| | let phaseY = animator.phaseY 248| | 249| | for high in indices 250| | { 251| | guard 252| | let dataSet = bubbleData[high.dataSetIndex] as? BubbleChartDataSetProtocol, 253| | dataSet.isHighlightEnabled, 254| | let entry = dataSet.entryForXValue(high.x, closestToY: high.y) as? BubbleChartDataEntry, 255| | isInBoundsX(entry: entry, dataSet: dataSet) 256| | else { continue } 257| | 258| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 259| | 260| | _sizeBuffer[0].x = 0.0 261| | _sizeBuffer[0].y = 0.0 262| | _sizeBuffer[1].x = 1.0 263| | _sizeBuffer[1].y = 0.0 264| | 265| | trans.pointValuesToPixel(&_sizeBuffer) 266| | 267| | let normalizeSize = dataSet.isNormalizeSizeEnabled 268| | 269| | // calcualte the full width of 1 step on the x-axis 270| | let maxBubbleWidth: CGFloat = abs(_sizeBuffer[1].x - _sizeBuffer[0].x) 271| | let maxBubbleHeight: CGFloat = abs(viewPortHandler.contentBottom - viewPortHandler.contentTop) 272| | let referenceSize: CGFloat = min(maxBubbleHeight, maxBubbleWidth) 273| | 274| | _pointBuffer.x = CGFloat(entry.x) 275| | _pointBuffer.y = CGFloat(entry.y * phaseY) 276| | trans.pointValueToPixel(&_pointBuffer) 277| | 278| | let shapeSize = getShapeSize(entrySize: entry.size, maxSize: dataSet.maxSize, reference: referenceSize, normalizeSize: normalizeSize) 279| | let shapeHalf = shapeSize / 2.0 280| | 281| | guard 282| | viewPortHandler.isInBoundsTop(_pointBuffer.y + shapeHalf), 283| | viewPortHandler.isInBoundsBottom(_pointBuffer.y - shapeHalf), 284| | viewPortHandler.isInBoundsLeft(_pointBuffer.x + shapeHalf) 285| | else { continue } 286| | 287| | guard viewPortHandler.isInBoundsRight(_pointBuffer.x - shapeHalf) else { break } 288| | 289| | let originalColor = dataSet.color(atIndex: Int(entry.x)) 290| | 291| | var h: CGFloat = 0.0 292| | var s: CGFloat = 0.0 293| | var b: CGFloat = 0.0 294| | var a: CGFloat = 0.0 295| | 296| | originalColor.getHue(&h, saturation: &s, brightness: &b, alpha: &a) 297| | 298| | let color = NSUIColor(hue: h, saturation: s, brightness: b * 0.5, alpha: a) 299| | let rect = CGRect( 300| | x: _pointBuffer.x - shapeHalf, 301| | y: _pointBuffer.y - shapeHalf, 302| | width: shapeSize, 303| | height: shapeSize) 304| | 305| | context.setLineWidth(dataSet.highlightCircleWidth) 306| | context.setStrokeColor(color.cgColor) 307| | context.strokeEllipse(in: rect) 308| | 309| | high.setDraw(x: _pointBuffer.x, y: _pointBuffer.y) 310| | } 311| | } 312| | 313| | /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. 314| | private func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] 315| | { 316| | guard let chart = dataProvider as? BubbleChartView else { return [] } 317| | 318| | let dataSetCount = chart.bubbleData?.dataSetCount ?? 0 319| | 320| | return Array(repeating: [NSUIAccessibilityElement](), 321| | count: dataSetCount) 322| | } 323| | 324| | /// Creates an NSUIAccessibleElement representing individual bubbles location and relative size. 325| | private func createAccessibleElement(withIndex idx: Int, 326| | container: BubbleChartView, 327| | dataSet: BubbleChartDataSetProtocol, 328| | dataSetIndex: Int, 329| | shapeSize: CGFloat, 330| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement 331| | { 332| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 333| | let xAxis = container.xAxis 334| | 335| | guard let e = dataSet.entryForIndex(idx) else { return element } 336| | guard let dataProvider = dataProvider else { return element } 337| | 338| | // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. 339| | // i.e. due to the Double conversion, if there are more than one data set that are grouped, 340| | // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. 341| | let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" 342| | 343| | let elementValueText = dataSet.valueFormatter.stringForValue(e.y, 344| | entry: e, 345| | dataSetIndex: dataSetIndex, 346| | viewPortHandler: viewPortHandler) 347| | 348| | let dataSetCount = dataProvider.bubbleData?.dataSetCount ?? -1 349| | let doesContainMultipleDataSets = dataSetCount > 1 350| | 351| | element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText), bubble size: \(String(format: "%.2f", (shapeSize/dataSet.maxSize) * 100)) %" 352| | 353| | modifier(element) 354| | 355| | return element 356| | } 357| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/CandleStickChartRenderer.swift: 1| |// 2| |// CandleStickChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class CandleStickChartRenderer: LineScatterCandleRadarRenderer 16| |{ 17| | @objc open weak var dataProvider: CandleChartDataProvider? 18| | 19| | @objc public init(dataProvider: CandleChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) 20| | { 21| | super.init(animator: animator, viewPortHandler: viewPortHandler) 22| | 23| | self.dataProvider = dataProvider 24| | } 25| | 26| | open override func drawData(context: CGContext) 27| | { 28| | guard let dataProvider = dataProvider, let candleData = dataProvider.candleData else { return } 29| | 30| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 31| | accessibleChartElements.removeAll() 32| | 33| | // Make the chart header the first element in the accessible elements array 34| | if let chart = dataProvider as? CandleStickChartView { 35| | let element = createAccessibleHeader(usingChart: chart, 36| | andData: candleData, 37| | withDefaultDescription: "CandleStick Chart") 38| | accessibleChartElements.append(element) 39| | } 40| | 41| | for set in candleData.dataSets as! [CandleChartDataSetProtocol] where set.isVisible 42| | { 43| | drawDataSet(context: context, dataSet: set) 44| | } 45| | } 46| | 47| 0| private var _shadowPoints = [CGPoint](repeating: CGPoint(), count: 4) 48| 0| private var _rangePoints = [CGPoint](repeating: CGPoint(), count: 2) 49| 0| private var _openPoints = [CGPoint](repeating: CGPoint(), count: 2) 50| 0| private var _closePoints = [CGPoint](repeating: CGPoint(), count: 2) 51| 0| private var _bodyRect = CGRect() 52| 0| private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) 53| | 54| | @objc open func drawDataSet(context: CGContext, dataSet: CandleChartDataSetProtocol) 55| | { 56| | guard 57| | let dataProvider = dataProvider 58| | else { return } 59| | 60| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 61| | 62| | let phaseY = animator.phaseY 63| | let barSpace = dataSet.barSpace 64| | let showCandleBar = dataSet.showCandleBar 65| | 66| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 67| | 68| | context.saveGState() 69| | 70| | context.setLineWidth(dataSet.shadowWidth) 71| | 72| | for j in _xBounds 73| | { 74| | // get the entry 75| | guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { continue } 76| | 77| | let xPos = e.x 78| | 79| | let open = e.open 80| | let close = e.close 81| | let high = e.high 82| | let low = e.low 83| | 84| | let doesContainMultipleDataSets = (dataProvider.candleData?.dataSets.count ?? 1) > 1 85| | var accessibilityMovementDescription = "neutral" 86| | var accessibilityRect = CGRect(x: CGFloat(xPos) + 0.5 - barSpace, 87| | y: CGFloat(low * phaseY), 88| | width: (2 * barSpace) - 1.0, 89| | height: (CGFloat(abs(high - low) * phaseY))) 90| | trans.rectValueToPixel(&accessibilityRect) 91| | 92| | if showCandleBar 93| | { 94| | // calculate the shadow 95| | 96| | _shadowPoints[0].x = CGFloat(xPos) 97| | _shadowPoints[1].x = CGFloat(xPos) 98| | _shadowPoints[2].x = CGFloat(xPos) 99| | _shadowPoints[3].x = CGFloat(xPos) 100| | 101| | if open > close 102| | { 103| | _shadowPoints[0].y = CGFloat(high * phaseY) 104| | _shadowPoints[1].y = CGFloat(open * phaseY) 105| | _shadowPoints[2].y = CGFloat(low * phaseY) 106| | _shadowPoints[3].y = CGFloat(close * phaseY) 107| | } 108| | else if open < close 109| | { 110| | _shadowPoints[0].y = CGFloat(high * phaseY) 111| | _shadowPoints[1].y = CGFloat(close * phaseY) 112| | _shadowPoints[2].y = CGFloat(low * phaseY) 113| | _shadowPoints[3].y = CGFloat(open * phaseY) 114| | } 115| | else 116| | { 117| | _shadowPoints[0].y = CGFloat(high * phaseY) 118| | _shadowPoints[1].y = CGFloat(open * phaseY) 119| | _shadowPoints[2].y = CGFloat(low * phaseY) 120| | _shadowPoints[3].y = _shadowPoints[1].y 121| | } 122| | 123| | trans.pointValuesToPixel(&_shadowPoints) 124| | 125| | // draw the shadows 126| | 127| | var shadowColor: NSUIColor! = nil 128| | if dataSet.shadowColorSameAsCandle 129| | { 130| | if open > close 131| | { 132| | shadowColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) 133| | } 134| | else if open < close 135| | { 136| | shadowColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) 137| | } 138| | else 139| | { 140| | shadowColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) 141| | } 142| | } 143| | 144| | if shadowColor === nil 145| | { 146| | shadowColor = dataSet.shadowColor ?? dataSet.color(atIndex: j) 147| | } 148| | 149| | context.setStrokeColor(shadowColor.cgColor) 150| | context.strokeLineSegments(between: _shadowPoints) 151| | 152| | // calculate the body 153| | 154| | _bodyRect.origin.x = CGFloat(xPos) - 0.5 + barSpace 155| | _bodyRect.origin.y = CGFloat(close * phaseY) 156| | _bodyRect.size.width = (CGFloat(xPos) + 0.5 - barSpace) - _bodyRect.origin.x 157| | _bodyRect.size.height = CGFloat(open * phaseY) - _bodyRect.origin.y 158| | 159| | trans.rectValueToPixel(&_bodyRect) 160| | 161| | // draw body differently for increasing and decreasing entry 162| | 163| | if open > close 164| | { 165| | accessibilityMovementDescription = "decreasing" 166| | 167| | let color = dataSet.decreasingColor ?? dataSet.color(atIndex: j) 168| | 169| | if dataSet.isDecreasingFilled 170| | { 171| | context.setFillColor(color.cgColor) 172| | context.fill(_bodyRect) 173| | } 174| | else 175| | { 176| | context.setStrokeColor(color.cgColor) 177| | context.stroke(_bodyRect) 178| | } 179| | } 180| | else if open < close 181| | { 182| | accessibilityMovementDescription = "increasing" 183| | 184| | let color = dataSet.increasingColor ?? dataSet.color(atIndex: j) 185| | 186| | if dataSet.isIncreasingFilled 187| | { 188| | context.setFillColor(color.cgColor) 189| | context.fill(_bodyRect) 190| | } 191| | else 192| | { 193| | context.setStrokeColor(color.cgColor) 194| | context.stroke(_bodyRect) 195| | } 196| | } 197| | else 198| | { 199| | let color = dataSet.neutralColor ?? dataSet.color(atIndex: j) 200| | 201| | context.setStrokeColor(color.cgColor) 202| | context.stroke(_bodyRect) 203| | } 204| | } 205| | else 206| | { 207| | _rangePoints[0].x = CGFloat(xPos) 208| | _rangePoints[0].y = CGFloat(high * phaseY) 209| | _rangePoints[1].x = CGFloat(xPos) 210| | _rangePoints[1].y = CGFloat(low * phaseY) 211| | 212| | _openPoints[0].x = CGFloat(xPos) - 0.5 + barSpace 213| | _openPoints[0].y = CGFloat(open * phaseY) 214| | _openPoints[1].x = CGFloat(xPos) 215| | _openPoints[1].y = CGFloat(open * phaseY) 216| | 217| | _closePoints[0].x = CGFloat(xPos) + 0.5 - barSpace 218| | _closePoints[0].y = CGFloat(close * phaseY) 219| | _closePoints[1].x = CGFloat(xPos) 220| | _closePoints[1].y = CGFloat(close * phaseY) 221| | 222| | trans.pointValuesToPixel(&_rangePoints) 223| | trans.pointValuesToPixel(&_openPoints) 224| | trans.pointValuesToPixel(&_closePoints) 225| | 226| | // draw the ranges 227| | var barColor: NSUIColor! = nil 228| | 229| | if open > close 230| | { 231| | accessibilityMovementDescription = "decreasing" 232| | barColor = dataSet.decreasingColor ?? dataSet.color(atIndex: j) 233| | } 234| | else if open < close 235| | { 236| | accessibilityMovementDescription = "increasing" 237| | barColor = dataSet.increasingColor ?? dataSet.color(atIndex: j) 238| | } 239| | else 240| | { 241| | barColor = dataSet.neutralColor ?? dataSet.color(atIndex: j) 242| | } 243| | 244| | context.setStrokeColor(barColor.cgColor) 245| | context.strokeLineSegments(between: _rangePoints) 246| | context.strokeLineSegments(between: _openPoints) 247| | context.strokeLineSegments(between: _closePoints) 248| | } 249| | 250| | let axElement = createAccessibleElement(withIndex: j, 251| | container: dataProvider, 252| | dataSet: dataSet) 253| | { (element) in 254| | element.accessibilityLabel = "\(doesContainMultipleDataSets ? "\(dataSet.label ?? "Dataset")" : "") " + "\(xPos) - \(accessibilityMovementDescription). low: \(low), high: \(high), opening: \(open), closing: \(close)" 255| | element.accessibilityFrame = accessibilityRect 256| | } 257| | 258| | accessibleChartElements.append(axElement) 259| | 260| | } 261| | 262| | // Post this notification to let VoiceOver account for the redrawn frames 263| | accessibilityPostLayoutChangedNotification() 264| | 265| | context.restoreGState() 266| | } 267| | 268| | open override func drawValues(context: CGContext) 269| | { 270| | guard 271| | let dataProvider = dataProvider, 272| | let candleData = dataProvider.candleData 273| | else { return } 274| | 275| | // if values are drawn 276| | if isDrawingValuesAllowed(dataProvider: dataProvider) 277| | { 278| | let dataSets = candleData.dataSets 279| | 280| | let phaseY = animator.phaseY 281| | 282| | var pt = CGPoint() 283| | 284| | for i in dataSets.indices 285| | { 286| | guard let 287| | dataSet = dataSets[i] as? BarLineScatterCandleBubbleChartDataSetProtocol, 288| | shouldDrawValues(forDataSet: dataSet) 289| | else { continue } 290| | 291| | let valueFont = dataSet.valueFont 292| | 293| | let formatter = dataSet.valueFormatter 294| | 295| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 296| | let valueToPixelMatrix = trans.valueToPixelMatrix 297| | 298| | let iconsOffset = dataSet.iconsOffset 299| | 300| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 301| | 302| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 303| | 304| | let lineHeight = valueFont.lineHeight 305| | let yOffset: CGFloat = lineHeight + 5.0 306| | 307| | for j in _xBounds 308| | { 309| | guard let e = dataSet.entryForIndex(j) as? CandleChartDataEntry else { break } 310| | 311| | pt.x = CGFloat(e.x) 312| | pt.y = CGFloat(e.high * phaseY) 313| | pt = pt.applying(valueToPixelMatrix) 314| | 315| | if (!viewPortHandler.isInBoundsRight(pt.x)) 316| | { 317| | break 318| | } 319| | 320| | if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) 321| | { 322| | continue 323| | } 324| | 325| | if dataSet.isDrawValuesEnabled 326| | { 327| | context.drawText(formatter.stringForValue(e.high, 328| | entry: e, 329| | dataSetIndex: i, 330| | viewPortHandler: viewPortHandler), 331| | at: CGPoint(x: pt.x, 332| | y: pt.y - yOffset), 333| | align: .center, 334| | angleRadians: angleRadians, 335| | attributes: [.font: valueFont, 336| | .foregroundColor: dataSet.valueTextColorAt(j)]) 337| | } 338| | 339| | if let icon = e.icon, dataSet.isDrawIconsEnabled 340| | { 341| | context.drawImage(icon, 342| | atCenter: CGPoint(x: pt.x + iconsOffset.x, 343| | y: pt.y + iconsOffset.y), 344| | size: icon.size) 345| | } 346| | } 347| | } 348| | } 349| | } 350| | 351| | open override func drawExtras(context: CGContext) 352| | { 353| | } 354| | 355| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 356| | { 357| | guard 358| | let dataProvider = dataProvider, 359| | let candleData = dataProvider.candleData 360| | else { return } 361| | 362| | context.saveGState() 363| | 364| | for high in indices 365| | { 366| | guard 367| | let set = candleData[high.dataSetIndex] as? CandleChartDataSetProtocol, 368| | set.isHighlightEnabled 369| | else { continue } 370| | 371| | guard let e = set.entryForXValue(high.x, closestToY: high.y) as? CandleChartDataEntry else { continue } 372| | 373| | if !isInBoundsX(entry: e, dataSet: set) 374| | { 375| | continue 376| | } 377| | 378| | let trans = dataProvider.getTransformer(forAxis: set.axisDependency) 379| | 380| | context.setStrokeColor(set.highlightColor.cgColor) 381| | context.setLineWidth(set.highlightLineWidth) 382| | 383| | if set.highlightLineDashLengths != nil 384| | { 385| | context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) 386| | } 387| | else 388| | { 389| | context.setLineDash(phase: 0.0, lengths: []) 390| | } 391| | 392| | let lowValue = e.low * Double(animator.phaseY) 393| | let highValue = e.high * Double(animator.phaseY) 394| | let y = (lowValue + highValue) / 2.0 395| | 396| | let pt = trans.pixelForValues(x: e.x, y: y) 397| | 398| | high.setDraw(pt: pt) 399| | 400| | // draw the lines 401| | drawHighlightLines(context: context, point: pt, set: set) 402| | } 403| | 404| | context.restoreGState() 405| | } 406| | 407| | private func createAccessibleElement(withIndex idx: Int, 408| | container: CandleChartDataProvider, 409| | dataSet: CandleChartDataSetProtocol, 410| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { 411| | 412| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 413| | 414| | // The modifier allows changing of traits and frame depending on highlight, rotation, etc 415| | modifier(element) 416| | 417| | return element 418| | } 419| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/CombinedChartRenderer.swift: 1| |// 2| |// CombinedChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class CombinedChartRenderer: NSObject, DataRenderer 16| |{ 17| | public let viewPortHandler: ViewPortHandler 18| | 19| 3| public final var accessibleChartElements: [NSUIAccessibilityElement] = [] 20| | 21| | public let animator: Animator 22| | 23| | @objc open weak var chart: CombinedChartView? 24| | 25| | /// if set to true, all values are drawn above their bars, instead of below their top 26| | @objc open var drawValueAboveBarEnabled = true 27| | 28| | /// if set to true, a grey area is drawn behind each bar that indicates the maximum value 29| | @objc open var drawBarShadowEnabled = false 30| | 31| 3| internal var _renderers = [DataRenderer]() 32| | 33| 3| internal var _drawOrder: [CombinedChartView.DrawOrder] = [.bar, .bubble, .line, .candle, .scatter] 34| | 35| | @objc public init(chart: CombinedChartView, animator: Animator, viewPortHandler: ViewPortHandler) 36| | { 37| | self.chart = chart 38| | self.viewPortHandler = viewPortHandler 39| | self.animator = animator 40| | 41| | super.init() 42| | 43| | createRenderers() 44| | } 45| | 46| | /// Creates the renderers needed for this combined-renderer in the required order. Also takes the DrawOrder into consideration. 47| | internal func createRenderers() 48| | { 49| | _renderers = [DataRenderer]() 50| | 51| | guard let chart = chart else { return } 52| | 53| | for order in drawOrder 54| | { 55| | switch (order) 56| | { 57| | case .bar: 58| | if chart.barData !== nil 59| | { 60| | _renderers.append(BarChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) 61| | } 62| | break 63| | 64| | case .line: 65| | if chart.lineData !== nil 66| | { 67| | _renderers.append(LineChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) 68| | } 69| | break 70| | 71| | case .candle: 72| | if chart.candleData !== nil 73| | { 74| | _renderers.append(CandleStickChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) 75| | } 76| | break 77| | 78| | case .scatter: 79| | if chart.scatterData !== nil 80| | { 81| | _renderers.append(ScatterChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) 82| | } 83| | break 84| | 85| | case .bubble: 86| | if chart.bubbleData !== nil 87| | { 88| | _renderers.append(BubbleChartRenderer(dataProvider: chart, animator: animator, viewPortHandler: viewPortHandler)) 89| | } 90| | break 91| | } 92| | } 93| | 94| | } 95| | 96| | open func initBuffers() 97| | { 98| | _renderers.forEach { $0.initBuffers() } 99| | } 100| | 101| | open func drawData(context: CGContext) 102| | { 103| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 104| | accessibleChartElements.removeAll() 105| | 106| | if 107| | let combinedChart = chart, 108| | let data = combinedChart.data { 109| | // Make the chart header the first element in the accessible elements array 110| | let element = createAccessibleHeader(usingChart: combinedChart, 111| | andData: data, 112| | withDefaultDescription: "Combined Chart") 113| | accessibleChartElements.append(element) 114| | } 115| | 116| | // TODO: Due to the potential complexity of data presented in Combined charts, a more usable way 117| | // for VO accessibility would be to use axis based traversal rather than by dataset. 118| | // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) 119| | _renderers.forEach { $0.drawData(context: context) } 120| | } 121| | 122| | open func drawValues(context: CGContext) 123| | { 124| | _renderers.forEach { $0.drawValues(context: context) } 125| | } 126| | 127| | open func drawExtras(context: CGContext) 128| | { 129| | _renderers.forEach { $0.drawExtras(context: context) } 130| | } 131| | 132| | open func drawHighlighted(context: CGContext, indices: [Highlight]) 133| | { 134| | for renderer in _renderers 135| | { 136| | var data: ChartData? 137| | 138| | if renderer is BarChartRenderer 139| | { 140| | data = (renderer as! BarChartRenderer).dataProvider?.barData 141| | } 142| | else if renderer is LineChartRenderer 143| | { 144| | data = (renderer as! LineChartRenderer).dataProvider?.lineData 145| | } 146| | else if renderer is CandleStickChartRenderer 147| | { 148| | data = (renderer as! CandleStickChartRenderer).dataProvider?.candleData 149| | } 150| | else if renderer is ScatterChartRenderer 151| | { 152| | data = (renderer as! ScatterChartRenderer).dataProvider?.scatterData 153| | } 154| | else if renderer is BubbleChartRenderer 155| | { 156| | data = (renderer as! BubbleChartRenderer).dataProvider?.bubbleData 157| | } 158| | 159| | let dataIndex = data == nil ? nil : (chart?.data as? CombinedChartData)?.allData.firstIndex(of: data!) 160| | 161| | let dataIndices = indices.filter{ $0.dataIndex == dataIndex || $0.dataIndex == -1 } 162| | 163| | renderer.drawHighlighted(context: context, indices: dataIndices) 164| | } 165| | } 166| | 167| | open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool 168| | { 169| | guard let data = dataProvider?.data else { return false } 170| | return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleX) 171| | } 172| | 173| | /// All sub-renderers. 174| | @objc open var subRenderers: [DataRenderer] 175| | { 176| | get { return _renderers } 177| | set { _renderers = newValue } 178| | } 179| | 180| | // MARK: Accessors 181| | 182| | /// `true` if drawing values above bars is enabled, `false` ifnot 183| | @objc open var isDrawValueAboveBarEnabled: Bool { return drawValueAboveBarEnabled } 184| | 185| | /// `true` if drawing shadows (maxvalue) for each bar is enabled, `false` ifnot 186| | @objc open var isDrawBarShadowEnabled: Bool { return drawBarShadowEnabled } 187| | 188| | /// the order in which the provided data objects should be drawn. 189| | /// The earlier you place them in the provided array, the further they will be in the background. 190| | /// e.g. if you provide [DrawOrder.Bar, DrawOrder.Line], the bars will be drawn behind the lines. 191| | open var drawOrder: [CombinedChartView.DrawOrder] 192| | { 193| | get 194| | { 195| | return _drawOrder 196| | } 197| | set 198| | { 199| | if !newValue.isEmpty 200| | { 201| | _drawOrder = newValue 202| | } 203| | } 204| | } 205| | 206| | public func createAccessibleHeader(usingChart chart: ChartViewBase, andData data: ChartData, withDefaultDescription defaultDescription: String) -> NSUIAccessibilityElement { 207| | return AccessibleHeader.create(usingChart: chart, andData: data, withDefaultDescription: defaultDescription) 208| | } 209| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/HorizontalBarChartRenderer.swift: 1| |// 2| |// HorizontalBarChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if !os(OSX) 16| | import UIKit 17| |#endif 18| | 19| | 20| |open class HorizontalBarChartRenderer: BarChartRenderer 21| |{ 22| | private class Buffer 23| | { 24| | var rects = [CGRect]() 25| | } 26| | 27| | public override init(dataProvider: BarChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) 28| | { 29| | super.init(dataProvider: dataProvider, animator: animator, viewPortHandler: viewPortHandler) 30| | } 31| | 32| | // [CGRect] per dataset 33| 6| private var _buffers = [Buffer]() 34| | 35| | open override func initBuffers() 36| | { 37| | if let barData = dataProvider?.barData 38| | { 39| | // Matche buffers count to dataset count 40| | if _buffers.count != barData.count 41| | { 42| | while _buffers.count < barData.count 43| | { 44| | _buffers.append(Buffer()) 45| | } 46| | while _buffers.count > barData.count 47| | { 48| | _buffers.removeLast() 49| | } 50| | } 51| | 52| | for i in barData.indices 53| | { 54| | let set = barData.dataSets[i] as! BarChartDataSetProtocol 55| | let size = set.entryCount * (set.isStacked ? set.stackSize : 1) 56| | if _buffers[i].rects.count != size 57| | { 58| | _buffers[i].rects = [CGRect](repeating: CGRect(), count: size) 59| | } 60| | } 61| | } 62| | else 63| | { 64| | _buffers.removeAll() 65| | } 66| | } 67| | 68| | private func prepareBuffer(dataSet: BarChartDataSetProtocol, index: Int) 69| | { 70| | guard let 71| | dataProvider = dataProvider, 72| | let barData = dataProvider.barData 73| | else { return } 74| | 75| | let barWidthHalf = barData.barWidth / 2.0 76| | 77| | let buffer = _buffers[index] 78| | var bufferIndex = 0 79| | let containsStacks = dataSet.isStacked 80| | 81| | let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) 82| | let phaseY = animator.phaseY 83| | var barRect = CGRect() 84| | var x: Double 85| | var y: Double 86| | 87| | for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) 88| | { 89| | guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } 90| | 91| | let vals = e.yValues 92| | 93| | x = e.x 94| | y = e.y 95| | 96| | if !containsStacks || vals == nil 97| | { 98| | let bottom = CGFloat(x - barWidthHalf) 99| | let top = CGFloat(x + barWidthHalf) 100| | var right = isInverted 101| | ? (y <= 0.0 ? CGFloat(y) : 0) 102| | : (y >= 0.0 ? CGFloat(y) : 0) 103| | var left = isInverted 104| | ? (y >= 0.0 ? CGFloat(y) : 0) 105| | : (y <= 0.0 ? CGFloat(y) : 0) 106| | 107| | // multiply the height of the rect with the phase 108| | if right > 0 109| | { 110| | right *= CGFloat(phaseY) 111| | } 112| | else 113| | { 114| | left *= CGFloat(phaseY) 115| | } 116| | 117| | barRect.origin.x = left 118| | barRect.size.width = right - left 119| | barRect.origin.y = top 120| | barRect.size.height = bottom - top 121| | 122| | buffer.rects[bufferIndex] = barRect 123| | bufferIndex += 1 124| | } 125| | else 126| | { 127| | var posY = 0.0 128| | var negY = -e.negativeSum 129| | var yStart = 0.0 130| | 131| | // fill the stack 132| | for k in vals!.indices 133| | { 134| | let value = vals![k] 135| | 136| | if value == 0.0 && (posY == 0.0 || negY == 0.0) 137| | { 138| | // Take care of the situation of a 0.0 value, which overlaps a non-zero bar 139| | y = value 140| | yStart = y 141| | } 142| | else if value >= 0.0 143| | { 144| | y = posY 145| | yStart = posY + value 146| | posY = yStart 147| | } 148| | else 149| | { 150| | y = negY 151| | yStart = negY + abs(value) 152| | negY += abs(value) 153| | } 154| | 155| | let bottom = CGFloat(x - barWidthHalf) 156| | let top = CGFloat(x + barWidthHalf) 157| | var right = isInverted 158| | ? (y <= yStart ? CGFloat(y) : CGFloat(yStart)) 159| | : (y >= yStart ? CGFloat(y) : CGFloat(yStart)) 160| | var left = isInverted 161| | ? (y >= yStart ? CGFloat(y) : CGFloat(yStart)) 162| | : (y <= yStart ? CGFloat(y) : CGFloat(yStart)) 163| | 164| | // multiply the height of the rect with the phase 165| | right *= CGFloat(phaseY) 166| | left *= CGFloat(phaseY) 167| | 168| | barRect.origin.x = left 169| | barRect.size.width = right - left 170| | barRect.origin.y = top 171| | barRect.size.height = bottom - top 172| | 173| | buffer.rects[bufferIndex] = barRect 174| | bufferIndex += 1 175| | } 176| | } 177| | } 178| | } 179| | 180| 6| private var _barShadowRectBuffer: CGRect = CGRect() 181| | 182| | open override func drawDataSet(context: CGContext, dataSet: BarChartDataSetProtocol, index: Int) 183| | { 184| | guard let dataProvider = dataProvider else { return } 185| | 186| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 187| | 188| | prepareBuffer(dataSet: dataSet, index: index) 189| | trans.rectValuesToPixel(&_buffers[index].rects) 190| | 191| | let borderWidth = dataSet.barBorderWidth 192| | let borderColor = dataSet.barBorderColor 193| | let drawBorder = borderWidth > 0.0 194| | 195| | context.saveGState() 196| | 197| | // draw the bar shadow before the values 198| | if dataProvider.isDrawBarShadowEnabled 199| | { 200| | guard let barData = dataProvider.barData else { return } 201| | 202| | let barWidth = barData.barWidth 203| | let barWidthHalf = barWidth / 2.0 204| | var x: Double = 0.0 205| | 206| | for i in stride(from: 0, to: min(Int(ceil(Double(dataSet.entryCount) * animator.phaseX)), dataSet.entryCount), by: 1) 207| | { 208| | guard let e = dataSet.entryForIndex(i) as? BarChartDataEntry else { continue } 209| | 210| | x = e.x 211| | 212| | _barShadowRectBuffer.origin.y = CGFloat(x - barWidthHalf) 213| | _barShadowRectBuffer.size.height = CGFloat(barWidth) 214| | 215| | trans.rectValueToPixel(&_barShadowRectBuffer) 216| | 217| | if !viewPortHandler.isInBoundsTop(_barShadowRectBuffer.origin.y + _barShadowRectBuffer.size.height) 218| | { 219| | break 220| | } 221| | 222| | if !viewPortHandler.isInBoundsBottom(_barShadowRectBuffer.origin.y) 223| | { 224| | continue 225| | } 226| | 227| | _barShadowRectBuffer.origin.x = viewPortHandler.contentLeft 228| | _barShadowRectBuffer.size.width = viewPortHandler.contentWidth 229| | 230| | context.setFillColor(dataSet.barShadowColor.cgColor) 231| | context.fill(_barShadowRectBuffer) 232| | } 233| | } 234| | 235| | let buffer = _buffers[index] 236| | 237| | let isSingleColor = dataSet.colors.count == 1 238| | 239| | if isSingleColor 240| | { 241| | context.setFillColor(dataSet.color(atIndex: 0).cgColor) 242| | } 243| | 244| | // In case the chart is stacked, we need to accomodate individual bars within accessibilityOrdereredElements 245| | let isStacked = dataSet.isStacked 246| | let stackSize = isStacked ? dataSet.stackSize : 1 247| | 248| | for j in buffer.rects.indices 249| | { 250| | let barRect = buffer.rects[j] 251| | 252| | if (!viewPortHandler.isInBoundsTop(barRect.origin.y + barRect.size.height)) 253| | { 254| | break 255| | } 256| | 257| | if (!viewPortHandler.isInBoundsBottom(barRect.origin.y)) 258| | { 259| | continue 260| | } 261| | 262| | if !isSingleColor 263| | { 264| | // Set the color for the currently drawn value. If the index is out of bounds, reuse colors. 265| | context.setFillColor(dataSet.color(atIndex: j).cgColor) 266| | } 267| | 268| | context.fill(barRect) 269| | 270| | if drawBorder 271| | { 272| | context.setStrokeColor(borderColor.cgColor) 273| | context.setLineWidth(borderWidth) 274| | context.stroke(barRect) 275| | } 276| | 277| | // Create and append the corresponding accessibility element to accessibilityOrderedElements (see BarChartRenderer) 278| | if let chart = dataProvider as? BarChartView 279| | { 280| | let element = createAccessibleElement(withIndex: j, 281| | container: chart, 282| | dataSet: dataSet, 283| | dataSetIndex: index, 284| | stackSize: stackSize) 285| | { (element) in 286| | element.accessibilityFrame = barRect 287| | } 288| | 289| | accessibilityOrderedElements[j/stackSize].append(element) 290| | } 291| | } 292| | 293| | context.restoreGState() 294| | } 295| | 296| | open override func prepareBarHighlight( 297| | x: Double, 298| | y1: Double, 299| | y2: Double, 300| | barWidthHalf: Double, 301| | trans: Transformer, 302| | rect: inout CGRect) 303| | { 304| | let top = x - barWidthHalf 305| | let bottom = x + barWidthHalf 306| | let left = y1 307| | let right = y2 308| | 309| | rect.origin.x = CGFloat(left) 310| | rect.origin.y = CGFloat(top) 311| | rect.size.width = CGFloat(right - left) 312| | rect.size.height = CGFloat(bottom - top) 313| | 314| | trans.rectValueToPixelHorizontal(&rect, phaseY: animator.phaseY) 315| | } 316| | 317| | open override func drawValues(context: CGContext) 318| | { 319| | // if values are drawn 320| | if isDrawingValuesAllowed(dataProvider: dataProvider) 321| | { 322| | guard 323| | let dataProvider = dataProvider, 324| | let barData = dataProvider.barData 325| | else { return } 326| | 327| | let dataSets = barData.dataSets 328| | 329| | let textAlign = NSTextAlignment.left 330| | 331| | let valueOffsetPlus: CGFloat = 5.0 332| | var posOffset: CGFloat 333| | var negOffset: CGFloat 334| | let drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled 335| | 336| | for dataSetIndex in barData.indices 337| | { 338| | guard let 339| | dataSet = dataSets[dataSetIndex] as? BarChartDataSetProtocol, 340| | shouldDrawValues(forDataSet: dataSet) 341| | else { continue } 342| | 343| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 344| | 345| | let isInverted = dataProvider.isInverted(axis: dataSet.axisDependency) 346| | 347| | let valueFont = dataSet.valueFont 348| | let yOffset = -valueFont.lineHeight / 2.0 349| | 350| | let formatter = dataSet.valueFormatter 351| | 352| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 353| | 354| | let phaseY = animator.phaseY 355| | 356| | let iconsOffset = dataSet.iconsOffset 357| | 358| | let buffer = _buffers[dataSetIndex] 359| | 360| | // if only single values are drawn (sum) 361| | if !dataSet.isStacked 362| | { 363| | for j in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) 364| | { 365| | guard let e = dataSet.entryForIndex(j) as? BarChartDataEntry else { continue } 366| | 367| | let rect = buffer.rects[j] 368| | 369| | let y = rect.origin.y + rect.size.height / 2.0 370| | 371| | if !viewPortHandler.isInBoundsTop(rect.origin.y) 372| | { 373| | break 374| | } 375| | 376| | if !viewPortHandler.isInBoundsX(rect.origin.x) 377| | { 378| | continue 379| | } 380| | 381| | if !viewPortHandler.isInBoundsBottom(rect.origin.y) 382| | { 383| | continue 384| | } 385| | 386| | let val = e.y 387| | let valueText = formatter.stringForValue( 388| | val, 389| | entry: e, 390| | dataSetIndex: dataSetIndex, 391| | viewPortHandler: viewPortHandler) 392| | 393| | // calculate the correct offset depending on the draw position of the value 394| | let valueTextWidth = valueText.size(withAttributes: [.font: valueFont]).width 395| | posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) 396| | negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) - rect.size.width 397| | 398| | if isInverted 399| | { 400| | posOffset = -posOffset - valueTextWidth 401| | negOffset = -negOffset - valueTextWidth 402| | } 403| | 404| | if dataSet.isDrawValuesEnabled 405| | { 406| | drawValue( 407| | context: context, 408| | value: valueText, 409| | xPos: (rect.origin.x + rect.size.width) 410| | + (val >= 0.0 ? posOffset : negOffset), 411| | yPos: y + yOffset, 412| | font: valueFont, 413| | align: textAlign, 414| | color: dataSet.valueTextColorAt(j), 415| | anchor: CGPoint.zero, 416| | angleRadians: angleRadians) 417| | } 418| | 419| | if let icon = e.icon, dataSet.isDrawIconsEnabled 420| | { 421| | var px = (rect.origin.x + rect.size.width) 422| | + (val >= 0.0 ? posOffset : negOffset) 423| | var py = y 424| | 425| | px += iconsOffset.x 426| | py += iconsOffset.y 427| | 428| | context.drawImage(icon, 429| | atCenter: CGPoint(x: px, y: py), 430| | size: icon.size) 431| | } 432| | } 433| | } 434| | else 435| | { 436| | // if each value of a potential stack should be drawn 437| | 438| | var bufferIndex = 0 439| | 440| | for index in 0 ..< Int(ceil(Double(dataSet.entryCount) * animator.phaseX)) 441| | { 442| | guard let e = dataSet.entryForIndex(index) as? BarChartDataEntry else { continue } 443| | 444| | let rect = buffer.rects[bufferIndex] 445| | 446| | let vals = e.yValues 447| | 448| | // we still draw stacked bars, but there is one non-stacked in between 449| | if vals == nil 450| | { 451| | if !viewPortHandler.isInBoundsTop(rect.origin.y) 452| | { 453| | break 454| | } 455| | 456| | if !viewPortHandler.isInBoundsX(rect.origin.x) 457| | { 458| | continue 459| | } 460| | 461| | if !viewPortHandler.isInBoundsBottom(rect.origin.y) 462| | { 463| | continue 464| | } 465| | 466| | let val = e.y 467| | let valueText = formatter.stringForValue( 468| | val, 469| | entry: e, 470| | dataSetIndex: dataSetIndex, 471| | viewPortHandler: viewPortHandler) 472| | 473| | // calculate the correct offset depending on the draw position of the value 474| | let valueTextWidth = valueText.size(withAttributes: [NSAttributedString.Key.font: valueFont]).width 475| | posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) 476| | negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) 477| | 478| | if isInverted 479| | { 480| | posOffset = -posOffset - valueTextWidth 481| | negOffset = -negOffset - valueTextWidth 482| | } 483| | 484| | if dataSet.isDrawValuesEnabled 485| | { 486| | drawValue( 487| | context: context, 488| | value: valueText, 489| | xPos: (rect.origin.x + rect.size.width) 490| | + (val >= 0.0 ? posOffset : negOffset), 491| | yPos: rect.origin.y + yOffset, 492| | font: valueFont, 493| | align: textAlign, 494| | color: dataSet.valueTextColorAt(index), 495| | anchor: CGPoint.zero, 496| | angleRadians: angleRadians) 497| | } 498| | 499| | if let icon = e.icon, dataSet.isDrawIconsEnabled 500| | { 501| | var px = (rect.origin.x + rect.size.width) 502| | + (val >= 0.0 ? posOffset : negOffset) 503| | var py = rect.origin.y 504| | 505| | px += iconsOffset.x 506| | py += iconsOffset.y 507| | 508| | context.drawImage(icon, 509| | atCenter: CGPoint(x: px, y: py), 510| | size: icon.size) 511| | } 512| | } 513| | else 514| | { 515| | let vals = vals! 516| | var transformed = [CGPoint]() 517| | 518| | var posY = 0.0 519| | var negY = -e.negativeSum 520| | 521| | for k in vals.indices 522| | { 523| | let value = vals[k] 524| | var y: Double 525| | 526| | if value == 0.0 && (posY == 0.0 || negY == 0.0) 527| | { 528| | // Take care of the situation of a 0.0 value, which overlaps a non-zero bar 529| | y = value 530| | } 531| | else if value >= 0.0 532| | { 533| | posY += value 534| | y = posY 535| | } 536| | else 537| | { 538| | y = negY 539| | negY -= value 540| | } 541| | 542| | transformed.append(CGPoint(x: CGFloat(y * phaseY), y: 0.0)) 543| | } 544| | 545| | trans.pointValuesToPixel(&transformed) 546| | 547| | for k in transformed.indices 548| | { 549| | let val = vals[k] 550| | let valueText = formatter.stringForValue( 551| | val, 552| | entry: e, 553| | dataSetIndex: dataSetIndex, 554| | viewPortHandler: viewPortHandler) 555| | 556| | // calculate the correct offset depending on the draw position of the value 557| | let valueTextWidth = valueText.size(withAttributes: [.font: valueFont]).width 558| | posOffset = (drawValueAboveBar ? valueOffsetPlus : -(valueTextWidth + valueOffsetPlus)) 559| | negOffset = (drawValueAboveBar ? -(valueTextWidth + valueOffsetPlus) : valueOffsetPlus) 560| | 561| | if isInverted 562| | { 563| | posOffset = -posOffset - valueTextWidth 564| | negOffset = -negOffset - valueTextWidth 565| | } 566| | 567| | let drawBelow = (val == 0.0 && negY == 0.0 && posY > 0.0) || val < 0.0 568| | 569| | let x = transformed[k].x + (drawBelow ? negOffset : posOffset) 570| | let y = rect.origin.y + rect.size.height / 2.0 571| | 572| | if (!viewPortHandler.isInBoundsTop(y)) 573| | { 574| | break 575| | } 576| | 577| | if (!viewPortHandler.isInBoundsX(x)) 578| | { 579| | continue 580| | } 581| | 582| | if (!viewPortHandler.isInBoundsBottom(y)) 583| | { 584| | continue 585| | } 586| | 587| | if dataSet.isDrawValuesEnabled 588| | { 589| | drawValue(context: context, 590| | value: valueText, 591| | xPos: x, 592| | yPos: y + yOffset, 593| | font: valueFont, 594| | align: textAlign, 595| | color: dataSet.valueTextColorAt(index), 596| | anchor: CGPoint.zero, 597| | angleRadians: angleRadians) 598| | } 599| | 600| | if let icon = e.icon, dataSet.isDrawIconsEnabled 601| | { 602| | context.drawImage(icon, 603| | atCenter: CGPoint(x: x + iconsOffset.x, 604| | y: y + iconsOffset.y), 605| | size: icon.size) 606| | } 607| | } 608| | } 609| | 610| | bufferIndex += vals?.count ?? 1 611| | } 612| | } 613| | } 614| | } 615| | } 616| | 617| | open override func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool 618| | { 619| | guard let data = dataProvider?.data 620| | else { return false } 621| | return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * self.viewPortHandler.scaleY) 622| | } 623| | 624| | /// Sets the drawing position of the highlight object based on the riven bar-rect. 625| | internal override func setHighlightDrawPos(highlight high: Highlight, barRect: CGRect) 626| | { 627| | high.setDraw(x: barRect.midY, y: barRect.origin.x + barRect.size.width) 628| | } 629| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/LegendRenderer.swift: 1| |// 2| |// LegendRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |@objc(ChartLegendRenderer) 16| |open class LegendRenderer: NSObject, Renderer 17| |{ 18| | @objc public let viewPortHandler: ViewPortHandler 19| | 20| | /// the legend object this renderer renders 21| | @objc open var legend: Legend? 22| | 23| | @objc public init(viewPortHandler: ViewPortHandler, legend: Legend?) 24| | { 25| | self.viewPortHandler = viewPortHandler 26| | self.legend = legend 27| | 28| | super.init() 29| | } 30| | 31| | /// Prepares the legend and calculates all needed forms, labels and colors. 32| | @objc open func computeLegend(data: ChartData) 33| | { 34| | guard let legend = legend else { return } 35| | 36| | if !legend.isLegendCustom 37| | { 38| | var entries: [LegendEntry] = [] 39| | 40| | // loop for building up the colors and labels used in the legend 41| | for dataSet in data 42| | { 43| | let clrs: [NSUIColor] = dataSet.colors 44| | let entryCount = dataSet.entryCount 45| | 46| | // if we have a barchart with stacked bars 47| | if dataSet is BarChartDataSetProtocol && 48| | (dataSet as! BarChartDataSetProtocol).isStacked 49| | { 50| | let bds = dataSet as! BarChartDataSetProtocol 51| | let sLabels = bds.stackLabels 52| | let minEntries = min(clrs.count, bds.stackSize) 53| | 54| | for j in 0.. 0 58| | { 59| | let labelIndex = j % minEntries 60| | label = sLabels.indices.contains(labelIndex) ? sLabels[labelIndex] : nil 61| | } 62| | else 63| | { 64| | label = nil 65| | } 66| | 67| | let entry = LegendEntry(label: label) 68| | entry.form = dataSet.form 69| | entry.formSize = dataSet.formSize 70| | entry.formLineWidth = dataSet.formLineWidth 71| | entry.formLineDashPhase = dataSet.formLineDashPhase 72| | entry.formLineDashLengths = dataSet.formLineDashLengths 73| | entry.formColor = clrs[j] 74| | 75| | entries.append(entry) 76| | } 77| | 78| | if dataSet.label != nil 79| | { 80| | // add the legend description label 81| | let entry = LegendEntry(label: dataSet.label) 82| | entry.form = .none 83| | 84| | entries.append(entry) 85| | } 86| | } 87| | else if dataSet is PieChartDataSetProtocol 88| | { 89| | let pds = dataSet as! PieChartDataSetProtocol 90| | 91| | for j in 0..= 1 119| | { 120| | var prevDx: CGFloat = 0.0 121| | var prevDy: CGFloat = 0.0 122| | var curDx: CGFloat = 0.0 123| | var curDy: CGFloat = 0.0 124| | 125| | // Take an extra point from the left, and an extra from the right. 126| | // That's because we need 4 points for a cubic bezier (cubic=4), otherwise we get lines moving and doing weird stuff on the edges of the chart. 127| | // So in the starting `prev` and `cur`, go -2, -1 128| | 129| | let firstIndex = _xBounds.min + 1 130| | 131| | var prevPrev: ChartDataEntry! = nil 132| | var prev: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 2, 0)) 133| | var cur: ChartDataEntry! = dataSet.entryForIndex(max(firstIndex - 1, 0)) 134| | var next: ChartDataEntry! = cur 135| | var nextIndex: Int = -1 136| | 137| | if cur == nil { return } 138| | 139| | // let the spline start 140| | cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) 141| | 142| | for j in _xBounds.dropFirst() // same as firstIndex 143| | { 144| | prevPrev = prev 145| | prev = cur 146| | cur = nextIndex == j ? next : dataSet.entryForIndex(j) 147| | 148| | nextIndex = j + 1 < dataSet.entryCount ? j + 1 : j 149| | next = dataSet.entryForIndex(nextIndex) 150| | 151| | if next == nil { break } 152| | 153| | prevDx = CGFloat(cur.x - prevPrev.x) * intensity 154| | prevDy = CGFloat(cur.y - prevPrev.y) * intensity 155| | curDx = CGFloat(next.x - prev.x) * intensity 156| | curDy = CGFloat(next.y - prev.y) * intensity 157| | 158| | cubicPath.addCurve( 159| | to: CGPoint( 160| | x: CGFloat(cur.x), 161| | y: CGFloat(cur.y) * CGFloat(phaseY)), 162| | control1: CGPoint( 163| | x: CGFloat(prev.x) + prevDx, 164| | y: (CGFloat(prev.y) + prevDy) * CGFloat(phaseY)), 165| | control2: CGPoint( 166| | x: CGFloat(cur.x) - curDx, 167| | y: (CGFloat(cur.y) - curDy) * CGFloat(phaseY)), 168| | transform: valueToPixelMatrix) 169| | } 170| | } 171| | 172| | context.saveGState() 173| | defer { context.restoreGState() } 174| | 175| | if dataSet.isDrawFilledEnabled 176| | { 177| | // Copy this path because we make changes to it 178| | let fillPath = cubicPath.mutableCopy() 179| | 180| | drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) 181| | } 182| | 183| | if dataSet.isDrawLineWithGradientEnabled 184| | { 185| | drawGradientLine(context: context, dataSet: dataSet, spline: cubicPath, matrix: valueToPixelMatrix) 186| | } 187| | else 188| | { 189| | drawLine(context: context, spline: cubicPath, drawingColor: drawingColor) 190| | } 191| | } 192| | 193| | @objc open func drawHorizontalBezier(context: CGContext, dataSet: LineChartDataSetProtocol) 194| | { 195| | guard let dataProvider = dataProvider else { return } 196| | 197| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 198| | 199| | let phaseY = animator.phaseY 200| | 201| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 202| | 203| | // get the color that is specified for this position from the DataSet 204| | let drawingColor = dataSet.colors.first! 205| | 206| | // the path for the cubic-spline 207| | let cubicPath = CGMutablePath() 208| | 209| | let valueToPixelMatrix = trans.valueToPixelMatrix 210| | 211| | if _xBounds.range >= 1 212| | { 213| | var prev: ChartDataEntry! = dataSet.entryForIndex(_xBounds.min) 214| | var cur: ChartDataEntry! = prev 215| | 216| | if cur == nil { return } 217| | 218| | // let the spline start 219| | cubicPath.move(to: CGPoint(x: CGFloat(cur.x), y: CGFloat(cur.y * phaseY)), transform: valueToPixelMatrix) 220| | 221| | for j in _xBounds.dropFirst() 222| | { 223| | prev = cur 224| | cur = dataSet.entryForIndex(j) 225| | 226| | let cpx = CGFloat(prev.x + (cur.x - prev.x) / 2.0) 227| | 228| | cubicPath.addCurve( 229| | to: CGPoint( 230| | x: CGFloat(cur.x), 231| | y: CGFloat(cur.y * phaseY)), 232| | control1: CGPoint( 233| | x: cpx, 234| | y: CGFloat(prev.y * phaseY)), 235| | control2: CGPoint( 236| | x: cpx, 237| | y: CGFloat(cur.y * phaseY)), 238| | transform: valueToPixelMatrix) 239| | } 240| | } 241| | 242| | context.saveGState() 243| | defer { context.restoreGState() } 244| | 245| | if dataSet.isDrawFilledEnabled 246| | { 247| | // Copy this path because we make changes to it 248| | let fillPath = cubicPath.mutableCopy() 249| | 250| | drawCubicFill(context: context, dataSet: dataSet, spline: fillPath!, matrix: valueToPixelMatrix, bounds: _xBounds) 251| | } 252| | 253| | if dataSet.isDrawLineWithGradientEnabled 254| | { 255| | drawGradientLine(context: context, dataSet: dataSet, spline: cubicPath, matrix: valueToPixelMatrix) 256| | } 257| | else 258| | { 259| | drawLine(context: context, spline: cubicPath, drawingColor: drawingColor) 260| | } 261| | } 262| | 263| | open func drawCubicFill( 264| | context: CGContext, 265| | dataSet: LineChartDataSetProtocol, 266| | spline: CGMutablePath, 267| | matrix: CGAffineTransform, 268| | bounds: XBounds) 269| | { 270| | guard 271| | let dataProvider = dataProvider 272| | else { return } 273| | 274| | if bounds.range <= 0 275| | { 276| | return 277| | } 278| | 279| | let fillMin = dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0 280| | 281| | var pt1 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min + bounds.range)?.x ?? 0.0), y: fillMin) 282| | var pt2 = CGPoint(x: CGFloat(dataSet.entryForIndex(bounds.min)?.x ?? 0.0), y: fillMin) 283| | pt1 = pt1.applying(matrix) 284| | pt2 = pt2.applying(matrix) 285| | 286| | spline.addLine(to: pt1) 287| | spline.addLine(to: pt2) 288| | spline.closeSubpath() 289| | 290| | if dataSet.fill != nil 291| | { 292| | drawFilledPath(context: context, path: spline, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) 293| | } 294| | else 295| | { 296| | drawFilledPath(context: context, path: spline, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) 297| | } 298| | } 299| | 300| 9| private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) 301| | 302| | @objc open func drawLinear(context: CGContext, dataSet: LineChartDataSetProtocol) 303| | { 304| | guard let dataProvider = dataProvider else { return } 305| | 306| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 307| | 308| | let valueToPixelMatrix = trans.valueToPixelMatrix 309| | 310| | let entryCount = dataSet.entryCount 311| | let isDrawSteppedEnabled = dataSet.mode == .stepped 312| | let pointsPerEntryPair = isDrawSteppedEnabled ? 4 : 2 313| | 314| | let phaseY = animator.phaseY 315| | 316| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 317| | 318| | // if drawing filled is enabled 319| | if dataSet.isDrawFilledEnabled && entryCount > 0 320| | { 321| | drawLinearFill(context: context, dataSet: dataSet, trans: trans, bounds: _xBounds) 322| | } 323| | 324| | context.saveGState() 325| | defer { context.restoreGState() } 326| | 327| | // more than 1 color 328| | if dataSet.colors.count > 1, !dataSet.isDrawLineWithGradientEnabled 329| | { 330| | if _lineSegments.count != pointsPerEntryPair 331| | { 332| | // Allocate once in correct size 333| | _lineSegments = [CGPoint](repeating: CGPoint(), count: pointsPerEntryPair) 334| | } 335| | 336| | for j in _xBounds.dropLast() 337| | { 338| | var e: ChartDataEntry! = dataSet.entryForIndex(j) 339| | 340| | if e == nil { continue } 341| | 342| | _lineSegments[0].x = CGFloat(e.x) 343| | _lineSegments[0].y = CGFloat(e.y * phaseY) 344| | 345| | if j < _xBounds.max 346| | { 347| | // TODO: remove the check. 348| | // With the new XBounds iterator, j is always smaller than _xBounds.max 349| | // Keeping this check for a while, if xBounds have no further breaking changes, it should be safe to remove the check 350| | e = dataSet.entryForIndex(j + 1) 351| | 352| | if e == nil { break } 353| | 354| | if isDrawSteppedEnabled 355| | { 356| | _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: _lineSegments[0].y) 357| | _lineSegments[2] = _lineSegments[1] 358| | _lineSegments[3] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) 359| | } 360| | else 361| | { 362| | _lineSegments[1] = CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)) 363| | } 364| | } 365| | else 366| | { 367| | _lineSegments[1] = _lineSegments[0] 368| | } 369| | 370| | _lineSegments = _lineSegments.map { $0.applying(valueToPixelMatrix) } 371| | 372| | if (!viewPortHandler.isInBoundsRight(_lineSegments[0].x)) 373| | { 374| | break 375| | } 376| | 377| | // Determine the start and end coordinates of the line, and make sure they differ. 378| | guard 379| | let firstCoordinate = _lineSegments.first, 380| | let lastCoordinate = _lineSegments.last, 381| | firstCoordinate != lastCoordinate else { continue } 382| | 383| | // make sure the lines don't do shitty things outside bounds 384| | if !viewPortHandler.isInBoundsLeft(lastCoordinate.x) || 385| | !viewPortHandler.isInBoundsTop(max(firstCoordinate.y, lastCoordinate.y)) || 386| | !viewPortHandler.isInBoundsBottom(min(firstCoordinate.y, lastCoordinate.y)) 387| | { 388| | continue 389| | } 390| | 391| | // get the color that is set for this line-segment 392| | context.setStrokeColor(dataSet.color(atIndex: j).cgColor) 393| | context.strokeLineSegments(between: _lineSegments) 394| | } 395| | } 396| | else 397| | { // only one color per dataset 398| | guard dataSet.entryForIndex(_xBounds.min) != nil else { 399| | return 400| | } 401| | 402| | var firstPoint = true 403| | 404| | let path = CGMutablePath() 405| | for x in stride(from: _xBounds.min, through: _xBounds.range + _xBounds.min, by: 1) 406| | { 407| | guard let e1 = dataSet.entryForIndex(x == 0 ? 0 : (x - 1)) else { continue } 408| | guard let e2 = dataSet.entryForIndex(x) else { continue } 409| | 410| | let startPoint = 411| | CGPoint( 412| | x: CGFloat(e1.x), 413| | y: CGFloat(e1.y * phaseY)) 414| | .applying(valueToPixelMatrix) 415| | 416| | if firstPoint 417| | { 418| | path.move(to: startPoint) 419| | firstPoint = false 420| | } 421| | else 422| | { 423| | path.addLine(to: startPoint) 424| | } 425| | 426| | if isDrawSteppedEnabled 427| | { 428| | let steppedPoint = 429| | CGPoint( 430| | x: CGFloat(e2.x), 431| | y: CGFloat(e1.y * phaseY)) 432| | .applying(valueToPixelMatrix) 433| | path.addLine(to: steppedPoint) 434| | } 435| | 436| | let endPoint = 437| | CGPoint( 438| | x: CGFloat(e2.x), 439| | y: CGFloat(e2.y * phaseY)) 440| | .applying(valueToPixelMatrix) 441| | path.addLine(to: endPoint) 442| | } 443| | 444| | if !firstPoint 445| | { 446| | if dataSet.isDrawLineWithGradientEnabled { 447| | drawGradientLine(context: context, dataSet: dataSet, spline: path, matrix: valueToPixelMatrix) 448| | } else { 449| | context.beginPath() 450| | context.addPath(path) 451| | context.setStrokeColor(dataSet.color(atIndex: 0).cgColor) 452| | context.strokePath() 453| | } 454| | } 455| | } 456| | } 457| | 458| | open func drawLinearFill(context: CGContext, dataSet: LineChartDataSetProtocol, trans: Transformer, bounds: XBounds) 459| | { 460| | guard let dataProvider = dataProvider else { return } 461| | 462| | let filled = generateFilledPath( 463| | dataSet: dataSet, 464| | fillMin: dataSet.fillFormatter?.getFillLinePosition(dataSet: dataSet, dataProvider: dataProvider) ?? 0.0, 465| | bounds: bounds, 466| | matrix: trans.valueToPixelMatrix) 467| | 468| | if dataSet.fill != nil 469| | { 470| | drawFilledPath(context: context, path: filled, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) 471| | } 472| | else 473| | { 474| | drawFilledPath(context: context, path: filled, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) 475| | } 476| | } 477| | 478| | /// Generates the path that is used for filled drawing. 479| | private func generateFilledPath(dataSet: LineChartDataSetProtocol, fillMin: CGFloat, bounds: XBounds, matrix: CGAffineTransform) -> CGPath 480| | { 481| | let phaseY = animator.phaseY 482| | let isDrawSteppedEnabled = dataSet.mode == .stepped 483| | let matrix = matrix 484| | 485| | var e: ChartDataEntry! 486| | 487| | let filled = CGMutablePath() 488| | 489| | e = dataSet.entryForIndex(bounds.min) 490| | if e != nil 491| | { 492| | filled.move(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) 493| | filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) 494| | } 495| | 496| | // create a new path 497| | for x in stride(from: (bounds.min + 1), through: bounds.range + bounds.min, by: 1) 498| | { 499| | guard let e = dataSet.entryForIndex(x) else { continue } 500| | 501| | if isDrawSteppedEnabled 502| | { 503| | guard let ePrev = dataSet.entryForIndex(x-1) else { continue } 504| | filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(ePrev.y * phaseY)), transform: matrix) 505| | } 506| | 507| | filled.addLine(to: CGPoint(x: CGFloat(e.x), y: CGFloat(e.y * phaseY)), transform: matrix) 508| | } 509| | 510| | // close up 511| | e = dataSet.entryForIndex(bounds.range + bounds.min) 512| | if e != nil 513| | { 514| | filled.addLine(to: CGPoint(x: CGFloat(e.x), y: fillMin), transform: matrix) 515| | } 516| | filled.closeSubpath() 517| | 518| | return filled 519| | } 520| | 521| | open override func drawValues(context: CGContext) 522| | { 523| | guard 524| | let dataProvider = dataProvider, 525| | let lineData = dataProvider.lineData 526| | else { return } 527| | 528| | if isDrawingValuesAllowed(dataProvider: dataProvider) 529| | { 530| | let dataSets = lineData.dataSets 531| | 532| | let phaseY = animator.phaseY 533| | 534| | var pt = CGPoint() 535| | 536| | for i in dataSets.indices 537| | { 538| | guard let 539| | dataSet = dataSets[i] as? LineChartDataSetProtocol, 540| | shouldDrawValues(forDataSet: dataSet) 541| | else { continue } 542| | 543| | let valueFont = dataSet.valueFont 544| | 545| | let formatter = dataSet.valueFormatter 546| | 547| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 548| | 549| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 550| | let valueToPixelMatrix = trans.valueToPixelMatrix 551| | 552| | let iconsOffset = dataSet.iconsOffset 553| | 554| | // make sure the values do not interfear with the circles 555| | var valOffset = Int(dataSet.circleRadius * 1.75) 556| | 557| | if !dataSet.isDrawCirclesEnabled 558| | { 559| | valOffset = valOffset / 2 560| | } 561| | 562| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 563| | 564| | for j in _xBounds 565| | { 566| | guard let e = dataSet.entryForIndex(j) else { break } 567| | 568| | pt.x = CGFloat(e.x) 569| | pt.y = CGFloat(e.y * phaseY) 570| | pt = pt.applying(valueToPixelMatrix) 571| | 572| | if (!viewPortHandler.isInBoundsRight(pt.x)) 573| | { 574| | break 575| | } 576| | 577| | if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) 578| | { 579| | continue 580| | } 581| | 582| | if dataSet.isDrawValuesEnabled 583| | { 584| | context.drawText(formatter.stringForValue(e.y, 585| | entry: e, 586| | dataSetIndex: i, 587| | viewPortHandler: viewPortHandler), 588| | at: CGPoint(x: pt.x, 589| | y: pt.y - CGFloat(valOffset) - valueFont.lineHeight), 590| | align: .center, 591| | angleRadians: angleRadians, 592| | attributes: [.font: valueFont, 593| | .foregroundColor: dataSet.valueTextColorAt(j)]) 594| | } 595| | 596| | if let icon = e.icon, dataSet.isDrawIconsEnabled 597| | { 598| | context.drawImage(icon, 599| | atCenter: CGPoint(x: pt.x + iconsOffset.x, 600| | y: pt.y + iconsOffset.y), 601| | size: icon.size) 602| | } 603| | } 604| | } 605| | } 606| | } 607| | 608| | open override func drawExtras(context: CGContext) 609| | { 610| | drawCircles(context: context) 611| | } 612| | 613| | private func drawCircles(context: CGContext) 614| | { 615| | guard 616| | let dataProvider = dataProvider, 617| | let lineData = dataProvider.lineData 618| | else { return } 619| | 620| | let phaseY = animator.phaseY 621| | 622| | let dataSets = lineData.dataSets 623| | 624| | var pt = CGPoint() 625| | var rect = CGRect() 626| | 627| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 628| | accessibleChartElements.removeAll() 629| | accessibilityOrderedElements = accessibilityCreateEmptyOrderedElements() 630| | 631| | // Make the chart header the first element in the accessible elements array 632| | if let chart = dataProvider as? LineChartView { 633| | let element = createAccessibleHeader(usingChart: chart, 634| | andData: lineData, 635| | withDefaultDescription: "Line Chart") 636| | accessibleChartElements.append(element) 637| | } 638| | 639| | context.saveGState() 640| | 641| | for i in dataSets.indices 642| | { 643| | guard let dataSet = lineData[i] as? LineChartDataSetProtocol else { continue } 644| | 645| | // Skip Circles and Accessibility if not enabled, 646| | // reduces CPU significantly if not needed 647| | if !dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0 648| | { 649| | continue 650| | } 651| | 652| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 653| | let valueToPixelMatrix = trans.valueToPixelMatrix 654| | 655| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 656| | 657| | let circleRadius = dataSet.circleRadius 658| | let circleDiameter = circleRadius * 2.0 659| | let circleHoleRadius = dataSet.circleHoleRadius 660| | let circleHoleDiameter = circleHoleRadius * 2.0 661| | 662| | let drawCircleHole = dataSet.isDrawCircleHoleEnabled && 663| | circleHoleRadius < circleRadius && 664| | circleHoleRadius > 0.0 665| | let drawTransparentCircleHole = drawCircleHole && 666| | (dataSet.circleHoleColor == nil || 667| | dataSet.circleHoleColor == NSUIColor.clear) 668| | 669| | for j in _xBounds 670| | { 671| | guard let e = dataSet.entryForIndex(j) else { break } 672| | 673| | pt.x = CGFloat(e.x) 674| | pt.y = CGFloat(e.y * phaseY) 675| | pt = pt.applying(valueToPixelMatrix) 676| | 677| | if (!viewPortHandler.isInBoundsRight(pt.x)) 678| | { 679| | break 680| | } 681| | 682| | // make sure the circles don't do shitty things outside bounds 683| | if (!viewPortHandler.isInBoundsLeft(pt.x) || !viewPortHandler.isInBoundsY(pt.y)) 684| | { 685| | continue 686| | } 687| | 688| | // Accessibility element geometry 689| | let scaleFactor: CGFloat = 3 690| | let accessibilityRect = CGRect(x: pt.x - (scaleFactor * circleRadius), 691| | y: pt.y - (scaleFactor * circleRadius), 692| | width: scaleFactor * circleDiameter, 693| | height: scaleFactor * circleDiameter) 694| | // Create and append the corresponding accessibility element to accessibilityOrderedElements 695| | if let chart = dataProvider as? LineChartView 696| | { 697| | let element = createAccessibleElement(withIndex: j, 698| | container: chart, 699| | dataSet: dataSet, 700| | dataSetIndex: i) 701| | { (element) in 702| | element.accessibilityFrame = accessibilityRect 703| | } 704| | 705| | accessibilityOrderedElements[i].append(element) 706| | } 707| | 708| | context.setFillColor(dataSet.getCircleColor(atIndex: j)!.cgColor) 709| | 710| | rect.origin.x = pt.x - circleRadius 711| | rect.origin.y = pt.y - circleRadius 712| | rect.size.width = circleDiameter 713| | rect.size.height = circleDiameter 714| | 715| | if drawTransparentCircleHole 716| | { 717| | // Begin path for circle with hole 718| | context.beginPath() 719| | context.addEllipse(in: rect) 720| | 721| | // Cut hole in path 722| | rect.origin.x = pt.x - circleHoleRadius 723| | rect.origin.y = pt.y - circleHoleRadius 724| | rect.size.width = circleHoleDiameter 725| | rect.size.height = circleHoleDiameter 726| | context.addEllipse(in: rect) 727| | 728| | // Fill in-between 729| | context.fillPath(using: .evenOdd) 730| | } 731| | else 732| | { 733| | context.fillEllipse(in: rect) 734| | 735| | if drawCircleHole 736| | { 737| | context.setFillColor(dataSet.circleHoleColor!.cgColor) 738| | 739| | // The hole rect 740| | rect.origin.x = pt.x - circleHoleRadius 741| | rect.origin.y = pt.y - circleHoleRadius 742| | rect.size.width = circleHoleDiameter 743| | rect.size.height = circleHoleDiameter 744| | 745| | context.fillEllipse(in: rect) 746| | } 747| | } 748| | } 749| | } 750| | 751| | context.restoreGState() 752| | 753| | // Merge nested ordered arrays into the single accessibleChartElements. 754| | accessibleChartElements.append(contentsOf: accessibilityOrderedElements.flatMap { $0 } ) 755| | accessibilityPostLayoutChangedNotification() 756| | } 757| | 758| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 759| | { 760| | guard 761| | let dataProvider = dataProvider, 762| | let lineData = dataProvider.lineData 763| | else { return } 764| | 765| | let chartXMax = dataProvider.chartXMax 766| | 767| | context.saveGState() 768| | 769| | for high in indices 770| | { 771| | guard let set = lineData[high.dataSetIndex] as? LineChartDataSetProtocol, 772| | set.isHighlightEnabled 773| | else { continue } 774| | 775| | guard let e = set.entryForXValue(high.x, closestToY: high.y) else { continue } 776| | 777| | if !isInBoundsX(entry: e, dataSet: set) 778| | { 779| | continue 780| | } 781| | 782| | context.setStrokeColor(set.highlightColor.cgColor) 783| | context.setLineWidth(set.highlightLineWidth) 784| | if set.highlightLineDashLengths != nil 785| | { 786| | context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) 787| | } 788| | else 789| | { 790| | context.setLineDash(phase: 0.0, lengths: []) 791| | } 792| | 793| | let x = e.x // get the x-position 794| | let y = e.y * Double(animator.phaseY) 795| | 796| | if x > chartXMax * animator.phaseX 797| | { 798| | continue 799| | } 800| | 801| | let trans = dataProvider.getTransformer(forAxis: set.axisDependency) 802| | 803| | let pt = trans.pixelForValues(x: x, y: y) 804| | 805| | high.setDraw(pt: pt) 806| | 807| | // draw the lines 808| | drawHighlightLines(context: context, point: pt, set: set) 809| | } 810| | 811| | context.restoreGState() 812| | } 813| | 814| | func drawGradientLine(context: CGContext, dataSet: LineChartDataSetProtocol, spline: CGPath, matrix: CGAffineTransform) 815| | { 816| | guard let gradientPositions = dataSet.gradientPositions else 817| | { 818| | assertionFailure("Must set `gradientPositions if `dataSet.isDrawLineWithGradientEnabled` is true") 819| | return 820| | } 821| | 822| | // `insetBy` is applied since bounding box 823| | // doesn't take into account line width 824| | // so that peaks are trimmed since 825| | // gradient start and gradient end calculated wrong 826| | let boundingBox = spline.boundingBox 827| | .insetBy(dx: -dataSet.lineWidth / 2, dy: -dataSet.lineWidth / 2) 828| | 829| | guard !boundingBox.isNull, !boundingBox.isInfinite, !boundingBox.isEmpty else { 830| | return 831| | } 832| | 833| | let gradientStart = CGPoint(x: 0, y: boundingBox.minY) 834| | let gradientEnd = CGPoint(x: 0, y: boundingBox.maxY) 835| | let gradientColorComponents: [CGFloat] = dataSet.colors 836| | .reversed() 837| | .reduce(into: []) { (components, color) in 838| | guard let (r, g, b, a) = color.nsuirgba else { 839| | return 840| | } 841| | components += [r, g, b, a] 842| | } 843| | let gradientLocations: [CGFloat] = gradientPositions.reversed() 844| | .map { (position) in 845| | let location = CGPoint(x: boundingBox.minX, y: position) 846| | .applying(matrix) 847| | let normalizedLocation = (location.y - boundingBox.minY) 848| | / (boundingBox.maxY - boundingBox.minY) 849| | return normalizedLocation.clamped(to: 0...1) 850| | } 851| | 852| | let baseColorSpace = CGColorSpaceCreateDeviceRGB() 853| | guard let gradient = CGGradient( 854| | colorSpace: baseColorSpace, 855| | colorComponents: gradientColorComponents, 856| | locations: gradientLocations, 857| | count: gradientLocations.count) else { 858| | return 859| | } 860| | 861| | context.saveGState() 862| | defer { context.restoreGState() } 863| | 864| | context.beginPath() 865| | context.addPath(spline) 866| | context.replacePathWithStrokedPath() 867| | context.clip() 868| | context.drawLinearGradient(gradient, start: gradientStart, end: gradientEnd, options: []) 869| | } 870| | 871| | /// Creates a nested array of empty subarrays each of which will be populated with NSUIAccessibilityElements. 872| | /// This is marked internal to support HorizontalBarChartRenderer as well. 873| | private func accessibilityCreateEmptyOrderedElements() -> [[NSUIAccessibilityElement]] 874| | { 875| | guard let chart = dataProvider as? LineChartView else { return [] } 876| | 877| | let dataSetCount = chart.lineData?.dataSetCount ?? 0 878| | 879| | return Array(repeating: [NSUIAccessibilityElement](), 880| | count: dataSetCount) 881| | } 882| | 883| | /// Creates an NSUIAccessibleElement representing the smallest meaningful bar of the chart 884| | /// i.e. in case of a stacked chart, this returns each stack, not the combined bar. 885| | /// Note that it is marked internal to support subclass modification in the HorizontalBarChart. 886| | private func createAccessibleElement(withIndex idx: Int, 887| | container: LineChartView, 888| | dataSet: LineChartDataSetProtocol, 889| | dataSetIndex: Int, 890| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement 891| | { 892| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 893| | let xAxis = container.xAxis 894| | 895| | guard let e = dataSet.entryForIndex(idx) else { return element } 896| | guard let dataProvider = dataProvider else { return element } 897| | 898| | // NOTE: The formatter can cause issues when the x-axis labels are consecutive ints. 899| | // i.e. due to the Double conversion, if there are more than one data set that are grouped, 900| | // there is the possibility of some labels being rounded up. A floor() might fix this, but seems to be a brute force solution. 901| | let label = xAxis.valueFormatter?.stringForValue(e.x, axis: xAxis) ?? "\(e.x)" 902| | 903| | let elementValueText = dataSet.valueFormatter.stringForValue(e.y, 904| | entry: e, 905| | dataSetIndex: dataSetIndex, 906| | viewPortHandler: viewPortHandler) 907| | 908| | let dataSetCount = dataProvider.lineData?.dataSetCount ?? -1 909| | let doesContainMultipleDataSets = dataSetCount > 1 910| | 911| | element.accessibilityLabel = "\(doesContainMultipleDataSets ? (dataSet.label ?? "") + ", " : "") \(label): \(elementValueText)" 912| | 913| | modifier(element) 914| | 915| | return element 916| | } 917| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/PieChartRenderer.swift: 1| |// 2| |// PieChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |#if !os(OSX) 16| | import UIKit 17| |#endif 18| | 19| | 20| |open class PieChartRenderer: NSObject, DataRenderer 21| |{ 22| | public let viewPortHandler: ViewPortHandler 23| | 24| 6| public final var accessibleChartElements: [NSUIAccessibilityElement] = [] 25| | 26| | public let animator: Animator 27| | 28| | @objc open weak var chart: PieChartView? 29| | 30| | @objc public init(chart: PieChartView, animator: Animator, viewPortHandler: ViewPortHandler) 31| | { 32| | self.viewPortHandler = viewPortHandler 33| | self.animator = animator 34| | self.chart = chart 35| | 36| | super.init() 37| | } 38| | 39| | open func drawData(context: CGContext) 40| | { 41| | guard let chart = chart else { return } 42| | 43| | let pieData = chart.data 44| | 45| | if pieData != nil 46| | { 47| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 48| | accessibleChartElements.removeAll() 49| | 50| | for set in pieData!.dataSets as! [PieChartDataSetProtocol] 51| | where set.isVisible && set.entryCount > 0 52| | { 53| | drawDataSet(context: context, dataSet: set) 54| | } 55| | } 56| | } 57| | 58| | @objc open func calculateMinimumRadiusForSpacedSlice( 59| | center: CGPoint, 60| | radius: CGFloat, 61| | angle: CGFloat, 62| | arcStartPointX: CGFloat, 63| | arcStartPointY: CGFloat, 64| | startAngle: CGFloat, 65| | sweepAngle: CGFloat) -> CGFloat 66| | { 67| | let angleMiddle = startAngle + sweepAngle / 2.0 68| | 69| | // Other point of the arc 70| | let arcEndPointX = center.x + radius * cos((startAngle + sweepAngle).DEG2RAD) 71| | let arcEndPointY = center.y + radius * sin((startAngle + sweepAngle).DEG2RAD) 72| | 73| | // Middle point on the arc 74| | let arcMidPointX = center.x + radius * cos(angleMiddle.DEG2RAD) 75| | let arcMidPointY = center.y + radius * sin(angleMiddle.DEG2RAD) 76| | 77| | // This is the base of the contained triangle 78| | let basePointsDistance = sqrt( 79| | pow(arcEndPointX - arcStartPointX, 2) + 80| | pow(arcEndPointY - arcStartPointY, 2)) 81| | 82| | // After reducing space from both sides of the "slice", 83| | // the angle of the contained triangle should stay the same. 84| | // So let's find out the height of that triangle. 85| | let containedTriangleHeight = (basePointsDistance / 2.0 * 86| | tan((180.0 - angle).DEG2RAD / 2.0)) 87| | 88| | // Now we subtract that from the radius 89| | var spacedRadius = radius - containedTriangleHeight 90| | 91| | // And now subtract the height of the arc that's between the triangle and the outer circle 92| | spacedRadius -= sqrt( 93| | pow(arcMidPointX - (arcEndPointX + arcStartPointX) / 2.0, 2) + 94| | pow(arcMidPointY - (arcEndPointY + arcStartPointY) / 2.0, 2)) 95| | 96| | return spacedRadius 97| | } 98| | 99| | /// Calculates the sliceSpace to use based on visible values and their size compared to the set sliceSpace. 100| | @objc open func getSliceSpace(dataSet: PieChartDataSetProtocol) -> CGFloat 101| | { 102| | guard 103| | dataSet.automaticallyDisableSliceSpacing, 104| | let data = chart?.data as? PieChartData 105| | else { return dataSet.sliceSpace } 106| | 107| | let spaceSizeRatio = dataSet.sliceSpace / min(viewPortHandler.contentWidth, viewPortHandler.contentHeight) 108| | let minValueRatio = dataSet.yMin / data.yValueSum * 2.0 109| | 110| | let sliceSpace = spaceSizeRatio > CGFloat(minValueRatio) 111| | ? 0.0 112| | : dataSet.sliceSpace 113| | 114| | return sliceSpace 115| | } 116| | 117| | @objc open func drawDataSet(context: CGContext, dataSet: PieChartDataSetProtocol) 118| | { 119| | guard let chart = chart else {return } 120| | 121| | var angle: CGFloat = 0.0 122| | let rotationAngle = chart.rotationAngle 123| | 124| | let phaseX = animator.phaseX 125| | let phaseY = animator.phaseY 126| | 127| | let entryCount = dataSet.entryCount 128| | let drawAngles = chart.drawAngles 129| | let center = chart.centerCircleBox 130| | let radius = chart.radius 131| | let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled 132| | let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 133| | 134| | var visibleAngleCount = 0 135| | for j in 0 ..< entryCount 136| | { 137| | guard let e = dataSet.entryForIndex(j) else { continue } 138| | if ((abs(e.y) > Double.ulpOfOne)) 139| | { 140| | visibleAngleCount += 1 141| | } 142| | } 143| | 144| | let sliceSpace = visibleAngleCount <= 1 ? 0.0 : getSliceSpace(dataSet: dataSet) 145| | 146| | context.saveGState() 147| | 148| | // Make the chart header the first element in the accessible elements array 149| | // We can do this in drawDataSet, since we know PieChartView can have only 1 dataSet 150| | // Also since there's only 1 dataset, we don't use the typical createAccessibleHeader() here. 151| | // NOTE: - Since we want to summarize the total count of slices/portions/elements, use a default string here 152| | // This is unlike when we are naming individual slices, wherein it's alright to not use a prefix as descriptor. 153| | // i.e. We want to VO to say "3 Elements" even if the developer didn't specify an accessibility prefix 154| | // If prefix is unspecified it is safe to assume they did not want to use "Element 1", so that uses a default empty string 155| | let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Element" 156| | let description = chart.chartDescription.text ?? dataSet.label ?? chart.centerText ?? "Pie Chart" 157| | 158| | let 159| | element = NSUIAccessibilityElement(accessibilityContainer: chart) 160| | element.accessibilityLabel = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s"))" 161| | element.accessibilityFrame = chart.bounds 162| | element.isHeader = true 163| | accessibleChartElements.append(element) 164| | 165| | for j in 0 ..< entryCount 166| | { 167| | let sliceAngle = drawAngles[j] 168| | var innerRadius = userInnerRadius 169| | 170| | guard let e = dataSet.entryForIndex(j) else { continue } 171| | 172| | defer 173| | { 174| | // From here on, even when skipping (i.e for highlight), 175| | // increase the angle 176| | angle += sliceAngle * CGFloat(phaseX) 177| | } 178| | 179| | // draw only if the value is greater than zero 180| | if abs(e.y) < Double.ulpOfOne { continue } 181| | 182| | // Skip if highlighted 183| | if dataSet.isHighlightEnabled && chart.needsHighlight(index: j) 184| | { 185| | continue 186| | } 187| | 188| | let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 189| | 190| | context.setFillColor(dataSet.color(atIndex: j).cgColor) 191| | 192| | let sliceSpaceAngleOuter = visibleAngleCount == 1 ? 193| | 0.0 : 194| | sliceSpace / radius.DEG2RAD 195| | let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) 196| | var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) 197| | if sweepAngleOuter < 0.0 198| | { 199| | sweepAngleOuter = 0.0 200| | } 201| | 202| | let arcStartPointX = center.x + radius * cos(startAngleOuter.DEG2RAD) 203| | let arcStartPointY = center.y + radius * sin(startAngleOuter.DEG2RAD) 204| | 205| | let path = CGMutablePath() 206| | 207| | path.move(to: CGPoint(x: arcStartPointX, 208| | y: arcStartPointY)) 209| | 210| | path.addRelativeArc(center: center, radius: radius, startAngle: startAngleOuter.DEG2RAD, delta: sweepAngleOuter.DEG2RAD) 211| | 212| | if drawInnerArc && 213| | (innerRadius > 0.0 || accountForSliceSpacing) 214| | { 215| | if accountForSliceSpacing 216| | { 217| | var minSpacedRadius = calculateMinimumRadiusForSpacedSlice( 218| | center: center, 219| | radius: radius, 220| | angle: sliceAngle * CGFloat(phaseY), 221| | arcStartPointX: arcStartPointX, 222| | arcStartPointY: arcStartPointY, 223| | startAngle: startAngleOuter, 224| | sweepAngle: sweepAngleOuter) 225| | if minSpacedRadius < 0.0 226| | { 227| | minSpacedRadius = -minSpacedRadius 228| | } 229| | innerRadius = min(max(innerRadius, minSpacedRadius), radius) 230| | } 231| | 232| | let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? 233| | 0.0 : 234| | sliceSpace / innerRadius.DEG2RAD 235| | let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) 236| | var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) 237| | if sweepAngleInner < 0.0 238| | { 239| | sweepAngleInner = 0.0 240| | } 241| | let endAngleInner = startAngleInner + sweepAngleInner 242| | 243| | path.addLine( 244| | to: CGPoint( 245| | x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), 246| | y: center.y + innerRadius * sin(endAngleInner.DEG2RAD))) 247| | 248| | path.addRelativeArc(center: center, radius: innerRadius, startAngle: endAngleInner.DEG2RAD, delta: -sweepAngleInner.DEG2RAD) 249| | } 250| | else 251| | { 252| | if accountForSliceSpacing 253| | { 254| | let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 255| | 256| | let sliceSpaceOffset = 257| | calculateMinimumRadiusForSpacedSlice( 258| | center: center, 259| | radius: radius, 260| | angle: sliceAngle * CGFloat(phaseY), 261| | arcStartPointX: arcStartPointX, 262| | arcStartPointY: arcStartPointY, 263| | startAngle: startAngleOuter, 264| | sweepAngle: sweepAngleOuter) 265| | 266| | let arcEndPointX = center.x + sliceSpaceOffset * cos(angleMiddle.DEG2RAD) 267| | let arcEndPointY = center.y + sliceSpaceOffset * sin(angleMiddle.DEG2RAD) 268| | 269| | path.addLine( 270| | to: CGPoint( 271| | x: arcEndPointX, 272| | y: arcEndPointY)) 273| | } 274| | else 275| | { 276| | path.addLine(to: center) 277| | } 278| | } 279| | 280| | path.closeSubpath() 281| | 282| | context.beginPath() 283| | context.addPath(path) 284| | context.fillPath(using: .evenOdd) 285| | 286| | let axElement = createAccessibleElement(withIndex: j, 287| | container: chart, 288| | dataSet: dataSet) 289| | { (element) in 290| | element.accessibilityFrame = path.boundingBoxOfPath 291| | } 292| | 293| | accessibleChartElements.append(axElement) 294| | } 295| | 296| | // Post this notification to let VoiceOver account for the redrawn frames 297| | accessibilityPostLayoutChangedNotification() 298| | 299| | context.restoreGState() 300| | } 301| | 302| | open func drawValues(context: CGContext) 303| | { 304| | guard 305| | let chart = chart, 306| | let data = chart.data 307| | else { return } 308| | 309| | let center = chart.centerCircleBox 310| | 311| | // get whole the radius 312| | let radius = chart.radius 313| | let rotationAngle = chart.rotationAngle 314| | let drawAngles = chart.drawAngles 315| | let absoluteAngles = chart.absoluteAngles 316| | 317| | let phaseX = animator.phaseX 318| | let phaseY = animator.phaseY 319| | 320| | var labelRadiusOffset = radius / 10.0 * 3.0 321| | 322| | if chart.drawHoleEnabled 323| | { 324| | labelRadiusOffset = (radius - (radius * chart.holeRadiusPercent)) / 2.0 325| | } 326| | 327| | let labelRadius = radius - labelRadiusOffset 328| | 329| | let dataSets = data.dataSets 330| | 331| | let yValueSum = (data as! PieChartData).yValueSum 332| | 333| | let drawEntryLabels = chart.isDrawEntryLabelsEnabled 334| | let usePercentValuesEnabled = chart.usePercentValuesEnabled 335| | let sliceTextDrawingThreshold = chart.sliceTextDrawingThreshold 336| | 337| | var angle: CGFloat = 0.0 338| | var xIndex = 0 339| | 340| | context.saveGState() 341| | defer { context.restoreGState() } 342| | 343| | for i in dataSets.indices 344| | { 345| | guard let dataSet = dataSets[i] as? PieChartDataSetProtocol else { continue } 346| | 347| | let drawValues = dataSet.isDrawValuesEnabled 348| | 349| | if !drawValues && !drawEntryLabels && !dataSet.isDrawIconsEnabled 350| | { 351| | continue 352| | } 353| | 354| | let iconsOffset = dataSet.iconsOffset 355| | 356| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 357| | 358| | let xValuePosition = dataSet.xValuePosition 359| | let yValuePosition = dataSet.yValuePosition 360| | 361| | let valueFont = dataSet.valueFont 362| | let entryLabelFont = dataSet.entryLabelFont 363| | let lineHeight = valueFont.lineHeight 364| | 365| | let formatter = dataSet.valueFormatter 366| | 367| | for j in 0 ..< dataSet.entryCount 368| | { 369| | guard let e = dataSet.entryForIndex(j) else { continue } 370| | let pe = e as? PieChartDataEntry 371| | 372| | if xIndex == 0 373| | { 374| | angle = 0.0 375| | } 376| | else 377| | { 378| | angle = absoluteAngles[xIndex - 1] * CGFloat(phaseX) 379| | } 380| | 381| | let sliceAngle = drawAngles[xIndex] 382| | let sliceSpace = getSliceSpace(dataSet: dataSet) 383| | let sliceSpaceMiddleAngle = sliceSpace / labelRadius.DEG2RAD 384| | 385| | // offset needed to center the drawn text in the slice 386| | let angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2.0) / 2.0 387| | 388| | angle = angle + angleOffset 389| | 390| | let transformedAngle = rotationAngle + angle * CGFloat(phaseY) 391| | 392| | let value = usePercentValuesEnabled ? e.y / yValueSum * 100.0 : e.y 393| | let valueText = formatter.stringForValue( 394| | value, 395| | entry: e, 396| | dataSetIndex: i, 397| | viewPortHandler: viewPortHandler) 398| | 399| | let sliceXBase = cos(transformedAngle.DEG2RAD) 400| | let sliceYBase = sin(transformedAngle.DEG2RAD) 401| | 402| | let drawXOutside = sliceAngle > sliceTextDrawingThreshold && drawEntryLabels && xValuePosition == .outsideSlice 403| | let drawYOutside = sliceAngle > sliceTextDrawingThreshold && drawValues && yValuePosition == .outsideSlice 404| | let drawXInside = sliceAngle > sliceTextDrawingThreshold && drawEntryLabels && xValuePosition == .insideSlice 405| | let drawYInside = sliceAngle > sliceTextDrawingThreshold && drawValues && yValuePosition == .insideSlice 406| | 407| | let valueTextColor = dataSet.valueTextColorAt(j) 408| | let entryLabelColor = dataSet.entryLabelColor 409| | 410| | if drawXOutside || drawYOutside 411| | { 412| | let valueLineLength1 = dataSet.valueLinePart1Length 413| | let valueLineLength2 = dataSet.valueLinePart2Length 414| | let valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage 415| | 416| | var pt2: CGPoint 417| | var labelPoint: CGPoint 418| | var align: NSTextAlignment 419| | 420| | var line1Radius: CGFloat 421| | 422| | if chart.drawHoleEnabled 423| | { 424| | line1Radius = (radius - (radius * chart.holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius * chart.holeRadiusPercent) 425| | } 426| | else 427| | { 428| | line1Radius = radius * valueLinePart1OffsetPercentage 429| | } 430| | 431| | let polyline2Length = dataSet.valueLineVariableLength 432| | ? labelRadius * valueLineLength2 * abs(sin(transformedAngle.DEG2RAD)) 433| | : labelRadius * valueLineLength2 434| | 435| | let pt0 = CGPoint( 436| | x: line1Radius * sliceXBase + center.x, 437| | y: line1Radius * sliceYBase + center.y) 438| | 439| | let pt1 = CGPoint( 440| | x: labelRadius * (1 + valueLineLength1) * sliceXBase + center.x, 441| | y: labelRadius * (1 + valueLineLength1) * sliceYBase + center.y) 442| | 443| | if transformedAngle.truncatingRemainder(dividingBy: 360.0) >= 90.0 && transformedAngle.truncatingRemainder(dividingBy: 360.0) <= 270.0 444| | { 445| | pt2 = CGPoint(x: pt1.x - polyline2Length, y: pt1.y) 446| | align = .right 447| | labelPoint = CGPoint(x: pt2.x - 5, y: pt2.y - lineHeight) 448| | } 449| | else 450| | { 451| | pt2 = CGPoint(x: pt1.x + polyline2Length, y: pt1.y) 452| | align = .left 453| | labelPoint = CGPoint(x: pt2.x + 5, y: pt2.y - lineHeight) 454| | } 455| | 456| | DrawLine: do 457| | { 458| | if dataSet.useValueColorForLine 459| | { 460| | context.setStrokeColor(dataSet.color(atIndex: j).cgColor) 461| | } 462| | else if let valueLineColor = dataSet.valueLineColor 463| | { 464| | context.setStrokeColor(valueLineColor.cgColor) 465| | } 466| | else 467| | { 468| | return 469| | } 470| | context.setLineWidth(dataSet.valueLineWidth) 471| | 472| | context.move(to: CGPoint(x: pt0.x, y: pt0.y)) 473| | context.addLine(to: CGPoint(x: pt1.x, y: pt1.y)) 474| | context.addLine(to: CGPoint(x: pt2.x, y: pt2.y)) 475| | 476| | context.drawPath(using: CGPathDrawingMode.stroke) 477| | } 478| | 479| | if drawXOutside && drawYOutside 480| | { 481| | context.drawText(valueText, 482| | at: labelPoint, 483| | align: align, 484| | angleRadians: angleRadians, 485| | attributes: [.font: valueFont, 486| | .foregroundColor: valueTextColor]) 487| | 488| | if j < data.entryCount && pe?.label != nil 489| | { 490| | context.drawText(pe!.label!, 491| | at: CGPoint(x: labelPoint.x, 492| | y: labelPoint.y + lineHeight), 493| | align: align, 494| | angleRadians: angleRadians, 495| | attributes: [.font: entryLabelFont ?? valueFont, 496| | .foregroundColor: entryLabelColor ?? valueTextColor]) 497| | } 498| | } 499| | else if drawXOutside 500| | { 501| | if j < data.entryCount && pe?.label != nil 502| | { 503| | context.drawText(pe!.label!, 504| | at: CGPoint(x: labelPoint.x, 505| | y: labelPoint.y + lineHeight / 2.0), 506| | align: align, 507| | angleRadians: angleRadians, 508| | attributes: [.font: entryLabelFont ?? valueFont, 509| | .foregroundColor: entryLabelColor ?? valueTextColor]) 510| | } 511| | } 512| | else if drawYOutside 513| | { 514| | context.drawText(valueText, 515| | at: CGPoint(x: labelPoint.x, 516| | y: labelPoint.y + lineHeight / 2.0), 517| | align: align, 518| | angleRadians: angleRadians, 519| | attributes: [.font: valueFont, 520| | .foregroundColor: valueTextColor]) 521| | } 522| | } 523| | 524| | if drawXInside || drawYInside 525| | { 526| | // calculate the text position 527| | let x = labelRadius * sliceXBase + center.x 528| | let y = labelRadius * sliceYBase + center.y - lineHeight 529| | 530| | if drawXInside && drawYInside 531| | { 532| | context.drawText(valueText, 533| | at: CGPoint(x: x, y: y), 534| | align: .center, 535| | angleRadians: angleRadians, 536| | attributes: [.font: valueFont, .foregroundColor: valueTextColor]) 537| | 538| | if j < data.entryCount && pe?.label != nil 539| | { 540| | context.drawText(pe!.label!, 541| | at: CGPoint(x: x, y: y + lineHeight), 542| | align: .center, 543| | angleRadians: angleRadians, 544| | attributes: [.font: entryLabelFont ?? valueFont, 545| | .foregroundColor: entryLabelColor ?? valueTextColor]) 546| | } 547| | } 548| | else if drawXInside 549| | { 550| | if j < data.entryCount && pe?.label != nil 551| | { 552| | context.drawText(pe!.label!, 553| | at: CGPoint(x: x, y: y + lineHeight / 2.0), 554| | align: .center, 555| | angleRadians: angleRadians, 556| | attributes: [.font: entryLabelFont ?? valueFont, 557| | .foregroundColor: entryLabelColor ?? valueTextColor]) 558| | } 559| | } 560| | else if drawYInside 561| | { 562| | context.drawText(valueText, 563| | at: CGPoint(x: x, y: y + lineHeight / 2.0), 564| | align: .center, 565| | angleRadians: angleRadians, 566| | attributes: [.font: valueFont, .foregroundColor: valueTextColor]) 567| | } 568| | } 569| | 570| | if let icon = e.icon, dataSet.isDrawIconsEnabled 571| | { 572| | // calculate the icon's position 573| | 574| | let x = (labelRadius + iconsOffset.y) * sliceXBase + center.x 575| | var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y 576| | y += iconsOffset.x 577| | 578| | context.drawImage(icon, 579| | atCenter: CGPoint(x: x, y: y), 580| | size: icon.size) 581| | } 582| | 583| | xIndex += 1 584| | } 585| | } 586| | } 587| | 588| | open func drawExtras(context: CGContext) 589| | { 590| | drawHole(context: context) 591| | drawCenterText(context: context) 592| | } 593| | 594| | open func initBuffers() { } 595| | 596| | open func isDrawingValuesAllowed(dataProvider: ChartDataProvider?) -> Bool 597| | { 598| | guard let data = dataProvider?.data else { return false } 599| | return data.entryCount < Int(CGFloat(dataProvider?.maxVisibleCount ?? 0) * viewPortHandler.scaleX) 600| | } 601| | 602| | /// draws the hole in the center of the chart and the transparent circle / hole 603| | private func drawHole(context: CGContext) 604| | { 605| | guard let chart = chart else { return } 606| | 607| | if chart.drawHoleEnabled 608| | { 609| | context.saveGState() 610| | 611| | let radius = chart.radius 612| | let holeRadius = radius * chart.holeRadiusPercent 613| | let center = chart.centerCircleBox 614| | 615| | if let holeColor = chart.holeColor 616| | { 617| | if holeColor != NSUIColor.clear 618| | { 619| | // draw the hole-circle 620| | context.setFillColor(chart.holeColor!.cgColor) 621| | context.fillEllipse(in: CGRect(x: center.x - holeRadius, y: center.y - holeRadius, width: holeRadius * 2.0, height: holeRadius * 2.0)) 622| | } 623| | } 624| | 625| | // only draw the circle if it can be seen (not covered by the hole) 626| | if let transparentCircleColor = chart.transparentCircleColor 627| | { 628| | if transparentCircleColor != NSUIColor.clear && 629| | chart.transparentCircleRadiusPercent > chart.holeRadiusPercent 630| | { 631| | let alpha = animator.phaseX * animator.phaseY 632| | let secondHoleRadius = radius * chart.transparentCircleRadiusPercent 633| | 634| | // make transparent 635| | context.setAlpha(CGFloat(alpha)) 636| | context.setFillColor(transparentCircleColor.cgColor) 637| | 638| | // draw the transparent-circle 639| | context.beginPath() 640| | context.addEllipse(in: CGRect( 641| | x: center.x - secondHoleRadius, 642| | y: center.y - secondHoleRadius, 643| | width: secondHoleRadius * 2.0, 644| | height: secondHoleRadius * 2.0)) 645| | context.addEllipse(in: CGRect( 646| | x: center.x - holeRadius, 647| | y: center.y - holeRadius, 648| | width: holeRadius * 2.0, 649| | height: holeRadius * 2.0)) 650| | context.fillPath(using: .evenOdd) 651| | } 652| | } 653| | 654| | context.restoreGState() 655| | } 656| | } 657| | 658| | /// draws the description text in the center of the pie chart makes most sense when center-hole is enabled 659| | private func drawCenterText(context: CGContext) 660| | { 661| | guard 662| | let chart = chart, 663| | let centerAttributedText = chart.centerAttributedText 664| | else { return } 665| | 666| | if chart.drawCenterTextEnabled && centerAttributedText.length > 0 667| | { 668| | let center = chart.centerCircleBox 669| | let offset = chart.centerTextOffset 670| | let innerRadius = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled ? chart.radius * chart.holeRadiusPercent : chart.radius 671| | 672| | let x = center.x + offset.x 673| | let y = center.y + offset.y 674| | 675| | let holeRect = CGRect( 676| | x: x - innerRadius, 677| | y: y - innerRadius, 678| | width: innerRadius * 2.0, 679| | height: innerRadius * 2.0) 680| | var boundingRect = holeRect 681| | 682| | if chart.centerTextRadiusPercent > 0.0 683| | { 684| | boundingRect = boundingRect.insetBy(dx: (boundingRect.width - boundingRect.width * chart.centerTextRadiusPercent) / 2.0, dy: (boundingRect.height - boundingRect.height * chart.centerTextRadiusPercent) / 2.0) 685| | } 686| | 687| | let textBounds = centerAttributedText.boundingRect(with: boundingRect.size, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) 688| | 689| | var drawingRect = boundingRect 690| | drawingRect.origin.x += (boundingRect.size.width - textBounds.size.width) / 2.0 691| | drawingRect.origin.y += (boundingRect.size.height - textBounds.size.height) / 2.0 692| | drawingRect.size = textBounds.size 693| | 694| | context.saveGState() 695| | 696| | let clippingPath = CGPath(ellipseIn: holeRect, transform: nil) 697| | context.beginPath() 698| | context.addPath(clippingPath) 699| | context.clip() 700| | 701| | centerAttributedText.draw(with: drawingRect, options: [.usesLineFragmentOrigin, .usesFontLeading, .truncatesLastVisibleLine], context: nil) 702| | 703| | context.restoreGState() 704| | } 705| | } 706| | 707| | open func drawHighlighted(context: CGContext, indices highlights: [Highlight]) 708| | { 709| | guard 710| | let chart = chart, 711| | let data = chart.data 712| | else { return } 713| | 714| | context.saveGState() 715| | 716| | let phaseX = animator.phaseX 717| | let phaseY = animator.phaseY 718| | 719| | var angle: CGFloat = 0.0 720| | let rotationAngle = chart.rotationAngle 721| | 722| | let drawAngles = chart.drawAngles 723| | let absoluteAngles = chart.absoluteAngles 724| | let center = chart.centerCircleBox 725| | let radius = chart.radius 726| | let drawInnerArc = chart.drawHoleEnabled && !chart.drawSlicesUnderHoleEnabled 727| | let userInnerRadius = drawInnerArc ? radius * chart.holeRadiusPercent : 0.0 728| | 729| | // Append highlighted accessibility slices into this array, so we can prioritize them over unselected slices 730| | var highlightedAccessibleElements: [NSUIAccessibilityElement] = [] 731| | 732| | for hightlight in highlights 733| | { 734| | // get the index to highlight 735| | let index = Int(hightlight.x) 736| | guard index < drawAngles.count, 737| | let set = data[hightlight.dataSetIndex] as? PieChartDataSetProtocol, 738| | set.isHighlightEnabled 739| | else 740| | { 741| | continue 742| | } 743| | 744| | let entryCount = set.entryCount 745| | var visibleAngleCount = 0 746| | for j in 0 ..< entryCount 747| | { 748| | guard let e = set.entryForIndex(j) else { continue } 749| | if ((abs(e.y) > Double.ulpOfOne)) 750| | { 751| | visibleAngleCount += 1 752| | } 753| | } 754| | 755| | if index == 0 756| | { 757| | angle = 0.0 758| | } 759| | else 760| | { 761| | angle = absoluteAngles[index - 1] * CGFloat(phaseX) 762| | } 763| | 764| | let sliceSpace = visibleAngleCount <= 1 ? 0.0 : set.sliceSpace 765| | 766| | let sliceAngle = drawAngles[index] 767| | var innerRadius = userInnerRadius 768| | 769| | let shift = set.selectionShift 770| | let highlightedRadius = radius + shift 771| | 772| | let accountForSliceSpacing = sliceSpace > 0.0 && sliceAngle <= 180.0 773| | 774| | context.setFillColor(set.highlightColor?.cgColor ?? set.color(atIndex: index).cgColor) 775| | 776| | let sliceSpaceAngleOuter = visibleAngleCount == 1 ? 777| | 0.0 : 778| | sliceSpace / radius.DEG2RAD 779| | 780| | let sliceSpaceAngleShifted = visibleAngleCount == 1 ? 781| | 0.0 : 782| | sliceSpace / highlightedRadius.DEG2RAD 783| | 784| | let startAngleOuter = rotationAngle + (angle + sliceSpaceAngleOuter / 2.0) * CGFloat(phaseY) 785| | var sweepAngleOuter = (sliceAngle - sliceSpaceAngleOuter) * CGFloat(phaseY) 786| | if sweepAngleOuter < 0.0 787| | { 788| | sweepAngleOuter = 0.0 789| | } 790| | 791| | let startAngleShifted = rotationAngle + (angle + sliceSpaceAngleShifted / 2.0) * CGFloat(phaseY) 792| | var sweepAngleShifted = (sliceAngle - sliceSpaceAngleShifted) * CGFloat(phaseY) 793| | if sweepAngleShifted < 0.0 794| | { 795| | sweepAngleShifted = 0.0 796| | } 797| | 798| | let path = CGMutablePath() 799| | 800| | path.move(to: CGPoint(x: center.x + highlightedRadius * cos(startAngleShifted.DEG2RAD), 801| | y: center.y + highlightedRadius * sin(startAngleShifted.DEG2RAD))) 802| | 803| | path.addRelativeArc(center: center, radius: highlightedRadius, startAngle: startAngleShifted.DEG2RAD, 804| | delta: sweepAngleShifted.DEG2RAD) 805| | 806| | var sliceSpaceRadius: CGFloat = 0.0 807| | if accountForSliceSpacing 808| | { 809| | sliceSpaceRadius = calculateMinimumRadiusForSpacedSlice( 810| | center: center, 811| | radius: radius, 812| | angle: sliceAngle * CGFloat(phaseY), 813| | arcStartPointX: center.x + radius * cos(startAngleOuter.DEG2RAD), 814| | arcStartPointY: center.y + radius * sin(startAngleOuter.DEG2RAD), 815| | startAngle: startAngleOuter, 816| | sweepAngle: sweepAngleOuter) 817| | } 818| | 819| | if drawInnerArc && 820| | (innerRadius > 0.0 || accountForSliceSpacing) 821| | { 822| | if accountForSliceSpacing 823| | { 824| | var minSpacedRadius = sliceSpaceRadius 825| | if minSpacedRadius < 0.0 826| | { 827| | minSpacedRadius = -minSpacedRadius 828| | } 829| | innerRadius = min(max(innerRadius, minSpacedRadius), radius) 830| | } 831| | 832| | let sliceSpaceAngleInner = visibleAngleCount == 1 || innerRadius == 0.0 ? 833| | 0.0 : 834| | sliceSpace / innerRadius.DEG2RAD 835| | let startAngleInner = rotationAngle + (angle + sliceSpaceAngleInner / 2.0) * CGFloat(phaseY) 836| | var sweepAngleInner = (sliceAngle - sliceSpaceAngleInner) * CGFloat(phaseY) 837| | if sweepAngleInner < 0.0 838| | { 839| | sweepAngleInner = 0.0 840| | } 841| | let endAngleInner = startAngleInner + sweepAngleInner 842| | 843| | path.addLine( 844| | to: CGPoint( 845| | x: center.x + innerRadius * cos(endAngleInner.DEG2RAD), 846| | y: center.y + innerRadius * sin(endAngleInner.DEG2RAD))) 847| | 848| | path.addRelativeArc(center: center, radius: innerRadius, 849| | startAngle: endAngleInner.DEG2RAD, 850| | delta: -sweepAngleInner.DEG2RAD) 851| | } 852| | else 853| | { 854| | if accountForSliceSpacing 855| | { 856| | let angleMiddle = startAngleOuter + sweepAngleOuter / 2.0 857| | 858| | let arcEndPointX = center.x + sliceSpaceRadius * cos(angleMiddle.DEG2RAD) 859| | let arcEndPointY = center.y + sliceSpaceRadius * sin(angleMiddle.DEG2RAD) 860| | 861| | path.addLine( 862| | to: CGPoint( 863| | x: arcEndPointX, 864| | y: arcEndPointY)) 865| | } 866| | else 867| | { 868| | path.addLine(to: center) 869| | } 870| | } 871| | 872| | path.closeSubpath() 873| | 874| | context.beginPath() 875| | context.addPath(path) 876| | context.fillPath(using: .evenOdd) 877| | 878| | let axElement = createAccessibleElement(withIndex: index, 879| | container: chart, 880| | dataSet: set) 881| | { (element) in 882| | element.accessibilityFrame = path.boundingBoxOfPath 883| | element.isSelected = true 884| | } 885| | 886| | highlightedAccessibleElements.append(axElement) 887| | } 888| | 889| | // Prepend selected slices before the already rendered unselected ones. 890| | // NOTE: - This relies on drawDataSet() being called before drawHighlighted in PieChartView. 891| | if !accessibleChartElements.isEmpty { 892| | accessibleChartElements.insert(contentsOf: highlightedAccessibleElements, at: 1) 893| | } 894| | 895| | context.restoreGState() 896| | } 897| | 898| | /// Creates an NSUIAccessibilityElement representing a slice of the PieChart. 899| | /// The element only has it's container and label set based on the chart and dataSet. Use the modifier to alter traits and frame. 900| | private func createAccessibleElement(withIndex idx: Int, 901| | container: PieChartView, 902| | dataSet: PieChartDataSetProtocol, 903| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { 904| | 905| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 906| | 907| | guard let e = dataSet.entryForIndex(idx) else { return element } 908| | guard let data = container.data as? PieChartData else { return element } 909| | 910| | let formatter = dataSet.valueFormatter 911| | 912| | var elementValueText = formatter.stringForValue( 913| | e.y, 914| | entry: e, 915| | dataSetIndex: idx, 916| | viewPortHandler: viewPortHandler) 917| | 918| | if container.usePercentValuesEnabled { 919| | let value = e.y / data.yValueSum * 100.0 920| | let valueText = formatter.stringForValue( 921| | value, 922| | entry: e, 923| | dataSetIndex: idx, 924| | viewPortHandler: viewPortHandler) 925| | 926| | elementValueText = valueText 927| | } 928| | 929| | let pieChartDataEntry = (dataSet.entryForIndex(idx) as? PieChartDataEntry) 930| | let isCount = data.accessibilityEntryLabelSuffixIsCount 931| | let prefix = data.accessibilityEntryLabelPrefix?.appending("\(idx + 1)") ?? pieChartDataEntry?.label ?? "" 932| | let suffix = data.accessibilityEntryLabelSuffix ?? "" 933| | element.accessibilityLabel = "\(prefix) : \(elementValueText) \(suffix + (isCount ? (e.y == 1.0 ? "" : "s") : "") )" 934| | 935| | // The modifier allows changing of traits and frame depending on highlight, rotation, etc 936| | modifier(element) 937| | 938| | return element 939| | } 940| | 941| | public func createAccessibleHeader(usingChart chart: ChartViewBase, andData data: ChartData, withDefaultDescription defaultDescription: String) -> NSUIAccessibilityElement { 942| | return AccessibleHeader.create(usingChart: chart, andData: data, withDefaultDescription: defaultDescription) 943| | } 944| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/RadarChartRenderer.swift: 1| |// 2| |// RadarChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class RadarChartRenderer: LineRadarRenderer 16| |{ 17| | private lazy var accessibilityXLabels: [String] = { 18| | guard let chart = chart else { return [] } 19| | guard let formatter = chart.xAxis.valueFormatter else { return [] } 20| | 21| | let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 22| | return stride(from: 0, to: maxEntryCount, by: 1).map { 23| | formatter.stringForValue(Double($0), axis: chart.xAxis) 24| | } 25| | }() 26| | 27| | @objc open weak var chart: RadarChartView? 28| | 29| | @objc public init(chart: RadarChartView, animator: Animator, viewPortHandler: ViewPortHandler) 30| | { 31| | super.init(animator: animator, viewPortHandler: viewPortHandler) 32| | 33| | self.chart = chart 34| | } 35| | 36| | open override func drawData(context: CGContext) 37| | { 38| | guard let chart = chart else { return } 39| | 40| | let radarData = chart.data 41| | 42| | if radarData != nil 43| | { 44| | let mostEntries = radarData?.maxEntryCountSet?.entryCount ?? 0 45| | 46| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 47| | self.accessibleChartElements.removeAll() 48| | 49| | // Make the chart header the first element in the accessible elements array 50| | if let accessibilityHeaderData = radarData as? RadarChartData { 51| | let element = createAccessibleHeader(usingChart: chart, 52| | andData: accessibilityHeaderData, 53| | withDefaultDescription: "Radar Chart") 54| | self.accessibleChartElements.append(element) 55| | } 56| | 57| | for set in radarData!.dataSets as! [RadarChartDataSetProtocol] where set.isVisible 58| | { 59| | drawDataSet(context: context, dataSet: set, mostEntries: mostEntries) 60| | } 61| | } 62| | } 63| | 64| | /// Draws the RadarDataSet 65| | /// 66| | /// - Parameters: 67| | /// - context: 68| | /// - dataSet: 69| | /// - mostEntries: the entry count of the dataset with the most entries 70| | internal func drawDataSet(context: CGContext, dataSet: RadarChartDataSetProtocol, mostEntries: Int) 71| | { 72| | guard let chart = chart else { return } 73| | 74| | context.saveGState() 75| | 76| | let phaseX = animator.phaseX 77| | let phaseY = animator.phaseY 78| | 79| | let sliceangle = chart.sliceAngle 80| | 81| | // calculate the factor that is needed for transforming the value to pixels 82| | let factor = chart.factor 83| | 84| | let center = chart.centerOffsets 85| | let entryCount = dataSet.entryCount 86| | let path = CGMutablePath() 87| | var hasMovedToPoint = false 88| | 89| | let prefix: String = chart.data?.accessibilityEntryLabelPrefix ?? "Item" 90| | let description = dataSet.label ?? "" 91| | 92| | // Make a tuple of (xLabels, value, originalIndex) then sort it 93| | // This is done, so that the labels are narrated in decreasing order of their corresponding value 94| | // Otherwise, there is no non-visual logic to the data presented 95| | let accessibilityEntryValues = Array(0 ..< entryCount).map { (dataSet.entryForIndex($0)?.y ?? 0, $0) } 96| | let accessibilityAxisLabelValueTuples = zip(accessibilityXLabels, accessibilityEntryValues).map { ($0, $1.0, $1.1) }.sorted { $0.1 > $1.1 } 97| | let accessibilityDataSetDescription: String = description + ". \(entryCount) \(prefix + (entryCount == 1 ? "" : "s")). " 98| | let accessibilityFrameWidth: CGFloat = 22.0 // To allow a tap target of 44x44 99| | 100| | var accessibilityEntryElements: [NSUIAccessibilityElement] = [] 101| | 102| | for j in 0 ..< entryCount 103| | { 104| | guard let e = dataSet.entryForIndex(j) else { continue } 105| | 106| | let p = center.moving(distance: CGFloat((e.y - chart.chartYMin) * Double(factor) * phaseY), 107| | atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) 108| | 109| | if p.x.isNaN 110| | { 111| | continue 112| | } 113| | 114| | if !hasMovedToPoint 115| | { 116| | path.move(to: p) 117| | hasMovedToPoint = true 118| | } 119| | else 120| | { 121| | path.addLine(to: p) 122| | } 123| | 124| | let accessibilityLabel = accessibilityAxisLabelValueTuples[j].0 125| | let accessibilityValue = accessibilityAxisLabelValueTuples[j].1 126| | let accessibilityValueIndex = accessibilityAxisLabelValueTuples[j].2 127| | 128| | let axp = center.moving(distance: CGFloat((accessibilityValue - chart.chartYMin) * Double(factor) * phaseY), 129| | atAngle: sliceangle * CGFloat(accessibilityValueIndex) * CGFloat(phaseX) + chart.rotationAngle) 130| | 131| | let axDescription = description + " - " + accessibilityLabel + ": \(accessibilityValue) \(chart.data?.accessibilityEntryLabelSuffix ?? "")" 132| | let axElement = createAccessibleElement(withDescription: axDescription, 133| | container: chart, 134| | dataSet: dataSet) 135| | { (element) in 136| | element.accessibilityFrame = CGRect(x: axp.x - accessibilityFrameWidth, 137| | y: axp.y - accessibilityFrameWidth, 138| | width: 2 * accessibilityFrameWidth, 139| | height: 2 * accessibilityFrameWidth) 140| | } 141| | 142| | accessibilityEntryElements.append(axElement) 143| | } 144| | 145| | // if this is the largest set, close it 146| | if dataSet.entryCount < mostEntries 147| | { 148| | // if this is not the largest set, draw a line to the center before closing 149| | path.addLine(to: center) 150| | } 151| | 152| | path.closeSubpath() 153| | 154| | // draw filled 155| | if dataSet.isDrawFilledEnabled 156| | { 157| | if dataSet.fill != nil 158| | { 159| | drawFilledPath(context: context, path: path, fill: dataSet.fill!, fillAlpha: dataSet.fillAlpha) 160| | } 161| | else 162| | { 163| | drawFilledPath(context: context, path: path, fillColor: dataSet.fillColor, fillAlpha: dataSet.fillAlpha) 164| | } 165| | } 166| | 167| | // draw the line (only if filled is disabled or alpha is below 255) 168| | if !dataSet.isDrawFilledEnabled || dataSet.fillAlpha < 1.0 169| | { 170| | context.setStrokeColor(dataSet.color(atIndex: 0).cgColor) 171| | context.setLineWidth(dataSet.lineWidth) 172| | context.setAlpha(1.0) 173| | 174| | context.beginPath() 175| | context.addPath(path) 176| | context.strokePath() 177| | 178| | let axElement = createAccessibleElement(withDescription: accessibilityDataSetDescription, 179| | container: chart, 180| | dataSet: dataSet) 181| | { (element) in 182| | element.isHeader = true 183| | element.accessibilityFrame = path.boundingBoxOfPath 184| | } 185| | 186| | accessibleChartElements.append(axElement) 187| | accessibleChartElements.append(contentsOf: accessibilityEntryElements) 188| | } 189| | 190| | accessibilityPostLayoutChangedNotification() 191| | 192| | context.restoreGState() 193| | } 194| | 195| | open override func drawValues(context: CGContext) 196| | { 197| | guard 198| | let chart = chart, 199| | let data = chart.data 200| | else { return } 201| | 202| | let phaseX = animator.phaseX 203| | let phaseY = animator.phaseY 204| | 205| | let sliceangle = chart.sliceAngle 206| | 207| | // calculate the factor that is needed for transforming the value to pixels 208| | let factor = chart.factor 209| | 210| | let center = chart.centerOffsets 211| | 212| | let yoffset = CGFloat(5.0) 213| | 214| | for i in data.indices 215| | { 216| | guard let 217| | dataSet = data[i] as? RadarChartDataSetProtocol, 218| | shouldDrawValues(forDataSet: dataSet) 219| | else { continue } 220| | 221| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 222| | 223| | let entryCount = dataSet.entryCount 224| | 225| | let iconsOffset = dataSet.iconsOffset 226| | 227| | for j in 0 ..< entryCount 228| | { 229| | guard let e = dataSet.entryForIndex(j) else { continue } 230| | 231| | let p = center.moving(distance: CGFloat(e.y - chart.chartYMin) * factor * CGFloat(phaseY), 232| | atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) 233| | 234| | let valueFont = dataSet.valueFont 235| | 236| | let formatter = dataSet.valueFormatter 237| | 238| | if dataSet.isDrawValuesEnabled 239| | { 240| | context.drawText(formatter.stringForValue(e.y, 241| | entry: e, 242| | dataSetIndex: i, 243| | viewPortHandler: viewPortHandler), 244| | at: CGPoint(x: p.x, y: p.y - yoffset - valueFont.lineHeight), 245| | align: .center, 246| | angleRadians: angleRadians, 247| | attributes: [.font: valueFont, 248| | .foregroundColor: dataSet.valueTextColorAt(j)]) 249| | } 250| | 251| | if let icon = e.icon, dataSet.isDrawIconsEnabled 252| | { 253| | var pIcon = center.moving(distance: CGFloat(e.y) * factor * CGFloat(phaseY) + iconsOffset.y, 254| | atAngle: sliceangle * CGFloat(j) * CGFloat(phaseX) + chart.rotationAngle) 255| | pIcon.y += iconsOffset.x 256| | 257| | context.drawImage(icon, 258| | atCenter: CGPoint(x: pIcon.x, y: pIcon.y), 259| | size: icon.size) 260| | } 261| | } 262| | } 263| | } 264| | 265| | open override func drawExtras(context: CGContext) 266| | { 267| | drawWeb(context: context) 268| | } 269| | 270| 0| private var _webLineSegmentsBuffer = [CGPoint](repeating: CGPoint(), count: 2) 271| | 272| | @objc open func drawWeb(context: CGContext) 273| | { 274| | guard 275| | let chart = chart, 276| | let data = chart.data 277| | else { return } 278| | 279| | let sliceangle = chart.sliceAngle 280| | 281| | context.saveGState() 282| | 283| | // calculate the factor that is needed for transforming the value to 284| | // pixels 285| | let factor = chart.factor 286| | let rotationangle = chart.rotationAngle 287| | 288| | let center = chart.centerOffsets 289| | 290| | // draw the web lines that come from the center 291| | context.setLineWidth(chart.webLineWidth) 292| | context.setStrokeColor(chart.webColor.cgColor) 293| | context.setAlpha(chart.webAlpha) 294| | 295| | let xIncrements = 1 + chart.skipWebLineCount 296| | let maxEntryCount = chart.data?.maxEntryCountSet?.entryCount ?? 0 297| | 298| | for i in stride(from: 0, to: maxEntryCount, by: xIncrements) 299| | { 300| | let p = center.moving(distance: CGFloat(chart.yRange) * factor, 301| | atAngle: sliceangle * CGFloat(i) + rotationangle) 302| | 303| | _webLineSegmentsBuffer[0].x = center.x 304| | _webLineSegmentsBuffer[0].y = center.y 305| | _webLineSegmentsBuffer[1].x = p.x 306| | _webLineSegmentsBuffer[1].y = p.y 307| | 308| | context.strokeLineSegments(between: _webLineSegmentsBuffer) 309| | } 310| | 311| | // draw the inner-web 312| | context.setLineWidth(chart.innerWebLineWidth) 313| | context.setStrokeColor(chart.innerWebColor.cgColor) 314| | context.setAlpha(chart.webAlpha) 315| | 316| | let labelCount = chart.yAxis.entryCount 317| | 318| | for j in 0 ..< labelCount 319| | { 320| | for i in 0 ..< data.entryCount 321| | { 322| | let r = CGFloat(chart.yAxis.entries[j] - chart.chartYMin) * factor 323| | 324| | let p1 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i) + rotationangle) 325| | let p2 = center.moving(distance: r, atAngle: sliceangle * CGFloat(i + 1) + rotationangle) 326| | 327| | _webLineSegmentsBuffer[0].x = p1.x 328| | _webLineSegmentsBuffer[0].y = p1.y 329| | _webLineSegmentsBuffer[1].x = p2.x 330| | _webLineSegmentsBuffer[1].y = p2.y 331| | 332| | context.strokeLineSegments(between: _webLineSegmentsBuffer) 333| | } 334| | } 335| | 336| | context.restoreGState() 337| | } 338| | 339| 0| private var _highlightPointBuffer = CGPoint() 340| | 341| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 342| | { 343| | guard 344| | let chart = chart, 345| | let radarData = chart.data as? RadarChartData 346| | else { return } 347| | 348| | context.saveGState() 349| | 350| | let sliceangle = chart.sliceAngle 351| | 352| | // calculate the factor that is needed for transforming the value pixels 353| | let factor = chart.factor 354| | 355| | let center = chart.centerOffsets 356| | 357| | for high in indices 358| | { 359| | guard 360| | let set = chart.data?[high.dataSetIndex] as? RadarChartDataSetProtocol, 361| | set.isHighlightEnabled 362| | else { continue } 363| | 364| | guard let e = set.entryForIndex(Int(high.x)) as? RadarChartDataEntry 365| | else { continue } 366| | 367| | if !isInBoundsX(entry: e, dataSet: set) 368| | { 369| | continue 370| | } 371| | 372| | context.setLineWidth(radarData.highlightLineWidth) 373| | if radarData.highlightLineDashLengths != nil 374| | { 375| | context.setLineDash(phase: radarData.highlightLineDashPhase, lengths: radarData.highlightLineDashLengths!) 376| | } 377| | else 378| | { 379| | context.setLineDash(phase: 0.0, lengths: []) 380| | } 381| | 382| | context.setStrokeColor(set.highlightColor.cgColor) 383| | 384| | let y = e.y - chart.chartYMin 385| | 386| | _highlightPointBuffer = center.moving(distance: CGFloat(y) * factor * CGFloat(animator.phaseY), 387| | atAngle: sliceangle * CGFloat(high.x) * CGFloat(animator.phaseX) + chart.rotationAngle) 388| | 389| | high.setDraw(pt: _highlightPointBuffer) 390| | 391| | // draw the lines 392| | drawHighlightLines(context: context, point: _highlightPointBuffer, set: set) 393| | 394| | if set.isDrawHighlightCircleEnabled 395| | { 396| | if !_highlightPointBuffer.x.isNaN && !_highlightPointBuffer.y.isNaN 397| | { 398| | var strokeColor = set.highlightCircleStrokeColor 399| | if strokeColor == nil 400| | { 401| | strokeColor = set.color(atIndex: 0) 402| | } 403| | if set.highlightCircleStrokeAlpha < 1.0 404| | { 405| | strokeColor = strokeColor?.withAlphaComponent(set.highlightCircleStrokeAlpha) 406| | } 407| | 408| | drawHighlightCircle( 409| | context: context, 410| | atPoint: _highlightPointBuffer, 411| | innerRadius: set.highlightCircleInnerRadius, 412| | outerRadius: set.highlightCircleOuterRadius, 413| | fillColor: set.highlightCircleFillColor, 414| | strokeColor: strokeColor, 415| | strokeWidth: set.highlightCircleStrokeWidth) 416| | } 417| | } 418| | } 419| | 420| | context.restoreGState() 421| | } 422| | 423| | internal func drawHighlightCircle( 424| | context: CGContext, 425| | atPoint point: CGPoint, 426| | innerRadius: CGFloat, 427| | outerRadius: CGFloat, 428| | fillColor: NSUIColor?, 429| | strokeColor: NSUIColor?, 430| | strokeWidth: CGFloat) 431| | { 432| | context.saveGState() 433| | 434| | if let fillColor = fillColor 435| | { 436| | context.beginPath() 437| | context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) 438| | if innerRadius > 0.0 439| | { 440| | context.addEllipse(in: CGRect(x: point.x - innerRadius, y: point.y - innerRadius, width: innerRadius * 2.0, height: innerRadius * 2.0)) 441| | } 442| | 443| | context.setFillColor(fillColor.cgColor) 444| | context.fillPath(using: .evenOdd) 445| | } 446| | 447| | if let strokeColor = strokeColor 448| | { 449| | context.beginPath() 450| | context.addEllipse(in: CGRect(x: point.x - outerRadius, y: point.y - outerRadius, width: outerRadius * 2.0, height: outerRadius * 2.0)) 451| | context.setStrokeColor(strokeColor.cgColor) 452| | context.setLineWidth(strokeWidth) 453| | context.strokePath() 454| | } 455| | 456| | context.restoreGState() 457| | } 458| | 459| | private func createAccessibleElement(withDescription description: String, 460| | container: RadarChartView, 461| | dataSet: RadarChartDataSetProtocol, 462| | modifier: (NSUIAccessibilityElement) -> ()) -> NSUIAccessibilityElement { 463| | 464| | let element = NSUIAccessibilityElement(accessibilityContainer: container) 465| | element.accessibilityLabel = description 466| | 467| | // The modifier allows changing of traits and frame depending on highlight, rotation, etc 468| | modifier(element) 469| | 470| | return element 471| | } 472| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/ScatterChartRenderer.swift: 1| |// 2| |// ScatterChartRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |open class ScatterChartRenderer: LineScatterCandleRadarRenderer 16| |{ 17| | @objc open weak var dataProvider: ScatterChartDataProvider? 18| | 19| | @objc public init(dataProvider: ScatterChartDataProvider, animator: Animator, viewPortHandler: ViewPortHandler) 20| | { 21| | super.init(animator: animator, viewPortHandler: viewPortHandler) 22| | 23| | self.dataProvider = dataProvider 24| | } 25| | 26| | open override func drawData(context: CGContext) 27| | { 28| | guard let scatterData = dataProvider?.scatterData else { return } 29| | 30| | // If we redraw the data, remove and repopulate accessible elements to update label values and frames 31| | accessibleChartElements.removeAll() 32| | 33| | if let chart = dataProvider as? ScatterChartView { 34| | // Make the chart header the first element in the accessible elements array 35| | let element = createAccessibleHeader(usingChart: chart, 36| | andData: scatterData, 37| | withDefaultDescription: "Scatter Chart") 38| | accessibleChartElements.append(element) 39| | } 40| | 41| | // TODO: Due to the potential complexity of data presented in Scatter charts, a more usable way 42| | // for VO accessibility would be to use axis based traversal rather than by dataset. 43| | // Hence, accessibleChartElements is not populated below. (Individual renderers guard against dataSource being their respective views) 44| | for i in scatterData.indices 45| | { 46| | guard let set = scatterData[i] as? ScatterChartDataSetProtocol else 47| | { 48| | fatalError("Datasets for ScatterChartRenderer must conform to ScatterChartDataSetProtocol") 49| | } 50| | 51| | guard set.isVisible else { continue } 52| | 53| | drawDataSet(context: context, dataSet: set) 54| | } 55| | } 56| | 57| 0| private var _lineSegments = [CGPoint](repeating: CGPoint(), count: 2) 58| | 59| | @objc open func drawDataSet(context: CGContext, dataSet: ScatterChartDataSetProtocol) 60| | { 61| | guard let dataProvider = dataProvider else { return } 62| | 63| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 64| | 65| | let phaseY = animator.phaseY 66| | 67| | let entryCount = dataSet.entryCount 68| | 69| | var point = CGPoint() 70| | 71| | let valueToPixelMatrix = trans.valueToPixelMatrix 72| | 73| | if let renderer = dataSet.shapeRenderer 74| | { 75| | context.saveGState() 76| | 77| | for j in 0 ..< Int(min(ceil(Double(entryCount) * animator.phaseX), Double(entryCount))) 78| | { 79| | guard let e = dataSet.entryForIndex(j) else { continue } 80| | 81| | point.x = CGFloat(e.x) 82| | point.y = CGFloat(e.y * phaseY) 83| | point = point.applying(valueToPixelMatrix) 84| | 85| | if !viewPortHandler.isInBoundsRight(point.x) 86| | { 87| | break 88| | } 89| | 90| | if !viewPortHandler.isInBoundsLeft(point.x) || 91| | !viewPortHandler.isInBoundsY(point.y) 92| | { 93| | continue 94| | } 95| | 96| | renderer.renderShape(context: context, dataSet: dataSet, viewPortHandler: viewPortHandler, point: point, color: dataSet.color(atIndex: j)) 97| | } 98| | 99| | context.restoreGState() 100| | } 101| | else 102| | { 103| | print("There's no ShapeRenderer specified for ScatterDataSet", terminator: "\n") 104| | } 105| | } 106| | 107| | open override func drawValues(context: CGContext) 108| | { 109| | guard 110| | let dataProvider = dataProvider, 111| | let scatterData = dataProvider.scatterData 112| | else { return } 113| | 114| | // if values are drawn 115| | if isDrawingValuesAllowed(dataProvider: dataProvider) 116| | { 117| | guard let dataSets = scatterData.dataSets as? [ScatterChartDataSetProtocol] else { return } 118| | 119| | let phaseY = animator.phaseY 120| | 121| | var pt = CGPoint() 122| | 123| | for i in scatterData.indices 124| | { 125| | let dataSet = dataSets[i] 126| | 127| | guard shouldDrawValues(forDataSet: dataSet) 128| | else { continue } 129| | 130| | let valueFont = dataSet.valueFont 131| | 132| | let formatter = dataSet.valueFormatter 133| | 134| | let trans = dataProvider.getTransformer(forAxis: dataSet.axisDependency) 135| | let valueToPixelMatrix = trans.valueToPixelMatrix 136| | 137| | let iconsOffset = dataSet.iconsOffset 138| | 139| | let angleRadians = dataSet.valueLabelAngle.DEG2RAD 140| | 141| | let shapeSize = dataSet.scatterShapeSize 142| | let lineHeight = valueFont.lineHeight 143| | 144| | _xBounds.set(chart: dataProvider, dataSet: dataSet, animator: animator) 145| | 146| | for j in _xBounds 147| | { 148| | guard let e = dataSet.entryForIndex(j) else { break } 149| | 150| | pt.x = CGFloat(e.x) 151| | pt.y = CGFloat(e.y * phaseY) 152| | pt = pt.applying(valueToPixelMatrix) 153| | 154| | if (!viewPortHandler.isInBoundsRight(pt.x)) 155| | { 156| | break 157| | } 158| | 159| | // make sure the lines don't do shitty things outside bounds 160| | if (!viewPortHandler.isInBoundsLeft(pt.x) 161| | || !viewPortHandler.isInBoundsY(pt.y)) 162| | { 163| | continue 164| | } 165| | 166| | let text = formatter.stringForValue( 167| | e.y, 168| | entry: e, 169| | dataSetIndex: i, 170| | viewPortHandler: viewPortHandler) 171| | 172| | if dataSet.isDrawValuesEnabled 173| | { 174| | context.drawText(text, 175| | at: CGPoint(x: pt.x, 176| | y: pt.y - shapeSize - lineHeight), 177| | align: .center, 178| | angleRadians: angleRadians, 179| | attributes: [.font: valueFont, 180| | .foregroundColor: dataSet.valueTextColorAt(j)] 181| | ) 182| | } 183| | 184| | if let icon = e.icon, dataSet.isDrawIconsEnabled 185| | { 186| | context.drawImage(icon, 187| | atCenter: CGPoint(x: pt.x + iconsOffset.x, 188| | y: pt.y + iconsOffset.y), 189| | size: icon.size) 190| | } 191| | } 192| | } 193| | } 194| | } 195| | 196| | open override func drawExtras(context: CGContext) 197| | { 198| | 199| | } 200| | 201| | open override func drawHighlighted(context: CGContext, indices: [Highlight]) 202| | { 203| | guard 204| | let dataProvider = dataProvider, 205| | let scatterData = dataProvider.scatterData 206| | else { return } 207| | 208| | context.saveGState() 209| | 210| | for high in indices 211| | { 212| | guard 213| | let set = scatterData[high.dataSetIndex] as? ScatterChartDataSetProtocol, 214| | set.isHighlightEnabled 215| | else { continue } 216| | 217| | guard let entry = set.entryForXValue(high.x, closestToY: high.y) else { continue } 218| | 219| | if !isInBoundsX(entry: entry, dataSet: set) { continue } 220| | 221| | context.setStrokeColor(set.highlightColor.cgColor) 222| | context.setLineWidth(set.highlightLineWidth) 223| | if set.highlightLineDashLengths != nil 224| | { 225| | context.setLineDash(phase: set.highlightLineDashPhase, lengths: set.highlightLineDashLengths!) 226| | } 227| | else 228| | { 229| | context.setLineDash(phase: 0.0, lengths: []) 230| | } 231| | 232| | let x = entry.x // get the x-position 233| | let y = entry.y * Double(animator.phaseY) 234| | 235| | let trans = dataProvider.getTransformer(forAxis: set.axisDependency) 236| | 237| | let pt = trans.pixelForValues(x: x, y: y) 238| | 239| | high.setDraw(pt: pt) 240| | 241| | // draw the lines 242| | drawHighlightLines(context: context, point: pt, set: set) 243| | } 244| | 245| | context.restoreGState() 246| | } 247| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Renderers/XAxisRenderer.swift: 1| |// 2| |// XAxisRenderer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| | 16| |@objc(ChartXAxisRenderer) 17| |open class XAxisRenderer: NSObject, AxisRenderer 18| |{ 19| | public let viewPortHandler: ViewPortHandler 20| | public let axis: XAxis 21| | public let transformer: Transformer? 22| | 23| | @objc public init(viewPortHandler: ViewPortHandler, axis: XAxis, transformer: Transformer?) 24| | { 25| | self.viewPortHandler = viewPortHandler 26| | self.axis = axis 27| | self.transformer = transformer 28| | 29| | super.init() 30| | } 31| | 32| | open func computeAxis(min: Double, max: Double, inverted: Bool) 33| | { 34| | var min = min, max = max 35| | 36| | if let transformer = self.transformer, 37| | viewPortHandler.contentWidth > 10, 38| | !viewPortHandler.isFullyZoomedOutX 39| | { 40| | // calculate the starting and entry point of the y-labels (depending on 41| | // zoom / contentrect bounds) 42| | let p1 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentLeft, y: viewPortHandler.contentTop)) 43| | let p2 = transformer.valueForTouchPoint(CGPoint(x: viewPortHandler.contentRight, y: viewPortHandler.contentTop)) 44| | 45| | min = inverted ? Double(p2.x) : Double(p1.x) 46| | max = inverted ? Double(p1.x) : Double(p2.x) 47| | } 48| | 49| | computeAxisValues(min: min, max: max) 50| | } 51| | 52| | open func computeAxisValues(min: Double, max: Double) 53| | { 54| | let yMin = min 55| | let yMax = max 56| | 57| | let labelCount = axis.labelCount 58| | let range = abs(yMax - yMin) 59| | 60| | guard 61| | labelCount != 0, 62| | range > 0, 63| | range.isFinite 64| | else 65| | { 66| | axis.entries = [] 67| | axis.centeredEntries = [] 68| | return 69| | } 70| | 71| | // Find out how much spacing (in y value space) between axis values 72| | let rawInterval = range / Double(labelCount) 73| | var interval = rawInterval.roundedToNextSignificant() 74| | 75| | // If granularity is enabled, then do not allow the interval to go below specified granularity. 76| | // This is used to avoid repeated values when rounding values for display. 77| | if axis.granularityEnabled 78| | { 79| | interval = Swift.max(interval, axis.granularity) 80| | } 81| | 82| | // Normalize interval 83| | let intervalMagnitude = pow(10.0, Double(Int(log10(interval)))).roundedToNextSignificant() 84| | let intervalSigDigit = Int(interval / intervalMagnitude) 85| | if intervalSigDigit > 5 86| | { 87| | // Use one order of magnitude higher, to avoid intervals like 0.9 or 90 88| | interval = floor(10.0 * Double(intervalMagnitude)) 89| | } 90| | 91| | var n = axis.centerAxisLabelsEnabled ? 1 : 0 92| | 93| | // force label count 94| | if axis.isForceLabelsEnabled 95| | { 96| | interval = range / Double(labelCount - 1) 97| | 98| | // Ensure stops contains at least n elements. 99| | axis.entries.removeAll(keepingCapacity: true) 100| | axis.entries.reserveCapacity(labelCount) 101| | 102| | let values = stride(from: yMin, to: Double(labelCount) * interval + yMin, by: interval) 103| | axis.entries.append(contentsOf: values) 104| | 105| | n = labelCount 106| | } 107| | else 108| | { 109| | // no forced count 110| | 111| | var first = interval == 0.0 ? 0.0 : ceil(yMin / interval) * interval 112| | 113| | if axis.centerAxisLabelsEnabled 114| | { 115| | first -= interval 116| | } 117| | 118| | let last = interval == 0.0 ? 0.0 : (floor(yMax / interval) * interval).nextUp 119| | 120| | if interval != 0.0, last != first 121| | { 122| | stride(from: first, through: last, by: interval).forEach { _ in n += 1 } 123| | } 124| | 125| | // Ensure stops contains at least n elements. 126| | axis.entries.removeAll(keepingCapacity: true) 127| | axis.entries.reserveCapacity(labelCount) 128| | 129| | let start = first, end = first + Double(n) * interval 130| | 131| | // Fix for IEEE negative zero case (Where value == -0.0, and 0.0 == -0.0) 132| | let values = stride(from: start, to: end, by: interval).map { $0 == 0.0 ? 0.0 : $0 } 133| | axis.entries.append(contentsOf: values) 134| | } 135| | 136| | // set decimals 137| | if interval < 1 138| | { 139| | axis.decimals = Int(ceil(-log10(interval))) 140| | } 141| | else 142| | { 143| | axis.decimals = 0 144| | } 145| | 146| | if axis.centerAxisLabelsEnabled 147| | { 148| | let offset: Double = interval / 2.0 149| | axis.centeredEntries = axis.entries[.. 1 288| | { 289| | let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width 290| | 291| | if width > viewPortHandler.offsetRight * 2.0, 292| | position.x + width > viewPortHandler.chartWidth 293| | { 294| | position.x -= width / 2.0 295| | } 296| | } 297| | else if i == 0 298| | { // avoid clipping of the first 299| | let width = labelns.boundingRect(with: labelMaxSize, options: .usesLineFragmentOrigin, attributes: labelAttrs, context: nil).size.width 300| | position.x += width / 2.0 301| | } 302| | } 303| | 304| | drawLabel(context: context, 305| | formattedLabel: label, 306| | x: position.x, 307| | y: pos, 308| | attributes: labelAttrs, 309| | constrainedTo: labelMaxSize, 310| | anchor: anchor, 311| | angleRadians: labelRotationAngleRadians) 312| | } 313| | } 314| | 315| | @objc open func drawLabel( 316| | context: CGContext, 317| | formattedLabel: String, 318| | x: CGFloat, 319| | y: CGFloat, 320| | attributes: [NSAttributedString.Key : Any], 321| | constrainedTo size: CGSize, 322| | anchor: CGPoint, 323| | angleRadians: CGFloat) 324| | { 325| | context.drawMultilineText(formattedLabel, 326| | at: CGPoint(x: x, y: y), 327| | constrainedTo: size, 328| | anchor: anchor, 329| | angleRadians: angleRadians, 330| | attributes: attributes) 331| | } 332| | 333| | open func renderGridLines(context: CGContext) 334| | { 335| | guard 336| | let transformer = self.transformer, 337| | axis.isEnabled, 338| | axis.isDrawGridLinesEnabled 339| | else { return } 340| | 341| | context.saveGState() 342| | defer { context.restoreGState() } 343| | 344| | context.clip(to: self.gridClippingRect) 345| | 346| | context.setShouldAntialias(axis.gridAntialiasEnabled) 347| | context.setStrokeColor(axis.gridColor.cgColor) 348| | context.setLineWidth(axis.gridLineWidth) 349| | context.setLineCap(axis.gridLineCap) 350| | 351| | if axis.gridLineDashLengths != nil 352| | { 353| | context.setLineDash(phase: axis.gridLineDashPhase, lengths: axis.gridLineDashLengths) 354| | } 355| | else 356| | { 357| | context.setLineDash(phase: 0.0, lengths: []) 358| | } 359| | 360| | let valueToPixelMatrix = transformer.valueToPixelMatrix 361| | 362| | var position = CGPoint.zero 363| | 364| | let entries = axis.entries 365| | 366| | for entry in entries 367| | { 368| | position.x = CGFloat(entry) 369| | position.y = CGFloat(entry) 370| | position = position.applying(valueToPixelMatrix) 371| | 372| | drawGridLine(context: context, x: position.x, y: position.y) 373| | } 374| | } 375| | 376| | @objc open var gridClippingRect: CGRect 377| | { 378| | var contentRect = viewPortHandler.contentRect 379| | let dx = self.axis.gridLineWidth 380| | contentRect.origin.x -= dx / 2.0 381| | contentRect.size.width += dx 382| | return contentRect 383| | } 384| | 385| | @objc open func drawGridLine(context: CGContext, x: CGFloat, y: CGFloat) 386| | { 387| | guard x >= viewPortHandler.offsetLeft && x <= viewPortHandler.chartWidth else { return } 388| | 389| | context.beginPath() 390| | context.move(to: CGPoint(x: x, y: viewPortHandler.contentTop)) 391| | context.addLine(to: CGPoint(x: x, y: viewPortHandler.contentBottom)) 392| | context.strokePath() 393| | } 394| | 395| | open func renderLimitLines(context: CGContext) 396| | { 397| | guard 398| | let transformer = self.transformer, 399| | !axis.limitLines.isEmpty 400| | else { return } 401| | 402| | let trans = transformer.valueToPixelMatrix 403| | 404| | var position = CGPoint.zero 405| | 406| | for l in axis.limitLines where l.isEnabled 407| | { 408| | context.saveGState() 409| | defer { context.restoreGState() } 410| | 411| | var clippingRect = viewPortHandler.contentRect 412| | clippingRect.origin.x -= l.lineWidth / 2.0 413| | clippingRect.size.width += l.lineWidth 414| | context.clip(to: clippingRect) 415| | 416| | position.x = CGFloat(l.limit) 417| | position.y = 0.0 418| | position = position.applying(trans) 419| | 420| | renderLimitLineLine(context: context, limitLine: l, position: position) 421| | renderLimitLineLabel(context: context, limitLine: l, position: position, yOffset: 2.0 + l.yOffset) 422| | } 423| | } 424| | 425| | @objc open func renderLimitLineLine(context: CGContext, limitLine: ChartLimitLine, position: CGPoint) 426| | { 427| | context.beginPath() 428| | context.move(to: CGPoint(x: position.x, y: viewPortHandler.contentTop)) 429| | context.addLine(to: CGPoint(x: position.x, y: viewPortHandler.contentBottom)) 430| | 431| | context.setStrokeColor(limitLine.lineColor.cgColor) 432| | context.setLineWidth(limitLine.lineWidth) 433| | if limitLine.lineDashLengths != nil 434| | { 435| | context.setLineDash(phase: limitLine.lineDashPhase, lengths: limitLine.lineDashLengths!) 436| | } 437| | else 438| | { 439| | context.setLineDash(phase: 0.0, lengths: []) 440| | } 441| | 442| | context.strokePath() 443| | } 444| | 445| | @objc open func renderLimitLineLabel(context: CGContext, limitLine: ChartLimitLine, position: CGPoint, yOffset: CGFloat) 446| | { 447| | let label = limitLine.label 448| | 449| | // if drawing the limit-value label is enabled 450| | guard limitLine.drawLabelEnabled, !label.isEmpty else { return } 451| | 452| | let labelLineHeight = limitLine.valueFont.lineHeight 453| | 454| | let xOffset: CGFloat = limitLine.lineWidth + limitLine.xOffset 455| | 456| | let align: NSTextAlignment 457| | let point: CGPoint 458| | 459| | switch limitLine.labelPosition 460| | { 461| | case .rightTop: 462| | align = .left 463| | point = CGPoint(x: position.x + xOffset, 464| | y: viewPortHandler.contentTop + yOffset) 465| | 466| | case .rightBottom: 467| | align = .left 468| | point = CGPoint(x: position.x + xOffset, 469| | y: viewPortHandler.contentBottom - labelLineHeight - yOffset) 470| | 471| | case .leftTop: 472| | align = .right 473| | point = CGPoint(x: position.x - xOffset, 474| | y: viewPortHandler.contentTop + yOffset) 475| | 476| | case .leftBottom: 477| | align = .right 478| | point = CGPoint(x: position.x - xOffset, 479| | y: viewPortHandler.contentBottom - labelLineHeight - yOffset) 480| | } 481| | 482| | context.drawText(label, 483| | at: point, 484| | align: align, 485| | attributes: [.font: limitLine.valueFont, 486| | .foregroundColor: limitLine.valueTextColor]) 487| | } 488| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Utils/Transformer.swift: 1| |// 2| |// Transformer.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |/// Transformer class that contains all matrices and is responsible for transforming values into pixels on the screen and backwards. 16| |@objc(ChartTransformer) 17| |open class Transformer: NSObject 18| |{ 19| | /// matrix to map the values to the screen pixels 20| 104| internal var matrixValueToPx = CGAffineTransform.identity 21| | 22| | /// matrix for handling the different offsets of the chart 23| 104| internal var matrixOffset = CGAffineTransform.identity 24| | 25| | internal var viewPortHandler: ViewPortHandler 26| | 27| | @objc public init(viewPortHandler: ViewPortHandler) 28| | { 29| | self.viewPortHandler = viewPortHandler 30| | } 31| | 32| | /// Prepares the matrix that transforms values to pixels. Calculates the scale factors from the charts size and offsets. 33| | @objc open func prepareMatrixValuePx(chartXMin: Double, deltaX: CGFloat, deltaY: CGFloat, chartYMin: Double) 34| | { 35| | var scaleX = (viewPortHandler.contentWidth / deltaX) 36| | var scaleY = (viewPortHandler.contentHeight / deltaY) 37| | 38| | if .infinity == scaleX 39| | { 40| | scaleX = 0.0 41| | } 42| | if .infinity == scaleY 43| | { 44| | scaleY = 0.0 45| | } 46| | 47| | // setup all matrices 48| | matrixValueToPx = CGAffineTransform.identity 49| | .scaledBy(x: scaleX, y: -scaleY) 50| | .translatedBy(x: CGFloat(-chartXMin), y: CGFloat(-chartYMin)) 51| | } 52| | 53| | /// Prepares the matrix that contains all offsets. 54| | @objc open func prepareMatrixOffset(inverted: Bool) 55| | { 56| | if !inverted 57| | { 58| | matrixOffset = CGAffineTransform(translationX: viewPortHandler.offsetLeft, y: viewPortHandler.chartHeight - viewPortHandler.offsetBottom) 59| | } 60| | else 61| | { 62| | matrixOffset = CGAffineTransform(scaleX: 1.0, y: -1.0) 63| | .translatedBy(x: viewPortHandler.offsetLeft, y: -viewPortHandler.offsetTop) 64| | } 65| | } 66| | 67| | /// Transform an array of points with all matrices. 68| | // VERY IMPORTANT: Keep matrix order "value-touch-offset" when transforming. 69| | open func pointValuesToPixel(_ points: inout [CGPoint]) 70| | { 71| | let trans = valueToPixelMatrix 72| | points = points.map { $0.applying(trans) } 73| | } 74| | 75| | open func pointValueToPixel(_ point: inout CGPoint) 76| | { 77| | point = point.applying(valueToPixelMatrix) 78| | } 79| | 80| | @objc open func pixelForValues(x: Double, y: Double) -> CGPoint 81| | { 82| | return CGPoint(x: x, y: y).applying(valueToPixelMatrix) 83| | } 84| | 85| | /// Transform a rectangle with all matrices. 86| | open func rectValueToPixel(_ r: inout CGRect) 87| | { 88| | r = r.applying(valueToPixelMatrix) 89| | } 90| | 91| | /// Transform a rectangle with all matrices with potential animation phases. 92| | open func rectValueToPixel(_ r: inout CGRect, phaseY: Double) 93| | { 94| | // multiply the height of the rect with the phase 95| | var bottom = r.origin.y + r.size.height 96| | bottom *= CGFloat(phaseY) 97| | let top = r.origin.y * CGFloat(phaseY) 98| | r.size.height = bottom - top 99| | r.origin.y = top 100| | 101| | r = r.applying(valueToPixelMatrix) 102| | } 103| | 104| | /// Transform a rectangle with all matrices. 105| | open func rectValueToPixelHorizontal(_ r: inout CGRect) 106| | { 107| | r = r.applying(valueToPixelMatrix) 108| | } 109| | 110| | /// Transform a rectangle with all matrices with potential animation phases. 111| | open func rectValueToPixelHorizontal(_ r: inout CGRect, phaseY: Double) 112| | { 113| | // multiply the height of the rect with the phase 114| | let left = r.origin.x * CGFloat(phaseY) 115| | let right = (r.origin.x + r.size.width) * CGFloat(phaseY) 116| | r.size.width = right - left 117| | r.origin.x = left 118| | 119| | r = r.applying(valueToPixelMatrix) 120| | } 121| | 122| | /// transforms multiple rects with all matrices 123| | open func rectValuesToPixel(_ rects: inout [CGRect]) 124| | { 125| | let trans = valueToPixelMatrix 126| | rects = rects.map { $0.applying(trans) } 127| | } 128| | 129| | /// Transforms the given array of touch points (pixels) into values on the chart. 130| | open func pixelsToValues(_ pixels: inout [CGPoint]) 131| | { 132| | let trans = pixelToValueMatrix 133| | pixels = pixels.map { $0.applying(trans) } 134| | } 135| | 136| | /// Transforms the given touch point (pixels) into a value on the chart. 137| | open func pixelToValues(_ pixel: inout CGPoint) 138| | { 139| | pixel = pixel.applying(pixelToValueMatrix) 140| | } 141| | 142| | /// - Returns: The x and y values in the chart at the given touch point 143| | /// (encapsulated in a CGPoint). This method transforms pixel coordinates to 144| | /// coordinates / values in the chart. 145| | @objc open func valueForTouchPoint(_ point: CGPoint) -> CGPoint 146| | { 147| | return point.applying(pixelToValueMatrix) 148| | } 149| | 150| | /// - Returns: The x and y values in the chart at the given touch point 151| | /// (x/y). This method transforms pixel coordinates to 152| | /// coordinates / values in the chart. 153| | @objc open func valueForTouchPoint(x: CGFloat, y: CGFloat) -> CGPoint 154| | { 155| | return CGPoint(x: x, y: y).applying(pixelToValueMatrix) 156| | } 157| | 158| | @objc open var valueToPixelMatrix: CGAffineTransform 159| | { 160| | return 161| | matrixValueToPx.concatenating(viewPortHandler.touchMatrix) 162| | .concatenating(matrixOffset 163| | ) 164| | } 165| | 166| | @objc open var pixelToValueMatrix: CGAffineTransform 167| | { 168| | return valueToPixelMatrix.inverted() 169| | } 170| |} /Users/travis/build/danielgindi/Charts/Source/Charts/Utils/ViewPortHandler.swift: 1| |// 2| |// ViewPortHandler.swift 3| |// Charts 4| |// 5| |// Copyright 2015 Daniel Cohen Gindi & Philipp Jahoda 6| |// A port of MPAndroidChart for iOS 7| |// Licensed under Apache License 2.0 8| |// 9| |// https://github.com/danielgindi/Charts 10| |// 11| | 12| |import Foundation 13| |import CoreGraphics 14| | 15| |/// Class that contains information about the charts current viewport settings, including offsets, scale & translation levels, ... 16| |@objc(ChartViewPortHandler) 17| |open class ViewPortHandler: NSObject 18| |{ 19| | /// matrix used for touch events 20| 52| @objc open private(set) var touchMatrix = CGAffineTransform.identity 21| | 22| | /// this rectangle defines the area in which graph values can be drawn 23| 52| @objc open private(set) var contentRect = CGRect() 24| | 25| | @objc open private(set) var chartWidth: CGFloat = 0 26| | @objc open private(set) var chartHeight: CGFloat = 0 27| | 28| | /// minimum scale value on the y-axis 29| | @objc open private(set) var minScaleY: CGFloat = 1.0 30| | 31| | /// maximum scale value on the y-axis 32| 52| @objc open private(set) var maxScaleY = CGFloat.greatestFiniteMagnitude 33| | 34| | /// minimum scale value on the x-axis 35| | @objc open private(set) var minScaleX: CGFloat = 1.0 36| | 37| | /// maximum scale value on the x-axis 38| 52| @objc open private(set) var maxScaleX = CGFloat.greatestFiniteMagnitude 39| | 40| | /// contains the current scale factor of the x-axis 41| | @objc open private(set) var scaleX: CGFloat = 1.0 42| | 43| | /// contains the current scale factor of the y-axis 44| | @objc open private(set) var scaleY: CGFloat = 1.0 45| | 46| | /// current translation (drag / pan) distance on the x-axis 47| | @objc open private(set) var transX: CGFloat = 0 48| | 49| | /// current translation (drag / pan) distance on the y-axis 50| | @objc open private(set) var transY: CGFloat = 0 51| | 52| | /// offset that allows the chart to be dragged over its bounds on the x-axis 53| | private var transOffsetX: CGFloat = 0 54| | 55| | /// offset that allows the chart to be dragged over its bounds on the x-axis 56| | private var transOffsetY: CGFloat = 0 57| | 58| | /// Constructor - don't forget calling setChartDimens(...) 59| | @objc public init(width: CGFloat, height: CGFloat) 60| | { 61| | super.init() 62| | 63| | setChartDimens(width: width, height: height) 64| | } 65| | 66| | @objc open func setChartDimens(width: CGFloat, height: CGFloat) 67| | { 68| | let offsetLeft = self.offsetLeft 69| | let offsetTop = self.offsetTop 70| | let offsetRight = self.offsetRight 71| | let offsetBottom = self.offsetBottom 72| | 73| | chartHeight = height 74| | chartWidth = width 75| | 76| | restrainViewPort(offsetLeft: offsetLeft, offsetTop: offsetTop, offsetRight: offsetRight, offsetBottom: offsetBottom) 77| | } 78| | 79| | @objc open var hasChartDimens: Bool 80| | { 81| | return chartHeight > 0.0 82| | && chartWidth > 0.0 83| | } 84| | 85| | @objc open func restrainViewPort(offsetLeft: CGFloat, offsetTop: CGFloat, offsetRight: CGFloat, offsetBottom: CGFloat) 86| | { 87| | contentRect.origin.x = offsetLeft 88| | contentRect.origin.y = offsetTop 89| | contentRect.size.width = chartWidth - offsetLeft - offsetRight 90| | contentRect.size.height = chartHeight - offsetBottom - offsetTop 91| | } 92| | 93| | @objc open var offsetLeft: CGFloat 94| | { 95| | return contentRect.origin.x 96| | } 97| | 98| | @objc open var offsetRight: CGFloat 99| | { 100| | return chartWidth - contentRect.size.width - contentRect.origin.x 101| | } 102| | 103| | @objc open var offsetTop: CGFloat 104| | { 105| | return contentRect.origin.y 106| | } 107| | 108| | @objc open var offsetBottom: CGFloat 109| | { 110| | return chartHeight - contentRect.size.height - contentRect.origin.y 111| | } 112| | 113| | @objc open var contentTop: CGFloat 114| | { 115| | return contentRect.origin.y 116| | } 117| | 118| | @objc open var contentLeft: CGFloat 119| | { 120| | return contentRect.origin.x 121| | } 122| | 123| | @objc open var contentRight: CGFloat 124| | { 125| | return contentRect.origin.x + contentRect.size.width 126| | } 127| | 128| | @objc open var contentBottom: CGFloat 129| | { 130| | return contentRect.origin.y + contentRect.size.height 131| | } 132| | 133| | @objc open var contentWidth: CGFloat 134| | { 135| | return contentRect.size.width 136| | } 137| | 138| | @objc open var contentHeight: CGFloat 139| | { 140| | return contentRect.size.height 141| | } 142| | 143| | @objc open var contentCenter: CGPoint 144| | { 145| | return CGPoint(x: contentRect.origin.x + contentRect.size.width / 2.0, y: contentRect.origin.y + contentRect.size.height / 2.0) 146| | } 147| | 148| | // MARK: - Scaling/Panning etc. 149| | 150| | /// Zooms by the specified zoom factors. 151| | @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform 152| | { 153| | return touchMatrix.scaledBy(x: scaleX, y: scaleY) 154| | } 155| | 156| | /// Zooms around the specified center 157| | @objc open func zoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform 158| | { 159| | return touchMatrix.translatedBy(x: x, y: y) 160| | .scaledBy(x: scaleX, y: scaleY) 161| | .translatedBy(x: -x, y: -y) 162| | } 163| | 164| | /// Zooms in by 1.4, x and y are the coordinates (in pixels) of the zoom center. 165| | @objc open func zoomIn(x: CGFloat, y: CGFloat) -> CGAffineTransform 166| | { 167| | return zoom(scaleX: 1.4, scaleY: 1.4, x: x, y: y) 168| | } 169| | 170| | /// Zooms out by 0.7, x and y are the coordinates (in pixels) of the zoom center. 171| | @objc open func zoomOut(x: CGFloat, y: CGFloat) -> CGAffineTransform 172| | { 173| | return zoom(scaleX: 0.7, scaleY: 0.7, x: x, y: y) 174| | } 175| | 176| | /// Zooms out to original size. 177| | @objc open func resetZoom() -> CGAffineTransform 178| | { 179| | return zoom(scaleX: 1.0, scaleY: 1.0, x: 0.0, y: 0.0) 180| | } 181| | 182| | /// Sets the scale factor to the specified values. 183| | @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat) -> CGAffineTransform 184| | { 185| | var matrix = touchMatrix 186| | matrix.a = scaleX 187| | matrix.d = scaleY 188| | return matrix 189| | } 190| | 191| | /// Sets the scale factor to the specified values. x and y is pivot. 192| | @objc open func setZoom(scaleX: CGFloat, scaleY: CGFloat, x: CGFloat, y: CGFloat) -> CGAffineTransform 193| | { 194| | var matrix = touchMatrix 195| | matrix.a = 1.0 196| | matrix.d = 1.0 197| | matrix = matrix.translatedBy(x: x, y: y) 198| | .scaledBy(x: scaleX, y: scaleY) 199| | .translatedBy(x: -x, y: -y) 200| | return matrix 201| | } 202| | 203| | /// Resets all zooming and dragging and makes the chart fit exactly it's bounds. 204| | @objc open func fitScreen() -> CGAffineTransform 205| | { 206| | minScaleX = 1.0 207| | minScaleY = 1.0 208| | 209| | return .identity 210| | } 211| | 212| | /// Translates to the specified point. 213| | @objc open func translate(pt: CGPoint) -> CGAffineTransform 214| | { 215| | let translateX = pt.x - offsetLeft 216| | let translateY = pt.y - offsetTop 217| | 218| | let matrix = touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) 219| | 220| | return matrix 221| | } 222| | 223| | /// Centers the viewport around the specified position (x-index and y-value) in the chart. 224| | /// Centering the viewport outside the bounds of the chart is not possible. 225| | /// Makes most sense in combination with the setScaleMinima(...) method. 226| | @objc open func centerViewPort(pt: CGPoint, chart: ChartViewBase) 227| | { 228| | let translateX = pt.x - offsetLeft 229| | let translateY = pt.y - offsetTop 230| | 231| | let matrix = touchMatrix.concatenating(CGAffineTransform(translationX: -translateX, y: -translateY)) 232| | refresh(newMatrix: matrix, chart: chart, invalidate: true) 233| | } 234| | 235| | /// call this method to refresh the graph with a given matrix 236| | @objc @discardableResult open func refresh(newMatrix: CGAffineTransform, chart: ChartViewBase, invalidate: Bool) -> CGAffineTransform 237| | { 238| | touchMatrix = newMatrix 239| | 240| | // make sure scale and translation are within their bounds 241| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 242| | 243| | chart.setNeedsDisplay() 244| | 245| | return touchMatrix 246| | } 247| | 248| | /// limits the maximum scale and X translation of the given matrix 249| | private func limitTransAndScale(matrix: inout CGAffineTransform, content: CGRect) 250| | { 251| | // min scale-x is 1 252| | scaleX = min(max(minScaleX, matrix.a), maxScaleX) 253| | 254| | // min scale-y is 1 255| | scaleY = min(max(minScaleY, matrix.d), maxScaleY) 256| | 257| | let width = content.width 258| | let height = content.height 259| | 260| | let maxTransX = -width * (scaleX - 1.0) 261| | transX = min(max(matrix.tx, maxTransX - transOffsetX), transOffsetX) 262| | 263| | let maxTransY = height * (scaleY - 1.0) 264| | transY = max(min(matrix.ty, maxTransY + transOffsetY), -transOffsetY) 265| | 266| | matrix.tx = transX 267| | matrix.a = scaleX 268| | matrix.ty = transY 269| | matrix.d = scaleY 270| | } 271| | 272| | /// Sets the minimum scale factor for the x-axis 273| | @objc open func setMinimumScaleX(_ xScale: CGFloat) 274| | { 275| | minScaleX = max(xScale, 1) 276| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 277| | } 278| | 279| | /// Sets the maximum scale factor for the x-axis 280| | @objc open func setMaximumScaleX(_ xScale: CGFloat) 281| | { 282| | maxScaleX = xScale == 0 ? .greatestFiniteMagnitude : xScale 283| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 284| | } 285| | 286| | /// Sets the minimum and maximum scale factors for the x-axis 287| | @objc open func setMinMaxScaleX(minScaleX: CGFloat, maxScaleX: CGFloat) 288| | { 289| | self.minScaleX = max(minScaleX, 1) 290| | self.maxScaleX = maxScaleX == 0 ? .greatestFiniteMagnitude : maxScaleX 291| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 292| | } 293| | 294| | /// Sets the minimum scale factor for the y-axis 295| | @objc open func setMinimumScaleY(_ yScale: CGFloat) 296| | { 297| | minScaleY = max(yScale, 1) 298| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 299| | } 300| | 301| | /// Sets the maximum scale factor for the y-axis 302| | @objc open func setMaximumScaleY(_ yScale: CGFloat) 303| | { 304| | maxScaleY = yScale == 0 ? .greatestFiniteMagnitude : yScale 305| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 306| | } 307| | 308| | @objc open func setMinMaxScaleY(minScaleY: CGFloat, maxScaleY: CGFloat) 309| | { 310| | 311| | self.minScaleY = max(minScaleY, 1) 312| | self.maxScaleY = maxScaleY == 0 ? .greatestFiniteMagnitude : maxScaleY 313| | limitTransAndScale(matrix: &touchMatrix, content: contentRect) 314| | } 315| | 316| | // MARK: - Boundaries Check 317| | 318| | @objc open func isInBoundsX(_ x: CGFloat) -> Bool 319| | { 320| | return isInBoundsLeft(x) && isInBoundsRight(x) 321| | } 322| | 323| | @objc open func isInBoundsY(_ y: CGFloat) -> Bool 324| | { 325| | return isInBoundsTop(y) && isInBoundsBottom(y) 326| | } 327| | 328| | /** 329| | A method to check whether coordinate lies within the viewport. 330| | 331| | - Parameters: 332| | - point: a coordinate. 333| | */ 334| | @objc open func isInBounds(point: CGPoint) -> Bool 335| | { 336| | return isInBounds(x: point.x, y: point.y) 337| | } 338| | 339| | @objc open func isInBounds(x: CGFloat, y: CGFloat) -> Bool 340| | { 341| | return isInBoundsX(x) && isInBoundsY(y) 342| | } 343| | 344| | @objc open func isInBoundsLeft(_ x: CGFloat) -> Bool 345| | { 346| | return contentRect.origin.x <= x + 1.0 347| | } 348| | 349| | @objc open func isInBoundsRight(_ x: CGFloat) -> Bool 350| | { 351| | let x = floor(x * 100.0) / 100.0 352| | return (contentRect.origin.x + contentRect.size.width) >= x - 1.0 353| | } 354| | 355| | @objc open func isInBoundsTop(_ y: CGFloat) -> Bool 356| | { 357| | return contentRect.origin.y <= y 358| | } 359| | 360| | @objc open func isInBoundsBottom(_ y: CGFloat) -> Bool 361| | { 362| | let normalizedY = floor(y * 100.0) / 100.0 363| | return (contentRect.origin.y + contentRect.size.height) >= normalizedY 364| | } 365| | 366| | /** 367| | A method to check whether a line between two coordinates intersects with the view port by using a linear function. 368| | 369| | Linear function (calculus): `y = ax + b` 370| | 371| | Note: this method will not check for collision with the right edge of the view port, as we assume lines run from left 372| | to right (e.g. `startPoint < endPoint`). 373| | 374| | - Parameters: 375| | - startPoint: the start coordinate of the line. 376| | - endPoint: the end coordinate of the line. 377| | */ 378| | @objc open func isIntersectingLine(from startPoint: CGPoint, to endPoint: CGPoint) -> Bool 379| | { 380| | // If start- and/or endpoint fall within the viewport, bail out early. 381| | if isInBounds(point: startPoint) || isInBounds(point: endPoint) { return true } 382| | // check if x in bound when it's a vertical line 383| | if startPoint.x == endPoint.x { return isInBoundsX(startPoint.x) } 384| | 385| | // Calculate the slope (`a`) of the line (e.g. `a = (y2 - y1) / (x2 - x1)`). 386| | let a = (endPoint.y - startPoint.y) / (endPoint.x - startPoint.x) 387| | // Calculate the y-correction (`b`) of the line (e.g. `b = y1 - (a * x1)`). 388| | let b = startPoint.y - (a * startPoint.x) 389| | 390| | // Check for colission with the left edge of the view port (e.g. `y = (a * minX) + b`). 391| | // if a is 0, it's a horizontal line; checking b here is still valid, as b is `point.y` all the time 392| | if isInBoundsY((a * contentRect.minX) + b) { return true } 393| | 394| | // Skip unnecessary check for collision with the right edge of the view port 395| | // (e.g. `y = (a * maxX) + b`), as such a line will either begin inside the view port, 396| | // or intersect the left, top or bottom edges of the view port. Leaving this logic here for clarity's sake: 397| | // if isInBoundsY((a * contentRect.maxX) + b) { return true } 398| | 399| | // While slope `a` can theoretically never be `0`, we should protect against division by zero. 400| | guard a != 0 else { return false } 401| | 402| | // Check for collision with the bottom edge of the view port (e.g. `x = (maxY - b) / a`). 403| | if isInBoundsX((contentRect.maxY - b) / a) { return true } 404| | 405| | // Check for collision with the top edge of the view port (e.g. `x = (minY - b) / a`). 406| | if isInBoundsX((contentRect.minY - b) / a) { return true } 407| | 408| | // This line does not intersect the view port. 409| | return false 410| | } 411| | 412| | /// if the chart is fully zoomed out, return true 413| | @objc open var isFullyZoomedOut: Bool 414| | { 415| | return isFullyZoomedOutX && isFullyZoomedOutY 416| | } 417| | 418| | /// `true` if the chart is fully zoomed out on it's y-axis (vertical). 419| | @objc open var isFullyZoomedOutY: Bool 420| | { 421| | return !(scaleY > minScaleY || minScaleY > 1.0) 422| | } 423| | 424| | /// `true` if the chart is fully zoomed out on it's x-axis (horizontal). 425| | @objc open var isFullyZoomedOutX: Bool 426| | { 427| | return !(scaleX > minScaleX || minScaleX > 1.0) 428| | } 429| | 430| | /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the x-axis. 431| | @objc open func setDragOffsetX(_ offset: CGFloat) 432| | { 433| | transOffsetX = offset 434| | } 435| | 436| | /// Set an offset in pixels that allows the user to drag the chart over it's bounds on the y-axis. 437| | @objc open func setDragOffsetY(_ offset: CGFloat) 438| | { 439| | transOffsetY = offset 440| | } 441| | 442| | /// `true` if both drag offsets (x and y) are zero or smaller. 443| | @objc open var hasNoDragOffset: Bool 444| | { 445| | return transOffsetX <= 0.0 && transOffsetY <= 0.0 446| | } 447| | 448| | /// `true` if the chart is not yet fully zoomed out on the x-axis 449| | @objc open var canZoomOutMoreX: Bool 450| | { 451| | return scaleX > minScaleX 452| | } 453| | 454| | /// `true` if the chart is not yet fully zoomed in on the x-axis 455| | @objc open var canZoomInMoreX: Bool 456| | { 457| | return scaleX < maxScaleX 458| | } 459| | 460| | /// `true` if the chart is not yet fully zoomed out on the y-axis 461| | @objc open var canZoomOutMoreY: Bool 462| | { 463| | return scaleY > minScaleY 464| | } 465| | 466| | /// `true` if the chart is not yet fully zoomed in on the y-axis 467| | @objc open var canZoomInMoreY: Bool 468| | { 469| | return scaleY < maxScaleY 470| | } 471| |} <<<<<< EOF # path=./ChartsTests.xctest.coverage.txt 1| |import XCTest 2| |import FBSnapshotTestCase 3| |@testable import Charts 4| | 5| |class BarChartTests: FBSnapshotTestCase 6| |{ 7| | override func setUp() 8| 31| { 9| 31| super.setUp() 10| 31| 11| 31| // Set to `true` to re-capture all snapshots 12| 31| self.recordMode = false 13| 31| } 14| | 15| | override func tearDown() 16| 31| { 17| 31| // Put teardown code here. This method is called after the invocation of each test method in the class. 18| 31| super.tearDown() 19| 31| } 20| | 21| | //MARK: Prepare 22| | func setupCustomValuesDataEntries(values: [Double]) -> [ChartDataEntry] 23| 28| { 24| 28| var entries: [ChartDataEntry] = Array() 25| 28| for (i, value) in values.enumerated() 26| 840| { 27| 840| entries.append(BarChartDataEntry(x: Double(i), y: value, icon: UIImage(named: "icon", in: Bundle(for: self.classForCoder), compatibleWith: nil))) 28| 840| } 29| 28| return entries 30| 28| } 31| | 32| | func setupDefaultValuesDataEntries() -> [ChartDataEntry] 33| 11| { 34| 11| let values: [Double] = [8, 104, -81, 93, 52, -44, 97, 101, -75, 28, 35| 11| -76, 25, 20, -13, 52, 44, -57, 23, 45, -91, 36| 11| 99, 14, -84, 48, 40, -71, 106, 41, -45, 61] 37| 11| return setupCustomValuesDataEntries(values: values) 38| 11| } 39| | 40| | func setupPositiveValuesDataEntries() -> [ChartDataEntry] 41| 8| { 42| 8| let values: [Double] = [8, 104, 81, 93, 52, 44, 97, 101, 75, 28, 43| 8| 76, 25, 20, 13, 52, 44, 57, 23, 45, 91, 44| 8| 99, 14, 84, 48, 40, 71, 106, 41, 45, 61] 45| 8| return setupCustomValuesDataEntries(values: values) 46| 8| } 47| | 48| | func setupNegativeValuesDataEntries() -> [ChartDataEntry] 49| 8| { 50| 8| let values: [Double] = [-8, -104, -81, -93, -52, -44, -97, -101, -75, -28, 51| 8| -76, -25, -20, -13, -52, -44, -57, -23, -45, -91, 52| 8| -99, -14, -84, -48, -40, -71, -106, -41, -45, -61] 53| 8| return setupCustomValuesDataEntries(values: values) 54| 8| } 55| | 56| | func setupZeroValuesDataEntries() -> [ChartDataEntry] 57| 1| { 58| 1| let values = [Double](repeating: 0.0, count: 30) 59| 1| return setupCustomValuesDataEntries(values: values) 60| 1| } 61| | 62| | func setupStackedValuesDataEntries() -> [ChartDataEntry] 63| 3| { 64| 3| var entries: [ChartDataEntry] = Array() 65| 3| entries.append(BarChartDataEntry(x: 0, yValues: [28, 50, 60, 30, 42], icon: UIImage(named: "icon"))) 66| 3| entries.append(BarChartDataEntry(x: 1, yValues: [-20, -36, -52, -40, -15], icon: UIImage(named: "icon"))) 67| 3| entries.append(BarChartDataEntry(x: 2, yValues: [10, 30, 40, 90, 72], icon: UIImage(named: "icon"))) 68| 3| entries.append(BarChartDataEntry(x: 3, yValues: [-40, -50, -30, -60, -20], icon: UIImage(named: "icon"))) 69| 3| entries.append(BarChartDataEntry(x: 4, yValues: [10, 40, 60, 45, 62], icon: UIImage(named: "icon"))) 70| 3| return entries 71| 3| } 72| | 73| | func setupDefaultStackedDataSet(chartDataEntries: [ChartDataEntry]) -> BarChartDataSet 74| 3| { 75| 3| let dataSet = BarChartDataSet(entries: chartDataEntries, label: "Stacked bar chart unit test data") 76| 3| dataSet.drawIconsEnabled = false 77| 3| dataSet.iconsOffset = CGPoint(x: 0, y: -10.0) 78| 3| dataSet.colors = Array(arrayLiteral:NSUIColor(red: 46/255.0, green: 204/255.0, blue: 113/255.0, alpha: 1.0), 79| 3| NSUIColor(red: 241/255.0, green: 196/255.0, blue: 15/255.0, alpha: 1.0), 80| 3| NSUIColor(red: 231/255.0, green: 76/255.0, blue: 60/255.0, alpha: 1.0), 81| 3| NSUIColor(red: 52/255.0, green: 152/255.0, blue: 219/255.0, alpha: 1.0) 82| 3| ) 83| 3| return dataSet 84| 3| } 85| | 86| | func setupDefaultDataSet(chartDataEntries: [ChartDataEntry]) -> BarChartDataSet 87| 27| { 88| 27| let dataSet = BarChartDataSet(entries: chartDataEntries, label: "Bar chart unit test data") 89| 27| dataSet.drawIconsEnabled = false 90| 27| dataSet.iconsOffset = CGPoint(x: 0, y: -10.0) 91| 27| return dataSet 92| 27| } 93| | 94| | func setupDefaultChart(dataSets: [BarChartDataSet]) -> BarChartView 95| 31| { 96| 31| let data = BarChartData(dataSets: dataSets) 97| 31| data.barWidth = 0.85 98| 31| 99| 31| let chart = BarChartView(frame: CGRect(x: 0, y: 0, width: 480, height: 350)) 100| 31| chart.backgroundColor = NSUIColor.clear 101| 31| chart.data = data 102| 31| return chart 103| 31| } 104| | 105| | //MARK: Start Test 106| | func testDefaultValues() 107| 1| { 108| 1| let dataEntries = setupDefaultValuesDataEntries() 109| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 110| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 111| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 112| 1| 113| 1| } 114| | 115| | func testDefaultBarDataSetLabels() 116| 1| { 117| 1| let dataEntries = setupDefaultValuesDataEntries() 118| 1| let dataSet = BarChartDataSet(entries: dataEntries) 119| 1| dataSet.drawIconsEnabled = false 120| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 121| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 122| 1| } 123| | 124| | func testZeroValues() 125| 1| { 126| 1| let dataEntries = setupZeroValuesDataEntries() 127| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 128| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 129| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 130| 1| } 131| | 132| | func testPositiveValues() 133| 1| { 134| 1| let dataEntries = setupPositiveValuesDataEntries() 135| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 136| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 137| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 138| 1| } 139| | 140| | func testPositiveValuesWithCustomAxisMaximum() 141| 1| { 142| 1| let dataEntries = setupPositiveValuesDataEntries() 143| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 144| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 145| 1| chart.leftAxis.axisMaximum = 50 146| 1| chart.clipValuesToContentEnabled = true 147| 1| chart.notifyDataSetChanged() 148| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 149| 1| } 150| | 151| | func testPositiveValuesWithCustomAxisMaximum2() 152| 1| { 153| 1| let dataEntries = setupPositiveValuesDataEntries() 154| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 155| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 156| 1| chart.leftAxis.axisMaximum = -10 157| 1| chart.notifyDataSetChanged() 158| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 159| 1| } 160| | 161| | func testPositiveValuesWithCustomAxisMinimum() 162| 1| { 163| 1| let dataEntries = setupPositiveValuesDataEntries() 164| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 165| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 166| 1| chart.leftAxis.axisMinimum = 50 167| 1| chart.notifyDataSetChanged() 168| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 169| 1| } 170| | 171| | func testPositiveValuesWithCustomAxisMinimum2() 172| 1| { 173| 1| let dataEntries = setupPositiveValuesDataEntries() 174| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 175| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 176| 1| chart.leftAxis.axisMinimum = 110 177| 1| chart.notifyDataSetChanged() 178| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 179| 1| } 180| | 181| | func testPositiveValuesWithCustomAxisMaximumAndCustomAxisMaximum() 182| 1| { 183| 1| let dataEntries = setupPositiveValuesDataEntries() 184| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 185| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 186| 1| //If min is greater than max, then min and max will be exchanged. 187| 1| chart.leftAxis.axisMaximum = 200 188| 1| chart.leftAxis.axisMinimum = -10 189| 1| chart.notifyDataSetChanged() 190| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 191| 1| } 192| | 193| | func testNegativeValues() 194| 1| { 195| 1| let dataEntries = setupNegativeValuesDataEntries() 196| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 197| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 198| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 199| 1| } 200| | 201| | func testNegativeValuesWithCustomAxisMaximum() 202| 1| { 203| 1| let dataEntries = setupNegativeValuesDataEntries() 204| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 205| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 206| 1| chart.leftAxis.axisMaximum = 10 207| 1| chart.notifyDataSetChanged() 208| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 209| 1| } 210| | 211| | func testNegativeValuesWithCustomAxisMaximum2() 212| 1| { 213| 1| let dataEntries = setupNegativeValuesDataEntries() 214| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 215| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 216| 1| chart.leftAxis.axisMaximum = -150 217| 1| chart.notifyDataSetChanged() 218| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 219| 1| } 220| | 221| | 222| | func testNegativeValuesWithCustomAxisMinimum() 223| 1| { 224| 1| let dataEntries = setupNegativeValuesDataEntries() 225| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 226| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 227| 1| chart.leftAxis.axisMinimum = -200 228| 1| chart.notifyDataSetChanged() 229| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 230| 1| } 231| | 232| | func testNegativeValuesWithCustomAxisMinimum2() 233| 1| { 234| 1| let dataEntries = setupNegativeValuesDataEntries() 235| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 236| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 237| 1| chart.leftAxis.axisMinimum = 10 238| 1| chart.notifyDataSetChanged() 239| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 240| 1| } 241| | 242| | func testNegativeValuesWithCustomAxisMaximumAndCustomAxisMaximum() 243| 1| { 244| 1| let dataEntries = setupNegativeValuesDataEntries() 245| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 246| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 247| 1| //If min is greater than max, then min and max will be exchanged. 248| 1| chart.leftAxis.axisMaximum = 10 249| 1| chart.leftAxis.axisMinimum = -200 250| 1| chart.notifyDataSetChanged() 251| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 252| 1| } 253| | 254| | func testHidesValues() 255| 1| { 256| 1| let dataEntries = setupDefaultValuesDataEntries() 257| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 258| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 259| 1| dataSet.drawValuesEnabled = false 260| 1| chart.notifyDataSetChanged() 261| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 262| 1| } 263| | 264| | func testNotDrawValueAboveBars() 265| 1| { 266| 1| let dataEntries = setupDefaultValuesDataEntries() 267| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 268| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 269| 1| chart.drawValueAboveBarEnabled = false 270| 1| chart.notifyDataSetChanged() 271| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 272| 1| } 273| | 274| | func testStackedDrawValues() 275| 1| { 276| 1| let dataEntries = setupStackedValuesDataEntries() 277| 1| let dataSet = setupDefaultStackedDataSet(chartDataEntries: dataEntries) 278| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 279| 1| chart.notifyDataSetChanged() 280| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 281| 1| } 282| | 283| | func testStackedNotDrawValues() 284| 1| { 285| 1| let dataEntries = setupStackedValuesDataEntries() 286| 1| let dataSet = setupDefaultStackedDataSet(chartDataEntries: dataEntries) 287| 1| dataSet.drawValuesEnabled = false 288| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 289| 1| chart.notifyDataSetChanged() 290| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 291| 1| } 292| | 293| | func testStackedNotDrawValuesAboveBars() 294| 1| { 295| 1| let dataEntries = setupStackedValuesDataEntries() 296| 1| let dataSet = setupDefaultStackedDataSet(chartDataEntries: dataEntries) 297| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 298| 1| chart.drawValueAboveBarEnabled = false 299| 1| chart.notifyDataSetChanged() 300| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 301| 1| } 302| | 303| | func testHideLeftAxis() 304| 1| { 305| 1| let dataEntries = setupDefaultValuesDataEntries() 306| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 307| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 308| 1| chart.leftAxis.enabled = false 309| 1| chart.notifyDataSetChanged() 310| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 311| 1| } 312| | 313| | func testHideRightAxis() 314| 1| { 315| 1| let dataEntries = setupDefaultValuesDataEntries() 316| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 317| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 318| 1| chart.rightAxis.enabled = false 319| 1| chart.notifyDataSetChanged() 320| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 321| 1| } 322| | 323| | func testInvertedLeftAxis() 324| 1| { 325| 1| let dataEntries = setupDefaultValuesDataEntries() 326| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 327| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 328| 1| chart.leftAxis.inverted = true 329| 1| chart.notifyDataSetChanged() 330| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 331| 1| } 332| | 333| | func testInvertedLeftAxisWithNegativeValues() 334| 1| { 335| 1| let dataEntries = setupNegativeValuesDataEntries() 336| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 337| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 338| 1| chart.leftAxis.inverted = true 339| 1| chart.notifyDataSetChanged() 340| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 341| 1| } 342| | 343| | func testInvertedLeftAxisWithPositiveValues() 344| 1| { 345| 1| let dataEntries = setupPositiveValuesDataEntries() 346| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 347| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 348| 1| chart.leftAxis.inverted = true 349| 1| chart.notifyDataSetChanged() 350| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 351| 1| } 352| | 353| | func testInvertedRightAxis() 354| 1| { 355| 1| let dataEntries = setupDefaultValuesDataEntries() 356| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 357| 1| dataSet.axisDependency = .right 358| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 359| 1| chart.rightAxis.inverted = true 360| 1| chart.notifyDataSetChanged() 361| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 362| 1| } 363| | 364| | func testInvertedRightAxisWithNegativeValues() 365| 1| { 366| 1| let dataEntries = setupNegativeValuesDataEntries() 367| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 368| 1| dataSet.axisDependency = .right 369| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 370| 1| chart.rightAxis.inverted = true 371| 1| chart.notifyDataSetChanged() 372| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 373| 1| } 374| | 375| | func testInvertedRightAxisWithPositiveValues() 376| 1| { 377| 1| let dataEntries = setupPositiveValuesDataEntries() 378| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 379| 1| dataSet.axisDependency = .right 380| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 381| 1| chart.rightAxis.inverted = true 382| 1| chart.notifyDataSetChanged() 383| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 384| 1| } 385| | 386| | func testHideHorizontalGridlines() 387| 1| { 388| 1| let dataEntries = setupDefaultValuesDataEntries() 389| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 390| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 391| 1| chart.leftAxis.drawGridLinesEnabled = false 392| 1| chart.rightAxis.drawGridLinesEnabled = false 393| 1| chart.notifyDataSetChanged() 394| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 395| 1| } 396| | 397| | func testHideVerticalGridlines() 398| 1| { 399| 1| let dataEntries = setupDefaultValuesDataEntries() 400| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 401| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 402| 1| chart.xAxis.drawGridLinesEnabled = false 403| 1| chart.notifyDataSetChanged() 404| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 405| 1| } 406| | 407| | func testDrawIcons() 408| 1| { 409| 1| let dataEntries = setupDefaultValuesDataEntries() 410| 1| let dataSet = setupDefaultDataSet(chartDataEntries: dataEntries) 411| 1| let chart = setupDefaultChart(dataSets: [dataSet]) 412| 1| dataSet.drawIconsEnabled = true 413| 1| chart.notifyDataSetChanged() 414| 1| ChartsSnapshotVerifyView(chart, identifier: Snapshot.identifier(UIScreen.main.bounds.size), overallTolerance: Snapshot.tolerance) 415| 1| } 416| |} <<<<<< EOF # path=fixes ./Source/Charts/Filters/DataApproximator.swift:11,14,17,20,23,25,26,28,32,39,42,43,57,59,62,63,66,68,70,72,74,77,78,79,81,84,88,89,90,92,95,98,100,102,108,109,111,113,114,115 ./Source/Charts/Filters/DataApproximator+N.swift:10,13,18,21,23,24,25,29,32,36,39,42,43,46,48,52,53,54,55,56,60,61,64,65,66,67,72,75,77,79,84,88,91,94,97,100,101,106,107,111,112,114,118,119,121,122,127,128,131,135,138,142,146,147,148,150,151,152,153 ./Source/Charts/Renderers/RadarChartRenderer.swift:11,14,16,20,24,26,28,30,32,34,35,37,39,41,43,45,48,55,56,58,60,61,62,63,71,73,75,78,80,83,88,91,99,101,103,105,108,110,112,113,115,118,120,122,123,127,130,140,141,143,144,147,150,151,153,156,158,160,162,164,165,166,169,173,177,184,185,188,189,191,193,194,196,201,204,206,209,211,213,215,220,222,224,226,228,230,233,235,237,239,249,250,252,256,260,261,262,263,264,266,268,269,271,273,278,280,282,287,289,294,297,299,302,307,309,310,315,317,319,321,323,326,331,333,334,335,337,338,340,342,347,349,351,354,356,358,363,366,368,370,371,374,376,378,380,381,383,385,388,390,393,395,397,400,402,404,406,407,416,417,418,419,421,422,431,433,435,439,441,442,445,446,448,454,455,457,458,463,466,469,471,472 ./Source/Charts/Renderers/LegendRenderer.swift:11,14,17,19,22,24,27,29,30,33,35,37,39,42,45,49,53,55,58,61,63,65,66,74,76,77,79,83,85,86,88,90,92,100,102,103,105,109,111,112,115,117,125,127,135,137,139,140,142,144,147,149,151,153,154,162,164,165,166,167,169,170,173,174,176,178,180,182,183,188,190,195,200,203,207,209,211,213,215,217,219,220,222,224,225,227,229,231,233,235,236,238,240,241,243,245,247,249,252,253,257,261,263,265,267,269,270,271,272,274,276,280,283,285,288,291,294,295,297,299,303,306,309,310,314,319,320,322,324,326,328,329,336,338,340,341,342,344,346,348,349,351,353,354,362,364,366,367,369,371,373,374,375,377,381,383,385,391,397,399,401,402,404,408,410,412,414,416,418,420,421,428,430,432,433,434,436,438,440,442,444,445,447,449,450,452,454,456,459,460,464,466,469,470,471,472,473,475,483,488,491,493,494,496,499,501,505,509,512,515,517,520,522,526,528,530,532,534,536,537,539,545,546,547,550,552,553 ./Source/Charts/Renderers/YAxisRendererHorizontalBarChart.swift:11,14,16,18,20,21,24,26,31,34,37,38,40,41,44,49,52,55,58,60,63,66,69,72,74,81,82,84,89,92,96,98,100,102,103,106,109,111,114,116,117,124,127,130,132,134,140,141,142,144,150,151,155,160,161,163,165,168,170,171,174,179,182,187,190,192,194,196,198,200,201,205,206,208,210,212,214,217,220,222,225,230,233,237,241,243,245,247,248,250,252,255,257,260,263,265,270,275,280,285,286,291,292,293,294 ./Source/Charts/Renderers/BubbleChartRenderer.swift:11,14,16,19,21,23,25,27,28,30,35,39,46,47,49,51,52,56,57,63,69,70,73,75,77,79,81,83,85,90,92,95,97,102,104,106,110,113,119,121,123,130,133,136,144,145,147,148,149,150,152,159,162,164,166,168,170,173,175,178,180,182,184,186,188,192,194,199,205,209,211,219,220,222,227,228,229,230,231,233,234,235,236,238,243,246,248,250,257,259,264,266,268,273,277,280,286,288,290,295,297,304,308,310,311,312,315,317,319,322,323,331,334,337,342,347,350,352,354,356,357 ./Source/Charts/Renderers/LineChartRenderer.swift:11,14,16,22,24,26,28,30,31,33,35,37,39,41,42,44,46,47,48,50,52,54,55,57,60,62,64,66,67,69,72,76,79,82,83,85,86,91,96,97,99,101,103,105,107,110,112,115,117,119,124,128,130,136,138,141,143,147,150,152,157,169,170,171,174,176,179,181,182,184,186,188,190,191,192,194,196,198,200,202,205,208,210,212,215,217,220,222,225,227,239,240,241,244,246,249,251,252,254,256,258,260,261,262,269,273,275,277,278,280,285,289,291,293,295,297,298,299,301,303,305,307,309,313,315,317,320,322,323,326,329,331,334,335,337,339,341,344,346,351,353,355,359,361,363,364,366,368,369,371,373,375,376,382,387,389,390,394,395,397,400,401,403,406,409,415,417,420,422,424,425,427,434,435,442,443,445,453,454,455,456,457,459,461,467,469,471,473,475,476,477,480,484,486,488,491,494,495,498,500,502,505,506,508,509,513,515,517,519,520,522,527,529,531,533,535,537,542,544,546,548,551,553,556,558,560,561,563,565,567,571,573,575,576,578,580,581,583,594,595,597,602,603,604,605,606,607,609,611,612,614,619,621,623,626,630,637,638,640,642,644,648,650,651,654,656,661,668,670,672,676,678,680,681,684,686,687,696,703,704,706,707,709,714,716,720,727,730,732,734,736,738,744,746,747,748,749,750,752,756,757,759,764,766,768,770,774,776,778,780,781,785,787,789,791,792,795,797,799,800,802,804,806,809,810,812,813,815,817,820,821,828,831,832,840,842,850,851,859,860,863,869,870,874,876,878,881,882,891,894,897,902,907,910,912,914,916,917 ./Source/Charts/Renderers/AxisRendererBase.swift:11,14,17,20,23,25,27,30,31,34,36,37,40,42,43,46,48,49,52,54,55,62,64,66,69,72,74,77,79,82,83,84,85,87,88,91,93,96,99,101,105,106,110,114,116,117,122,126,127,129,132,134,138,140,142,145,146,148,150,152,154,156,158,159,161,163,165,167,168,170,172,173,177,181,183,186,187,189,192,193,194,197,199,201,203,204,206,209,211,213,215,216,217,218 ./Source/Charts/Renderers/YAxisRenderer.swift:11,14,15,18,22,24,28,30,31,34,39,42,45,48,50,52,55,57,60,61,63,65,68,70,73,74,75,81,82,84,89,92,96,98,100,102,103,105,110,112,117,118,119,127,130,133,135,137,143,144,145,147,149,151,153,157,162,164,166,168,170,171,174,175,177,180,181,182,184,190,191,195,200,201,203,205,208,210,211,214,219,222,227,230,232,234,236,238,240,241,245,246,248,250,252,254,257,259,261,263,266,271,275,279,283,285,287,289,290,292,294,297,299,302,305,307,312,317,322,327,328,333,334,335,337,339,343,347,350,351,353,354,356,359,362,368,372,373,377,381,383,384,389,392,393,395,398,400,404,407,409,411,413,415,417,419,420,422,424,426,427,431,435,436,439,441,443,445,446,448,451,454,455,456 ./Source/Charts/Renderers/ScatterChartRenderer.swift:11,14,16,18,20,22,24,25,27,29,32,39,40,45,47,49,50,52,54,55,56,58,60,62,64,66,68,70,72,74,76,78,80,84,86,88,89,92,94,95,97,98,100,102,104,105,106,108,113,116,118,120,122,124,126,129,131,133,136,138,140,143,145,147,149,153,155,157,158,162,164,165,171,173,182,183,185,190,191,192,193,194,195,197,198,199,200,202,207,209,211,216,218,220,224,226,228,230,231,234,236,238,240,243,244,246,247 ./Source/Charts/Renderers/CombinedChartRenderer.swift:11,14,16,18,20,22,24,27,30,32,34,36,40,42,44,45,48,50,52,54,56,59,61,63,66,68,70,73,75,77,80,82,84,87,89,91,92,93,94,95,97,99,100,102,105,114,115,120,121,123,125,126,128,130,131,133,135,137,139,141,143,145,147,149,151,153,155,157,158,160,162,164,165,166,168,171,172,175,178,179,181,184,187,192,194,196,198,200,202,203,204,205,208,209 ./Source/Charts/Renderers/CandleStickChartRenderer.swift:11,14,16,18,20,22,24,25,27,29,32,39,40,42,44,45,46,53,55,59,61,65,67,69,71,73,76,78,83,91,93,95,100,102,107,109,114,116,121,122,124,126,129,131,133,135,137,139,141,142,143,145,147,148,151,153,158,160,162,164,166,168,170,173,175,178,179,181,183,185,187,190,192,195,196,198,200,203,204,206,211,216,221,225,228,230,233,235,238,240,242,243,248,249,256,257,259,260,261,264,266,267,269,274,277,279,281,283,285,290,292,294,297,299,301,303,306,308,310,314,316,318,319,321,323,324,326,337,338,340,345,346,347,348,349,350,352,353,354,356,361,363,365,370,372,374,376,377,379,382,384,386,388,390,391,395,397,399,402,403,405,406,411,413,416,418,419 ./Source/Charts/Renderers/BarChartRenderer.swift:11,14,18,20,42,44,46,48,50,52,53,56,58,60,63,65,67,69,71,72,73,80,81,82,84,89,91,94,97,99,101,105,107,109,113,116,118,122,124,128,130,134,135,142,146,152,153,155,162,199,202,205,207,209,211,212,214,217,219,221,223,224,226,231,232,236,240,242,244,246,247,253,254,255,256,258,263,267,274,275,278,281,282,284,286,287,291,292,294,296,298,300,303,307,310,313,315,319,322,324,326,329,331,333,335,338,341,342,343,345,348,350,352,355,356,357,359,361,363,364,368,370,372,375,377,380,381,383,385,389,390,393,401,402,404,405,406,407,415,420,425,427,428,430,433,438,440,445,447,452,454,456,462,464,467,468,470,472,474,476,478,481,484,486,488,490,492,496,498,500,517,518,520,525,528,532,533,534,535,537,539,541,543,545,547,549,551,554,557,560,562,565,567,570,572,575,577,580,581,583,584,586,588,591,596,598,613,614,616,621,622,623,625,629,631,647,648,650,654,657,661,662,663,665,666,667,668,669,670,673,675,677,679,682,683,684,685,687,688,689,690,692,697,701,703,708,710,712,714,717,719,722,724,726,729,731,733,736,737,739,742,743,745,747,749,750,751,752,755,757,758,762,764,767,770,771,781,784,787,792,798,800,802,809,810,813,819,824,825,826,829,831,833,835,836 ./Source/Charts/Renderers/XAxisRenderer.swift:11,14,15,18,22,24,28,30,31,33,35,39,44,47,48,50,51,53,56,59,65,69,70,74,78,80,81,86,89,90,92,95,97,101,104,106,108,110,112,114,116,117,119,121,123,124,128,130,134,135,138,140,142,144,145,147,151,152,154,155,157,159,161,164,166,171,172,174,179,181,185,188,191,194,198,199,200,202,204,209,212,216,218,220,222,223,227,233,234,238,244,245,246,249,251,254,258,262,265,267,269,270,272,274,278,280,283,285,288,290,293,295,296,298,301,302,303,312,313,314,324,331,332,334,340,343,345,350,352,354,356,358,359,361,363,365,367,371,373,374,375,377,383,384,386,388,393,394,396,401,403,405,407,410,415,419,422,423,424,426,430,434,436,438,440,441,443,444,446,448,451,453,455,458,460,465,470,475,480,481,487,488 ./Source/Charts/Renderers/LineRadarRenderer.swift:11,14,17,19,21,22,25,26,30,33,35,37,38,41,45,48,51,53,54 ./Source/Charts/Renderers/BarLineScatterCandleBubbleRenderer.swift:11,14,17,19,21,23,25,27,30,32,33,35,37,39,41,44,47,48,54,56,57,60,62,63,65,67,70,71,74,77,80,83,85,86,87,88,92,94,95,100,102,105,108,112,113,114,117,118,119,123,125,126,129,130,131,135,138,139,142,143,144,147,148,149,151,153,155,156 ./Source/Charts/Renderers/Scatter/XShapeRenderer.swift:13,15,22,25,28,35,36 ./Source/Charts/Renderers/Scatter/SquareShapeRenderer.swift:13,15,22,30,32,41,43,50,51,53,61,62,63 ./Source/Charts/Renderers/Scatter/ShapeRenderer.swift:11,14,17,32 ./Source/Charts/Renderers/Scatter/ChevronDownShapeRenderer.swift:13,15,22,25,28,35,36 ./Source/Charts/Renderers/Scatter/CircleShapeRenderer.swift:13,15,22,30,32,41,43,50,51,53,61,62,63 ./Source/Charts/Renderers/Scatter/CrossShapeRenderer.swift:13,15,22,25,28,35,36 ./Source/Charts/Renderers/Scatter/TriangleShapeRenderer.swift:13,15,22,29,31,37,39,41,46,47,49,51,53,55,62,64,65,66 ./Source/Charts/Renderers/Scatter/ChevronUpShapeRenderer.swift:13,15,22,25,28,35,36 ./Source/Charts/Renderers/XAxisRendererHorizontalBarChart.swift:11,14,16,18,20,22,24,25,27,29,33,38,41,42,44,45,47,49,51,55,60,61,63,69,71,75,78,81,84,88,89,90,93,95,99,101,104,106,110,112,115,123,124,125,126,135,141,142,144,150,151,153,155,160,161,163,168,171,175,177,179,181,182,186,191,192,196,201,202,203,205,207,209,211,213,215,217,220,225,229,233,237,239,241,243,244,246,248,251,253,256,259,261,266,271,276,281,282,287,288,289,290 ./Source/Charts/Renderers/HorizontalBarChartRenderer.swift:11,14,18,19,21,23,25,26,28,30,31,34,36,38,41,43,45,47,49,50,51,53,57,59,60,61,63,65,66,67,69,74,76,80,86,88,90,92,95,97,106,109,111,113,115,116,121,124,126,130,133,135,137,141,143,147,149,153,154,163,167,172,175,176,177,178,179,181,183,185,187,190,194,196,199,201,205,207,209,211,214,216,218,220,221,223,225,226,229,232,233,234,236,238,240,242,243,247,249,251,253,255,256,258,260,261,263,266,267,269,271,275,276,279,287,288,290,291,292,294,295,303,308,313,315,316,318,321,326,328,330,335,337,342,344,346,349,351,353,355,357,359,362,364,366,368,370,372,374,375,377,379,380,382,384,385,392,397,399,402,403,405,417,418,420,424,427,431,432,433,435,437,439,441,443,445,447,450,452,454,455,457,459,460,462,464,465,472,477,479,482,483,485,497,498,500,504,507,511,512,514,517,520,522,525,527,530,532,535,537,540,541,543,544,546,548,555,560,562,565,566,568,571,573,575,576,578,580,581,583,585,586,588,598,599,601,606,607,608,609,611,612,613,614,615,616,618,622,623,626,628,629 ./Source/Charts/Renderers/XAxisRendererRadarChart.swift:11,14,16,18,20,22,24,25,27,33,38,40,43,45,47,51,59,60,61,70,76,77,79,81,82 ./Source/Charts/Renderers/AxisRenderer.swift:11,14,15,17,19,22,25,28,31,34,37,42,45 ./Source/Charts/Renderers/DataRenderer.swift:11,14,17,26,28,30,32,34,40,44,46,57,58,63,67,73,75,76 ./Source/Charts/Renderers/YAxisRendererRadarChart.swift:11,14,15,17,19,21,23,25,26,28,31,36,40,41,45,49,51,52,56,58,62,63,66,69,71,75,78,80,82,84,86,88,90,91,93,95,97,98,100,104,108,109,112,114,116,118,119,121,124,125,129,130,132,138,141,144,146,149,152,165,166,167,169,174,176,178,181,183,186,188,190,194,196,198,200,201,203,205,207,212,214,215,218,219,220 ./Source/Charts/Renderers/PieChartRenderer.swift:11,14,18,19,21,23,25,27,29,31,35,37,38,40,42,44,46,49,52,54,55,56,57,66,68,72,76,81,87,90,95,97,98,101,106,109,113,115,116,118,120,123,126,133,136,139,141,142,143,145,147,157,164,166,169,171,173,177,178,181,184,186,187,189,191,198,200,201,204,206,209,211,214,216,226,228,230,231,238,240,242,247,249,251,253,255,265,268,273,275,277,278,279,281,285,291,292,294,295,298,300,301,303,308,310,316,319,321,323,325,326,328,330,332,336,339,342,344,346,348,350,352,353,355,357,360,364,366,368,371,373,375,377,379,380,384,387,389,391,398,401,406,409,411,415,419,421,423,425,427,429,430,434,438,442,444,448,450,454,455,457,459,461,463,465,467,469,471,475,477,478,480,487,489,497,498,500,502,510,511,513,521,522,523,525,529,531,537,539,546,547,549,551,558,559,561,567,568,569,571,573,577,581,582,584,585,586,587,589,592,593,595,597,600,601,604,606,608,610,614,616,618,622,623,624,627,630,633,637,651,652,653,655,656,657,660,665,667,671,674,681,683,685,686,688,693,695,700,702,704,705,706,708,713,715,718,721,728,731,733,740,742,743,747,750,752,753,754,756,758,760,762,763,765,768,771,773,775,779,783,787,789,790,794,796,797,799,802,805,808,817,818,821,823,826,828,830,831,838,840,842,847,851,853,855,857,860,865,867,869,870,871,873,877,884,885,887,888,893,894,896,897,904,906,909,911,917,925,927,928,934,937,939,940,943,944 ./Source/Charts/Renderers/Renderer.swift:11,14,17,20 ./Source/Charts/Renderers/LineScatterCandleRadarRenderer.swift:11,14,17,19,21,22,29,30,33,38,39,42,47,48,49 ./Source/Charts/Highlight/BarHighlighter.swift:11,14,17,19,24,26,29,34,36,38,39,40,42,44,45,47,49,50,63,68,71,73,74,79,84,92,93,99,106,107,108 ./Source/Charts/Highlight/RadarHighlighter.swift:11,14,17,19,21,23,25,28,30,33,36,37,38,40,41,47,49,54,59,61,63,65,68,71,72,74,75 ./Source/Charts/Highlight/Highlight.swift:11,14,17,20,23,26,29,32,35,40,43,46,49,51,53,54,71,73,82,83,98,104,105,120,122,129,130,137,142,143,149,152,153,161,163,166,169,170,173,176,177,179,181,183,184,185,186,191,193,195,196,202,203 ./Source/Charts/Highlight/PieHighlighter.swift:11,14,17,19,24,26,27 ./Source/Charts/Highlight/HorizontalBarHighlighter.swift:11,14,17,19,21,24,27,32,33,35,36,42,44,47,50,51,56,57,58,60,62,63 ./Source/Charts/Highlight/PieRadarHighlighter.swift:11,14,17,19,21,23,26,29,30,32,34,36,37,39,42,44,46,48,49,50,51,58,60,61 ./Source/Charts/Highlight/Range.swift:11,13,16,19,21,24,26,27,32,34,36,38,40,41,42,44,46,47,49,51,52 ./Source/Charts/Highlight/ChartHighlighter.swift:11,14,16,19,21,23,24,26,29,30,35,37,40,41,48,50,53,56,58,60,62,63,71,73,75,77,81,82,84,85,92,94,97,100,101,105,107,108,109,111,119,122,124,126,128,130,133,134,135,136,138,139,147,149,152,154,155,156,158,159,161,163,164,166,168,169,171,173,174 ./Source/Charts/Highlight/CombinedHighlighter.swift:11,14,17,20,22,24,27,28,30,32,37,39,41,45,48,50,52,54,56,59,60,61,62,63,65,66 ./Source/Charts/Highlight/Highlighter.swift:11,14,17,23 ./Source/Charts/Charts/CandleStickChartView.swift:11,14,17,19,21,23,26,27,29,31,33,34 ./Source/Charts/Charts/RadarChartView.swift:11,14,15,19,22,25,28,31,34,37,40,43,46,48,50,51,53,55,56,58,60,63,65,68,70,71,73,75,77,80,81,83,85,88,91,93,94,96,98,99,101,103,105,108,110,112,113,115,117,119,120,122,124,125,127,129,131,132,134,136,137,139,141,143,145,147,148,151,155,156,159,161,162,164,167,169,174,175,178,180,181,185,187,189,191,193,194,195,197,199,200,202,204,205,207,210,211,214,217,220 ./Source/Charts/Charts/BarLineChartViewBase.swift:11,14,18,21,25,28,33,36,39,42,45,49,52,57,60,64,68,72,76,80,83,87,94,97,99,101,102,104,106,107,109,111,112,114,116,119,121,126,128,132,135,142,143,145,149,152,153,156,159,162,164,166,167,168,170,172,174,177,180,181,183,185,186,188,190,191,193,195,196,198,200,201,205,208,212,213,215,217,218,220,222,223,225,227,228,230,234,235,237,240,244,245,248,250,251,253,255,257,259,260,262,264,265,267,269,270,274,276,279,281,283,285,287,288,290,292,294,295,298,301,304,306,308,310,312,314,315,317,319,320,322,323,325,328,329,331,334,335,337,339,341,344,346,351,353,354,356,358,359,361,364,368,369,371,374,376,378,380,383,386,388,390,393,396,399,400,401,403,405,408,411,414,415,416,417,418,420,422,427,432,435,437,438,440,442,443,445,447,450,452,454,456,458,461,462,463,468,474,475,478,479,482,484,486,487,489,493,494,496,500,501,503,505,506,507,509,511,515,516,523,525,529,531,533,535,536,538,540,542,544,547,549,552,553,554,555,557,559,561,562,564,566,569,571,573,575,577,578,581,582,583,584,587,589,591,594,596,598,600,602,605,607,609,611,613,614,615,616,619,621,623,627,628,630,634,636,640,643,645,647,649,651,652,655,659,661,663,665,667,668,669,671,672,673,675,677,679,681,683,685,686,691,693,695,698,700,702,704,705,707,710,712,716,717,719,721,724,725,726,728,730,732,734,735,737,739,742,744,746,748,750,751,753,755,757,759,761,763,766,767,768,770,772,774,776,779,782,783,785,787,788,790,793,794,795,796,798,800,802,804,806,808,810,811,812,814,817,819,821,823,824,827,828,830,834,835,837,839,842,843,844,846,848,851,853,858,860,864,865,867,869,871,875,876,877,879,881,887,889,890,892,895,897,899,900,902,903,905,906,909,911,913,914,916,918,921,923,925,927,931,933,935,939,942,944,945,950,952,953,955,957,959,960,963,964,966,968,970,971,972,974,975,977,980,982,985,989,990,993,995,998,1002,1003,1006,1009,1013,1014,1028,1031,1035,1036,1052,1063,1064,1076,1084,1085,1104,1108,1125,1127,1128,1147,1149,1150,1168,1170,1171,1174,1177,1180,1181,1184,1187,1188,1190,1192,1193,1200,1203,1204,1211,1214,1215,1223,1229,1230,1237,1240,1241,1248,1251,1252,1260,1264,1265,1269,1276,1278,1279,1287,1289,1296,1298,1299,1308,1310,1317,1319,1320,1336,1340,1342,1353,1355,1356,1372,1374,1375,1390,1392,1393,1405,1408,1415,1417,1418,1433,1437,1440,1451,1453,1454,1469,1471,1472,1486,1488,1489,1493,1495,1497,1501,1503,1507,1508,1509,1512,1515,1516,1518,1521,1523,1525,1527,1529,1530,1531,1534,1536,1538,1540,1541,1544,1546,1548,1550,1553,1554,1555,1558,1560,1561,1564,1566,1568,1570,1572,1573,1574,1577,1579,1581,1583,1585,1586,1587,1590,1592,1598,1599,1600,1602,1604,1606,1608,1610,1615,1616,1617,1618,1620,1622,1624,1626,1628,1633,1634,1635,1636,1639,1642,1644,1646,1648,1650,1653,1654,1655,1656,1660,1662,1663,1666,1672,1674,1675,1679,1681,1682,1686,1688,1689,1695,1697,1698,1702,1704,1705,1708,1710,1712,1714,1715,1718,1721,1722,1724,1725,1728,1730,1731,1734,1736,1737,1740,1744,1746,1748,1750,1752,1753,1754,1757,1759,1761,1763,1765,1770,1771,1772,1773,1777,1781,1783,1784,1788,1790,1791,1794,1796,1798,1799,1801,1803,1804,1807,1809,1810,1814,1817,1818,1822,1825,1827,1829,1831,1833,1834,1835,1840,1842,1844,1846,1848,1849,1853,1855,1857,1859,1861,1862,1863,1870,1872,1874,1876,1878,1879,1880,1883,1885,1887,1889,1891,1892,1893,1895,1900,1902,1904,1906,1908,1909,1910,1913,1915,1917,1919,1921,1922,1923,1925,1927,1928,1931,1935,1937,1939,1940,1943,1947,1949,1951,1952 ./Source/Charts/Charts/LineChartView.swift:11,14,17,19,21,23,24,26,28 ./Source/Charts/Charts/CombinedChartView.swift:11,14,17,20,24,30,31,33,35,37,40,42,44,45,47,49,51,53,55,57,60,61,62,64,66,68,70,73,75,76,77,78,81,83,86,87,90,92,101,102,104,106,108,110,111,112,114,116,118,120,121,122,124,126,128,130,131,132,134,136,138,140,141,142,144,146,148,150,151,152,154,156,158,160,161,162,164,167,170,171,174,177,178,181,184,189,191,193,195,197,198,199,202,205,207,210,215,217,219,224,227,229,230,232,235,237,238,241,244,245,246 ./Source/Charts/Charts/HorizontalBarChartView.swift:11,14,17,19,21,24,29,31,32,34,39,42,45,48,51,53,55,58,61,64,65,66,69,72,75,77,78,81,84,86,89,90,91,92,94,99,104,107,109,110,112,114,115,117,119,122,124,126,128,130,133,134,135,140,146,149,150,152,155,156,158,160,161,163,168,171,173,178,180,182,184,185,187,189,191,193,194,196,198,201,202,204,205,208,212,214,216,217,220,224,226,228,229,231,233,236,237,239,242,243,245,249,250,252,255,256,258,261,262,264,268,269 ./Source/Charts/Charts/ChartViewBase.swift:12,15,19,22,29,32,35,38,41,44,45,47,49,52,55,57,59,61,64,66,68,70,71,72,75,76,77,80,83,87,90,93,96,99,102,105,108,111,114,117,119,123,130,133,136,140,144,147,150,153,156,159,162,164,169,170,172,174,177,178,180,183,184,186,189,190,192,196,199,200,202,205,210,212,213,216,219,220,223,225,226,230,232,233,236,238,239,242,244,245,248,251,253,255,257,261,262,263,265,269,270,271,273,276,278,281,286,295,297,298,300,303,304,305,308,310,317,320,325,330,331,333,336,337,339,344,347,348,351,353,354,359,361,362,368,371,375,376,379,380,390,392,393,404,406,407,417,419,420,431,433,436,437,439,441,443,445,446,447,454,456,457,460,466,470,472,474,475,478,480,483,484,487,488,493,495,498,499,501,502,505,507,510,517,519,524,527,529,532,535,538,539,540,543,545,546,548,558,560,561,571,573,574,583,585,586,595,597,598,606,608,609,617,619,620,628,630,631,638,640,641,649,651,652,660,662,663,670,672,673,675,678,680,681,684,686,687,689,691,692,694,696,697,699,701,702,706,709,710,713,715,716,719,721,722,725,727,730,732,734,738,740,743,744,745,747,749,751,753,754,756,759,760,772,774,777,780,781,783,785,787,789,791,792,794,795,797,799,801,803,806,808,812,815,818,819,820,821,822,824,826,828,829,830,832,834,835,837,839,841,843,845,846,847,851,853,854,858,860,862,864,866,870,871,872,874,878,881,883,884,886,888,890,891,893,895,896,898,900,902,904,905,906,908,910,912,913,914,916,918,920,921,922,924,926,928,929,930 ./Source/Charts/Charts/BubbleChartView.swift:11,14,16,18,20,22,23,25,27 ./Source/Charts/Charts/PieRadarChartViewBase.swift:11,15,19,20,23,26,29,32,35,38,43,45,47,48,50,52,53,55,57,58,60,62,64,66,72,73,75,77,78,80,82,84,85,86,88,90,92,94,95,97,99,100,102,107,109,111,113,115,117,120,122,125,127,129,132,135,137,143,146,149,152,154,156,159,160,161,162,164,167,170,172,174,177,180,183,184,185,187,189,192,197,201,202,204,206,208,210,212,215,216,217,222,223,228,230,232,234,236,238,239,240,245,247,248,252,254,259,261,263,265,266,269,272,274,275,277,278,282,285,286,289,291,293,296,298,300,302,304,305,307,309,311,313,314,317,319,320,324,326,327,333,335,337,339,343,344,345,349,351,352,355,362,363,366,368,369,372,374,375,379,381,382,384,386,387,389,391,392,394,403,405,407,409,414,415,416,425,427,428,430,432,435,437,439,440,444,446,448,449,451,453,454,456,458,459,461,463,465,466,467,469,473,475,478,479,481,485,487,489,491,493,494,496,498,499,501,503,505,506,513,515,517,520,521,522,524,526,528,530,532,534,538,539,540,541,543,545,547,548,549,552,555,557,559,561,562,563,565,567,568,569,571,573,576,577,579,581,582,583,585,587,589,590,592,595,596,598,600,601,602,604,606,608,610,613,616,618,620,622,623,625,627,628,629,631,633,635,637,638,640,642,643,644,646,648,650,651,653,655,657,658,660,662,663,665,667,669,670,672,678,683,685,689,691,693,694,696,697,699,700,702,706,710,716,724,727,729,731,733,734,738,739,742,744,747,748,751,753,754,756,758,761,762,763,765,767,769,771,773,775,777,779,780,781,784,788,789,792,796,797,800,802,804,806,808,811,812,813,816,818,820,822,823,825,827,830,832,834,837,839,841,843,845,849,850,851,852,854 ./Source/Charts/Charts/BarChartView.swift:11,14,17,20,23,25,27,29,31,34,35,37,40,42,46,48,50,51,59,60,63,65,68,69,72,74,83,84,87,92,95,97,102,104,106,108,109,119,122,125,126,129,130,138,140,141,143,146,149,152,153,154,157,160,163,164,165,169,173,176,178,180,183,186 ./Source/Charts/Charts/ScatterChartView.swift:11,14,17,19,21,23,26,27,29,31 ./Source/Charts/Charts/PieChartView.swift:11,14,18,21,24,27,30,33,36,38,41,44,47,50,53,56,61,63,66,69,71,74,76,78,79,81,83,84,86,88,90,92,93,95,97,99,101,102,105,107,108,110,112,114,115,117,119,121,123,125,126,129,131,132,135,137,138,141,145,146,148,150,153,155,156,158,160,162,168,169,170,172,174,175,177,179,184,186,188,190,191,194,197,199,200,202,203,206,209,212,215,217,218,220,223,225,227,229,230,232,234,236,239,243,245,246,249,252,254,256,259,261,263,265,267,269,271,273,275,277,279,280,282,283,284,285,288,290,291,294,296,297,300,302,303,306,309,310,312,317,318,321,326,327,332,334,335,339,341,342,347,349,351,353,356,357,358,363,365,367,369,372,373,374,377,379,380,383,385,387,389,392,393,394,397,399,401,402,403,406,408,410,412,415,417,419,423,430,432,433,434,437,439,441,443,446,447,448,451,453,455,457,460,461,462,465,467,469,471,474,475,476,479,481,483,484,485,487,489,490,492,494,495,497,499,500,503,505,506,509,511,512,517,519,521,523,526,527,528,533,535,537,539,542,543,544,549,551,553,555,558,559,560,563,566,569,570,571,574,577,580,581,582,585,587,589,591,594,595,596,599,601,603,604,605,608,610,612,614,617,618,619,622,624,626,627,628,631,633,635,637,640,641,642,647,649,651,653,655,657,659,660,662,664,665,666,667,670,673,674,675 ./Source/Charts/Animation/ChartAnimationEasing.swift:11,14,17,49,50,52,54,56,119,120,121,123,125,129,130,134,135,139,141,142,144,145,149,150,155,156,160,162,165,166,170,171,176,177,181,183,186,187,191,192,197,198,202,204,206,209,210,211,215,216,220,221,225,226,229,230,233,234,237,239,241,243,244,247,249,250,253,254,258,259,264,265,269,271,274,275,278,280,281,284,286,287,292,293,296,298,299,302,304,305,309,310,313,315,316,319,321,322,326,329,332,333,338,339,345,346,351,354,358,359,362,363,367,369,371,374,376,379,381,384,385,386,389,391,393,394 ./Source/Charts/Animation/Animator.swift:11,15,18,21,24,25,28,32,35,38,42,45,49,52,55,57,59,60,62,64,65,67,69,72,75,78,81,84,85,88,89,91,93,98,100,101,103,104,106,111,113,114,116,117,118,120,122,124,127,129,131,132,133,143,145,155,158,161,163,166,167,168,178,180,181,190,192,193,202,204,205,213,219,221,224,227,230,231,232,240,242,243,251,257,259,262,265,268,269,270,278,280,281 ./Source/Charts/Formatters/DefaultFillFormatter.swift:11,14,18,22,24,26,28,30,31,33,35,36,40,43,45,47,49,52,54,55,57,58 ./Source/Charts/Formatters/AxisValueFormatter.swift:11,13,17,18,29,30 ./Source/Charts/Formatters/DefaultValueFormatter.swift:11,13,17,23,25,27,29,31,33,34,35,37,39,41,42,43,45,47,51,52,53,55,60,63,64,66,69,71,72,74,79,82,83,85,88,90,91,96,98,99,104,109,110,111 ./Source/Charts/Formatters/DefaultAxisValueFormatter.swift:11,13,16,20,22,24,27,30,33,34,35,39,42,44,46,50,51,52,53,55,57,60,61,63,65,67,68,70,72,77,78,80,82,84,85,87,89,90,93,98,99,100 ./Source/Charts/Formatters/ValueFormatter.swift:11,13,18,21,22,36 ./Source/Charts/Formatters/IndexAxisValueFormatter.swift:11,13,17,19,21,23,24,25,27,29,31,32,34,36,37,40,44,45 ./Source/Charts/Formatters/FillFormatter.swift:11,14,18,21 ./Source/Charts/Utils/Fill.swift:11,14,17,18,21,22,25,26,28,29,32,33,35,37,40,41,43,45,46,48,51,54,55,56,59,60,63,65,69,70,72,74,75,77,80,83,84,85,88,89,91,93,96,97,99,102,105,106,107,110,111,114,116,120,121,123,126,139,147,148,149,152,153,159,166,173,174,176,184,185,187,190,193,209,210 ./Source/Charts/Utils/Transformer.swift:11,14,18,21,24,26,28,30,31,34,37,39,41,43,45,46,51,52,55,57,59,61,64,65,66,70,73,74,76,78,79,81,83,84,87,89,90,93,100,102,103,106,108,109,112,118,120,121,124,127,128,131,134,135,138,140,141,146,148,149,154,156,157,159,164,165,167,169,170 ./Source/Charts/Utils/Platform+Touch Handling.swift:11,14,17,21,23,24,26,28,29,31,33,34,36,38,39,41,43,44,46,48,49,51,53,54,56,58,59,60,62,64,66,67,69,70,73,76,79,81,83,84,86,88,89,91,93,94,96,98,99,101,103,104,106,108,109,111,113,114,116,118,119,120,122,125,132,133 ./Source/Charts/Utils/ViewPortHandler.swift:11,14,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,62,64,65,67,72,75,77,78,80,83,84,86,91,92,94,96,97,99,101,102,104,106,107,109,111,112,114,116,117,119,121,122,124,126,127,129,131,132,134,136,137,139,141,142,144,146,147,149,152,154,155,158,162,163,166,168,169,172,174,175,178,180,181,184,189,190,193,201,202,205,208,210,211,214,217,219,221,222,227,230,233,234,237,239,242,244,246,247,250,253,256,259,262,265,270,271,274,277,278,281,284,285,288,292,293,296,299,300,303,306,307,309,310,314,315,317,319,321,322,324,326,327,330,335,337,338,340,342,343,345,347,348,350,353,354,356,358,359,361,364,365,368,370,373,379,384,389,393,398,401,404,407,410,411,414,416,417,420,422,423,426,428,429,432,434,435,438,440,441,444,446,447,450,452,453,456,458,459,462,464,465,468,470,471 ./Source/Charts/Utils/Platform+Gestures.swift:11,15,21,23,25,27,28,30,32,34,36,38,39,40,41,43,45,47,48,50,52,53,54,58,60,62,65,66,67,69,71,73,75,77,79,80,81,83,85,86,89,93,101,104,106,108,109,111,113,115,117,119,120,121,122,124,126,128,129,132,134,135,136,138,141,143,144,146,149,150,151,153,155,157,159,161,163,164,165,168,170,171 ./Source/Charts/Utils/Platform+Graphics.swift:11,13,15,16,18,20,21,23,25,26,30,32,34,35,37,39,40,42,44,45,47,49,50,52,54,55,57,59,60,62,64,65,67,69,71,75,77,79,80,82,86,87,89,91,92,94,99,100,102,107,108,110,112,115,117,118,121,123,125,127,130,134,135,136,138,140,143,146,150,151,153,154,156,158,161,162 ./Source/Charts/Utils/Platform+Accessibility.swift:11,13,16,18,20,21,23,25,26,29,31,33,35,37,38,39,41,43,45,46,47,49,53,54,56,58,60,61,63,66,67,68,69,71,75,77,78,80,83,84,86,88,89,91,93,94,96,99,100,101,103,106,107,109,112,113,115,117,118,121,123,125,127,129,130,131,133,135,137,138,139,141,143,145,146,148,150,151,152,154,156,158,159,161,163,165,168,181,182,183,185,188,190,193,194,195,198,200,202,203,205,207,208,209 ./Source/Charts/Utils/Platform.swift:2,8,14,16,22,25,26,28,29,30,32,34,36,37,38,40,42,45,46,47,49,51,53,54,55,57,59,60,62,66,71,74,78,81,83,85,86,88,91,93,94,96,98,101,104,106,108,109,110,112,114,115,117,119,121,123,125,126,127,129,131,132,134,136,138,140,142,143,144,145,147,153,156,159,160,163,164,165,167,169,171,172,173,175,177,180,181,182,184,189,191,194,195,197,200,201,203,205,206,208,210,211,213,215,219,221,224,225,226,228,230,231,232,234,236,239,240,241,243,245,247,248,249,251,253,255,256,257,259,261,263,265,267,269,270,271,272,274,276,277 ./Source/Charts/Utils/ChartColorTemplates.swift:11,14,16,18,26,27,29,37,38,40,48,49,51,59,60,62,70,71,73,80,81,83,86,88,90,96,98,101,104,108,110,112,115,118,119,121,122,124,126,147,149,170,172,190,191,193,194 ./Source/Charts/Utils/TransformerHorizontalBarChart.swift:11,14,17,20,22,24,26,30,31,32 ./Source/Charts/Utils/Platform+Color.swift:11,14,17,19,21,23,25,26,28,30,32,34,36,38,41,43,45,47,49,50,52,54,56 ./Source/Charts/Utils/ChartUtils.swift:11,14,16,18,20,22,24,26,28,30,31,32,33,35,37,39,40,42,44,45,49,52,53,54,56,58,61,62,64,69,70,71,73,76,82,88,89,91,97,99,104,106,107,108,110,113,116,117,118,120,121,123,127,129,131,133,137,140,142,145,148,149,151,153,155,156,158,159,161,163,165,167,169,171,173,175,176,177,179,181,183,185,187,191,193,196,198,201,202,206,208,210,212,214,216,219,220,223,225,226,228,229,231,233,235,237,239,241,243,244,246,248,250,252,256,258,261,263,266,267,271,273,275,277,279,282,283,286,288,289,291,292,294,297,298 ./Source/Charts/Components/Description.swift:11,14,18,22,25,27,36,38,39,42,45,48,51,54 ./Source/Charts/Components/MarkerImage.swift:11,14,17,20,22,24,27,29,31,32,34,36,38,40,42,44,46,48,49,52,54,56,58,60,61,63,65,67,69,70,72,73,75,77,78,80,82,84,86,88,90,92,94,95,101,105,106 ./Source/Charts/Components/Legend.swift:11,14,17,20,23,26,29,32,35,38,39,42,46,47,50,54,55,58,61,62,65,68,69,72,76,81,84,87,90,93,96,99,102,105,108,111,116,123,128,132,134,136,139,140,142,144,146,147,149,152,154,156,159,161,162,165,167,169,171,173,175,176,177,182,183,188,195,198,205,207,217,220,222,224,229,231,233,237,239,241,242,244,246,248,250,251,253,255,257,259,261,266,267,270,272,275,277,279,280,281,283,284,287,289,291,293,296,298,299,301,303,304,306,308,313,315,319,321,323,326,328,331,332,335,339,341,344,346,349,350,351,353,355,359,362,364,365,369,373,374,376,379,380,381,383,384,388,389,392,393,395,401,404,405,408,410,411,415,417,418 ./Source/Charts/Components/LegendEntry.swift:11,14,17,19,21,22,27,29,30,34,37,44,49,54,61,70,73 ./Source/Charts/Components/YAxis.swift:11,14,18,22,23,29,32,35,36,40,43,44,47,50,53,56,59,62,65,70,73,76,79,82,85,88,93,99,101,103,105,106,108,110,112,114,115,117,119,120,122,129,130,132,134,135,138,140,142,144,146,147,148,150,152,156,160,162,171,172,173,176,179,182,183,186,189,190,193,196,197,200,201,203,205,206 ./Source/Charts/Components/AxisBase.swift:11,14,18,20,22,23,26,29,34,40,43,46,48,52,55,56,58,60,61,64,69,74,77,80,83,86,91,94,100,102,108,110,112,114,116,119,120,121,124,126,128,129,130,133,135,137,139,141,143,145,146,147,149,150,153,155,157,158,163,165,169,171,172,174,176,178,179,180,182,184,186,191,196,199,202,205,208,213,218,221,225,226,230,231,238,240,242,244,247,249,250,251,253,256,257,260,263,265,266,269,272,273,276,278,279,282,284,285,287,290,292,293,295,298,300,301,303,308,310,312,314,318,319,320,325,327,329,331,335,336,337,344,348,351,354,357,358,361,364,365 ./Source/Charts/Components/XAxis.swift:11,14,17,20,26,27,30,33,36,39,42,45,48,54,57,63,65,67,69,70,72,74,75 ./Source/Charts/Components/ChartLimitLine.swift:11,14,15,19,22,27,28,31,36,39,43,45,47,48,50,53,54,56,60,61,64,66,68,70,72,73,74 ./Source/Charts/Components/MarkerView.swift:11,14,18,21,23,25,27,29,31,34,36,38,40,42,43,45,47,49,51,52,54,55,57,59,60,62,64,72,73,76,78,84,87,92,94,95,98,99,100 ./Source/Charts/Components/Marker.swift:11,14,17,22,29,36,39 ./Source/Charts/Components/ComponentBase.swift:11,14,15,19,22,26,30,32,34,35,37 ./Source/Charts/Jobs/AnimatedZoomViewJob.swift:11,14,16,25,42,51,61,62,64,67,70,73,78,80,83,84,86,89,90 ./Source/Charts/Jobs/ViewPortJob.swift:11,14,18,25,32,38,40,41,43,45,46 ./Source/Charts/Jobs/MoveViewJob.swift:11,14,17,19,24,27,28 ./Source/Charts/Jobs/ZoomViewJob.swift:11,14,17,21,31,35,42,43,44,46,49,52,57,59,62,65,66 ./Source/Charts/Jobs/AnimatedViewPortJob.swift:11,15,17,21,26,28,39,44,50,51,53,55,56,58,60,61,63,67,69,72,73,75,77,80,82,84,87,88,90,91,92,94,98,100,102,103,105,107,109,111,113,115,116,117,119,121,122,124,126,127 ./Source/Charts/Jobs/AnimatedMoveViewJob.swift:11,14,16,18,23,26,27 ./Source/Charts/Data/Implementations/Standard/ChartData.swift:11,13,15,16,25,27,32,37,41,43,45,47,48,50,53,54,56,59,60,62,64,65,69,71,72,74,76,79,80,83,92,94,97,99,102,104,106,108,109,111,113,114,115,116,119,121,124,126,128,130,131,133,135,136,137,138,139,142,147,149,153,157,158,159,162,167,169,173,177,178,179,183,185,186,188,191,194,196,198,200,201,204,206,208,210,211,212,213,215,217,219,221,223,225,226,228,230,232,234,236,237,238,239,242,244,246,248,251,252,253,260,263,264,272,275,276,279,282,283,289,292,293,297,300,301,305,306,309,311,314,316,318,319,321,322,328,333,335,336,339,341,342,345,348,349,352,354,355,358,360,361,364,367,368,371,373,374,377,379,380,383,385,386,389,391,392,396,399,400,404,406,407,412,414,415,418,420,421,424,426,427,428,431,434,436,438,439,441,443,444,446,448,449,451,454,455,456,459,461,463,464,465,469,472,475,476,479,483,484,486,488,492,493,495,497,500,501,503,505,509,510,512,514,517,518,520,522,525,526,528,530,533,534,536,538,541,542,543,546,555,559,560,562,565,566,568,570,573,574 ./Source/Charts/Data/Implementations/Standard/BarChartDataEntry.swift:11,13,15,18,21,24,27,29,31,32,35,37,38,41,44,45,48,51,52,55,59,60,63,68,69,72,75,76,79,82,83,86,90,91,93,95,97,98,101,102,105,107,108,111,113,114,116,119,121,123,125,127,128,135,137,139,141,143,145,146,148,151,153,155,158,160,163,164,165,166,168,171,174,177,182,183,184,187,189,190,192,194,201,202,209,212,214,216,218,219,221,222 ./Source/Charts/Data/Implementations/Standard/BarChartDataSet.swift:11,14,15,17,19,21,24,25,27,30,31,33,36,37,39,43,46,50,52,54,55,58,60,62,63,64,66,70,72,75,77,80,81,83,84,87,89,90,93,95,96,99,101,102,105,107,110,113,116,119,121,123,128,134,135 ./Source/Charts/Data/Implementations/Standard/RadarChartDataEntry.swift:11,14,16,18,20,21,25,27,28,33,36,37,39,41,44,45,47,49,51,53,54 ./Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartDataSet.swift:11,14,15,17,19,21,26,28,30,37,38 ./Source/Charts/Data/Implementations/Standard/PieChartDataEntry.swift:11,14,16,18,20,21,22,26,28,29,34,37,38,44,46,47,53,57,58,65,70,71,76,79,80,85,88,89,95,99,100,102,104,106,109,110,112,114,118,119 ./Source/Charts/Data/Implementations/Standard/CandleChartDataEntry.swift:11,13,15,18,21,24,27,29,31,32,34,36,41,42,44,47,48,50,53,54,56,60,61,64,66,67,70,72,73,76,78,80,82,84,85,86,88,90,97,98 ./Source/Charts/Data/Implementations/Standard/CombinedChartData.swift:11,13,15,21,23,25,26,28,30,31,33,35,36,38,40,42,44,47,48,49,51,53,55,57,60,61,62,64,66,68,70,73,74,75,77,79,81,83,86,87,88,90,92,94,96,99,100,101,103,105,110,115,117,119,121,124,126,128,129,131,133,134,136,138,139,141,143,144,146,148,150,152,154,156,157,159,161,163,165,167,168,169,170,171,172,175,177,179,181,183,185,187,189,191,193,195,197,198,200,201,203,205,206,208,210,211,213,215,217,219,220,221,223,224,226,229,230,232,235,236,238,244,246,247,254,259,260,267,269,271,272,274,276,278,279,282,283,285,288,289,292,293 ./Source/Charts/Data/Implementations/Standard/LineChartData.swift:11,13,16,18,20,21,23,25,26,28,30,31 ./Source/Charts/Data/Implementations/Standard/RadarChartData.swift:11,14,15,17,22,25,28,30,31,33,35,36,38,40,41,43,45,46,48,50,51 ./Source/Charts/Data/Implementations/Standard/CandleChartDataSet.swift:11,14,15,17,18,20,22,23,25,27,28,30,32,35,38,40,41,43,46,49,52,53,55,60,64,66,68,70,72,73,74,80,85,88,91,94,97,100,103,107,110,114,117 ./Source/Charts/Data/Implementations/Standard/LineRadarChartDataSet.swift:11,14,15,17,19,21,24,27,30,33,34,35,39,43,45,50,52,54,56,58,59,60,65,68,70,71,73,75,83,84,85 ./Source/Charts/Data/Implementations/Standard/BarLineScatterCandleBubbleChartData.swift:11,13,15,17,19,20,22,24,25,27,29,30 ./Source/Charts/Data/Implementations/Standard/BubbleChartDataEntry.swift:11,14,16,19,21,23,24,30,32,34,35,42,45,46,53,56,57,65,69,70,72,74,78,79 ./Source/Charts/Data/Implementations/Standard/ChartDataEntryBase.swift:11,13,15,18,21,24,26,28,29,35,37,39,40,44,46,48,50,51,55,57,59,61,62,67,69,71,74,75,77,79,81,82,83,88,90,92,93,95,96 ./Source/Charts/Data/Implementations/Standard/ChartDataEntry.swift:11,13,15,18,20,22,23,30,33,34,41,43,46,47,54,56,59,60,68,70,74,75,77,79,81,82,84,86,88,92,94,95,96,101,103,105,106,109,110 ./Source/Charts/Data/Implementations/Standard/PieChartDataSet.swift:11,14,16,19,22,23,25,28,29,31,34,35,37,40,41,43,45,46,48,50,55,57,59,61,66,67,68,69,72,75,78,81,84,87,90,93,96,99,102,105,108,110,112,129,130 ./Source/Charts/Data/Implementations/Standard/BubbleChartData.swift:11,14,16,18,20,21,23,25,26,28,30,31,34,36,37 ./Source/Charts/Data/Implementations/Standard/ChartDataSet.swift:11,13,17,21,22,26,28,30,32,33,35,37,38,40,42,44,46,47,49,51,52,54,60,70,71,74,77,80,83,85,90,92,94,95,97,100,102,105,109,110,112,115,116,118,121,122,128,131,132,135,138,141,144,148,154,157,159,160,172,175,177,179,180,190,192,193,197,199,202,204,207,210,212,214,215,217,220,223,225,227,229,230,232,233,235,237,239,241,243,245,246,247,248,250,251,262,266,268,270,274,276,280,282,286,288,290,292,295,297,300,301,302,304,305,307,309,311,314,316,317,319,322,324,325,326,329,331,333,334,337,339,342,344,347,350,351,352,354,355,356,358,359,366,368,369,380,383,384,394,396,402,404,406,407,409,410,413,415,416,424,428,429,436,439,440,447,450,451,455,457,458,460,462,464,466,472,474,475,476,481,484,485,488,489,492,493,500,504,505,506,507,512,513,514,520,521,526,527,532,533,537,538,543,544,548,549,553,554,559,560 ./Source/Charts/Data/Implementations/Standard/ScatterChartDataSet.swift:11,14,16,17,20,28,29,32,36,40,44,46,47,52,54,56,64,65,66,68,70,77,78 ./Source/Charts/Data/Implementations/Standard/ScatterChartData.swift:11,14,16,18,20,21,23,25,26,28,30,31,34,38,39 ./Source/Charts/Data/Implementations/Standard/PieChartData.swift:11,13,15,17,19,20,22,24,25,27,29,30,32,34,36,38,40,42,44,46,47,48,49,52,54,57,59,61,62,63,65,68,69,71,73,75,76,78,80,82,83,85,87,89,90,92,93,95,97,98,101,105,106,107 ./Source/Charts/Data/Implementations/Standard/CandleChartData.swift:11,13,15,17,19,20,22,24,25,27,29,30 ./Source/Charts/Data/Implementations/Standard/LineChartDataSet.swift:11,14,15,17,20,25,26,28,31,32,34,37,38,40,43,44,46,48,53,55,60,62,64,66,68,69,70,72,74,77,80,82,86,90,92,94,95,99,102,103,105,108,109,112,114,115,118,121,124,127,130,133,138,141,144,147,149,151,153,155,156,157,159,161,176,177 ./Source/Charts/Data/Implementations/Standard/BubbleChartDataSet.swift:11,14,15,17,19,21,25,27,30,32,34,35,37,40,42,44,52,53 ./Source/Charts/Data/Implementations/Standard/RadarChartDataSet.swift:11,14,15,17,19,21,22,24,27,28,30,33,34,36,38,42,45,47,51,53,55,57,59 ./Source/Charts/Data/Implementations/Standard/BarChartData.swift:11,14,16,18,20,21,23,25,26,28,30,31,36,46,50,51,54,58,60,62,64,67,71,73,75,77,78,79,82,83,88,91,93,94,95,96,98,99,106,108,109 ./Source/Charts/Data/Implementations/Standard/LineScatterCandleRadarChartDataSet.swift:11,13,14,16,18,20,23,26,29,32,36,39,40,42,44,49,50,51 ./Source/Charts/Data/Implementations/ChartBaseDataSet.swift:11,14,15,17,19,21,25,26,28,30,34,36,37,39,42,44,45,47,49,50,52,54,55,57,59,60,62,64,65,67,69,70,72,74,75,77,79,80,82,84,85,90,92,93,97,99,100,102,104,105,110,112,113,115,117,118,120,122,123,125,127,128,130,132,133,135,137,139,141,142,144,146,148,150,151,153,155,157,159,160,162,163,165,167,169,171,172,174,175,177,179,180,182,184,185,187,191,194,197,200,204,207,209,211,212,215,217,218,224,226,227,234,237,238,245,247,248,255,257,258,265,267,268,271,274,277,282,284,286,288,291,292,293,296,299,301,303,304,307,310,313,318,323,328,335,340,343,345,346,351,354,356,357,364,367,370,372,373,375,377,379,380,382,385,386,387,389,391,393,410,412,413 ./Source/Charts/Data/Interfaces/PieChartDataSetProtocol.swift:11,14,17,19,24,27,30,33,36,39,42,45,48,51,54,57,60,63,64 ./Source/Charts/Data/Interfaces/BarChartDataSetProtocol.swift:11,14,17,19,21,24,27,30,33,36,39,42 ./Source/Charts/Data/Interfaces/LineRadarChartDataSetProtocol.swift:11,14,17,19,21,24,28,32,37,42,45 ./Source/Charts/Data/Interfaces/BubbleChartDataSetProtocol.swift:11,14,17,19,22,24,27 ./Source/Charts/Data/Interfaces/LineChartDataSetProtocol.swift:11,14,15,18,20,22,27,32,35,38,41,44,46,50,54,57,60,63,66,69,72,75,80,83,86 ./Source/Charts/Data/Interfaces/CandleChartDataSetProtocol.swift:11,14,17,19,21,25,31,36,39,42,45,48,51,54,57,60,63,66 ./Source/Charts/Data/Interfaces/BarLineScatterCandleBubbleChartDataSetProtocol.swift:11,14,17,19,21,26 ./Source/Charts/Data/Interfaces/ChartDataSetProtocol.swift:11,14,17,19,22,25,29,32,35,38,41,44,49,61,71,75,86,91,102,115,124,133,142,149,156,161,166,168,171,174,177,181,185,187,189,191,194,197,200,205,208,211,214,219,224,229,234,241,246,249,254,257,264,267,270 ./Source/Charts/Data/Interfaces/LineScatterCandleRadarChartDataSetProtocol.swift:11,13,16,18,20,23,26,29,32,36 ./Source/Charts/Data/Interfaces/ScatterChartDataSetProtocol.swift:11,14,17,19,21,24,29,33,36 ./Source/Charts/Data/Interfaces/RadarChartDataSetProtocol.swift:11,14,17,19,21,24,26,28,32,34,36,38,40 ./Source/Charts/Interfaces/BarChartDataProvider.swift:11,14,17,19,23 ./Source/Charts/Interfaces/ChartDataProvider.swift:11,14,17,20,23,26,29,31,33,35,37,39 ./Source/Charts/Interfaces/CandleChartDataProvider.swift:11,14,17,19 ./Source/Charts/Interfaces/CombinedChartDataProvider.swift:11,14,17,19 ./Source/Charts/Interfaces/LineChartDataProvider.swift:11,14,17,19,21 ./Source/Charts/Interfaces/BarLineScatterCandleBubbleChartDataProvider.swift:11,14,17,20,23 ./Source/Charts/Interfaces/ScatterChartDataProvider.swift:11,14,17,19 ./Source/Charts/Interfaces/BubbleChartDataProvider.swift:11,14,17,19 ./Source/Supporting Files/Charts.h:11,13,19,22,25,27,28 ./Tests/Charts/ChartDataTests.swift:7,10,12,14,20,21,24,30,34,38,39,43,45,46,52,53,58,62,63,67,68,69 ./Tests/Charts/EquatableTests.swift:7,10,17,19,20,27,29,30,34,36,37,41,43,44,45 ./Tests/Charts/Snapshot.swift:3,5,7,18,20,21,22,24,26,28,29 ./Tests/Charts/PieChartTests.swift:4,6,7,10,12,14,17,20,22,24,26,27,31,38,43,44,46,49,50,52,54,55,57,60,61,63,66,67,69,72,73,75,79,80,82,86,87 ./Tests/Charts/LineChartTests.swift:4,6,7,10,12,14,17,22,24,26,28,29,33,39,40,42,45,46,48,50,51,53,56,57,59,62,63,65,68,69,71,74,75,77,80,81 ./Tests/Charts/HorizontalBarChartTests.swift:7,11,13,14,16,20,21,23,26,27,30,33,35,37,38,40,48,49,51,56,57,59,64,65,67,77,78,80,83,88,89,92,97,98,100,107,108,110,117,118,120,126,127,129,136,137,139,146,147 ./Tests/Charts/BarChartTests.swift:4,6,8,10,13,14,16,19,20,23,26,28,30,31,33,38,39,41,46,47,49,54,55,57,60,61,63,71,72,74,84,85,87,92,93,95,98,103,104,107,112,113,114,116,122,123,125,130,131,133,138,139,141,149,150,152,159,160,162,169,170,172,179,180,182,191,192,194,199,200,202,209,210,212,219,220,221,223,230,231,233,240,241,243,252,253,255,262,263,265,272,273,275,281,282,284,291,292,294,301,302,304,311,312,314,321,322,324,331,332,334,341,342,344,351,352,354,362,363,365,373,374,376,384,385,387,395,396,398,405,406,408,415,416 ./Tests/Charts/ChartUtilsTests.swift:3,5,9,10,14,15,17,19,22,24,25,27,29,32,34,35,37,39,42,44,45,47,49,52,54,55,57,59,62,64,65,67,69,72,74,75 ./Tests/Charts/CombinedChartTests.swift:7,11,13,17,19,21,24,34,35,37,41,43,45,47,48,51,55,56,58,62,64,66,68,69,73,74,76,79,80,82,84,85,87,93,94,96,102,103,104 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/Menu.xcplaygroundpage/Contents.swift:11,13,14,16,18,20,22,24,26,28,30,32,34,36,37,40 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/HorizontalBarChart.xcplaygroundpage/Contents.swift:13,17,22,23,67,75,78,82,87,93,98 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/BarChart.xcplaygroundpage/Contents.swift:10,14,18,25,26,29,41,43,49,55 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/StackedBarChart.xcplaygroundpage/Contents.swift:13,17,22,23,78,81,82,87,94,101,112 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/ScatterChart.xcplaygroundpage/Contents.swift:13,17,22,52,56,58,65,71,78,83,92,94,98,103,104 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/CombinedChart.xcplaygroundpage/Contents.swift:13,17,22,23,25,31,34,38,40,57,60,64,66,72,79,90,102,105,106,137,140,144,149,150 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/RadarChart.xcplaygroundpage/Contents.swift:13,17,22,63,70,73,83,98,100,104,105,110,111 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/PieChart.xcplaygroundpage/Contents.swift:13,17,22,23,30,35,48,50 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/BubbleChart.xcplaygroundpage/Contents.swift:13,17,22,24,54,58,70,78,82 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/CandleChart.xcplaygroundpage/Contents.swift:13,17,22,23,25,27,28,29,31,32,61,63,71,73,77,95,96 ./ChartsDemo-macOS/PlaygroundChart.playground/Pages/LineChart.xcplaygroundpage/Contents.swift:13,17,22,23,24,75,77,82,83,88,89,94,95,99,111,112,124,136,146,152,153 ./ChartsDemo-macOS/ChartsDemo-macOS/AppDelegate.swift:2,5,6,7 ./ChartsDemo-macOS/ChartsDemo-macOS/Demos/RadarDemoViewController.swift:10,14,16,18,20,22,26,29,34,40,41,42,44,46,47 ./ChartsDemo-macOS/ChartsDemo-macOS/Demos/PieDemoViewController.swift:10,14,16,18,20,22,25,27,30,32,34,42,44,46,48,49,51,53,54 ./ChartsDemo-macOS/ChartsDemo-macOS/Demos/BarDemoViewController.swift:10,14,16,18,20,22,27,30,35,39,43,49,51,53,55,56,58,63,65,67,68,69,70,71,73,75,76 ./ChartsDemo-macOS/ChartsDemo-macOS/Demos/LineDemoViewController.swift:10,14,16,18,20,22,26,29,34,39,41,43,44,46,48,49 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemo/FBAppDelegate.h:9,11,13,15 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemo/main.m:9,11,13,15,18,19 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemo/FBAppDelegate.m:9,11,13,15,23,24,26,29,30,32,35,36,38,40,41,43,45,46,48,50,51 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseDemoTests.m:9,11,13,15,17,19,25,26,28,33,34,36,43,48,51,52,53,55,60,63,64,66,68,71,81,84,85,87,89,93,94 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseDemo/FBSnapshotTestCaseDemoTests/FBSnapshotTestCaseSwiftTests.swift:9,11,16,17,23,24 ./Carthage/Checkouts/ios-snapshot-test-case/iOSSnapshotTestCaseCarthageDemo/iOSSnapshotTestCaseCarthageDemoSwiftTests/iOSSnapshotTestCaseCarthageDemoSwiftTests.swift:8,11,13,17,18,24,25 ./Carthage/Checkouts/ios-snapshot-test-case/iOSSnapshotTestCaseCarthageDemo/iOSSnapshotTestCaseCarthageDemo/AppDelegate.swift:8,10,13,15,18,20,21,22,25,29,31,32,36,37,41,42,45,46,49,50,53,54,55,56 ./Carthage/Checkouts/ios-snapshot-test-case/SnapshotTest.xctemplate/___FILEBASENAME___.m:8,11,15,17,19,21,23,25,26,28,31,32,34,36,39,40 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestCasePlatform.m:9,12,14,20,21,23,28,29,30,32,36,37,42,43,47,48,54,58,59,61 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestCase.h:9,12,14,16,18,21,33,36,47,57,68,71,72,83,93,96,102,103,109,110,112,116,118,122,126,129,133,138,141,143,145,147,150,152,155,159,166,170,173,190,209,226,245,262,281,292,295,297,301,304,306,310,312 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestController.m:9,15,18,24,31,34,35,37,39,43,45,47,48,50,52,54,55,57,62,69,70,75,82,83,89,96,97,98,105,110,111,112,116,133,134,136,137,142,148,149,155,158,162,163,168,178,180,181,187,189,193,196,199,204,207,211,220,222,223,226,227,231,234,235,239,241,244,245,249,251,252,254,258,273,277,278,282,283,286,287,290,297,298,302,306,310,311,318,327,328,330,332,333,338,340,346,348,349,354,368,370,374,382,383,384,386,387,389,395,400,402,403 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/SwiftSupport.swift:9,13,14,19,21,22,25,26,32,43,51,54,55,57,60,61,63,64,65,69,70,71 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestCase.m:9,12,15,16,18,20,23,24,26,29,30,32,34,35,37,40,41,43,45,46,48,51,52,54,56,57,59,62,63,65,67,68,70,72,73,75,82,90,91,99,102,103,107,108,112,113,116,117,120,126,127,134,143,146,147,148,153,154,155,156,163,171,172,180,188,189,196,204,205,213,221,222,226,232,234,235,237,241,244,246,247,249,253,256,258,259,261,269,278,279 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestCasePlatform.h:9,11,15,17,20,34,39,43,47,51,54,60,62,64 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Diff.h:30,32,34,36,38,40 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Snapshot.m:9,11,13,15,19,27,31,32,34,37,38,40,46,47,51,52,54,58,60,64,67,68,70,71 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Compare.m:30,32,44,46,48,52,58,63,64,79,86,87,90,93,97,109,110,113,115,116,120,125,126,135,140,142,143,146,151,152,158,166,170,171,172,175,177,178 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Diff.m:30,32,34,36,39,54,55 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Compare.h:30,32,34,36,39,46,48 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/Categories/UIImage+Snapshot.h:9,11,13,15,18,21,24,26 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCase/FBSnapshotTestController.h:9,12,14,16,24,29,34,39,44,49,56,61,64,66,68,70,74,79,84,89,94,100,113,126,141,158,169,182,197,213 ./Carthage/Checkouts/ios-snapshot-test-case/FBSnapshotTestCaseTests/FBSnapshotControllerTests.m:9,13,15,17,19,21,23,28,34,35,37,42,49,50,52,57,65,66,68,73,80,81,83,88,96,97,99,104,111,121,124,125,126,128,133,135,139,149,160,161,163,168,176,177,179,184,191,192,194,196,201,202 ./Carthage/Build/tvOS/FBSnapshotTestCase.framework/Headers/FBSnapshotTestCase-Swift.h:6,19,23,29,51,66,129,134,138,192,200,207,208 ./Carthage/Build/tvOS/FBSnapshotTestCase.framework/Headers/FBSnapshotTestCase.h:9,12,14,16,18,21,33,36,47,57,68,71,72,83,93,96,102,103,109,110,112,116,118,122,126,129,133,138,141,143,145,147,150,152,155,159,166,170,173,190,209,226,245,262,281,292,295,297,301,304,306,310,312 ./Carthage/Build/tvOS/FBSnapshotTestCase.framework/Headers/FBSnapshotTestCasePlatform.h:9,11,15,17,20,34,39,43,47,51,54,60,62,64 ./Carthage/Build/tvOS/FBSnapshotTestCase.framework/Headers/FBSnapshotTestController.h:9,12,14,16,24,29,34,39,44,49,56,61,64,66,68,70,74,79,84,89,94,100,113,126,141,158,169,182,197,213 ./ChartsDemo-iOS/Supporting Files/main.m:11,14,18,19 ./ChartsDemo-iOS/Supporting Files/ChartsDemo-Bridging-Header.h:4 ./ChartsDemo-iOS/Swift/Formatters/IntAxisValueFormatter.swift:8,11,15,16 ./ChartsDemo-iOS/Swift/Formatters/DayAxisValueFormatter.swift:8,11,18,21,22,27,30,37,43,44,46,47,48,58,59,61,64,67,68,69,73,78,79,82,83,85,86,90,95,96,98,99,107,108,109 ./ChartsDemo-iOS/Swift/Formatters/DateValueFormatter.swift:8,11,14,18,19,22,23 ./ChartsDemo-iOS/Swift/Formatters/LargeValueFormatter.swift:6,9,11,13,15,20,23,26,27,32,36,37,39,42,43,45,46,49,50,57,58 ./ChartsDemo-iOS/Swift/Components/XYMarkerView.swift:8,14,18,25,26,33,34,35 ./ChartsDemo-iOS/Swift/Components/RadarMarkerView.swift:11,17,20,24,25,29,30 ./ChartsDemo-iOS/Swift/DemoListViewController.swift:8,12,17,18,20,92,95,99,100,101,102,106,107,110,111,118,120,121,124,127,130,131 ./ChartsDemo-iOS/Swift/DemoBaseViewController.swift:8,13,53,94,95,96,97,105,108,110,114,115,119,120,123,124,126,132,134,138,140,144,147,150,153,156,161,166,170,175,176,180,181,182,188,189,194,196,198,200,208,216,224,227,236,237,240,241,249,251,255,264,269,279,280,283,284,287,291,293,296,298,303,304,307,308,310,311,312,314,315,316,317,322,323,325,326,331,332,334,335,336,340,341,343,344,348,354,356,358,359,363,366,368,369,370,371,372 ./ChartsDemo-iOS/Swift/AppDelegate.swift:8,12,15,17,21,22,25,28,30,31,35,36,40,41,44,45,48,49,52,53,54,55,56 ./ChartsDemo-iOS/Swift/Demos/MultipleLinesChartViewController.swift:8,13,15,21,24,40,42,44,48,51,58,62,63,68,69,71,72,76,80,90,92,93,97,101,102,105,110,112,116,118,122,124,128,130,133,134,135,139,141,142 ./ChartsDemo-iOS/Swift/Demos/MultipleBarChartViewController.swift:8,13,15,21,24,27,38,40,42,45,50,61,67,70,76,78,82,83,88,89,91,92,98,103,106,111,114,117,120,123,127,130,133,136,138,140,141,144,145,150,153,155,156 ./ChartsDemo-iOS/Swift/Demos/HalfPieChartViewController.swift:8,13,15,17,20,23,35,37,39,45,49,59,63,65,67,68,73,74,76,77,83,84,89,91,98,101,103,105,106,112,116,120,124,127,130,133,139,142,143,144 ./ChartsDemo-iOS/Swift/Demos/LineChartTimeViewController.swift:8,13,18,21,38,40,42,47,49,51,61,71,72,74,76,79,81,82,87,88,90,91,95,98,102,103,114,118,120,121,124,129,131,135,137,141,143,147,149,153,155,158,159,160,164,165 ./ChartsDemo-iOS/Swift/Demos/ColoredLineChartViewController.swift:8,13,16,19,21,26,30,32,33,34,37,40,42,47,49,55,57,59,60,65,66,68,76,78,79 ./ChartsDemo-iOS/Swift/Demos/LineChart2ViewController.swift:8,13,15,21,24,41,43,48,57,62,69,75,79,81,82,87,88,90,91,97,101,105,106,117,128,139,143,145,146,149,154,156,160,162,166,168,172,174,178,180,183,184,185,189,191,197,203,204 ./ChartsDemo-iOS/Swift/Demos/LineChart1ViewController.swift:8,11,13,19,22,41,43,48,55,58,64,70,79,81,84,92,94,98,100,101,106,107,109,110,115,116,120,126,130,132,134,135,163,164,165,168,173,175,179,181,185,187,191,193,197,203,207,208,209,213,215,216 ./ChartsDemo-iOS/Swift/Demos/NegativeStackedBarChartViewController.swift:8,13,15,17,27,30,44,45,47,49,52,61,72,81,83,84,89,90,92,93,107,117,120,123,124,127,128,129,133,134 ./ChartsDemo-iOS/Swift/Demos/ScatterChartViewController.swift:8,13,15,21,24,36,38,40,45,53,57,59,60,63,67,68,73,74,76,77,82,86,90,91,92,97,104,109,112,114,115,118,119,123,125,126 ./ChartsDemo-iOS/Swift/Demos/StackedBarChartViewController.swift:8,13,15,21,27,30,33,47,48,50,52,57,61,63,66,76,80,82,83,88,89,91,92,99,101,102,107,112,115,116,119,120,124,126,127 ./ChartsDemo-iOS/Swift/Demos/BubbleChartViewController.swift:8,13,15,21,24,37,39,41,46,52,57,59,62,66,67,72,73,75,76,82,87,92,93,98,104,108,114,116,117,120,121,126,128,129 ./ChartsDemo-iOS/Swift/Demos/LineChartFilledViewController.swift:8,13,15,21,24,27,29,33,35,37,41,43,45,50,52,56,57,62,63,65,66,71,75,76,90,91,105,106,109,111,112,116,118,119 ./ChartsDemo-iOS/Swift/Demos/PositiveNegativeBarChartViewController.swift:8,13,15,17,23,26,39,41,43,45,48,50,52,62,71,73,74,79,80,82,83,91,96,97,101,104,109,111,112,115,116,117,121,122 ./ChartsDemo-iOS/Swift/Demos/AnotherBarChartViewController.swift:8,13,15,21,24,27,37,39,44,47,49,53,54,55,60,61,63,64,70,71,82,86,87,89,90,93,94,99,101,102 ./ChartsDemo-iOS/Swift/Demos/PieChartViewController.swift:8,13,15,21,24,27,41,43,45,54,58,62,64,65,70,71,73,74,81,82,86,87,94,96,103,106,109,110,116,120,124,128,132,135,138,141,147,150,151,152,157,159,160 ./ChartsDemo-iOS/Swift/Demos/CubicLineChartViewController.swift:8,13,17,18,19,21,27,30,33,48,50,53,58,60,67,70,74,76,77,82,83,85,86,92,93,105,109,111,112,115,120,122,126,128,132,134,138,140,144,146,149,150,151,156,158,159 ./ChartsDemo-iOS/Swift/Demos/RadarChartViewController.swift:8,13,15,17,22,25,41,43,50,54,61,68,79,81,83,84,87,93,97,98,99,102,108,109,110,115,116,118,119,124,128,137,146,151,153,154,157,164,168,171,175,176,178,182,184,187,190,193,196,199,200,201,202,206,207 ./ChartsDemo-iOS/Swift/Demos/HorizontalBarChartViewController.swift:8,13,15,21,24,38,40,42,45,47,53,59,65,76,78,82,84,85,90,91,93,94,98,103,104,107,111,113,114,117,118,123,125,126 ./ChartsDemo-iOS/Swift/Demos/PiePolylineChartViewController.swift:8,11,13,19,22,25,38,40,42,45,49,51,52,57,58,60,61,67,68,71,72,79,85,87,96,99,100,106,110,114,118,122,125,128,131,137,140,141,142,147,149,150 ./ChartsDemo-iOS/Swift/Demos/CombinedChartViewController.swift:8,16,18,21,26,27,30,39,41,43,46,47,53,61,64,67,73,75,76,81,82,84,85,93,95,97,98,105,106,107,109,114,115,117,123,126,127,128,132,133,145,147,149,150,154,157,158,164,173,178,181,184,186,187,191,192,198,200,201,205,206,213,215,216,222,223,229,231,232,233,237,238 ./ChartsDemo-iOS/Swift/Demos/BarChartViewController.swift:8,16,18,24,27,30,40,42,44,47,49,56,62,70,78,89,98,102,103,108,109,111,112,115,123,124,125,136,141,142,144,145,148,149,154,156,157 ./ChartsDemo-iOS/Swift/Demos/SinusBarChartViewController.swift:8,13,15,19,22,34,36,38,42,46,53,59,70,73,75,76,81,82,84,85,89,90,93,98,100,101,104,105,109,110 ./ChartsDemo-iOS/Swift/Demos/CandleStickChartViewController.swift:8,13,15,21,24,39,41,43,48,54,59,61,64,68,69,74,75,77,78,88,90,91,103,106,107,113,118,122,123,124,129 ./ChartsDemo-iOS/Objective-C/DemoBaseViewController.m:11,13,15,17,19,21,23,26,28,30,31,33,36,38,40,41,43,45,53,54,56,59,60,62,65,66,68,69,70,71,73,75,77,79,81,82,84,85,87,89,91,92,94,95,97,100,101,103,105,106,108,110,111,113,115,116,118,120,121,123,126,128,129,131,134,136,137,139,142,143,145,147,149,151,152,153,155,156,157,159,161,163,167,168,174,176,178,180,182,184,186,188,192,193,195,197,199,201,202,204,205,207,209,211,212,214,215,217,219,221,222,224,225,227,232,234,236,237,239,241,243,245,248,249,251,252,253,255,257,259,260,262,269,271,275,290,295,304,305,307,309,310,312,314,316,320,322,325,327,328 ./ChartsDemo-iOS/Objective-C/AppDelegate.h:11,13,15,17,18,20 ./ChartsDemo-iOS/Objective-C/DemoListViewController.m:11,36,38,42,44,46,48,50,148,154,160,166,170,173,174,176,179,180,182,184,186,187,189,191,192,194,196,197,199,201,204,206,207,211,213,214,216,218,221,223,225,226 ./ChartsDemo-iOS/Objective-C/Formatters/DayAxisValueFormatter.h:6,9,11,13 ./ChartsDemo-iOS/Objective-C/Formatters/DateValueFormatter.m:11,12,14,16,18,20,22,24,27,30,32,33,35,37,38 ./ChartsDemo-iOS/Objective-C/Formatters/IntAxisValueFormatter.m:6,8,10,11,12,15,17,18 ./ChartsDemo-iOS/Objective-C/Formatters/DayAxisValueFormatter.m:6,8,10,13,14,16,19,21,28,30,31,34,38,41,43,45,47,49,51,53,75,76,78,79,80,82,84,86,88,90,92,94,96,97,99,100,102,104,105,107,108,110,113,115,117,120,123,124,126,127,128,130,133,135,139,140,142,143,145,147,149,151,153,155,157,159,161,162,164,165 ./ChartsDemo-iOS/Objective-C/Formatters/IntAxisValueFormatter.h:6,9,11 ./ChartsDemo-iOS/Objective-C/Formatters/DateValueFormatter.h:11,14,16 ./ChartsDemo-iOS/Objective-C/Formatters/LargeValueFormatter.swift:6,9,11,13,18,21,23,24,25,26,28,30,31,33,37,39,42,43,45,47,49,50,52,53,56,58,59,65,67,68 ./ChartsDemo-iOS/Objective-C/Components/XYMarkerView.swift:6,12,14,17,20,25,26,28,30,31,32 ./ChartsDemo-iOS/Objective-C/Components/BalloonMarker.swift:11,17,19,26,31,33,38,42,43,45,48,50,52,54,56,57,61,65,67,69,72,74,75,77,79,82,84,85,87,88,90,92,95,103,105,107,109,137,139,167,168,173,174,176,178,180,182,184,185,187,189,190,192,194,199,201,208,209 ./ChartsDemo-iOS/Objective-C/Components/RadarMarkerView.swift:11,17,19,21,23,26,27,29,32,33 ./ChartsDemo-iOS/Objective-C/AppDelegate.m:11,14,16,18,20,22,24,27,30,32,33,37,38,42,43,46,47,50,51,54,55 ./ChartsDemo-iOS/Objective-C/DemoBaseViewController.h:11,14,16,19,20,23,25,27,29,33 ./ChartsDemo-iOS/Objective-C/DemoListViewController.h:11,13,15,16,18 ./ChartsDemo-iOS/Objective-C/Demos/MultipleLinesChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/LineChartTimeViewController.m:11,15,17,21,23,25,27,29,31,48,50,52,58,60,62,72,83,85,87,90,91,93,96,97,99,101,104,105,107,108,110,113,115,118,120,123,124,127,132,134,145,148,152,154,155,156,158,160,162,164,165,168,169,171,173,175,176,179,180,182,184,186,187,190,191,193,195,203,204,205,207,208,210,212,214,215,218,219,221,222,224,226,228,230,231,233,235,237,238,240,242,243 ./ChartsDemo-iOS/Objective-C/Demos/SinusBarChartViewController.m:11,14,16,20,22,24,26,28,30,42,44,46,52,58,66,74,84,87,89,90,92,95,96,98,100,103,104,106,107,109,111,113,115,116,119,124,126,129,133,135,137,138,139,141,143,144,146,148,150,152,153,155,157,159,160,162,164,165 ./ChartsDemo-iOS/Objective-C/Demos/CubicLineChartViewController.m:11,14,16,17,19,21,23,25,26,28,30,36,38,40,42,44,46,63,65,68,70,76,78,86,89,93,95,96,98,101,102,104,106,109,110,112,113,115,117,119,123,124,127,132,134,148,152,154,155,156,158,160,162,164,165,168,169,171,173,175,176,179,180,182,184,186,187,190,191,193,195,197,198,200,201,203,205,207,208,211,212,214,215,217,219,222,224,225,227,229,231,232,234,236,237 ./ChartsDemo-iOS/Objective-C/Demos/NegativeStackedBarChartViewController.m:11,14,16,18,20,22,24,26,28,42,49,51,53,57,60,62,71,82,84,93,95,96,98,100,103,104,106,107,109,122,125,130,132,139,141,143,154,156,158,161,162,163,165,168,169,171,173,174,176,178,180,181,183,185,186,188,191,193,194 ./ChartsDemo-iOS/Objective-C/Demos/ScatterChartViewController.m:11,14,16,22,24,26,28,30,32,44,46,48,54,62,66,68,72,76,77,79,82,83,85,87,90,91,93,94,96,100,102,105,108,111,112,124,128,133,136,138,139,141,143,144,146,148,151,153,154,156,158,160,161,163,165,166 ./ChartsDemo-iOS/Objective-C/Demos/AnotherBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/StackedBarChartViewController.m:11,14,16,22,24,26,28,30,32,46,48,50,57,62,66,68,71,81,85,86,88,91,92,94,96,99,100,102,103,105,107,109,114,116,117,120,125,127,129,131,134,137,142,147,150,151,152,154,156,157,159,161,164,166,167,169,171,173,174,176,178,179 ./ChartsDemo-iOS/Objective-C/Demos/ColoredLineChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/HalfPieChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/BarChartViewController.m:11,15,17,23,25,27,29,31,33,47,49,51,54,56,64,70,78,87,97,107,111,112,114,117,118,120,122,125,126,128,129,131,133,135,137,144,145,146,149,154,156,160,163,166,168,170,171,172,174,176,177,179,181,184,186,187,189,191,193,194,196,198,199 ./ChartsDemo-iOS/Objective-C/Demos/RadarChartViewController.m:11,14,16,22,24,26,28,30,32,34,50,52,59,63,70,77,87,89,91,92,94,96,102,107,108,110,112,119,120,122,125,126,128,130,133,134,136,137,139,143,146,149,152,153,162,171,176,178,179,181,183,185,190,191,193,197,198,200,203,204,206,208,210,211,214,215,217,219,221,222,225,226,228,231,232,234,237,238,240,243,244,246,249,250,252,253,255,257,259,260,262,264,265,267,270,272,273 ./ChartsDemo-iOS/Objective-C/Demos/CombinedChartViewController.m:11,14,16,18,20,21,23,25,27,29,31,33,42,49,51,53,57,65,72,76,80,86,88,89,91,94,95,97,99,102,103,105,106,108,115,117,119,120,122,124,126,128,130,131,132,135,136,138,140,142,144,145,146,149,150,152,157,158,160,161,163,165,167,169,171,172,184,186,188,190,191,193,196,198,200,203,204,210,220,225,228,231,233,234,236,238,240,242,244,245,251,253,255,256,258,260,262,264,266,267,274,276,278,279,281,283,285,287,291,292,298,300,302,303,305,307,309,310,312,314,315,317,320,322,323 ./ChartsDemo-iOS/Objective-C/Demos/PieChartViewController.m:11,14,16,22,24,26,28,30,32,48,50,52,61,65,69,71,72,74,77,78,80,82,85,86,88,89,91,93,95,97,99,100,102,104,107,109,117,119,121,130,133,134,136,138,140,143,144,146,148,151,152,154,156,159,160,162,165,166,168,170,173,174,176,179,180,182,185,186,188,191,192,194,197,198,200,201,203,205,208,210,211,213,215,217,218,220,222,223 ./ChartsDemo-iOS/Objective-C/Demos/MultipleBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/LineChart1ViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/PositiveNegativeBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/CandleStickChartViewController.m:11,14,16,22,24,26,28,30,32,47,49,51,55,59,64,67,69,73,74,76,79,80,82,84,87,88,90,91,93,95,97,106,107,111,113,121,123,125,126,128,130,132,134,135,139,141,143,144,147,148,150,151,153,155,158,160,161,163,165,167,168,170,172,173 ./ChartsDemo-iOS/Objective-C/Demos/BubbleChartViewController.m:11,14,16,22,24,26,28,30,32,45,47,49,55,62,68,70,74,78,79,81,84,85,87,89,92,93,95,96,98,102,104,108,112,116,117,122,127,131,136,142,144,145,147,149,150,152,154,157,159,160,162,164,166,167,169,171,172 ./ChartsDemo-iOS/Objective-C/Demos/LineChart2ViewController.m:11,14,16,22,24,26,28,30,32,49,51,53,58,60,69,75,83,90,94,96,97,99,102,103,105,107,110,111,113,114,116,120,122,126,127,129,133,134,136,140,141,143,145,154,156,167,178,189,194,198,200,201,202,204,206,208,210,211,214,215,217,219,221,222,225,226,228,230,232,233,236,237,239,241,249,250,251,253,254,256,258,260,261,264,265,267,268,270,272,275,277,278,280,282,284,288,289,290,292,294,295 ./ChartsDemo-iOS/Objective-C/Demos/HorizontalBarChartViewController.m:11,14,16,22,24,26,28,30,32,46,48,50,53,55,62,68,75,85,87,91,93,94,96,99,100,102,104,107,108,110,111,113,116,118,120,124,125,128,133,135,137,139,142,146,148,149,150,152,154,155,157,159,162,164,165,167,169,171,172,174,176,177 ./ChartsDemo-iOS/Objective-C/Demos/PiePolylineChartViewController.h:8,10,12 ./ChartsDemo-iOS/Objective-C/Demos/LineChartFilledViewController.m:11,14,16,22,24,26,28,30,32,34,38,40,42,46,49,52,59,61,65,66,68,71,72,74,76,79,80,82,83,85,88,90,93,94,96,99,100,103,105,112,114,129,144,148,151,153,154,155,157,159,162,164,165,167,169,171,172,174,176,177 ./ChartsDemo-iOS/Objective-C/Demos/NegativeStackedBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/CubicLineChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/SinusBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/LineChartTimeViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/MultipleLinesChartViewController.m:11,14,16,22,24,26,28,30,32,48,50,52,58,64,70,74,75,77,80,81,83,85,88,89,91,92,94,96,98,100,102,104,107,108,113,118,119,123,127,128,130,132,134,136,137,140,141,143,145,147,148,151,152,154,156,158,159,162,163,165,167,175,176,177,179,180,182,183,185,187,190,192,193,195,197,199,200,202,204,205 ./ChartsDemo-iOS/Objective-C/Demos/ColoredLineChartViewController.m:11,14,16,18,20,22,24,26,28,30,33,40,42,43,44,46,48,51,53,59,61,67,69,71,72,74,77,78,80,82,84,87,88,90,98,100,101,103,105,107,108,110,112,113 ./ChartsDemo-iOS/Objective-C/Demos/StackedBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/AnotherBarChartViewController.m:11,14,16,22,24,26,28,30,32,44,46,48,53,57,60,62,66,67,69,72,73,75,77,80,81,83,84,86,88,90,94,95,98,103,105,109,112,114,117,118,120,121,123,125,126,128,130,133,135,136,138,140,142,143,145,147,148 ./ChartsDemo-iOS/Objective-C/Demos/ScatterChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/CandleStickChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/LineChart1ViewController.m:11,14,16,22,24,26,28,30,32,50,52,54,59,66,68,71,77,83,93,95,98,107,109,113,115,116,118,121,122,124,126,129,130,132,133,135,137,139,142,143,146,151,153,155,157,169,175,179,181,184,186,188,189,190,192,194,196,198,199,202,203,205,207,209,210,213,214,216,218,220,221,224,225,227,229,237,238,239,241,242,244,246,248,249,252,253,255,256,258,260,263,265,266,268,270,272,273,275,277,278 ./ChartsDemo-iOS/Objective-C/Demos/PositiveNegativeBarChartViewController.m:11,14,16,18,19,21,23,25,27,29,31,44,46,48,53,56,58,61,63,74,84,87,89,90,92,95,96,98,100,103,104,106,107,109,128,131,134,136,140,143,145,147,149,150,151,155,158,162,164,166,167,169,171,172,174,176,178,179,181,183,184,186,189,191,192,193 ./ChartsDemo-iOS/Objective-C/Demos/PieChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/MultipleBarChartViewController.m:11,15,17,23,25,27,29,31,33,46,48,50,54,63,73,79,82,89,91,95,96,98,101,102,104,106,109,110,112,113,115,120,125,127,131,133,137,141,145,149,150,153,162,164,168,171,173,176,179,182,185,191,195,198,201,204,206,208,209,210,212,214,215,217,219,222,225,227,228,230,232,234,235,237,239,240 ./ChartsDemo-iOS/Objective-C/Demos/CombinedChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/RadarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/BarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/HalfPieChartViewController.m:11,14,16,18,20,22,24,26,28,42,44,46,52,56,65,69,71,73,74,76,79,80,82,84,87,88,90,91,93,95,97,100,102,103,107,109,111,118,121,123,125,126,128,130,132,135,136,138,140,143,144,146,148,151,152,154,156,159,160,162,165,166,168,171,172,174,177,178,180,183,184,186,187,189,191,193,195,196,198,200,201 ./ChartsDemo-iOS/Objective-C/Demos/LineChartFilledViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/PiePolylineChartViewController.m:8,11,13,20,22,24,26,28,43,45,48,50,54,56,57,59,62,63,65,67,70,71,73,74,76,78,80,82,84,85,88,90,98,100,106,108,117,120,121,123,125,127,130,131,133,135,138,139,141,143,146,147,149,152,153,155,157,160,161,163,166,167,169,172,173,175,178,179,181,184,185,187,188,190,192,195,197,198,200,202,204,205,207,209,210,212 ./ChartsDemo-iOS/Objective-C/Demos/HorizontalBarChartViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/LineChart2ViewController.h:11,15,17 ./ChartsDemo-iOS/Objective-C/Demos/BubbleChartViewController.h:11,15,17 ./Package.swift:4 ./vendor/bundle/ruby/2.6.0/gems/json-2.3.0/ext/json/ext/fbuffer/fbuffer.h:1,4,6,10,14,24,33,40,45,52,54,59,72,74,81,82,84,87,88,90,92,93,95,97,101,102,104,108,109,110,112,117,118,119,122,125,127,129,131,133,137,138,141,143,146,147,148,150,154,160,161,163,167,168,170,173,177,178,180,185 ./vendor/bundle/ruby/2.6.0/gems/json-2.3.0/ext/json/ext/parser/parser.h:3,5,9,15,17,19,23,29,47,54,57,77,81,90 ./vendor/bundle/ruby/2.6.0/gems/json-2.3.0/ext/json/ext/parser/parser.c:1,5,11,14,18,20,27,29,46,48,64,65,67,88,90,91,95,101,102,104,105,106,111,113,114,116,117,119,123,126,127,129,130,132,134,135,137,139,143,161,167,173,185,196,212,238,247,257,259,260,273,286,297,313,329,345,371,387,403,419,428,455,458,459,461,469,474,475,476,480,481,482,483,484,489,491,492,494,495,497,499,500,502,504,505,507,509,513,531,543,546,550,559,560,566,570,574,578,582,586,591,592,596,601,602,606,608,612,614,618,620,633,644,660,676,692,834,864,867,868,870,875,876,877,878,883,885,886,888,889,891,893,894,896,898,899,902,904,908,913,953,958,961,962,964,974,975,976,977,982,984,985,987,988,990,994,997,998,1000,1001,1003,1005,1006,1008,1010,1011,1014,1016,1020,1025,1049,1065,1089,1107,1122,1126,1136,1139,1140,1142,1157,1158,1162,1163,1164,1165,1166,1171,1173,1174,1176,1177,1179,1182,1185,1187,1188,1190,1192,1193,1195,1197,1201,1227,1236,1246,1248,1249,1262,1283,1297,1313,1329,1345,1371,1387,1396,1413,1416,1417,1419,1425,1426,1427,1429,1433,1477,1486,1487,1490,1495,1500,1501,1504,1505,1506,1511,1513,1514,1516,1517,1520,1526,1528,1529,1531,1534,1536,1538,1540,1541,1544,1546,1550,1565,1571,1579,1580,1651,1659,1662,1663,1665,1674,1675,1676,1681,1686,1687,1688,1700,1702,1708,1712,1715,1716,1745,1748,1751,1772,1775,1781,1787,1793,1798,1804,1810,1816,1822,1829,1831,1841,1848,1849,1850,1855,1857,1858,1860,1861,1869,1874,1875,1877,1879,1880,1884,1886,1890,1908,1920,1923,1934,1945,1961,1977,1993,2002,2013,2016,2017,2019,2025,2026,2027,2029,2037,2038,2040,2044,2045,2047,2050,2051,2062,2064,2069,2070,2078,2081,2082,2084,2098,2101,2104,2107,2128,2129 ./vendor/bundle/ruby/2.6.0/gems/json-2.3.0/ext/json/ext/generator/generator.c:3,8,20,26,48,66,74,86,95,103,104,106,109,110,113,115,120,121,126,129,130,134,138,145,149,160,162,186,207,208,209,223,224,226,227,233,241,272,284,291,295,296,298,301,302,307,309,310,317,318,330,340,342,343,354,355,363,365,366,374,376,377,384,386,388,395,397,398,407,408,417,419,420,430,437,438,446,450,451,459,464,465,472,474,475,482,484,485,492,494,495,504,511,512,514,525,526,528,540,541,552,554,558,559,567,580,588,596,604,612,622,623,633,634,643,644,650,651,653,661,662,663,671,686,687,694,700,701,702,709,711,718,720,721,728,731,736,748,752,756,757,758,766,771,774,775,777,786,790,792,798,805,806,807,809,810,812,825,833,834,836,843,844,845,847,848,851,855,857,859,864,870,872,873,875,877,878,880,882,883,885,887,888,890,892,893,895,898,899,902,907,910,921,922,924,925,927,956,957,958,960,964,969,975,979,984,988,989,991,996,997,1006,1011,1012,1034,1042,1043,1051,1053,1058,1069,1070,1079,1087,1088,1089,1096,1099,1100,1107,1117,1122,1124,1125,1133,1136,1137,1145,1155,1160,1162,1163,1170,1173,1174,1181,1191,1196,1198,1199,1207,1210,1211,1219,1228,1233,1235,1236,1243,1246,1247,1254,1263,1268,1270,1271,1272,1280,1283,1284,1292,1295,1296,1304,1308,1309,1317,1320,1321,1329,1332,1333,1340,1343,1344,1352,1357,1358,1365,1368,1369,1377,1384,1386,1387,1392,1395,1399,1404,1436,1468,1499 ./vendor/bundle/ruby/2.6.0/gems/json-2.3.0/ext/json/ext/generator/generator.h:3,6,8,14,18,22,24,26,28,32,38,43,45,48,55,57,78,81,85,90,97,157,161,170 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Type.h:33,36,39,43,45,47,52,55,57,59,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/rbffi_endian.h:3,7,9,22,35,47,53,57,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MappedType.h:29,32,33,35,39,40,46,48,50,52,53,55,57,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Thread.c:29,37,50,59,61,64,71,72,75,81,90,91,94,100,101,104,107,110,111,114,116,118,120,121,124,126,128,131,137 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Buffer.c:30,43,47,54,60,62,65,68,72,74,75,78,82,83,85,86,98,102,104,108,114,115,118,121,122,126,127,130,131,133,134,142,145,150,155,156,160,163,165,166,169,171,172,175,179,182,189,191,192,201,204,206,208,209,219,221,222,230,233,235,237,239,240,241,247,260,262,270,273,278,281,282,286,290,291,293,294,295,299,301,306,307,309,310,313,315,316,319,321,328,334,356,364,365 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Types.h:30,33,37,61,64,67,70,73,77,80,83,85,87,89 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/LastError.h:29,32,36,37,39,41,43,45,47 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/ArrayType.h:29,32,36,40,41,49,52,53,55,57,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/DynamicLibrary.c:29,47,49,55,61,64,65,69,71,83,86,89,90,101,103,104,115,118,120,123,131,139,143,144,147,151,154,156,157,164,168,169,172,177,180,181,185,191,192,193,196,199,201,204,210,212,213,214,223,226,227,230,233,240,242,243,246,249,250,258,261,266,267,270,284,317,322,337,338,339 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Variadic.c:29,34,44,48,57,62,69,70,75,77,78,81,84,89,91,92,95,99,100,103,112,115,121,129,134,135,137,139,145,149,153,154,160,162,163,166,179,182,191,194,197,199,213,218,221,222,223,227,229,230,234,235,238,253,254,257,259,269,273,274,276,278,281,282,284,285,286,289,292,294,297,298 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/StructByValue.h:29,32,35,39,45,47,49,51,53,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/AbstractMemory.h:29,32,40,43,47,48,54,56,61,80,87,88,91,93,95,97,100,104,105,106,109,112,113,114,117,120,121,122,125,161,162,163,167,168,169,171,173,175 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/ffi.c:30,34,36,53,55,57,59,62,70,72,75,93 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/ClosurePool.h:28,31,34,42,44,48,50,53,55,57 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Pointer.c:29,43,45,48,51,54,57,60,61,68,70,71,74,77,81,83,84,98,102,104,115,116,124,126,130,136,138,139,141,143,144,157,160,166,167,171,172,176,177,182,183,189,192,194,195,198,202,205,207,213,215,216,226,229,231,233,234,245,247,248,256,259,261,267,268,270,271,280,282,284,286,287,296,298,300,303,304,306,307,315,317,319,321,322,328,340,342,350,353,358,361,362,366,370,371,373,374,375,376,384,386,388,393,395,398,400,401,403,404,407,409,411,413,414,423,425,428,430,431,439,441,443,445,446,447,450,454,456,457,460,462,463,466,469,484,501,507,508 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MemoryPointer.c:30,44,45,50,52,54,57,59,60,63,68,70,71,82,85,88,91,92,94,95,98,101,103,105,110,117,120,121,123,124,127,129,131,136,138,139,141,142,145,149,151,152,161,166,168,169,172,174,176,192,196,197 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Call.h:32,35,37,41,47,51,73,75,79,82,84,86,89,98,101,103,105,107 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Platform.c:29,45,49,51,54,69,70,73,82 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/LongDouble.c:5,9,13,16,20,21,25,26,29,30,33,36,37,40,41,45,46,49,50,51,54,57,58,61,63 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MethodHandle.h:29,32,36,39,40,43,44,49,51,53,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Function.c:29,38,51,57,60,74,84,93,95,96,101,107,109,111,117,119,121,122,124,128,130,147,148,159,160,163,166,168,174,176,177,180,184,185,188,191,192,195,196,198,199,216,217,222,224,233,243,244,249,251,253,254,262,265,266,269,271,272,275,278,285,286,287,291,292,303,304,306,307,310,312,314,316,318,324,330,331,332,336,338,344,348,349,351,353,354,363,365,367,369,370,380,383,385,389,390,394,395,398,399,405,408,409,412,414,415,424,426,428,430,432,433,436,438,440,442,443,451,453,455,458,459,462,464,465,468,470,476,478,484,488,491,497,500,505,509,513,515,522,524,529,530,531,537,540,543,545,552,553,554,556,557,561,563,565,567,572,573,577,578,580,582,583,586,588,593,594,598,600,602,604,607,608,612,613,615,617,618,621,623,628,630,633,635,637,647,649,650,652,655,658,659,662,664,674,680,684,685,735,741,745,746,751,752,754,755,757,763,764,802,804,808,812,814,817,819,823,825,829,832,835,836,839,841,845,846,848,849,852,854,857,859,860,863,866,871,872,874,875,878,884,887,907,917 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Struct.h:30,33,38,42,48,52,54,58,61,64,72,78,83,86,91,96,100,106,108,110,112 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/rbffi.h:29,32,34,38,40,42,50,52,54 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Thread.h:29,32,41,45,46,52,63,74,78,80,82,84 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MappedType.c:29,31,34,37,38,43,45,48,50,52,58,60,61,70,72,75,76,79,80,83,84,89,90,94,96,97,100,103,104,112,115,117,118,125,127,129,131,132,139,141,143,145,146,149,154,156,160,167,168 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Type.c:29,33,41,42,47,49,51,56,59,62,65,67,68,77,80,82,91,92,94,95,103,105,107,109,110,118,120,122,124,125,133,136,138,141,143,144,147,150,152,156,158,159,162,165,166,174,177,181,183,184,187,189,192,203,206,207,208,211,214,215,216,219,228,230,232,233,236,237,240,249,260,304,311,317,321,324,332,337,347,378,379 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Types.c:31,42,44,45,48,62,73,78,81,88,94,95,100,105,107,108,116,119,120,124,126,127,131,132,133,136,138,139 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/compat.h:29,32,34,38,42,46,50,54,58,66,73,77 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/StructLayout.c:30,32,54,56,60,65,67,68,71,74,78,80,81,84,87,88,99,103,105,107,111,112,114,118,119,126,133,140,141,143,144,152,156,157,165,169,170,178,182,183,191,194,196,197,205,209,210,219,221,226,227,229,230,240,242,247,248,250,252,253,262,264,266,268,269,281,284,286,293,294,296,298,299,302,305,306,315,319,322,325,328,329,339,342,343,346,349,352,354,361,364,365,372,373,376,379,380,382,386,390,391,395,398,399,402,406,407,409,410,411,414,417,426,428,429,440,444,458,465,466,469,471,474,477,478,482,483,486,487,488,493,494,497,498,500,501,510,516,518,523,524,528,529,534,537,538,540,541,544,546,548,550,551,559,561,563,565,566,574,576,578,580,581,589,591,593,595,596,599,607,608,611,616,617,618,621,623,631,638,645,652,659,666,673,683,686,689,697,698,699 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/DynamicLibrary.h:28,31,35,40,44,48,52,57,61,65,69,73,78,82,86,90,92,94,96,98 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/ArrayType.c:29,33,38,40,43,46,48,55,57,58,61,63,64,67,71,72,73,83,86,88,92,97,100,101,103,104,112,114,116,118,119,127,129,131,133,134,137,139,141,156,161,162 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/win32/stdbool.h:3,7 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/win32/stdint.h:8,15,21,25,29,33,37,41,50,59,62,65,68,71,76,83,87,94,98,105,110,116,120,124,132,135,143,146,149,153,157,161,165,173,177,180,185,187,190,196 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/LastError.c:30,44,46,51,57,64,68,70,72,75,77,79,81,82,83,86,89,90,93,95,96,99,102,105,108,110,111,114,116,119,120,122,123,125,126,134,136,137,146,148,150,151,160,161,167,169,170,180,183,185,186,189,196,201,203,204,207,214,217,222,228,229 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Struct.c:30,53,57,65,66,73,75,78,81,84,86,87,90,93,96,98,99,109,113,115,117,123,124,127,128,130,136,137,139,140,148,151,156,157,160,173,174,178,179,181,182,185,189,190,194,195,197,198,201,205,206,210,211,213,214,217,220,223,224,227,228,230,231,234,237,240,241,243,244,247,252,253,254,257,260,261,262,265,269,275,276,277,279,280,281,284,287,294,298,299,301,302,310,313,315,319,322,327,328,329,339,342,344,348,350,352,360,361,364,365,367,368,377,381,386,387,388,392,396,397,401,403,404,412,414,416,418,419,428,431,436,437,440,442,443,451,453,455,457,458,466,468,470,472,473,479,481,485,490,492,493,494,497,500,504,506,507,510,513,514,524,526,530,535,539,540,542,544,545,553,555,557,559,560,563,566,567,569,570,578,580,582,591,592,597,600,603,604,605,614,616,618,623,626,630,634,635,638,642,644,648,651,652,654,655,662,664,666,668,671,672,674,675,683,687,690,691,694,695,697,698,707,710,712,716,717,720,722,723,731,733,735,738,739,740,743,745,747,767,773,780,781,786,793,796,799,803,813,816,824,825 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Platform.h:29,32,36,38,39,41,43,45 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Call.c:31,65,70,80,82,88,91,93,98,102,108,109,110,112,116,117,122,123,126,128,135,136,140,145,148,149,153,158,161,162,166,170,174,181,182,186,193,194,198,205,206,210,217,218,222,229,230,234,241,242,246,253,254,258,265,266,270,277,278,282,289,290,294,295,299,302,303,307,315,316,324,327,331,334,335,336,337,340,349,351,352,355,357,359,360,363,367,368,371,377,379,382,393,397,401,403,406,410,414,415,418,419,422,423,426,428,429,432,434,436,438,441,443,445,447,449,451,455,457,458,461,462,465,467,468,469,472,476,477,483,484,487,489,490,491,494,498,499 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Function.h:29,32,36,42,44,46,50,55,74,76,81,83,85,87 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MethodHandle.c:28,52,56,64,65,67,74,82,83,84,87,91,92,96,98,99,102,108,109,114,116,117,120,123,124,125,128,130,131,134,140,142,145,147,156,157,159,160,161,164,166,167,174,177,185,187,188,190,191,192,195,197,200,225,228,231,234,236,237,239,243,270,273,276,277,279,283,285,288,293,294,295,297,298,301,305,306,310,311,313,314,317,319,324,326,327,330,332,333,335,336,339,343,345,349,355,356,358 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/LongDouble.h:29,32,34,38,42,45,47,49,51 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/AbstractMemory.c:31,41,44,51,60,65,68,73,75,77,133,179,180,185,187,188,191,193,194,201,211,214,216,217,220,222,223,226,228,229,232,234,235,242,250,264,267,282,283,284,286,289,291,292,295,297,298,300,301,309,313,314,322,324,326,328,329,340,344,347,350,353,355,360,361,362,372,376,379,382,385,388,393,394,395,407,413,418,422,423,435,440,445,448,451,453,457,458,465,467,468,469,471,472,482,485,489,490,492,493,494,507,510,514,517,520,522,523,535,538,541,544,546,547,563,568,570,576,581,582,585,587,589,590,600,602,603,615,618,622,623,625,626,634,636,638,640,641,651,654,656,658,660,661,664,666,667,670,672,674,676,678,679,682,687,688,691,692,695,703,704,705,708,710,715,716,718,719,722,724,725,727,728,747,750,786,790,791,810,816,834,839,1082,1090,1093,1100,1104,1105 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/MemoryPointer.h:30,33,40,44,49,51,53 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/include/ffi_common.h:5,9,12,16,18,49,58,62,67,76,81,86,87,92,101,105,108,113,140,142,148,150,152 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/include/ffi_cfi.h:3,6,9,11,31,33,53 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/err_bad_typedef.c:6,8,10,12,15,17,19,21,24,26 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float3.c:6,8,11,13,15,17,18,20,22,23,25,30,34,41,45,49,51,53,55,62,66,68,70,72,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/strlen4.c:6,8,10,12,14,15,17,31,35,41,47,53,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/many2.c:6,8,10,12,14,21,23,24,28,30,31,34,41,44,47,49,52,54,57 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_fl3.c:6,9,11,13,15,29,37,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct5.c:6,10,14,16,19,21,22,24,30,32,36,44,49,52,57,59,62,63,66 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct1.c:6,9,11,16,18,22,24,25,27,33,35,39,48,51,55,59,61,64,67 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct10.c:6,9,12,18,25,26,37,42,46,49,52,57 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/align_stdcall.c:6,8,10,15,17,18,20,32,36,38,40,46 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/align_mixed.c:6,8,10,15,17,18,20,32,36,38,40,46 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/ffitest.h:7,11,15,17,19,34,39,44,53,55,57,63,65,74,88,93,115,124,135 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/strlen3.c:6,8,10,12,14,15,17,28,32,37,42,47,49 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/va_struct3.c:6,9,12,14,18,20,27,30,35,48,49,52,56,59,62,66,69,74,78,83,90,96,98,101,107,110,112,118,123,125 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float4.c:6,9,12,14,18,20,22,24,25,27,34,37,41,43,47,49,53,55,59,61,62 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct6.c:6,10,14,16,19,21,22,24,30,32,36,44,47,50,53,56,58,61,64 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_uc.c:6,9,11,13,14,16,21,23,26,30,33,36,38 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/strlen.c:6,9,11,13,14,16,22,25,29,33,37,41,43,44 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/pyobjc-tc.c:6,9,14,19,24,26,30,31,32,34,42,46,54,62,70,80,85,86,87,89,90,97,104,107,109,111,112,114 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_sl.c:6,10,12,13,15,21,26,29,32,36,38 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct8.c:6,10,16,18,23,25,26,28,34,36,40,50,53,56,61,66,68,73,78,81 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct2.c:6,9,11,15,17,20,22,23,25,32,36,44,47,50,53,56,58,61,64,67 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_ll1.c:6,11,13,14,16,23,30,34,38,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_ldl.c:7,9,11,13,15,20,23,27,29,32,34 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float1.c:6,10,12,14,18,20,22,24,25,27,34,37,41,43,47,49,51,53,57,59,60 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_ll.c:6,10,12,13,15,21,24,28,30,33,34,36,39,41 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_dbl.c:6,9,11,14,16,21,24,28,30,34,36 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_dbl2.c:6,9,11,13,15,29,37,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/many.c:6,9,13,15,22,24,25,27,34,36,40,41,45,47,54,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/many_mixed.c:6,9,13,33,35,36,38,46,48,53,58,59,60,64,66,78 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/va_struct2.c:6,9,12,14,18,20,27,30,35,46,47,50,54,57,60,64,67,72,76,81,88,94,96,99,105,108,110,116,121,123 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/strlen2.c:6,8,10,12,14,15,17,28,32,37,42,47,49 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/offsets.c:6,10,12,18,21,25,31,36,44,46 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct7.c:6,10,15,17,21,23,24,26,32,34,38,47,50,53,57,61,63,67,71,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/negint.c:6,8,10,12,13,15,16,18,23,27,34,38,42,44,46,48,50,52 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_sc.c:6,9,11,13,15,21,24,28,31,34,36 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float_va.c:6,9,11,13,15,20,24,27,29,32,36,38,40,42,43,45,47,53,62,68,77,84,92,105,107 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/pr1172638.c:5,8,13,20,25,27,32,34,35,37,44,49,53,61,71,80,83,109,120,122,124,127 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_fl1.c:6,9,11,13,15,20,25,31,36 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float.c:6,8,10,12,14,16,18,19,21,26,31,40,44,49,51,53,55,57,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct3.c:6,9,11,14,16,18,20,21,23,30,34,41,44,48,51,53,55,57,60 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_ul.c:6,10,12,13,15,21,26,29,32,36,38 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct9.c:6,9,11,15,17,20,22,23,25,31,33,37,45,48,51,54,57,59,62,65,68 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_fl.c:6,9,11,13,15,20,23,27,29,33,35 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/va_1.c:6,9,12,14,18,20,27,30,45,50,53,56,59,62,66,77,78,81,85,88,91,95,98,109,114,118,123,130,146,148,151,157,160,162,173,189,194,196 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/float2.c:7,10,12,14,16,17,19,26,29,33,35,42,45,52,59,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/va_struct1.c:6,9,12,14,18,20,27,30,35,44,45,48,52,55,58,62,65,70,74,79,86,92,94,97,103,106,108,114,119,121 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_dbl1.c:6,9,11,13,15,30,38,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/return_fl2.c:6,9,12,14,17,19,25,34,42,45,49 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/promotion.c:6,11,13,15,16,18,28,37,41,44,51,56,59 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/struct4.c:6,9,11,16,18,20,22,23,25,31,33,37,46,49,52,56,58,60,61,64 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/uninitialized.c:3,5,10,12,16,18,19,21,27,37,42,45,49,53,55,58,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.call/many_double.c:6,9,13,27,34,36,37,39,46,48,52,53,57,59,70 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex1_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_align_complex_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_va_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex2_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/complex_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex1_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_struct_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_align_complex_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_struct_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex2_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_va_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/complex_int.c:6,8,12,14,19,20,41,42,48,50,54,60,67,71,76,84,86 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_align_complex_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/many_complex_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_struct_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/many_complex_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/complex_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/complex_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/many_complex_float.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_va_float.c:6,8,14 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/cls_complex_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex2_double.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.complex/return_complex1_longdouble.c:6,8 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct9.c:8,11,16,21,26,28,30,34,38,40,41,45,49,53,55,56,58,68,72,74,79,84,89,93,97,101,102,107,110,115,121,123,129,131 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_struct_va1.c:6,10,12,16,18,25,29,34,39,40,43,48,50,53,56,60,62,67,71,76,83,89,92,96,99,105,107,112,114 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_4byte.c:7,9,11,16,19,21,24,26,28,29,33,34,36,39,41,42,44,52,56,61,65,69,72,76,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_uint32.c:6,9,15,18,20,24,26,28,29,33,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_20byte.c:7,10,16,19,21,25,29,30,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_3float.c:7,9,11,17,20,22,26,29,31,32,36,38,41,43,44,46,54,58,63,68,72,75,79,84,87,93,95 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct3.c:8,11,16,21,23,25,29,33,35,36,40,43,46,48,49,51,60,63,65,70,75,79,83,84,88,91,95,101,102,104,111 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_7_1_byte.c:7,10,20,23,25,33,38,40,41,45,46,48,51,53,54,56,64,68,73,82,86,89,93,99,107,109,115,117 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/err_bad_abi.c:6,8,10,15,17,22,24,27,30,32,34,36 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_6_1_byte.c:7,10,19,22,24,31,36,38,39,43,44,46,49,51,52,54,62,66,71,79,83,86,90,96,103,105,111,113 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_ulong_va.c:6,8,10,12,15,17,19,20,22,24,30,34,38,45 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_ushort.c:6,9,12,14,17,19,21,27,30,34,36,41,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_4_1byte.c:8,11,18,21,23,28,32,34,35,39,40,42,45,47,48,50,58,62,67,73,77,80,84,89,91,96,98 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct7.c:8,11,16,21,23,25,29,33,35,36,40,43,46,48,49,51,60,63,65,70,75,79,83,84,88,91,95,101,103,109,111 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_schar.c:6,7,8,11,14,18,20,22,28,31,35,37,42,44 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_6byte.c:7,8,11,18,21,23,28,32,34,35,39,40,42,45,47,48,50,58,62,67,73,77,80,84,89,91,96,97,99 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_sint.c:6,9,12,16,18,20,26,29,33,35,40,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_2byte.c:8,11,16,19,21,24,26,28,29,33,34,36,39,41,42,44,52,56,61,65,69,72,76,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn2.c:8,11,14,24,35,36,40,42,48,66,70,73,81 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn6.c:7,10,14,27,40,41,42,49,51,57,75,79,82,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_sshortchar.c:6,9,12,14,16,18,20,21,22,25,28,33,35,36,37,40,42,51,56,62,68,72,77,79,84,86 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_float.c:6,9,15,18,20,24,26,28,29,33,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct.c:7,10,16,22,27,31,33,40,48,50,51,55,59,63,64,66,67,69,79,85,90,95,100,105,110,114,115,120,123,128,137,139,152 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_24byte.c:7,10,17,22,24,29,36,38,39,43,45,50,52,53,55,63,69,74,80,86,89,95,100,102,111,113 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_uchar.c:6,9,12,16,18,20,26,29,33,35,40,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_19byte.c:7,10,18,21,23,29,30,36,37,41,43,46,48,49,51,59,63,68,75,79,82,86,92,94,100,102 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct10.c:8,11,16,22,27,29,31,36,40,42,43,47,51,55,57,58,60,70,74,76,81,86,91,95,100,104,105,110,113,118,124,126,132,134 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_7byte.c:7,10,17,20,22,27,31,33,34,38,39,41,44,46,47,49,57,61,66,72,76,79,83,88,90,95,97 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_sshort.c:6,9,12,16,18,20,26,29,33,35,40,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_double.c:6,9,12,14,17,19,21,27,30,34,36,41,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/ffitest.h:7,11,15,17,19,34,39,44,53,55,57,63,65,74,88,93,115,124,135 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct4.c:8,11,16,21,23,25,29,33,35,36,40,43,46,48,49,51,60,63,65,70,75,79,83,84,88,91,95,101,103,109,111 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_5byte.c:7,10,16,19,21,25,29,31,32,36,37,39,42,44,45,47,55,59,64,69,73,76,80,85,89,91,96,98 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_64byte.c:8,11,22,27,29,38,41,43,44,48,50,55,57,58,60,68,74,79,89,95,98,104,110,112,122,124 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_sint32.c:6,9,15,18,20,24,26,28,29,33,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/huge_struct.c:7,11,13,66,119,130,151,153,154,157,208,215,216,219,222,226,230,243,245,250,263,265,290,293,295,311,313,339,341 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/problem1.c:6,9,15,17,22,23,26,28,29,32,35,38,40,41,42,44,52,56,61,66,70,73,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/stret_large2.c:8,11,14,32,38,40,56,60,62,63,66,68,73,75,76,78,86,92,97,114,120,123,129,136,138,146,148 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn1.c:8,11,12,15,25,36,37,42,48,66,70,73,81 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_18byte.c:7,10,17,20,22,27,28,33,34,38,40,43,45,46,48,56,60,65,71,75,78,82,87,89,94,96 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_pointer.c:6,9,15,18,20,24,30,32,33,37,38,40,43,45,46,48,56,60,65,70,74,77,81,86,88,93,95 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_uint.c:6,9,12,14,17,19,21,27,30,34,36,41,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn5.c:8,11,15,28,47,48,49,58,60,66,69,73,76,80,83,90,92 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_20byte1.c:7,8,9,12,18,21,23,27,31,32,36,38,41,43,44,46,54,58,63,68,72,75,79,84,86,91,93 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_9byte2.c:9,12,17,20,22,25,28,30,31,34,36,39,41,42,44,52,56,61,65,69,72,76,81,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_loc_fn0.c:8,9,10,11,14,18,29,41,42,43,48,50,56,74,78,82,85,87,95 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_longdouble.c:6,11,13,23,25,28,30,31,35,44,47,48,50,57,66,76,79,89,94,96,103,105 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_double_va.c:6,10,12,16,19,21,22,24,30,34,38,42,46,51,54,59,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_16byte.c:7,10,16,19,21,25,28,30,31,34,36,39,41,42,44,52,56,61,66,70,73,77,82,86,88,93,95 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_3_1byte.c:8,11,17,20,22,26,30,32,33,37,38,40,43,45,46,48,56,60,65,70,74,77,81,86,88,93,95 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct1.c:7,10,16,22,27,32,34,41,50,52,53,57,62,67,68,70,71,73,83,90,95,100,105,110,115,119,120,126,129,135,144,146,161 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct11.c:9,12,17,22,27,29,35,37,44,46,47,49,51,56,58,62,65,70,75,80,84,88,92,101,112,121 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_longdouble_split.c:6,9,11,21,25,27,35,41,43,44,47,49,57,62,64,65,69,71,74,76,77,79,87,91,96,105,109,112,116,122,124,130,132 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_sint16.c:6,9,15,18,20,24,26,28,29,33,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_3byte1.c:8,11,16,19,21,24,26,28,29,33,34,36,39,41,42,44,52,56,61,65,69,72,76,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct5.c:8,11,16,21,23,25,29,33,35,36,40,43,46,48,49,51,60,63,65,70,75,79,83,84,88,91,95,101,102,104,110,112 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_sshort.c:6,9,11,13,15,17,19,20,21,24,26,29,31,32,33,35,37,45,48,52,56,60,65,67,72,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/stret_medium2.c:8,12,24,30,32,42,45,47,48,51,53,58,60,61,63,71,77,82,93,99,102,108,114,116,123,125 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn0.c:8,9,10,11,14,18,29,41,42,43,48,50,56,74,78,81,89 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_float.c:6,9,12,14,17,18,20,22,28,31,35,42 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_longdouble_va.c:6,10,12,16,19,21,22,24,30,34,38,42,46,51,54,59,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/stret_medium.c:8,11,23,29,31,41,44,46,47,50,52,57,59,60,62,70,76,81,92,98,101,107,113,115,122,124 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_uint64.c:6,7,11,17,20,22,26,28,30,31,35,36,38,41,43,44,46,54,58,63,68,72,75,79,84,86,91,93 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_12byte.c:6,9,15,18,20,24,27,29,30,33,35,38,40,41,43,51,55,60,65,69,72,76,81,83,87,92,94 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_5_1_byte.c:7,10,18,21,23,29,34,36,37,41,42,44,47,49,50,52,60,64,69,76,80,83,87,93,99,101,107,109 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_dbls_struct.c:6,8,10,15,18,20,21,25,27,28,30,32,36,39,41,46,50,52,56,58,61,64,66 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_longdouble_split2.c:7,10,12,22,26,28,36,42,44,45,49,51,54,56,57,59,67,71,76,85,89,92,96,102,104,110,112,113,114,115 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/testclosure.c:6,9,16,18,23,24,28,30,32,34,35,36,38,45,47,52,58,61,64,66,70 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn4.c:8,10,12,16,29,48,49,50,59,61,67,70,73,77,80,87,89 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct2.c:8,11,16,21,23,25,29,32,34,35,39,42,45,47,48,50,59,62,64,69,74,78,82,83,87,90,94,100,102,108,110 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct8.c:8,11,16,21,26,28,30,34,38,40,41,45,49,53,55,56,58,68,72,74,79,84,89,93,97,101,102,107,110,115,121,123,129,131 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/stret_large.c:8,11,14,31,37,39,54,58,60,61,64,66,71,73,74,76,84,90,95,111,117,120,126,133,135,143,145 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_uchar.c:6,9,12,14,16,18,20,21,22,25,27,32,34,35,36,39,42,46,48,56,61,67,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_uchar_va.c:6,9,11,14,16,18,19,21,23,29,33,37,44 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_9byte1.c:9,12,17,20,22,25,28,30,31,34,36,39,41,42,44,52,56,61,65,69,72,76,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_ushort.c:6,9,11,13,15,17,19,20,21,24,26,29,31,32,33,35,37,45,48,52,56,60,65,67,72,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_uint16.c:6,9,15,18,20,24,26,28,29,33,34,36,39,41,42,44,52,56,61,66,70,73,77,82,84,89,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_many_mixed_float_double.c:6,11,13,16,21,26,28,30,32,34,42,43,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_longdouble.c:6,8,10,16,19,21,25,27,29,30,34,35,37,40,42,43,45,53,57,62,67,71,74,78,83,85,90,92 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/nested_struct6.c:8,11,16,21,26,28,30,34,38,40,41,45,49,53,55,56,58,68,72,74,79,84,89,93,97,101,102,107,110,115,121,123,129,131 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_3byte2.c:8,11,16,19,21,24,26,28,29,33,34,36,39,41,42,44,52,56,61,65,69,72,76,81,83,88,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_uint_va.c:6,8,10,12,15,17,19,20,22,24,30,34,38,45 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_many_mixed_args.c:6,11,13,16,21,23,26,28,31,33,35,39,41,49,51,56,57,61,63,70 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_ushortchar.c:6,9,12,14,16,18,20,21,22,25,28,33,35,36,37,40,42,51,56,62,68,72,77,79,84,86 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_sint64.c:6,10,16,19,21,25,27,29,30,34,35,37,40,42,43,45,53,57,62,67,71,74,78,83,85,90,92 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_fn3.c:8,11,14,24,35,36,37,41,43,49,67,71,74,82 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_ushort_va.c:6,9,11,14,16,18,19,21,23,29,33,37,44 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_pointer_stack.c:6,9,11,17,19,20,22,32,35,37,42,44,45,47,57,60,62,67,69,71,72,76,79,88,91,93,94,96,103,108,112,116,119,123,126,131,133,135,140,142 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_multi_schar.c:6,9,11,13,15,17,19,20,21,24,26,29,31,32,33,35,37,45,48,52,56,60,65,67,72,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_1_1byte.c:8,9,10,13,17,20,22,24,26,28,29,33,34,36,39,41,42,44,52,56,61,64,68,71,75,80,82,87,89 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/closure_simple.c:6,9,12,17,22,23,24,26,28,34,40,44,47,50,53,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_align_double.c:6,7,8,11,17,20,22,26,28,30,31,35,36,38,41,43,44,46,54,58,63,68,72,75,79,84,86,91,93 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_pointer.c:6,9,11,13,18,20,21,25,28,30,31,33,40,45,49,53,56,60,65,67,72,74 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_ulonglong.c:6,10,13,15,18,20,22,28,31,40,45,47 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.closures/cls_8byte.c:7,10,15,18,20,23,25,27,28,32,33,35,38,40,41,43,51,55,60,64,68,71,75,81,86,88 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.go/closure1.c:2,4,6,10,11,13,15,19,22,24,26,28 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.go/aa-direct.c:2,4,6,8,13,16,17,19,22,24,26,28,30,31 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.bhaible/test-call.c:4,9,14,18,20,27,36,39,41,55,56,58,60,64,66,70,75,78,85,104,105,108,112,115,117,118,121,124,132,135,138,139,143,149,153,157,158,162,168,172,176,177,181,187,191,195,196,200,206,210,214,215,219,225,229,233,234,238,240,243,245,251,255,258,259,263,269,273,276,277,281,287,291,294,295,299,305,309,312,313,317,323,327,330,331,335,341,345,348,349,353,356,358,360,365,369,372,373,377,383,387,390,391,395,401,405,408,409,413,419,423,426,427,431,437,441,444,445,450,453,455,461,465,472,473,478,481,487,495,499,504,505,509,517,521,524,525,529,535,539,542,543,547,553,557,560,561,565,571,575,578,579,583,589,593,598,599,603,611,615,618,619,623,629,633,636,637,641,647,651,654,655,659,665,669,672,673,677,683,687,690,691,695,701,705,708,709,713,719,723,726,727,731,737,741,744,745,749,755,759,762,763,767,773,777,780,781,785,791,795,798,799,803,809,813,816,817,821,827,831,834,835,839,845,849,852,853,857,863,867,870,871,875,881,885,888,889,893,899,903,906,907,911,917,921,924,925,930,933,935,940,949,951,952,955,958,963,972,974,975,978,981,986,995,997,998,1001,1004,1009,1018,1020,1021,1024,1027,1032,1041,1043,1044,1047,1050,1055,1064,1066,1067,1070,1073,1078,1087,1089,1090,1093,1096,1101,1110,1112,1113,1116,1119,1124,1133,1135,1136,1139,1141,1144,1154,1160,1170,1173,1174,1183,1193,1196,1197,1206,1216,1219,1220,1229,1239,1242,1243,1252,1262,1265,1266,1275,1285,1288,1289,1299,1309,1313,1314,1323,1333,1336,1337,1342,1344,1345,1348,1357,1362,1367,1373,1377,1380,1381,1390,1394,1397,1398,1407,1411,1414,1415,1424,1428,1431,1432,1441,1445,1448,1449,1458,1462,1465,1466,1475,1479,1482,1483,1492,1496,1499,1500,1509,1513,1516,1517,1526,1530,1533,1534,1543,1547,1550,1551,1560,1564,1567,1568,1577,1581,1584,1585,1594,1598,1601,1602,1611,1615,1618,1619,1628,1632,1635,1636,1645,1649,1652,1653,1662,1666,1669,1670,1679,1683,1686,1687,1696,1700,1703,1704,1713,1717,1720,1721,1726,1727,1730,1733,1743,1745 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.bhaible/alignof.h:3,8,13,16,19,21,35,49 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.bhaible/test-callback.c:18,20,27,36,39,41,55,56,58,60,64,66,70,75,78,83,96,97,99,102,106,107,110,118,125,127,137,149,165,189,192,201,211,223,239,263,294,297,306,316,328,344,367,370,381,384,396,408,421,433,444,456,469,480,490,501,513,526,542,559,580,602,612,623,635,648,664,681,702,723,726,734,742,750,758,766,774,782,790,797,800,812,824,836,848,860,873,885,901,904,914,925,937,950,964,979,995,1024,1053,1065,1078,1092,1107,1123,1140,1152,1165,1179,1194,1210,1226,1227,1253,1254,1256,1266,1269,1275,1280,1283,1286,1293,1298,1303,1310,1316,1321,1328,1334,1339,1346,1352,1357,1364,1370,1375,1382,1388,1393,1394,1397,1404,1410,1415,1422,1428,1433,1440,1446,1451,1458,1464,1469,1476,1482,1487,1494,1500,1505,1506,1507,1510,1517,1523,1528,1535,1541,1546,1553,1559,1564,1571,1577,1582,1589,1595,1600,1601,1604,1611,1617,1622,1623,1630,1637,1643,1648,1655,1661,1666,1673,1679,1684,1691,1697,1702,1709,1715,1720,1727,1733,1738,1745,1751,1756,1763,1769,1774,1781,1787,1792,1799,1805,1810,1817,1823,1828,1835,1841,1846,1853,1859,1864,1871,1877,1882,1889,1895,1900,1907,1913,1918,1925,1931,1936,1943,1949,1954,1961,1967,1972,1979,1985,1990,1997,2003,2008,2015,2021,2026,2033,2039,2044,2045,2048,2054,2065,2069,2071,2073,2079,2090,2094,2096,2098,2104,2115,2119,2121,2123,2129,2140,2144,2146,2148,2154,2165,2169,2171,2173,2179,2190,2194,2196,2198,2204,2215,2219,2221,2223,2229,2240,2244,2246,2248,2254,2265,2269,2271,2272,2283,2290,2302,2307,2314,2326,2331,2338,2350,2355,2362,2374,2379,2386,2398,2403,2410,2422,2427,2435,2447,2452,2460,2472,2479,2480,2481,2483,2492,2497,2502,2509,2515,2520,2527,2533,2538,2545,2551,2556,2563,2569,2574,2581,2587,2592,2599,2605,2610,2617,2623,2628,2635,2641,2646,2653,2659,2664,2671,2677,2682,2689,2695,2700,2707,2713,2718,2725,2731,2736,2743,2749,2754,2761,2767,2772,2779,2785,2790,2797,2803,2808,2815,2821,2826,2833,2839,2844,2851,2857,2862,2869,2875,2880,2881,2882,2884,2885 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/testsuite/libffi.bhaible/testcases.c:18,21,23,25,30,66,78,83,90,107,118,122,125,128,129,132,137,139,144,146,151,153,158,160,165,168,174,175,178,183,185,190,192,197,200,205,208,213,217,222,223,226,231,233,238,240,245,248,253,257,262,263,266,271,272,275,280,282,287,289,294,296,301,303,308,310,315,316,318,323,325,330,331,333,338,340,345,347,352,354,359,362,367,370,375,378,383,386,391,392,394,399,401,406,408,413,415,420,423,428,431,436,440,445,449,454,455,458,462,464,468,470,474,476,480,482,486,488,492,494,498,500,504,506,510,511,514,520,522,528,530,536,538,544,546,552,554,560,562,568,570,579,580,585,587,592,594,599,601,606,608,613,615,620,622,627,629,634,642,647,652,657,658,660,665,667,672,674,679,681,686,688,693,695,700,701,703,708,710,715,717,722,724,729,731,736,738,743 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/msvc_build/aarch64/aarch64_include/fficonfig.h:3,6,11,14,17,20,23,26,29,32,35,38,42,45,48,51,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,109,112,115,118,121,124,127,131,134,137,140,143,146,149,152,155,158,161,164,172,175,178,182,185,197,200,201,219 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/msvc_build/aarch64/aarch64_include/ffi.h:4,12,15,24,26,29,34,36,38,40,43,46,50,55,57,59,61,65,68,72,74,99,102,104,110,128,136,144,154,167,175,185,199,205,216,222,234,236,244,248,256,270,271,277,281,285,291,298,300,302,321,327,330,342,349,361,363,367,370,372,375,377,385,387,389,393,396,398,401,403,409,416,422,429,431,433,439,442,445,447,449,456,464,470,474,477,479,481,505,506,508,510 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m68k/ffitarget.h:5,13,16,25,27,30,34,38,46,48,52,54 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m68k/ffi.c:3,6,9,23,32,35,38,44,46,56,58,62,65,67,69,73,77,81,85,96,99,101,103,105,109,110,113,114,116,117,128,132,135,139,143,146,147,149,175,177,181,185,195,199,204,208,212,216,217,219,220,223,225,228,231,238,240,245,249,250,251,254,259,262,264,266,272,274,276,281,283,285,289,291,293,295,297,301,302,305,306,307,310,313,316,318,320,322,323,330,333,337,347,356,360,362 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/moxie/ffitarget.h:4,12,15,24,26,29,31,35,43,45,48,51 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/moxie/ffi.c:3,5,13,16,26,29,31,34,36,42,45,47,50,51,55,57,59,61,64,66,69,73,77,81,85,88,89,91,93,95,97,101,102,104,105,108,113,115,117,118,124,129,131,134,137,140,142,145,147,155,156,157,160,166,170,173,176,181,187,192,193,196,198,219,225,227,229,232,234,241,242,245,247,249,254,255,256,263,267,270,272,279,283,285 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/avr32/ffitarget.h:5,13,16,25,27,30,34,38,46,48,50,54 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/avr32/ffi.c:4,6,14,17,27,30,35,37,42,44,47,52,56,58,59,66,68,76,78,81,84,85,88,93,96,98,100,103,105,107,110,113,115,117,120,122,125,126,127,128,130,133,134,137,139,154,155,157,158,162,167,168,170,172,174,175,178,183,186,189,212,223,230,231,233,234,236,238,241,244,247,250,256,258,266,267,268,271,278,282,284,286,289,292,293,295,297,300,302,305,307,309,312,314,316,319,322,324,326,329,331,334,335,336,337,339,342,343,346,351,352,354,355,359,361,363,364,366,369,374,376,379,381,386,388,390,392,393,397,400,416,420,422,423 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m88k/ffitarget.h:23,27,30,34,42,44,48 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m88k/ffi.c:23,45,48,51,60,63,67,70,73,81,85,91,93,95,98,102,111,116,118,120,123,126,127,132,133,136,139,140,142,146,150,154,158,166,173,176,177,181,183,187,190,193,194,196,197,201,204,208,216,222,226,227,229,230,233,235,238,241,249,251,255,259,260,261,265,269,275,277,279,281,284,288,297,302,304,306,309,312,313,318,319,322,325,326,331,335,337,341,344,347,348,349,353,356,359,361,363,365,366,371,374,376,381,392,394,398,400 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/ia64/ffitarget.h:5,13,16,25,27,30,34,38,46,48,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/ia64/ia64_flags.h:3,5,7,15,18,28,32,35 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/ia64/ffi.c:5,7,15,18,28,31,35,37,42,47,50,51,53,55,60,61,63,66,72,73,78,81,84,87,90,93,95,104,105,106,109,112,114,126,127,128,131,134,136,138,143,145,150,152,157,160,161,162,166,169,171,173,180,186,194,196,198,200,204,209,210,212,215,216,218,219,220,222,225,227,235,239,246,248,251,253,257,259,262,263,265,268,270,272,273,276,279,280,285,288,289,291,294,298,300,304,307,311,313,336,340,344,348,350,356,365,367,371,375,377,381,385,391,392,393,396,398,401,402,403,405,406,413,421,423,430,434,438,440,445,448,451,454,461,463,464,465,469,474,479,484,487,490,510,513,519,524,527,533,538,543,549,554,556,560,564,566,571,573,577,583,584,588,591,593,595,598,599,600,602,604 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/xtensa/ffitarget.h:4,12,15,25,28,32,36,44,46,48,52 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/xtensa/ffi.c:3,5,13,16,26,29,42,44,46,47,49,50,54,56,81,83,87,88,93,95,96,98,102,115,118,121,124,126,127,131,133,161,163,166,169,172,177,181,182,185,186,187,188,189,191,196,199,205,207,210,212,214,215,218,220,223,224,227,234,238,242,247,248,249,252,257,261,263,266,269,270,274,276,279,283,285,289,290,293,294,296,298 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/closures.c:6,8,16,19,29,33,37,41,45,48,53,55,58,63,67,71,74,80,86,91,92,98,99,102,105,111,113,133,142,144,146,148,153,155,158,160,164,169,173,175,179,182,185,188,197,204,216,219,220,226,230,232,236,239,240,242,244,245,248,252,255,258,262,263,266,271,273,277,280,284,285,290,292,293,299,301,306,308,309,312,314,316,320,325,330,332,334,340,342,343,345,348,349,351,353,355,363,366,369,372,376,379,395,400,404,406,409,414,422,430,434,435,439,440,443,445,447,449,453,455,458,467,470,475,479,480,484,486,488,491,493,497,514,520,523,525,528,530,533,537,540,544,546,552,555,557,558,562,569,575,583,585,588,591,594,596,597,602,604,607,609,610,618,621,623,626,628,633,634,637,639,643,646,651,653,656,657,659,663,678,681,687,690,695,698,699,701,702,707,709,711,714,717,720,721,723,725,726,736,738,742,745,747,752,753,755,756,764,766,768,774,775,777,780,783,787,789,792,794,798,799,801,805,807,809,812,816,818,819,821,823,825,826,832,834,839,841,844,845,847,849,853,857,858,860,864,866,867,869,870,875,884,886,890,891,893,894,899,907,908,910,912,918,920,923,925,927,929,931,932,934,935,938,948,949,956,959,963,965,966,968,971,973,976,979,981,982,985,987,988,991,993,994,997 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffi_sysv.c:8,10,18,21,30,32,36,37,42,43,48,50,53,55,58,59,61,65,72,80,82,86,88,89,93,101,104,107,110,121,123,125,140,148,154,157,164,168,169,176,178,180,182,186,191,199,202,206,213,224,236,243,249,252,256,264,280,283,284,285,292,296,299,302,305,308,310,311,314,316,320,323,327,331,336,341,342,344,345,348,350,369,371,374,377,379,386,389,394,401,405,408,415,429,441,448,452,458,460,462,465,470,472,481,483,487,493,495,500,505,509,512,518,525,529,532,533,535,538,541,542,544,548,554,556,565,567,572,574,578,591,596,598,605,606,607,616,617,619,622,630,631,638,640,643,655,658,662,664,665,672,681,686,694,697,699,702,709,713,714,718,722,725,727,734,744,746,749,751,754,758,760,765,767,771,775,777,783,787,792,796,798,802,804,809,813,815,818,821,826,830,832,835,838,843,847,849,852,854,859,863,865,868,870,882,884,888,892,894,900,902,905,906,908,909,911,922 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffitarget.h:5,7,15,18,27,29,32,36,38,55,59,62,68,74,84,107,117,136,139,141,152,155,160,166,169,172,179,193,203 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffi_linux64.c:8,10,18,21,30,32,36,37,45,46,51,53,56,58,61,62,64,65,70,72,77,80,86,89,96,98,102,118,121,122,125,126,127,128,132,138,154,160,167,171,178,182,185,196,205,210,214,216,219,227,231,232,234,236,238,242,249,251,254,264,267,274,280,285,289,293,295,298,300,319,320,321,328,336,337,346,349,352,354,355,358,363,369,374,375,380,385,391,399,400,401,404,406,435,437,440,443,445,453,456,463,468,473,478,494,513,521,525,537,539,541,545,559,561,564,570,578,584,593,602,608,616,623,627,633,635,637,643,649,652,657,661,662,665,673,678,680,689,691,695,697,707,712,715,724,732,734,737,742,744,746,757,758,760,780,790,791,792,796,797,798,801,804,812,814,815,822,825,828,840,843,849,853,855,856,857,866,872,879,881,886,889,890,899,902,904,906,914,922,930,937,940,946,949,959,967,972,979,985,987,994,996,1000,1002,1004,1007,1012,1014,1016,1018,1020,1023,1028,1030,1037,1039,1048,1051,1055,1064,1066,1068,1071,1073,1075,1080,1082,1085,1094,1096,1099,1104,1110,1116,1118,1124,1127,1130,1131,1133,1134,1136,1139,1148,1152 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffi_powerpc.h:8,10,18,21,30,38,42,44,53,55,59,67,72,85,90 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffi.c:8,10,18,21,30,34,39,47,49,53,59,60,65,71,72,79,83,86,92,95,103,110,113,132,133,134,137,139,140,144,146,147,154,160,161,166,175 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/asm.h:3,5,13,16,25,27,28,36,42,76,83,92,114,117,123 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/powerpc/ffi_darwin.c:3,7,9,17,20,29,32,34,37,48,50,56,63,65,68,70,72,95,97,103,105,108,115,118,123,126,136,141,145,148,151,153,171,187,189,255,266,271,282,288,293,302,303,304,310,311,313,319,321,323,325,328,341,342,343,344,347,349,351,357,359,361,388,391,393,394,398,400,402,406,411,416,421,423,424,428,432,435,442,444,467,470,473,474,480,483,485,488,494,501,503,506,508,513,514,516,517,520,523,526,533,535,541,544,549,552,556,559,563,566,568,569,571,575,578,580,583,586,589,609,610,612,620,621,624,627,629,632,635,638,645,646,648,655,656,660,668,672,674,678,679,681,685,686,689,691,712,713,720,727,735,738,741,751,753,757,758,763,767,777,781,782,790,793,805,819,836,864,870,871,872,875,888,899,902,905,907,908,911,914,917,920,922,925,928,931,933,936,938,950,951,952,956,958,961,964,967,969,972,974,982,983,984,987,990,995,998,1000,1002,1036,1038,1045,1049,1051,1053,1055,1067,1085,1088,1090,1092,1095,1097,1105,1109,1111,1112,1117,1119,1121,1123,1128,1133,1135,1136,1139,1149,1150,1153,1159,1160,1162,1166,1170,1174,1181,1187,1191,1193,1195,1199,1208,1210,1212,1215,1219,1222,1225,1230,1231,1235,1238,1240,1250,1260,1271,1277,1281,1285,1286,1288,1294,1316,1330,1335,1340,1342,1344,1347,1352,1355,1357,1359,1366,1368,1372,1375,1377,1379,1382,1384,1390,1393,1398,1404,1406,1408,1415,1417,1418,1420,1423,1424,1428,1431,1432,1436,1439,1440 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m32r/ffitarget.h:5,13,16,24,26,29,33,35,39,41,48,52 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/m32r/ffi.c:4,6,14,17,26,29,31,34,36,43,46,48,51,52,55,59,61,65,67,71,73,75,79,83,87,91,100,103,104,106,108,110,112,114,117,119,122,123,125,128,129,132,133,134,136,137,141,144,148,152,155,159,165,170,171,173,174,177,179,181,184,189,191,194,196,201,204,206,209,211,213,215,217,220,223,224,225,227,231,232 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/or1k/ffitarget.h:3,5,13,16,26,29,33,35,39,47,49,53,56,58 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/or1k/ffi.c:3,5,13,16,26,29,32,34,40,44,46,50,52,53,56,59,61,64,68,72,76,80,84,91,94,97,101,102,107,109,110,117,118,120,124,126,128,136,137,141,144,145,150,152,159,160,161,162,165,168,171,175,178,186,188,190,193,194,197,198,201,204,206,208,213,218,225,229,232,240,243,246,249,250,252,255,260,261,262,263,270,274,277,281,287,292,295,298,300,301,302,304,306,313,315,317,318,319,322,324,328 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/tile/ffitarget.h:4,12,15,25,28,32,34,36,39,47,50,64 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/tile/ffi.c:3,5,13,16,26,35,36,39,47,51,52,55,61,66,69,70,71,75,77,81,85,89,93,103,107,112,115,117,119,128,132,136,140,141,142,143,146,154,156,161,164,165,167,171,179,181,184,187,190,191,194,196,198,199,200,204,207,208,209,212,213,220,227,230,232,236,242,245,250,252,254,259,262,265,268,272,275,277,278,279,288,299,302,306,310,312,316,317,320,324,332,336,340,341,345,346,349,351,354,355 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/s390/ffitarget.h:5,13,16,25,27,30,34,40,42,46,54,57,59,68,70 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/s390/internal.h:7,10 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/s390/ffi.c:4,6,14,17,30,35,37,42,45,52,55,57,62,64,71,74,77,79,88,91,93,99,103,106,109,115,121,124,125,128,129,131,139,142,147,150,152,154,159,166,171,175,188,204,208,209,211,215,217,223,226,231,236,237,240,243,250,257,262,274,277,284,285,286,289,291,293,294,296,304,311,320,322,325,330,331,333,340,342,347,349,355,360,363,370,375,379,384,387,412,429,434,441,443,451,459,470,474,475,476,478,479,482,484,485,489,491,492,494,502,510,512,516,520,523,526,532,535,538,544,547,552,557,558,561,567,568,571,579,586,603,612,620,628,632,633,638,639,640,643,646,655,660,664,676,683,690,694,695,696,698,706,713,723,725,728,732,736,738,739,741,743,747,750,754,756 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/prep_cif.c:4,12,15,25,29,31,33,36,38,40,43,46,48,51,53,57,60,65,68,70,71,80,87,92,93,98,101,104,107,108,114,118,122,125,130,138,143,150,167,169,170,175,183,185,189,193,197,203,205,207,208,210,216,218,220,223,225,226,233,235,236,238,244,246,247,249,252,257,261,263 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/cris/ffitarget.h:5,13,16,25,27,30,34,38,46,48,55 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/cris/ffi.c:6,8,16,19,28,31,33,36,38,40,44,46,48,52,54,56,59,61,62,67,68,71,77,79,81,84,86,88,90,93,96,98,101,103,111,113,117,119,123,128,132,137,140,142,148,151,152,154,155,161,165,170,175,177,181,183,185,189,191,195,197,200,202,207,208,211,212,214,216,217,220,222,231,235,236,238,239,244,247,249,252,254,256,259,261,269,270,271,274,278,283,288,295,298,305,311,314,317,320,322,325,327,329,333,334,339,342,343,344,347,353,358,360,362,363,365,372,384,386 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/debug.c:3,11,14,24,29,31,33,37,38,40,42,46,47,49,51,53,63,64 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/mips/ffitarget.h:5,13,16,25,27,30,34,47,57,71,82,84,87,94,98,113,123,146,193,203,226,230,232,236,242,244 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/mips/ffi.c:5,7,15,18,28,31,34,40,48,54,62,66,67,70,75,80,92,94,100,104,105,107,109,112,117,119,122,123,126,129,135,138,140,149,150,152,156,160,164,168,172,183,188,193,194,196,200,203,206,210,212,218,219,221,225,226,227,229,235,239,242,244,247,249,253,260,264,267,269,270,273,277,284,287,290,292,297,299,306,308,312,315,319,321,322,324,327,330,335,337,339,341,346,349,350,352,356,358,363,366,367,368,369,370,371,373,375,377,382,388,393,394,396,399,406,411,415,416,418,421,427,431,433,435,437,440,443,445,448,451,453,455,458,460,469,470,472,484,486,488,495,497,504,508,509,511,512,515,517,519,523,525,530,532,533,537,544,547,550,558,563,566,568,573,578,579,581,583,584,586,588,589,593,595,596,601,606,609,611,614,617,623,625,633,639,644,650,654,660,665,668,672,673,674,677,679,680,684,686,687,688,697,704,708,725,773,775,779,786,787,811,816,819,822,824,828,829,833,835,842,849,851,853,858,863,868,873,877,879,882,883,886,888,890,897,898,900,902,903,904,906,911,914,920,926,929,931,936,942,943,944,966,973,978,980,982,989,990,994,996,1000,1003,1006,1013,1015,1017,1020,1022,1027,1030,1032,1037,1042,1047,1052,1057,1062,1065,1071,1073,1078,1079,1082,1083,1086,1088,1089,1091,1099,1103,1105,1122,1126,1128,1129 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sh/ffitarget.h:5,13,16,25,27,30,34,36,40,48,52,54 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sh/ffi.c:4,6,14,17,27,30,32,37,43,47,52,54,55,58,60,63,66,68,76,79,80,81,84,87,89,92,98,101,102,103,104,106,107,110,112,123,126,128,132,135,140,142,144,147,150,153,157,161,165,169,173,176,178,180,183,186,189,192,195,198,205,208,220,221,222,229,231,233,236,239,242,246,250,254,258,262,265,267,269,272,275,278,281,284,287,289,293,296,299,302,305,308,311,315,316,317,319,320,323,331,333,336,339,342,350,359,369,370,373,383,385,388,392,400,404,405,407,408,411,413,416,419,422,428,430,433,435,443,444,449,450,455,462,465,468,474,484,488,493,495,496,504,512,517,526,529,533,536,539,543,546,548,551,554,557,562,567,571,574,576,578,581,586,589,594,595,598,607,610,622,623,624,629,631,633,636,639,642,647,652,656,659,661,663,666,669,672,675,678,681,683,687,690,693,696,699,702,706,710,711,712,714,717 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sh64/ffitarget.h:5,13,16,25,27,30,34,36,40,47,50,52,56,58 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sh64/ffi.c:4,6,14,17,27,30,32,35,38,39,42,52,54,55,58,60,66,68,70,73,74,77,79,82,86,88,92,96,100,104,108,111,113,115,117,122,129,133,135,139,142,144,146,149,150,151,153,154,157,164,168,170,173,180,183,188,193,196,200,215,216,217,220,224,232,236,237,239,240,250,255,258,261,264,270,272,275,277,285,286,291,292,295,302,304,307,329,333,337,339,340,348,352,359,362,366,369,372,376,379,382,385,387,389,401,404,405,407,409,411,413,416,418,427,428,435,443,445,449,452,454,456,458,461,462,463,465,468,469 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/frv/ffitarget.h:5,13,16,25,27,30,34,36,40,48,50,53,61 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/frv/ffi.c:5,7,15,18,28,31,33,36,38,44,47,51,53,55,57,60,62,64,68,73,76,80,84,88,92,95,96,98,100,102,104,108,109,111,112,115,120,122,124,125,131,136,138,141,144,147,149,152,153,155,163,164,165,168,174,179,183,189,192,194,216,218,223,224,227,233,235,239,245,246,247,254,262,264,281,285,290,292 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/pa/ffitarget.h:5,13,16,25,27,30,34,36,40,43,49,55,64,66,68,71,77 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/pa/ffi.c:6,9,17,20,30,33,36,38,42,51,53,55,57,63,82,83,85,100,106,112,116,121,124,127,130,133,138,141,143,150,153,156,158,160,162,166,170,174,178,186,193,199,205,207,214,218,220,228,230,234,237,240,242,246,250,253,254,258,259,261,263,266,269,271,272,274,276,277,279,283,285,287,289,295,302,305,306,307,314,316,317,320,323,329,336,345,350,354,355,359,363,367,368,370,371,375,377,379,382,385,393,395,398,399,401,407,411,412,413,420,430,432,438,442,444,446,448,458,464,474,479,483,489,492,496,503,509,512,514,518,522,525,526,529,530,533,536,539,562,566,570,574,582,587,590,593,598,599,602,605,607,611,616,618,619,623,625,632,637,640,643,653,674,686,712,716,718 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/metag/ffitarget.h:4,12,15,24,26,29,33,37,45,47,51,53 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/metag/ffi.c:3,12,15,24,27,29,31,36,38,43,45,50,51,53,56,58,62,65,69,86,91,92,93,97,98,101,104,108,112,116,118,119,122,126,129,130,132,159,161,162,164,174,176,180,182,187,194,202,203,206,207,209,212,214,217,219,234,235,236,237,239,246,248,253,258,262,264,265,266,273,276,279,288,290,292,293,297,302,305,310,311,313,317,323,328,330 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/ffitarget.h:5,7,15,18,27,29,32,36,38,41,46,51,56,58,79,91,100,126,128,131,136,145,147 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/internal.h:17,20,24 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/ffi64.c:6,8,16,19,29,32,37,39,42,54,56,61,63,70,73,76,82,86,99,101,103,107,110,113,117,124,128,136,146,149,150,156,162,164,175,177,179,182,184,187,189,193,195,198,201,218,224,228,231,235,239,240,243,245,247,252,256,257,259,260,262,269,273,274,277,282,288,292,293,298,302,303,305,307,310,321,325,328,338,339,340,342,343,347,351,355,359,363,382,383,386,388,389,391,396,399,405,412,414,418,462,468,470,472,478,489,490,494,519,523,524,529,533,535,538,541,543,546,547,550,553,555,556,560,566,569,574,579,580,585,587,589,594,597,599,601,606,608,612,617,619,623,625,627,638,651,663,664,665,666,668,671,672,677,680,683,686,689,690,696,700,703,706,709,710,711,714,723,730,741,748,753,756,760,762,763,771,777,782,784,791,792,795,798,803,805,809,814,820,823,826,828,831,832,835,838,841,846,847,848,849,852,855,856,859,865,869,876,882,884,885 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/ffi.c:8,10,18,21,31,38,49,53,60,64,67,69,80,81,83,131,134,144,147,151,173,177,179,181,183,186,188,190,191,194,196,205,211,214,215,216,218,226,228,234,245,255,257,261,268,273,276,278,292,293,294,301,306,309,313,317,323,324,327,332,334,336,340,343,345,348,349,351,354,363,373,375,380,382,386,387,388,390,392,393,396,398,399,403,405,406,408,412,414,421,424,432,440,442,445,450,457,458,461,464,469,471,475,478,480,483,484,486,489,493,502,504,509,511,515,516,517,519,520,522,527,528,535,539,541,558,559,563,567,571,573,574,578,582,584,586,602,603,607,609,610,612,614,617,624,628,635,639,640,642,651,652,656,660,664,666,667,670,677,681,684,686,700,701,702,709,713,716,720,724,731,732,735,739,741,745,747,751,754,757,759 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/ffiw64.c:4,6,14,17,27,33,39,41,48,51,54,56,58,64,65,68,81,94,96,98,106,108,109,113,118,120,123,127,132,133,138,142,145,148,149,151,153,169,170,171,173,174,177,179,180,184,186,187,188,191,198,208,210,216,217,220,224,226,227,231,233,239,240,244,246,247,249,255,265,269,273,279,283,284,286,290,292,297,302,304,305,309,310 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/asmnames.h:3,11,17,23,29 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/x86/internal64.h:17,19 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/types.c:3,5,13,16,26,30,33,35,46,47,62,63,68,77,79,82,88,101 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/microblaze/ffitarget.h:3,5,13,16,26,29,33,37,45,47,50,52 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/microblaze/ffi.c:3,5,13,16,26,29,33,35,39,43,48,51,52,54,57,64,65,68,69,72,78,81,83,121,124,134,135,136,137,139,142,147,149,150,152,156,163,164,166,174,175,176,180,184,195,200,202,207,208,213,214,217,219,243,245,261,263,264,274,275,276,281,286,290,292,295,301,307,315,319,321 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/arm/ffitarget.h:5,7,15,18,27,29,32,36,40,53,58,63,65,69,71,78,88 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/arm/ffi.c:7,9,17,20,30,38,43,45,49,57,61,64,75,76,79,81,83,96,106,114,120,125,128,129,131,132,135,143,146,148,151,152,154,158,159,160,164,170,175,179,182,183,185,189,192,196,199,206,212,217,223,224,229,230,231,235,238,245,248,252,263,268,275,279,281,289,290,298,301,303,306,307,312,316,319,321,322,327,331,333,334,336,338,345,350,354,360,363,371,373,376,379,382,385,388,391,392,394,398,402,404,407,409,412,413,416,417,420,422,423,427,429,430,434,437,439,442,444,447,448,450,453,457,458,460,461,465,471,474,476,479,480,482,486,488,491,493,495,497,501,505,510,513,515,518,520,521,522,527,528,530,531,533,538,544,550,551,557,563,564,569,571,577,579,581,585,588,594,601,617,621,623,624,628,630,632,636,639,643,645,646,648,652,655,658,661,664,668,670,671,673,674,677,680,683,686,689,692,695,696,698,699,703,706,709,713,717,732,733,738,743,745,749,750,751,755,768,771,774,777,780,783,784,788,789,792,795,798,802,804,807,810,813,814,818,821,826,829,834,835,838,845,847,851,852,853 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/arc/ffitarget.h:5,13,16,24,26,29,33,35,39,41,48,52 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/arc/ffi.c:3,5,13,16,25,28,31,33,41,44,47,52,54,56,59,60,62,65,68,71,75,78,80,82,86,90,94,98,102,105,106,108,110,112,114,116,118,121,122,125,126,128,129,133,136,140,144,150,155,156,158,159,163,166,168,171,175,177,180,182,187,191,192,193,197,203,205,208,211,212,214,217,220,223,227,231,232,234,236,237,239,244,246,248,255,258,259,264,266 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/riscv/ffitarget.h:3,5,13,16,25,27,30,34,38,40,43,53,56,58,60,67,69 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/riscv/ffi.c:6,8,16,19,29,32,35,43,47,49,57,59,65,73,78,84,93,95,96,105,110,120,125,126,128,130,147,162,163,168,169,170,184,185,190,191,205,206,207,211,220,221,222,226,228,237,243,244,250,251,252,257,266,267,268,272,274,285,291,292,299,300,301,307,309,311,312,317,318,320,324,325,329,333,342,354,355,358,363,367,371,373,377,378,381,383,384,388,390,391,393,395,398,401,404,415,419,421,423,424,426,430,433,437,439,440,449,460,464,470,474,476,480,481 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/dlmalloc.c:6,8,12,14,20,34,36,43,48,54,57,66,79,90,100,106,116,125,129,131,137,150,157,162,167,170,182,185,192,194,201,203,207,211,218,222,225,230,235,238,243,254,265,276,283,287,290,303,311,317,327,331,335,343,350,354,359,364,370,376,391,414,438,440,445,466,474,482,486,489,587,594,598,600,612,623,625,629,632,645,648,652,654,656,675,676,682,692,701,708,714,718,720,723,727,731,733,738,743,747,754,767,774,785,798,803,817,824,827,836,843,848,854,860,862,875,878,881,889,895,900,906,910,914,917,928,929,933,939,940,947,950,959,966,970,973,981,986,998,1002,1007,1009,1011,1017,1030,1038,1049,1055,1059,1065,1069,1076,1082,1088,1095,1102,1108,1114,1115,1123,1129,1135,1140,1142,1146,1154,1156,1158,1162,1164,1208,1255,1257,1261,1271,1274,1277,1282,1284,1290,1291,1295,1302,1306,1328,1330,1332,1340,1341,1343,1358,1360,1361,1365,1367,1372,1373,1379,1380,1395,1397,1398,1404,1410,1416,1419,1422,1423,1425,1427,1430,1437,1441,1449,1453,1455,1465,1471,1483,1484,1485,1488,1489,1498,1504,1512,1520,1521,1523,1526,1530,1538,1543,1545,1565,1567,1595,1600,1604,1615,1620,1628,1633,1635,1647,1656,1658,1665,1672,1674,1676,1682,1687,1691,1697,1701,1705,1709,1710,1712,1720,1724,1727,1732,1735,1739,1743,1746,1750,1754,1758,1761,1765,1772,1774,1778,1781,1797,1802,1825,1833,1840,1848,1856,1863,1870,1875,1879,1882,1884,1892,1906,1914,1921,1926,1939,1947,1949,1954,1957,1959,1968,1974,1978,1982,1985,1988,1990,1994,2003,2010,2020,2026,2039,2043,2048,2051,2054,2058,2063,2073,2094,2096,2098,2104,2113,2115,2121,2123,2125,2129,2133,2136,2141,2145,2149,2154,2158,2167,2168,2169,2178,2179,2180,2186,2194,2195,2197,2203,2205,2208,2212,2216,2220,2222,2230,2232,2235,2238,2241,2243,2247,2251,2253,2255,2257,2264,2272,2286,2288,2293,2297,2312,2330,2332,2336,2341,2346,2347,2349,2352,2357,2361,2363,2370,2371,2375,2387,2390,2393,2396,2399,2400,2402,2412,2428,2438,2445,2452,2453,2464,2466,2468,2470,2475,2480,2484,2486,2490,2494,2499,2504,2508,2510,2512,2517,2525,2527,2536,2540,2543,2544,2554,2556,2567,2572,2574,2590,2592,2593,2606,2614,2615,2616,2619,2624,2625,2638,2639,2652,2653,2663,2664,2682,2685,2686,2687,2699,2700,2701,2714,2726,2738,2743,2746,2747,2751,2752,2762,2763,2784,2785,2786,2787,2800,2801,2811,2818,2819,2820,2822,2823,2840,2845,2848,2850,2851,2853,2854,2864,2870,2871,2877,2878,2882,2884,2886,2906,2908,2910,2911,2919,2920,2922,2924,2926,2938,2946,2948,2949,2950,2954,2956,2957,2958,2960,2967,2985,2986,3005,3006,3022,3023,3035,3036,3038,3089,3090,3093,3107,3177,3178,3180,3184,3188,3189,3191,3206,3208,3218,3233,3241,3242,3244,3245,3269,3276,3277,3279,3280,3282,3289,3296,3297,3305,3306,3307,3309,3324,3326,3336,3340,3347,3352,3359,3363,3364,3367,3368,3369,3386,3389,3398,3408,3410,3418,3419,3421,3422,3424,3430,3432,3438,3439,3456,3462,3475,3476,3477,3486,3487,3488,3501,3502,3503,3504,3508,3511,3512,3514,3515,3525,3526,3527,3528,3543,3544,3545,3546,3547,3549,3552,3565,3566,3567,3580,3595,3598,3599,3600,3610,3611,3612,3615,3616,3618,3638,3641,3648,3651,3652,3653,3656,3658,3659,3664,3671,3682,3683,3684,3689,3697,3698,3700,3701,3702,3708,3709,3710,3714,3718,3719,3721,3722,3724,3732,3744,3752,3754,3755,3756,3764,3765,3766,3772,3774,3775,3789,3791,3792,3794,3796,3797,3805,3808,3814,3815,3816,3828,3830,3831,3832,3835,3836,3838,3843,3850,3852,3866,3867,3878,3879,3884,3885,3887,3891,3894,3901,3903,3904,3906,3907,3909,3919,3920,3924,3925,3934,3954,3958,3963,3965,3966,3976,3977,3978,3985,3988,3990,3991,3993,3994,3996,4005,4010,4022,4029,4036,4037,4042,4048,4049,4051,4064,4068,4070,4073,4074,4083,4084,4096,4100,4101,4102,4108,4111,4113,4116,4118,4121,4122,4123,4125,4127,4148,4151,4161,4173,4174,4196,4200,4201,4205,4206,4207,4215,4216,4217,4226,4232,4236,4237,4248,4249,4251,4255,4256,4258,4259,4266,4274,4291,4299,4304,4305,4308,4309,4310,4320,4324,4330,4339,4340,4341,4347,4348,4353,4354,4358,4359,4368,4373,4374,4382,4392,4395,4396,4397,4400,4401,4406,4407,4411,4412,4418,4419,4425,4426,4432,4434,4435,4438,4439,4442,4443,4447,4449,4452,4453,4459,4461,4462,4465,4466,4468,4470,4472,4491,4492,4497,4507,4508,4510,4511,4516,4522,4524,4525,4539,4540,4543,4545,4546,4551,4552,4558,4568,4580,4581,4603,4607,4608,4612,4613,4614,4622,4623,4624,4633,4639,4643,4644,4655,4656,4658,4662,4663,4665,4666,4678,4692,4700,4705,4706,4709,4710,4711,4721,4725,4731,4740,4741,4742,4748,4749,4754,4755,4756,4757,4765,4771,4776,4777,4785,4797,4799,4800,4801,4807,4809,4810,4818,4820,4821,4828,4830,4831,4839,4840,4843,4845,4846,4851,4854,4855,4856,4862,4865,4866,4867,4873,4876,4877,4878,4884,4886,4888,4891,4892,4894,4896,4899,4918,4925,4927,4930,4935,4937,4940,4942,4948,4950,4957,4959,4962,4964,4966,4967,4968,4971,4973,4975,4978,4981,4982,4983,4985,4986,5001,5004,5008,5024,5027,5040,5064,5080,5083,5092,5113,5124,5128,5132,5134,5143,5152,5161,5165 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/alpha/ffitarget.h:5,13,16,25,27,30,34,38,46,49,51,56 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/alpha/internal.h:7,19 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/alpha/ffi.c:4,6,14,17,27,32,43,49,53,57,58,61,63,64,67,71,74,77,80,97,103,111,114,115,116,120,160,173,177,179,187,189,190,193,195,204,209,215,221,224,225,226,230,235,240,245,249,252,254,259,261,277,283,290,295,302,305,306,307,310,311,314,316,317,321,323,324,331,333,336,343,347,349,352,355,357,358,363,366,370,372,373,379,383,387,391,394,395,397,400,405,407,420,426,431,434,437,443,450,454,459,472,477,480,486,493,497,499,502,505,508,511,512,514,515,518,521 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/aarch64/ffitarget.h:2,10,13,21,24,28,43,45,52,54,57,59,66,71,75,77,86,91 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/aarch64/internal.h:9,12,20,24,30,37,42,47,52,56,58,61,64 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/aarch64/ffi.c:2,10,13,21,33,44,46,50,52,55,57,61,63,67,69,73,76,86,87,89,93,96,99,102,105,109,111,112,114,115,118,121,124,127,130,133,136,137,139,140,145,148,151,155,159,170,176,180,181,186,191,193,197,198,199,203,221,224,227,230,233,236,237,241,242,245,248,250,254,259,263,270,271,276,278,288,291,293,294,297,299,321,322,323,329,332,379,381,387,389,392,396,417,420,424,445,450,452,454,458,463,466,469,470,473,477,479,509,517,520,523,530,532,535,536,539,542,543,550,552,553,559,563,565,569,575,582,587,593,596,601,607,610,615,618,622,636,641,652,653,655,661,663,666,670,672,677,680,682,685,690,694,696,698,706,708,711,718,720,727,728,730,732,735,736,739,743,745,746,748,751,752,755,757,758,763,765,767,769,772,779,782,784,789,803,805,807,809,820,824,826,827,831,835,837,840,845,849,851,853,855,862,865,869,876,880,882,884,888,890,894,907,915,919,921,924,933,935,940,941,943,946,950,952,956,958,960,962,968,970,973,976,978,982,983,985,988,989,992,996,998,999,1003,1005,1007,1008 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sparc/ffitarget.h:5,13,16,25,27,30,34,36,42,46,59,62,67,69,73,79,81 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sparc/internal.h:12,21,25 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sparc/ffi64.c:4,6,14,17,27,32,43,45,49,53,56,59,61,64,70,72,74,77,80,98,100,101,104,106,107,110,113,117,123,126,130,132,133,134,136,139,143,149,152,156,158,159,161,164,169,172,185,189,192,194,199,201,205,207,212,213,216,225,226,228,253,256,257,260,264,266,271,274,286,289,290,295,298,301,305,306,309,312,313,316,319,320,323,326,329,333,335,337,342,344,347,348,349,355,358,361,365,367,394,400,404,410,413,414,415,417,418,422,424,426,429,431,432,435,437,438,442,444,445,449,451,455,458,465,468,471,480,484,486,488,489,493,496,500,502,503,509,513,518,520,524,528,531,534,539,542,549,552,555,559,560,562,577,595,598,600,601,604,607 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/sparc/ffi.c:4,6,14,17,27,32,34,45,49,54,57,97,126,130,132,135,139,141,148,154,157,159,160,165,168,171,175,177,178,181,184,188,190,192,197,199,202,203,204,208,214,217,222,224,230,237,245,258,265,268,270,273,275,278,279,280,282,283,287,289,291,297,299,300,303,305,306,310,312,313,317,321,325,328,335,339,342,347,351,353,355,356,360,363,367,369,370,376,380,385,390,394,395,398,401,406,408,415,420,425,428,443,454,457,460,461,464,467 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/java_raw_api.c:3,5,8,10,18,21,31,37,41,43,46,49,51,53,68,69,70,72,73,74,77,80,82,84,86,91,96,105,109,113,118,119,120,122,124,127,141,147,148,152,154,155,158,161,163,165,173,181,189,197,205,213,217,226,230,239,240,241,242,244,247,250,256,266,270,273,275,276,279,282,288,295,299,302,304,305,312,315,320,321,323,327,330,334,335,342,344,351,354,355,357,358,362,368,370,371 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/vax/ffitarget.h:23,27,30,34,42,44,48 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/vax/ffi.c:23,30,33,36,41,45,49,52,58,60,66,68,72,74,77,79,83,87,91,95,99,102,104,106,108,112,113,116,117,119,120,123,126,130,134,137,138,150,157,158,160,161,164,166,169,172,179,181,185,189,190,191,195,199,202,206,208,210,212,215,219,222,223,224,227,230,233,235,237,239,240,245,248,250,258,264,270,274,276 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/bfin/ffitarget.h:3,5,13,16,26,29,33,41,43 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/bfin/ffi.c:4,6,14,17,29,32,35,44,49,54,56,61,62,66,68,104,109,111,112,121,127,135,136,137,138,145,164,170,184,189,192,193,194,195,196 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/raw_api.c:3,5,13,16,26,28,31,33,36,39,41,43,50,51,53,54,55,58,61,63,65,67,72,77,84,90,94,98,102,103,104,106,108,111,114,116,120,122,124,127,128,129,133,135,136,139,142,144,146,150,154,158,162,167,172,178,182,186,190,191,192,193,195,196,203,205,209,210,212,216,219,222,223,230,232,239,242,243,245,246,249,251,255,261,263,264,266 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/nios2/ffitarget.h:2,4,12,15,23,24,27,31,35,43,47,51 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/libffi/src/nios2/ffi.c:2,4,12,15,23,24,27,29,32,37,41,44,48,49,51,57,59,61,70,72,73,74,80,82,85,90,93,94,96,101,105,109,112,116,120,124,128,132,135,136,142,143,144,145,149,151,152,155,161,164,171,173,179,195,196,197,201,205,212,218,221,224,227,230,234,238,242,243,247,248,249,258,261,264,279,291,297,301,303,304 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/FunctionInfo.c:29,35,42,45,49,55,60,62,65,68,76,78,79,82,88,89,90,93,100,102,103,117,126,132,133,135,145,149,153,154,158,159,162,163,168,169,174,175,178,179,182,183,191,203,204,206,208,209,217,219,221,223,224,232,234,236,238,239,242,244,246,264,269,270,271 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/Pointer.h:29,32,38,42,44,49,57,59,61,63 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/ClosurePool.c:28,53,60,66,68,69,76,82,91,93,97,102,104,110,112,113,116,118,125,127,128,131,136,137,138,139,142,150,155,157,158,164,168,169,175,178,179,180,183,184,190,195,198,204,205,206,209,210,213,223,224,225,226,229,231,232,233,236,244,245,248,255,256,259,265,266,269,276,277,280,282,283 ./vendor/bundle/ruby/2.6.0/gems/ffi-1.12.2/ext/ffi_c/StructByValue.c:29,44,48,52,54,59,61,64,66,68,72,77,79,80,83,87,91,92,97,100,102,103,106,109,110,113,116,117,118,121,123,126,127,130,132,134,136,137,140,144,149,150 ./vendor/bundle/ruby/2.6.0/gems/xcpretty-0.3.0/spec/fixtures/NSStringTests.m:8,11,13,15,17,20,22,24,26,30,34,38,42,44,48,53,55,59,61,62,63 ./vendor/bundle/ruby/2.6.0/gems/rake-13.0.1/doc/example/a.c:2,4,6 ./vendor/bundle/ruby/2.6.0/gems/rake-13.0.1/doc/example/main.c:2,5,7,11 ./vendor/bundle/ruby/2.6.0/gems/rake-13.0.1/doc/example/b.c:2,4,6 <<<<<< EOF